refactor(runtime): own world reveal generation

This commit is contained in:
Erik 2026-07-26 17:09:54 +02:00
parent 4ab98b080e
commit a6860d5563
26 changed files with 1294 additions and 502 deletions

View file

@ -1,6 +1,7 @@
using AcDream.App.Audio;
using AcDream.Core.Selection;
using AcDream.Runtime.Entities;
using AcDream.Runtime.World;
namespace AcDream.App.Streaming;
@ -24,57 +25,51 @@ internal sealed class AlwaysAvailableWorldGeneration
}
/// <summary>
/// Single-writer state behind <see cref="IWorldGenerationAvailability"/>.
/// Superseding reveal generations stay continuously quiesced; only the exact
/// active generation may reopen the world.
/// Read-only graphical-host projection of the canonical Runtime transit
/// lifetime. It stores no availability or generation state.
/// </summary>
internal sealed class WorldGenerationAvailabilityState
: IWorldGenerationAvailability
{
public bool IsWorldAvailable => QuiescedGeneration == 0;
public long QuiescedGeneration { get; private set; }
private readonly RuntimeWorldTransitState _transit;
public void Begin(long generation)
public WorldGenerationAvailabilityState(
RuntimeWorldTransitState transit)
{
if (generation <= 0)
throw new ArgumentOutOfRangeException(nameof(generation));
QuiescedGeneration = generation;
_transit = transit ?? throw new ArgumentNullException(nameof(transit));
}
public bool End(long generation)
{
if (generation <= 0 || QuiescedGeneration != generation)
return false;
public bool IsWorldAvailable =>
_transit.IsWorldSimulationAvailable;
QuiescedGeneration = 0;
return true;
}
public long QuiescedGeneration =>
IsWorldAvailable ? 0 : _transit.Snapshot.Generation;
}
internal readonly record struct WorldGenerationQuiescenceEdge(
bool ShouldApply,
bool ClearWorldSelection);
/// <summary>
/// Owns the immediate observable edge of retail
/// Projects the immediate graphical/audio edge of retail
/// <c>SmartBox::UseTime @ 0x00455410</c>'s <c>blocking_for_cells</c>
/// branch. Deferred teardown may retain memory, but the old generation cannot
/// keep rendering, targeting, ticking, or producing world audio.
/// branch. Runtime owns whether the world is available; this class only
/// applies selection and audio side effects at the accepted Runtime edge.
/// </summary>
internal sealed class WorldGenerationQuiescence
{
private readonly WorldGenerationAvailabilityState _availability;
private readonly SelectionState _selection;
private readonly GpuWorldState _world;
private readonly Func<uint, RuntimeEntityKey?> _resolveProjectionKey;
private readonly IWorldAudioQuiescence? _audio;
private bool _effectsQuiesced;
public WorldGenerationQuiescence(
WorldGenerationAvailabilityState availability,
SelectionState selection,
GpuWorldState world,
Func<uint, RuntimeEntityKey?> resolveProjectionKey,
IWorldAudioQuiescence? audio)
{
_availability = availability
?? throw new ArgumentNullException(nameof(availability));
_selection = selection ?? throw new ArgumentNullException(nameof(selection));
_world = world ?? throw new ArgumentNullException(nameof(world));
_resolveProjectionKey = resolveProjectionKey
@ -82,22 +77,33 @@ internal sealed class WorldGenerationQuiescence
_audio = audio;
}
public IWorldGenerationAvailability Availability => _availability;
public void Begin(long generation)
/// <summary>
/// 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.
/// </summary>
public WorldGenerationQuiescenceEdge CaptureBegin()
{
bool firstEdge = _availability.IsWorldAvailable;
uint? selected = firstEdge ? _selection.SelectedObjectId : null;
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);
}
_availability.Begin(generation);
if (!firstEdge)
public void CommitBegin(in WorldGenerationQuiescenceEdge edge)
{
if (!edge.ShouldApply || _effectsQuiesced)
return;
if (clearWorldSelection)
_effectsQuiesced = true;
if (edge.ClearWorldSelection)
{
_selection.Clear(
SelectionChangeSource.System,
@ -107,9 +113,12 @@ internal sealed class WorldGenerationQuiescence
_audio?.SuspendWorldAudio();
}
public void End(long generation)
public void ObserveReleased()
{
if (_availability.End(generation))
_audio?.ResumeWorldAudio();
if (!_effectsQuiesced)
return;
_effectsQuiesced = false;
_audio?.ResumeWorldAudio();
}
}