using AcDream.App.Audio; using AcDream.Core.Selection; 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; } /// /// Single-writer state behind . /// Superseding reveal generations stay continuously quiesced; only the exact /// active generation may reopen the world. /// internal sealed class WorldGenerationAvailabilityState : IWorldGenerationAvailability { public bool IsWorldAvailable => QuiescedGeneration == 0; public long QuiescedGeneration { get; private set; } public void Begin(long generation) { if (generation <= 0) throw new ArgumentOutOfRangeException(nameof(generation)); QuiescedGeneration = generation; } public bool End(long generation) { if (generation <= 0 || QuiescedGeneration != generation) return false; QuiescedGeneration = 0; return true; } } /// /// Owns the immediate observable edge of retail /// SmartBox::UseTime @ 0x00455410's blocking_for_cells /// branch. Deferred teardown may retain memory, but the old generation cannot /// keep rendering, targeting, ticking, or producing world audio. /// internal sealed class WorldGenerationQuiescence { private readonly WorldGenerationAvailabilityState _availability; private readonly SelectionState _selection; private readonly GpuWorldState _world; private readonly IWorldAudioQuiescence? _audio; public WorldGenerationQuiescence( WorldGenerationAvailabilityState availability, SelectionState selection, GpuWorldState world, IWorldAudioQuiescence? audio) { _availability = availability ?? throw new ArgumentNullException(nameof(availability)); _selection = selection ?? throw new ArgumentNullException(nameof(selection)); _world = world ?? throw new ArgumentNullException(nameof(world)); _audio = audio; } public IWorldGenerationAvailability Availability => _availability; public void Begin(long generation) { bool firstEdge = _availability.IsWorldAvailable; uint? selected = firstEdge ? _selection.SelectedObjectId : null; bool clearWorldSelection = selected is uint selectedGuid && _world.IsLiveEntityVisible(selectedGuid); _availability.Begin(generation); if (!firstEdge) return; if (clearWorldSelection) { _selection.Clear( SelectionChangeSource.System, SelectionChangeReason.Cleared); } _audio?.SuspendWorldAudio(); } public void End(long generation) { if (_availability.End(generation)) _audio?.ResumeWorldAudio(); } }