refactor(runtime): acknowledge exact world host projections
This commit is contained in:
parent
73d0b54e38
commit
18d17d8bb1
17 changed files with 1677 additions and 72 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,8 +192,19 @@ public sealed class RuntimeTraceRecorder : IRuntimeEventObserver
|
|||
$"{checkpoint.Environment.ActiveDayGroupIndex}:" +
|
||||
$"{(int)checkpoint.Environment.Weather}:" +
|
||||
$"{(int)checkpoint.Environment.EnvironOverride};" +
|
||||
$"environment-owner={checkpoint.EnvironmentOwnership.IsInitialized}:" +
|
||||
$"{checkpoint.EnvironmentOwnership.DayGroupDefinitionCount}:" +
|
||||
$"{checkpoint.EnvironmentOwnership.ActiveDayGroupCount};" +
|
||||
$"portal={checkpoint.Portal.Generation}:{(int)checkpoint.Portal.Kind}:" +
|
||||
$"{checkpoint.Portal.DestinationCell:X8}:{checkpoint.Portal.IsReady}")));
|
||||
$"{checkpoint.Portal.DestinationCell:X8}:{checkpoint.Portal.IsReady};" +
|
||||
$"transit-owner={checkpoint.TransitOwnership.BufferedTeleportDestinationCount}:" +
|
||||
$"{checkpoint.TransitOwnership.PendingTeleportStartCount}:" +
|
||||
$"{checkpoint.TransitOwnership.ActiveTeleportCount}:" +
|
||||
$"{checkpoint.TransitOwnership.AcceptedTeleportDestinationCount}:" +
|
||||
$"{checkpoint.TransitOwnership.ActiveRevealCount}:" +
|
||||
$"{checkpoint.TransitOwnership.PendingDestinationReadinessCount}:" +
|
||||
$"{checkpoint.TransitOwnership.HostProjectionCount}:" +
|
||||
$"{checkpoint.TransitOwnership.PendingHostAcknowledgementCount}")));
|
||||
}
|
||||
|
||||
public void OnLifecycle(in RuntimeLifecycleDelta delta) =>
|
||||
|
|
|
|||
|
|
@ -128,6 +128,55 @@ public readonly record struct RuntimeDestinationReadiness(
|
|||
&& IsCollisionReady));
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum RuntimeWorldHostAcknowledgementStage
|
||||
{
|
||||
None = 0,
|
||||
ProjectionRegistered = 1 << 0,
|
||||
SimulationReleaseProjected = 1 << 1,
|
||||
DestinationReservationReleased = 1 << 2,
|
||||
TerminalProjected = 1 << 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exact identity of one graphical or direct host projection attached to a
|
||||
/// Runtime reveal generation. It contains no presentation object.
|
||||
/// </summary>
|
||||
public readonly record struct RuntimeWorldHostProjectionToken(
|
||||
long Generation,
|
||||
uint DestinationCell)
|
||||
{
|
||||
public bool IsValid => Generation != 0 && DestinationCell != 0u;
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeWorldHostAcknowledgement(
|
||||
RuntimeWorldHostProjectionToken Projection,
|
||||
RuntimeWorldHostAcknowledgementStage Stage);
|
||||
|
||||
public readonly record struct RuntimeWorldHostProjectionSnapshot(
|
||||
RuntimeWorldHostProjectionToken Token,
|
||||
RuntimeWorldHostAcknowledgementStage PendingAcknowledgements,
|
||||
bool ProjectionRegistered,
|
||||
bool SimulationReleaseProjected,
|
||||
bool DestinationReservationReleased,
|
||||
bool IsSuperseding)
|
||||
{
|
||||
public int PendingAcknowledgementCount =>
|
||||
Count(PendingAcknowledgements);
|
||||
|
||||
private static int Count(RuntimeWorldHostAcknowledgementStage stages)
|
||||
{
|
||||
int value = (int)stages;
|
||||
int count = 0;
|
||||
while (value != 0)
|
||||
{
|
||||
value &= value - 1;
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct RuntimePortalSnapshot(
|
||||
long Generation,
|
||||
RuntimePortalKind Kind,
|
||||
|
|
@ -166,6 +215,7 @@ public readonly record struct RuntimePortalSnapshot(
|
|||
public interface IRuntimePortalView
|
||||
{
|
||||
RuntimePortalSnapshot Snapshot { get; }
|
||||
RuntimeWorldTransitOwnershipSnapshot Ownership { get; }
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeStateCheckpoint(
|
||||
|
|
@ -184,7 +234,9 @@ public readonly record struct RuntimeStateCheckpoint(
|
|||
RuntimeActionSnapshot Actions,
|
||||
RuntimeMovementSnapshot Movement,
|
||||
RuntimeWorldEnvironmentSnapshot Environment,
|
||||
RuntimePortalSnapshot Portal);
|
||||
RuntimeWorldEnvironmentOwnershipSnapshot EnvironmentOwnership,
|
||||
RuntimePortalSnapshot Portal,
|
||||
RuntimeWorldTransitOwnershipSnapshot TransitOwnership);
|
||||
|
||||
public interface IGameRuntimeView
|
||||
{
|
||||
|
|
|
|||
|
|
@ -96,9 +96,15 @@ public readonly record struct RuntimeWorldEnvironmentSnapshot(
|
|||
WeatherKind Weather,
|
||||
EnvironOverride EnvironOverride);
|
||||
|
||||
public readonly record struct RuntimeWorldEnvironmentOwnershipSnapshot(
|
||||
bool IsInitialized,
|
||||
int DayGroupDefinitionCount,
|
||||
int ActiveDayGroupCount);
|
||||
|
||||
public interface IRuntimeWorldEnvironmentView
|
||||
{
|
||||
RuntimeWorldEnvironmentSnapshot Snapshot { get; }
|
||||
RuntimeWorldEnvironmentOwnershipSnapshot Ownership { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -152,6 +158,15 @@ public sealed class RuntimeWorldEnvironmentState
|
|||
Weather.Kind,
|
||||
Weather.Override);
|
||||
|
||||
public RuntimeWorldEnvironmentOwnershipSnapshot Ownership =>
|
||||
CaptureOwnership();
|
||||
|
||||
public RuntimeWorldEnvironmentOwnershipSnapshot CaptureOwnership() =>
|
||||
new(
|
||||
IsInitialized,
|
||||
_definition?.DayGroups.Count ?? 0,
|
||||
ActiveDayGroupIndex >= 0 ? 1 : 0);
|
||||
|
||||
public void Initialize(RuntimeWorldEnvironmentDefinition definition)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(definition);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,27 @@ using System.Globalization;
|
|||
|
||||
namespace AcDream.Runtime.World;
|
||||
|
||||
public readonly record struct RuntimeWorldTransitOwnershipSnapshot(
|
||||
int BufferedTeleportDestinationCount,
|
||||
int PendingTeleportStartCount,
|
||||
int ActiveTeleportCount,
|
||||
int AcceptedTeleportDestinationCount,
|
||||
int ActiveRevealCount,
|
||||
int PendingDestinationReadinessCount,
|
||||
int HostProjectionCount,
|
||||
int PendingHostAcknowledgementCount)
|
||||
{
|
||||
public bool IsSessionIdle =>
|
||||
BufferedTeleportDestinationCount == 0
|
||||
&& PendingTeleportStartCount == 0
|
||||
&& ActiveTeleportCount == 0
|
||||
&& AcceptedTeleportDestinationCount == 0
|
||||
&& ActiveRevealCount == 0
|
||||
&& PendingDestinationReadinessCount == 0
|
||||
&& HostProjectionCount == 0
|
||||
&& PendingHostAcknowledgementCount == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Canonical presentation-independent owner of local F751/Position
|
||||
/// correlation and one login/portal reveal generation. The graphical or
|
||||
|
|
@ -21,12 +42,34 @@ namespace AcDream.Runtime.World;
|
|||
public sealed class RuntimeWorldTransitState
|
||||
: IRuntimePortalView
|
||||
{
|
||||
private sealed class HostProjectionRecord(
|
||||
RuntimeWorldHostProjectionToken token)
|
||||
{
|
||||
public RuntimeWorldHostProjectionToken Token { get; } = token;
|
||||
public RuntimeWorldHostAcknowledgementStage Pending { get; set; } =
|
||||
RuntimeWorldHostAcknowledgementStage.ProjectionRegistered;
|
||||
public bool ProjectionRegistered { get; set; }
|
||||
public bool SimulationReleaseProjected { get; set; }
|
||||
public bool DestinationReservationReleased { get; set; }
|
||||
public bool IsSuperseding { get; set; }
|
||||
|
||||
public RuntimeWorldHostProjectionSnapshot Snapshot => new(
|
||||
Token,
|
||||
Pending,
|
||||
ProjectionRegistered,
|
||||
SimulationReleaseProjected,
|
||||
DestinationReservationReleased,
|
||||
IsSuperseding);
|
||||
}
|
||||
|
||||
public static readonly TimeSpan RetailWaitCueDelay =
|
||||
TimeSpan.FromSeconds(5);
|
||||
|
||||
private readonly Action<string> _log;
|
||||
private readonly Dictionary<ushort, RuntimeTeleportDestination>
|
||||
_bufferedDestinations = [];
|
||||
private readonly Dictionary<long, HostProjectionRecord>
|
||||
_hostProjections = [];
|
||||
private RuntimePortalSnapshot _snapshot = RuntimePortalSnapshot.Idle;
|
||||
private long _nextGeneration;
|
||||
private bool _hasLastTeleportStart;
|
||||
|
|
@ -45,6 +88,8 @@ public sealed class RuntimeWorldTransitState
|
|||
}
|
||||
|
||||
public RuntimePortalSnapshot Snapshot => _snapshot;
|
||||
public RuntimeWorldTransitOwnershipSnapshot Ownership =>
|
||||
CaptureOwnership();
|
||||
public bool IsWorldSimulationAvailable =>
|
||||
_snapshot.WorldSimulationAvailable;
|
||||
public bool IsTeleportActive => _teleportActive;
|
||||
|
|
@ -57,6 +102,30 @@ public sealed class RuntimeWorldTransitState
|
|||
public int DiagnosticFailureCount { get; private set; }
|
||||
public Exception? LastDiagnosticFailure { get; private set; }
|
||||
|
||||
public RuntimeWorldTransitOwnershipSnapshot CaptureOwnership()
|
||||
{
|
||||
int pendingHostAcknowledgements = 0;
|
||||
foreach (HostProjectionRecord host in _hostProjections.Values)
|
||||
{
|
||||
pendingHostAcknowledgements = checked(
|
||||
pendingHostAcknowledgements
|
||||
+ host.Snapshot.PendingAcknowledgementCount);
|
||||
}
|
||||
|
||||
bool revealActive = _snapshot.Generation != 0
|
||||
&& !_snapshot.Completed
|
||||
&& !_snapshot.Cancelled;
|
||||
return new RuntimeWorldTransitOwnershipSnapshot(
|
||||
_bufferedDestinations.Count,
|
||||
_hasPendingTeleportStart ? 1 : 0,
|
||||
_teleportActive ? 1 : 0,
|
||||
_hasAcceptedDestination ? 1 : 0,
|
||||
revealActive ? 1 : 0,
|
||||
revealActive && !_snapshot.IsReady ? 1 : 0,
|
||||
_hostProjections.Count,
|
||||
pendingHostAcknowledgements);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts initial-login reveal. Portal reveals must instead claim the
|
||||
/// exact F751-correlated destination through
|
||||
|
|
@ -69,15 +138,30 @@ public sealed class RuntimeWorldTransitState
|
|||
/// Atomically claims the one accepted Position paired with the active F751
|
||||
/// sequence and starts its reveal generation.
|
||||
/// </summary>
|
||||
public bool CanBeginPortalReveal(
|
||||
ushort teleportSequence,
|
||||
uint destinationCell)
|
||||
{
|
||||
if (!_teleportActive
|
||||
|| teleportSequence != _activeTeleportSequence
|
||||
|| !_hasAcceptedDestination)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RuntimeTeleportDestination destination = _acceptedDestination;
|
||||
return destination.TeleportSequence == teleportSequence
|
||||
&& destination.CellId != 0u
|
||||
&& destination.CellId == destinationCell;
|
||||
}
|
||||
|
||||
public bool TryBeginPortalReveal(
|
||||
ushort teleportSequence,
|
||||
uint destinationCell,
|
||||
out long generation)
|
||||
{
|
||||
generation = 0;
|
||||
if (!_teleportActive
|
||||
|| teleportSequence != _activeTeleportSequence
|
||||
|| !_hasAcceptedDestination)
|
||||
if (!CanBeginPortalReveal(teleportSequence, destinationCell))
|
||||
{
|
||||
LogRejected(
|
||||
"portal-begin-without-destination",
|
||||
|
|
@ -89,19 +173,6 @@ public sealed class RuntimeWorldTransitState
|
|||
}
|
||||
|
||||
RuntimeTeleportDestination destination = _acceptedDestination;
|
||||
if (destination.TeleportSequence != teleportSequence
|
||||
|| destination.CellId == 0u
|
||||
|| destination.CellId != destinationCell)
|
||||
{
|
||||
FailInvariant(
|
||||
"portal-begin-destination-mismatch",
|
||||
$"sequence={teleportSequence} "
|
||||
+ $"acceptedSequence={destination.TeleportSequence} "
|
||||
+ $"expectedCell=0x{destination.CellId:X8} "
|
||||
+ $"actualCell=0x{destinationCell:X8}");
|
||||
return false;
|
||||
}
|
||||
|
||||
generation = BeginRevealCore(
|
||||
RuntimePortalKind.Portal,
|
||||
destinationCell);
|
||||
|
|
@ -110,6 +181,159 @@ public sealed class RuntimeWorldTransitState
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers one presentation-independent host projection against the
|
||||
/// exact active reveal. Graphical and direct hosts receive the same token.
|
||||
/// </summary>
|
||||
public bool TryRegisterHostProjection(
|
||||
long generation,
|
||||
uint destinationCell,
|
||||
out RuntimeWorldHostProjectionToken token)
|
||||
{
|
||||
token = new RuntimeWorldHostProjectionToken(
|
||||
generation,
|
||||
destinationCell);
|
||||
if (!token.IsValid
|
||||
|| generation != _snapshot.Generation
|
||||
|| destinationCell != _snapshot.DestinationCell
|
||||
|| _snapshot.Cancelled
|
||||
|| _snapshot.Completed)
|
||||
{
|
||||
LogRejected(
|
||||
"host-register-mismatch",
|
||||
$"generation={generation} "
|
||||
+ $"cell=0x{destinationCell:X8}");
|
||||
token = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_hostProjections.TryGetValue(
|
||||
generation,
|
||||
out HostProjectionRecord? existing))
|
||||
{
|
||||
if (existing.Token == token)
|
||||
return true;
|
||||
|
||||
LogRejected(
|
||||
"host-register-conflict",
|
||||
$"generation={generation} "
|
||||
+ $"expectedCell=0x{existing.Token.DestinationCell:X8} "
|
||||
+ $"actualCell=0x{destinationCell:X8}");
|
||||
token = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
_hostProjections.Add(
|
||||
generation,
|
||||
new HostProjectionRecord(token));
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetHostProjection(
|
||||
in RuntimeWorldHostProjectionToken token,
|
||||
out RuntimeWorldHostProjectionSnapshot projection)
|
||||
{
|
||||
if (token.IsValid
|
||||
&& _hostProjections.TryGetValue(
|
||||
token.Generation,
|
||||
out HostProjectionRecord? host)
|
||||
&& host.Token == token)
|
||||
{
|
||||
projection = host.Snapshot;
|
||||
return true;
|
||||
}
|
||||
|
||||
projection = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the graphical reservation-release callback as due before the
|
||||
/// host executes it. A thrown callback therefore leaves an exact pending
|
||||
/// acknowledgement instead of losing the unfinished suffix.
|
||||
/// </summary>
|
||||
public bool RequireDestinationReservationRelease(
|
||||
in RuntimeWorldHostProjectionToken token)
|
||||
{
|
||||
if (!TryGetHostRecord(token, out HostProjectionRecord? host))
|
||||
return false;
|
||||
if (host.DestinationReservationReleased)
|
||||
return false;
|
||||
|
||||
host.Pending |= RuntimeWorldHostAcknowledgementStage
|
||||
.DestinationReservationReleased;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an older host projection into an exact withdrawal transaction
|
||||
/// while a replacement reveal keeps world simulation quiesced.
|
||||
/// </summary>
|
||||
public bool BeginHostProjectionSupersession(
|
||||
in RuntimeWorldHostProjectionToken token)
|
||||
{
|
||||
if (!TryGetHostRecord(token, out HostProjectionRecord? host))
|
||||
return false;
|
||||
|
||||
BeginHostProjectionSupersession(host);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool AcknowledgeHostProjection(
|
||||
in RuntimeWorldHostAcknowledgement acknowledgement)
|
||||
{
|
||||
RuntimeWorldHostAcknowledgementStage stage =
|
||||
acknowledgement.Stage;
|
||||
if (!IsSingleStage(stage)
|
||||
|| !TryGetHostRecord(
|
||||
acknowledgement.Projection,
|
||||
out HostProjectionRecord? host)
|
||||
|| (host.Pending & stage) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (stage)
|
||||
{
|
||||
case RuntimeWorldHostAcknowledgementStage.ProjectionRegistered:
|
||||
if (host.IsSuperseding)
|
||||
return false;
|
||||
host.ProjectionRegistered = true;
|
||||
break;
|
||||
case RuntimeWorldHostAcknowledgementStage
|
||||
.SimulationReleaseProjected:
|
||||
if (host.Token.Generation == _snapshot.Generation
|
||||
&& !_snapshot.WorldSimulationAvailable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
host.SimulationReleaseProjected = true;
|
||||
break;
|
||||
case RuntimeWorldHostAcknowledgementStage
|
||||
.DestinationReservationReleased:
|
||||
host.DestinationReservationReleased = true;
|
||||
break;
|
||||
case RuntimeWorldHostAcknowledgementStage.TerminalProjected:
|
||||
if (!host.IsSuperseding
|
||||
&& (host.Token.Generation != _snapshot.Generation
|
||||
|| (!_snapshot.Completed
|
||||
&& !_snapshot.Cancelled)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((host.Pending & ~stage) != 0)
|
||||
return false;
|
||||
host.Pending &= ~stage;
|
||||
_hostProjections.Remove(host.Token.Generation);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
host.Pending &= ~stage;
|
||||
return true;
|
||||
}
|
||||
|
||||
private long BeginRevealCore(
|
||||
RuntimePortalKind kind,
|
||||
uint destinationCell)
|
||||
|
|
@ -125,6 +349,12 @@ public sealed class RuntimeWorldTransitState
|
|||
{
|
||||
Log("superseded", _snapshot);
|
||||
}
|
||||
if (_hostProjections.TryGetValue(
|
||||
_snapshot.Generation,
|
||||
out HostProjectionRecord? superseded))
|
||||
{
|
||||
BeginHostProjectionSupersession(superseded);
|
||||
}
|
||||
|
||||
long generation = checked(++_nextGeneration);
|
||||
int failures = _snapshot.InvariantFailureCount;
|
||||
|
|
@ -372,6 +602,10 @@ public sealed class RuntimeWorldTransitState
|
|||
WorldSimulationAvailable = true,
|
||||
PortalMaterializationCount = count,
|
||||
};
|
||||
RequireHostStage(
|
||||
generation,
|
||||
RuntimeWorldHostAcknowledgementStage
|
||||
.SimulationReleaseProjected);
|
||||
Log("materialized", _snapshot);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -444,11 +678,16 @@ public sealed class RuntimeWorldTransitState
|
|||
return false;
|
||||
}
|
||||
|
||||
bool simulationWasAvailable =
|
||||
_snapshot.WorldSimulationAvailable;
|
||||
_snapshot = _snapshot with
|
||||
{
|
||||
Completed = true,
|
||||
WorldSimulationAvailable = true,
|
||||
};
|
||||
RequireTerminalHostProjection(
|
||||
generation,
|
||||
requireSimulationRelease: !simulationWasAvailable);
|
||||
Log("complete", _snapshot);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -463,11 +702,16 @@ public sealed class RuntimeWorldTransitState
|
|||
return false;
|
||||
}
|
||||
|
||||
bool simulationWasAvailable =
|
||||
_snapshot.WorldSimulationAvailable;
|
||||
_snapshot = _snapshot with
|
||||
{
|
||||
Cancelled = true,
|
||||
WorldSimulationAvailable = true,
|
||||
};
|
||||
RequireTerminalHostProjection(
|
||||
generation,
|
||||
requireSimulationRelease: !simulationWasAvailable);
|
||||
Log("cancel", _snapshot);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -479,6 +723,17 @@ public sealed class RuntimeWorldTransitState
|
|||
/// </summary>
|
||||
public void ResetSession()
|
||||
{
|
||||
if (_hostProjections.Count != 0)
|
||||
{
|
||||
RuntimeWorldTransitOwnershipSnapshot ownership =
|
||||
CaptureOwnership();
|
||||
throw new InvalidOperationException(
|
||||
"World transit cannot reset while a host projection "
|
||||
+ "still owns an acknowledgement suffix "
|
||||
+ $"(hosts={ownership.HostProjectionCount}, "
|
||||
+ $"pending={ownership.PendingHostAcknowledgementCount}).");
|
||||
}
|
||||
|
||||
_snapshot = RuntimePortalSnapshot.Idle;
|
||||
EndTeleport();
|
||||
_hasLastTeleportStart = false;
|
||||
|
|
@ -486,6 +741,105 @@ public sealed class RuntimeWorldTransitState
|
|||
_bufferedDestinations.Clear();
|
||||
}
|
||||
|
||||
private bool TryGetHostRecord(
|
||||
in RuntimeWorldHostProjectionToken token,
|
||||
out HostProjectionRecord host)
|
||||
{
|
||||
if (token.IsValid
|
||||
&& _hostProjections.TryGetValue(
|
||||
token.Generation,
|
||||
out HostProjectionRecord? candidate)
|
||||
&& candidate.Token == token)
|
||||
{
|
||||
host = candidate;
|
||||
return true;
|
||||
}
|
||||
|
||||
host = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void RequireHostStage(
|
||||
long generation,
|
||||
RuntimeWorldHostAcknowledgementStage stage)
|
||||
{
|
||||
if (!_hostProjections.TryGetValue(
|
||||
generation,
|
||||
out HostProjectionRecord? host))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool alreadyAcknowledged = stage switch
|
||||
{
|
||||
RuntimeWorldHostAcknowledgementStage.ProjectionRegistered =>
|
||||
host.ProjectionRegistered,
|
||||
RuntimeWorldHostAcknowledgementStage
|
||||
.SimulationReleaseProjected =>
|
||||
host.SimulationReleaseProjected,
|
||||
RuntimeWorldHostAcknowledgementStage
|
||||
.DestinationReservationReleased =>
|
||||
host.DestinationReservationReleased,
|
||||
_ => false,
|
||||
};
|
||||
if (!alreadyAcknowledged)
|
||||
host.Pending |= stage;
|
||||
}
|
||||
|
||||
private void RequireTerminalHostProjection(
|
||||
long generation,
|
||||
bool requireSimulationRelease)
|
||||
{
|
||||
if (!_hostProjections.TryGetValue(
|
||||
generation,
|
||||
out HostProjectionRecord? host))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// A host that is terminating no longer has to finish acquiring a
|
||||
// projection. It must instead release every resource it did acquire.
|
||||
host.Pending &=
|
||||
~RuntimeWorldHostAcknowledgementStage.ProjectionRegistered;
|
||||
if (requireSimulationRelease
|
||||
&& !host.SimulationReleaseProjected)
|
||||
{
|
||||
host.Pending |= RuntimeWorldHostAcknowledgementStage
|
||||
.SimulationReleaseProjected;
|
||||
}
|
||||
if (!host.DestinationReservationReleased)
|
||||
{
|
||||
host.Pending |= RuntimeWorldHostAcknowledgementStage
|
||||
.DestinationReservationReleased;
|
||||
}
|
||||
host.Pending |=
|
||||
RuntimeWorldHostAcknowledgementStage.TerminalProjected;
|
||||
}
|
||||
|
||||
private static void BeginHostProjectionSupersession(
|
||||
HostProjectionRecord host)
|
||||
{
|
||||
host.IsSuperseding = true;
|
||||
host.Pending &=
|
||||
~(RuntimeWorldHostAcknowledgementStage.ProjectionRegistered
|
||||
| RuntimeWorldHostAcknowledgementStage
|
||||
.SimulationReleaseProjected);
|
||||
if (!host.DestinationReservationReleased)
|
||||
{
|
||||
host.Pending |= RuntimeWorldHostAcknowledgementStage
|
||||
.DestinationReservationReleased;
|
||||
}
|
||||
host.Pending |=
|
||||
RuntimeWorldHostAcknowledgementStage.TerminalProjected;
|
||||
}
|
||||
|
||||
private static bool IsSingleStage(
|
||||
RuntimeWorldHostAcknowledgementStage stage)
|
||||
{
|
||||
int value = (int)stage;
|
||||
return value != 0 && (value & (value - 1)) == 0;
|
||||
}
|
||||
|
||||
private bool IsCurrentPortalDestination(
|
||||
long generation,
|
||||
ushort teleportSequence,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue