using AcDream.App.Audio; using AcDream.Core.Selection; using AcDream.Runtime.Entities; using AcDream.Runtime.World; namespace AcDream.App.Streaming; /// /// Read-only generation gate shared by world simulation and presentation. /// A hard login/portal reveal boundary makes the prior world unavailable /// immediately while its physical owners retire over later frames. /// public interface IWorldGenerationAvailability { bool IsWorldAvailable { get; } long QuiescedGeneration { get; } } internal sealed class AlwaysAvailableWorldGeneration : IWorldGenerationAvailability { public static AlwaysAvailableWorldGeneration Instance { get; } = new(); public bool IsWorldAvailable => true; public long QuiescedGeneration => 0; } /// /// Read-only graphical-host projection of the canonical Runtime transit /// lifetime. It stores no availability or generation state. /// internal sealed class WorldGenerationAvailabilityState : IWorldGenerationAvailability { private readonly RuntimeWorldTransitState _transit; public WorldGenerationAvailabilityState( RuntimeWorldTransitState transit) { _transit = transit ?? throw new ArgumentNullException(nameof(transit)); } public bool IsWorldAvailable => _transit.IsWorldSimulationAvailable; public long QuiescedGeneration => IsWorldAvailable ? 0 : _transit.Snapshot.Generation; } internal readonly record struct WorldGenerationQuiescenceEdge( bool ShouldApply, bool ClearWorldSelection); /// /// Projects the immediate graphical/audio edge of retail /// SmartBox::UseTime @ 0x00455410's blocking_for_cells /// branch. Runtime owns whether the world is available; this class only /// applies selection and audio side effects at the accepted Runtime edge. /// internal sealed class WorldGenerationQuiescence { private readonly SelectionState _selection; private readonly GpuWorldState _world; private readonly Func _resolveProjectionKey; private readonly IWorldAudioQuiescence? _audio; private bool _effectsQuiesced; private bool _selectionEdgeCommitted; private bool _audioSuspended; private bool _audioTransitionActive; public WorldGenerationQuiescence( SelectionState selection, GpuWorldState world, Func resolveProjectionKey, IWorldAudioQuiescence? audio) { _selection = selection ?? throw new ArgumentNullException(nameof(selection)); _world = world ?? throw new ArgumentNullException(nameof(world)); _resolveProjectionKey = resolveProjectionKey ?? throw new ArgumentNullException(nameof(resolveProjectionKey)); _audio = audio; } /// /// Captures the pre-quiescence graphical selection fact before Runtime /// closes world availability. The returned edge contains no authority and /// is committed only after Runtime accepts the new generation. /// public WorldGenerationQuiescenceEdge CaptureBegin() { if (_effectsQuiesced) return default; uint? selected = _selection.SelectedObjectId; bool clearWorldSelection = selected is uint selectedGuid && _resolveProjectionKey(selectedGuid) is RuntimeEntityKey key && _world.IsLiveEntityVisible(key); return new WorldGenerationQuiescenceEdge( ShouldApply: true, clearWorldSelection); } public void CommitBegin(in WorldGenerationQuiescenceEdge edge) { if (!edge.ShouldApply) return; _effectsQuiesced = true; if (!_selectionEdgeCommitted) { // SelectionState commits its value before notifying observers. // Record the edge first so a re-entrant reveal cannot replay it. _selectionEdgeCommitted = true; if (edge.ClearWorldSelection) { _selection.Clear( SelectionChangeSource.System, SelectionChangeReason.Cleared); } } ReconcileAudio(); } public void ObserveReleased() { if (!_effectsQuiesced && !_selectionEdgeCommitted && !_audioSuspended) { return; } _effectsQuiesced = false; ReconcileAudio(); if (!_effectsQuiesced && !_audioSuspended) _selectionEdgeCommitted = false; } private void ReconcileAudio() { if (_audio is null || _audioTransitionActive) return; _audioTransitionActive = true; try { while (_audioSuspended != _effectsQuiesced) { bool suspend = _effectsQuiesced; if (suspend) _audio.SuspendWorldAudio(); else _audio.ResumeWorldAudio(); _audioSuspended = suspend; } } finally { _audioTransitionActive = false; } } }