refactor(runtime): own world reveal generation
This commit is contained in:
parent
4ab98b080e
commit
a6860d5563
26 changed files with 1294 additions and 502 deletions
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue