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();
}
}