refactor(runtime): acknowledge exact world host projections

This commit is contained in:
Erik 2026-07-26 18:27:41 +02:00
parent 73d0b54e38
commit 18d17d8bb1
17 changed files with 1677 additions and 72 deletions

View file

@ -453,6 +453,8 @@ internal sealed class FrameRootCompositionPhase
lifecycleAutomation =
new WorldLifecycleAutomationController(
() => session.WorldReveal.Snapshot,
() => d.WorldEnvironment.Runtime.Ownership,
() => live.WorldTransit.Ownership,
() => session.WorldReveal.PortalMaterializationCount,
resourceSnapshots.Capture,
screenshots,

View file

@ -6,6 +6,7 @@ using AcDream.App.Streaming;
using AcDream.App.UI.Testing;
using AcDream.Core.Physics;
using AcDream.Runtime;
using AcDream.Runtime.World;
namespace AcDream.App.Diagnostics;
@ -103,6 +104,8 @@ internal sealed record WorldLifecycleCheckpoint(
DateTime TimestampUtc,
int ProcessId,
RuntimePortalSnapshot Reveal,
RuntimeWorldEnvironmentOwnershipSnapshot EnvironmentOwnership,
RuntimeWorldTransitOwnershipSnapshot TransitOwnership,
RenderFrameOutcome Render,
WorldLifecycleResourceSnapshot Resources);
@ -177,6 +180,10 @@ internal sealed class WorldLifecycleAutomationController :
};
private readonly Func<RuntimePortalSnapshot> _getReveal;
private readonly Func<RuntimeWorldEnvironmentOwnershipSnapshot>
_getEnvironmentOwnership;
private readonly Func<RuntimeWorldTransitOwnershipSnapshot>
_getTransitOwnership;
private readonly Func<int> _getPortalMaterializationCount;
private readonly Func<RenderFrameOutcome, WorldLifecycleResourceSnapshot>
_captureResources;
@ -191,6 +198,9 @@ internal sealed class WorldLifecycleAutomationController :
public WorldLifecycleAutomationController(
Func<RuntimePortalSnapshot> getReveal,
Func<RuntimeWorldEnvironmentOwnershipSnapshot>
getEnvironmentOwnership,
Func<RuntimeWorldTransitOwnershipSnapshot> getTransitOwnership,
Func<int> getPortalMaterializationCount,
Func<RenderFrameOutcome, WorldLifecycleResourceSnapshot> captureResources,
FrameScreenshotController screenshots,
@ -198,6 +208,11 @@ internal sealed class WorldLifecycleAutomationController :
Action<string>? log = null)
{
_getReveal = getReveal ?? throw new ArgumentNullException(nameof(getReveal));
_getEnvironmentOwnership = getEnvironmentOwnership
?? throw new ArgumentNullException(
nameof(getEnvironmentOwnership));
_getTransitOwnership = getTransitOwnership
?? throw new ArgumentNullException(nameof(getTransitOwnership));
_getPortalMaterializationCount = getPortalMaterializationCount
?? throw new ArgumentNullException(nameof(getPortalMaterializationCount));
_captureResources = captureResources ?? throw new ArgumentNullException(nameof(captureResources));
@ -289,6 +304,8 @@ internal sealed class WorldLifecycleAutomationController :
TimestampUtc: DateTime.UtcNow,
ProcessId: Environment.ProcessId,
Reveal: _getReveal(),
EnvironmentOwnership: _getEnvironmentOwnership(),
TransitOwnership: _getTransitOwnership(),
Render: outcome,
Resources: _captureResources(outcome));
string json = JsonSerializer.Serialize(checkpoint, JsonOptions);

View file

@ -126,7 +126,9 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
_actionView.Snapshot,
_movementView.Snapshot,
_environmentView.Snapshot,
_portalView.Snapshot);
_environmentView.Ownership,
_portalView.Snapshot,
_portalView.Ownership);
internal void Deactivate() => _active = false;
}

View file

@ -63,6 +63,9 @@ internal sealed class WorldGenerationQuiescence
private readonly Func<uint, RuntimeEntityKey?> _resolveProjectionKey;
private readonly IWorldAudioQuiescence? _audio;
private bool _effectsQuiesced;
private bool _selectionEdgeCommitted;
private bool _audioSuspended;
private bool _audioTransitionActive;
public WorldGenerationQuiescence(
SelectionState selection,
@ -99,26 +102,62 @@ internal sealed class WorldGenerationQuiescence
public void CommitBegin(in WorldGenerationQuiescenceEdge edge)
{
if (!edge.ShouldApply || _effectsQuiesced)
if (!edge.ShouldApply)
return;
_effectsQuiesced = true;
if (edge.ClearWorldSelection)
if (!_selectionEdgeCommitted)
{
_selection.Clear(
SelectionChangeSource.System,
SelectionChangeReason.Cleared);
// 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);
}
}
_audio?.SuspendWorldAudio();
ReconcileAudio();
}
public void ObserveReleased()
{
if (!_effectsQuiesced)
if (!_effectsQuiesced
&& !_selectionEdgeCommitted
&& !_audioSuspended)
{
return;
}
_effectsQuiesced = false;
_audio?.ResumeWorldAudio();
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;
}
}
}

View file

@ -37,13 +37,31 @@ internal interface IWorldRevealRenderResourceScheduler
/// </summary>
internal sealed class WorldRevealCoordinator
{
private sealed class HostProjection(
RuntimeWorldHostProjectionToken token,
int requiredRenderRadius,
in WorldGenerationQuiescenceEdge quiescenceEdge)
{
public RuntimeWorldHostProjectionToken Token { get; } = token;
public int RequiredRenderRadius { get; } = requiredRenderRadius;
public WorldGenerationQuiescenceEdge QuiescenceEdge { get; } =
quiescenceEdge;
public bool QuiescenceCommitted { get; set; }
public bool StreamingRegistered { get; set; }
public bool RenderResourcesRegistered { get; set; }
public bool SimulationReleaseProjected { get; set; }
public bool StreamingReleased { get; set; }
public bool RenderResourcesReleased { get; set; }
}
private readonly RuntimeWorldTransitState _transit;
private readonly WorldRevealReadinessBarrier _readiness;
private readonly WorldGenerationQuiescence? _quiescence;
private readonly IWorldRevealStreamingScheduler? _streaming;
private readonly IWorldRevealRenderResourceScheduler? _renderResources;
private long _reservationGeneration;
private bool _worldViewportReleased;
private readonly List<HostProjection> _hostProjections = [];
private bool _hostRetryActive;
private bool _hostRetryRequested;
public WorldRevealCoordinator(
RuntimeWorldTransitState transit,
@ -76,12 +94,15 @@ internal sealed class WorldRevealCoordinator
public int PortalMaterializationCount =>
_transit.Snapshot.PortalMaterializationCount;
public bool WaitCueShown => _transit.Snapshot.WaitCueShown;
public RuntimeWorldTransitOwnershipSnapshot Ownership =>
_transit.Ownership;
public long BeginLogin(uint destinationCell)
{
if (destinationCell == 0u)
throw new ArgumentOutOfRangeException(nameof(destinationCell));
WithdrawHostForReplacement();
WorldGenerationQuiescenceEdge quiescenceEdge =
_quiescence?.CaptureBegin() ?? default;
_readiness.Begin();
@ -100,7 +121,15 @@ internal sealed class WorldRevealCoordinator
{
if (destinationCell == 0u)
throw new ArgumentOutOfRangeException(nameof(destinationCell));
if (!_transit.CanBeginPortalReveal(
teleportSequence,
destinationCell))
{
generation = 0;
return false;
}
WithdrawHostForReplacement();
WorldGenerationQuiescenceEdge quiescenceEdge =
_quiescence?.CaptureBegin() ?? default;
if (!_transit.TryBeginPortalReveal(
@ -124,14 +153,21 @@ internal sealed class WorldRevealCoordinator
uint destinationCell,
in WorldGenerationQuiescenceEdge quiescenceEdge)
{
_quiescence?.CommitBegin(quiescenceEdge);
_reservationGeneration = generation;
_worldViewportReleased = false;
_streaming?.BeginDestinationReservation(
generation,
destinationCell,
WorldRevealReadinessBarrier.RequiredRenderRadius(destinationCell));
_renderResources?.BeginDestinationReveal(generation);
if (!_transit.TryRegisterHostProjection(
generation,
destinationCell,
out RuntimeWorldHostProjectionToken token))
{
throw new InvalidOperationException(
"Runtime rejected the exact reveal host projection.");
}
_hostProjections.Add(new HostProjection(
token,
WorldRevealReadinessBarrier.RequiredRenderRadius(
destinationCell),
quiescenceEdge));
RetryPendingHostWork();
}
/// <summary>
@ -146,6 +182,7 @@ internal sealed class WorldRevealCoordinator
public WorldRevealReadinessSnapshot Evaluate(uint destinationCell)
{
RetryPendingHostWork();
WorldRevealReadinessSnapshot snapshot = _readiness.Evaluate(destinationCell);
RuntimePortalSnapshot portal = _transit.Snapshot;
if (portal.Generation != 0)
@ -189,17 +226,17 @@ internal sealed class WorldRevealCoordinator
ushort teleportSequence,
uint destinationCell)
{
bool wasAvailable = _transit.IsWorldSimulationAvailable;
bool acknowledged = _transit.AcknowledgePortalMaterialized(
generation,
teleportSequence,
destinationCell);
ObserveSimulationRelease(wasAvailable);
RetryPendingHostWork();
return acknowledged;
}
public void ObserveWorldViewportVisible()
{
RetryPendingHostWork();
long generation = _transit.Snapshot.Generation;
if (generation != 0)
_transit.AcknowledgeWorldViewportVisible(generation);
@ -220,13 +257,12 @@ internal sealed class WorldRevealCoordinator
/// </summary>
public void RevealWorldViewport()
{
long generation = _reservationGeneration;
if (generation == 0 || _worldViewportReleased)
HostProjection? host = FindCurrentHostProjection();
if (host is null)
return;
_streaming?.EndDestinationReservation(generation);
_renderResources?.EndDestinationReveal(generation);
_worldViewportReleased = true;
_transit.RequireDestinationReservationRelease(host.Token);
RetryPendingHostWork();
}
public void Complete()
@ -235,10 +271,8 @@ internal sealed class WorldRevealCoordinator
if (generation == 0)
return;
bool wasAvailable = _transit.IsWorldSimulationAvailable;
_transit.Complete(generation);
ObserveSimulationRelease(wasAvailable);
EndHostLifetime();
RetryPendingHostWork();
}
public void Cancel()
@ -247,37 +281,284 @@ internal sealed class WorldRevealCoordinator
if (generation == 0)
return;
bool wasAvailable = _transit.IsWorldSimulationAvailable;
_transit.Cancel(generation);
ObserveSimulationRelease(wasAvailable);
EndHostLifetime();
RetryPendingHostWork();
}
public void ResetSession()
{
bool wasAvailable = _transit.IsWorldSimulationAvailable;
long generation = _transit.Snapshot.Generation;
if (generation != 0)
_transit.Cancel(generation);
ObserveSimulationRelease(wasAvailable);
EndHostLifetime();
RetryPendingHostWork();
_transit.ResetSession();
}
private void EndHostLifetime()
/// <summary>
/// Resumes the exact unfinished graphical-host suffix. Completed callback
/// stages never replay; a thrown callback leaves its Runtime
/// acknowledgement pending for the next call.
/// </summary>
internal void RetryPendingHostWork()
{
long generation = _reservationGeneration;
if (generation == 0)
_hostRetryRequested = true;
if (_hostRetryActive)
return;
RevealWorldViewport();
_reservationGeneration = 0;
_worldViewportReleased = false;
_hostRetryActive = true;
try
{
do
{
_hostRetryRequested = false;
int index = 0;
while (index < _hostProjections.Count)
{
HostProjection host = _hostProjections[index];
DrainHostProjection(host);
if (index < _hostProjections.Count
&& ReferenceEquals(
_hostProjections[index],
host))
{
index++;
}
}
}
while (_hostRetryRequested);
}
finally
{
_hostRetryActive = false;
}
}
private void ObserveSimulationRelease(bool wasAvailable)
private void DrainHostProjection(HostProjection host)
{
if (!wasAvailable && _transit.IsWorldSimulationAvailable)
if (!TryGetHostProjection(host, out var projection))
return;
RuntimeWorldHostAcknowledgementStage pending =
projection.PendingAcknowledgements;
if ((pending & RuntimeWorldHostAcknowledgementStage
.ProjectionRegistered) != 0)
{
CompleteHostRegistration(host);
}
if (!TryGetHostProjection(host, out projection))
return;
pending = projection.PendingAcknowledgements;
if ((pending & RuntimeWorldHostAcknowledgementStage
.SimulationReleaseProjected) != 0)
{
CompleteSimulationRelease(host);
}
if (!TryGetHostProjection(host, out projection))
return;
pending = projection.PendingAcknowledgements;
if ((pending & RuntimeWorldHostAcknowledgementStage
.DestinationReservationReleased) != 0)
{
CompleteDestinationReservationRelease(host);
}
if (!TryGetHostProjection(host, out projection))
return;
if ((projection.PendingAcknowledgements
& RuntimeWorldHostAcknowledgementStage.TerminalProjected)
== 0)
{
return;
}
Acknowledge(
host,
RuntimeWorldHostAcknowledgementStage.TerminalProjected);
RemoveHostProjection(host);
}
private void CompleteHostRegistration(HostProjection host)
{
if (!host.QuiescenceCommitted)
{
_quiescence?.CommitBegin(host.QuiescenceEdge);
host.QuiescenceCommitted = true;
}
if (!IsStagePending(
host,
RuntimeWorldHostAcknowledgementStage
.ProjectionRegistered))
{
return;
}
if (!host.StreamingRegistered)
{
_streaming?.BeginDestinationReservation(
host.Token.Generation,
host.Token.DestinationCell,
host.RequiredRenderRadius);
host.StreamingRegistered = true;
}
if (!IsStagePending(
host,
RuntimeWorldHostAcknowledgementStage
.ProjectionRegistered))
{
return;
}
if (!host.RenderResourcesRegistered)
{
_renderResources?.BeginDestinationReveal(
host.Token.Generation);
host.RenderResourcesRegistered = true;
}
if (!IsStagePending(
host,
RuntimeWorldHostAcknowledgementStage
.ProjectionRegistered))
{
return;
}
Acknowledge(
host,
RuntimeWorldHostAcknowledgementStage.ProjectionRegistered);
}
private void CompleteSimulationRelease(HostProjection host)
{
if (!host.SimulationReleaseProjected)
{
_quiescence?.ObserveReleased();
host.SimulationReleaseProjected = true;
}
if (!IsStagePending(
host,
RuntimeWorldHostAcknowledgementStage
.SimulationReleaseProjected))
{
return;
}
Acknowledge(
host,
RuntimeWorldHostAcknowledgementStage
.SimulationReleaseProjected);
}
private void CompleteDestinationReservationRelease(HostProjection host)
{
if (!host.StreamingReleased)
{
if (host.StreamingRegistered)
{
_streaming?.EndDestinationReservation(
host.Token.Generation);
}
host.StreamingReleased = true;
}
if (!IsStagePending(
host,
RuntimeWorldHostAcknowledgementStage
.DestinationReservationReleased))
{
return;
}
if (!host.RenderResourcesReleased)
{
if (host.RenderResourcesRegistered)
{
_renderResources?.EndDestinationReveal(
host.Token.Generation);
}
host.RenderResourcesReleased = true;
}
if (!IsStagePending(
host,
RuntimeWorldHostAcknowledgementStage
.DestinationReservationReleased))
{
return;
}
Acknowledge(
host,
RuntimeWorldHostAcknowledgementStage
.DestinationReservationReleased);
}
private void WithdrawHostForReplacement()
{
HostProjection? host = FindCurrentHostProjection();
if (host is null)
return;
if (!_transit.BeginHostProjectionSupersession(host.Token))
{
throw new InvalidOperationException(
"Runtime rejected reveal-host supersession.");
}
RetryPendingHostWork();
}
private HostProjection? FindCurrentHostProjection()
{
long generation = _transit.Snapshot.Generation;
for (int i = 0; i < _hostProjections.Count; i++)
{
HostProjection candidate = _hostProjections[i];
if (candidate.Token.Generation == generation)
return candidate;
}
return null;
}
private bool TryGetHostProjection(
HostProjection host,
out RuntimeWorldHostProjectionSnapshot projection)
{
if (_transit.TryGetHostProjection(host.Token, out projection))
return true;
RemoveHostProjection(host);
return false;
}
private bool IsStagePending(
HostProjection host,
RuntimeWorldHostAcknowledgementStage stage) =>
_transit.TryGetHostProjection(
host.Token,
out RuntimeWorldHostProjectionSnapshot projection)
&& (projection.PendingAcknowledgements & stage) != 0;
private void RemoveHostProjection(HostProjection host)
{
for (int i = 0; i < _hostProjections.Count; i++)
{
if (!ReferenceEquals(_hostProjections[i], host))
continue;
_hostProjections.RemoveAt(i);
return;
}
}
private void Acknowledge(
HostProjection host,
RuntimeWorldHostAcknowledgementStage stage)
{
if (!_transit.AcknowledgeHostProjection(
new RuntimeWorldHostAcknowledgement(
host.Token,
stage)))
{
throw new InvalidOperationException(
$"Runtime rejected reveal host acknowledgement {stage} "
+ $"for generation {host.Token.Generation}.");
}
}
}