refactor(runtime): unify generation reset for direct hosts

Move canonical per-session teardown into one retryable Runtime transaction, reduce App reset to projection acknowledgements, and prove the same GameRuntime graph through deterministic no-window lifecycle, gameplay, portal, fault, reconnect, and isolation gates.\n\nCo-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-27 00:43:26 +02:00
parent 7818494116
commit a9a822f206
28 changed files with 2707 additions and 345 deletions

View file

@ -2230,6 +2230,76 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
IsCurrentRecord(expectedRecord)
&& expectedRecord.CreateIntegrationVersion == expectedCreateIntegrationVersion;
/// <summary>
/// Retires only the graphical sidecar for an exact Runtime-owned
/// incarnation. The Runtime generation-reset transaction retains and
/// completes the canonical tombstone after this acknowledgement returns.
/// </summary>
internal void RetireGenerationProjection(
RuntimeEntityRecord canonical)
{
ArgumentNullException.ThrowIfNull(canonical);
if (_isClearing || _isRegisteringResources)
{
throw new InvalidOperationException(
_isClearing
? "Live entity projection teardown is already in progress."
: "Live entity projection teardown cannot begin inside atomic resource registration.");
}
LiveEntityRecord? record = null;
if (_projections.TryGet(canonical, out LiveEntityRecord active))
{
if (!_projections.RemoveActive(active))
{
throw new InvalidOperationException(
$"Exact App projection for 0x{canonical.ServerGuid:X8}/{canonical.Incarnation} could not be retired during generation reset.");
}
_projections.RetainTeardown(active);
record = active;
}
else if (_projections.TryGetTeardown(
canonical,
out LiveEntityRecord retained))
{
record = retained;
}
if (record is null)
return;
_isClearing = true;
try
{
TearDownRecord(record, completeCanonical: false);
_projections.ReleaseTeardown(record);
}
finally
{
_isClearing = false;
}
}
/// <summary>
/// Completes the App-only projection boundary after Runtime has retired
/// every exact canonical incarnation. No canonical owner is mutated here.
/// </summary>
internal void CompleteGenerationProjectionRetirement()
{
if (_projections.ActiveCount != 0
|| _projections.TeardownCount != 0)
{
throw new InvalidOperationException(
"Live entity projection storage has not converged at the generation-reset boundary.");
}
_spatialAnimations.Clear();
_physics.ClearSpatialWorksets();
_projections.ClearConverged();
_spatial.ClearLiveEntityLifetimeState();
_sessionClearPendingFinalization = false;
}
public void Clear()
{
if (_isClearing || _isRegisteringResources)
@ -2670,7 +2740,9 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
_sessionClearPendingFinalization = false;
}
private void TearDownRecord(LiveEntityRecord record)
private void TearDownRecord(
LiveEntityRecord record,
bool completeCanonical = true)
{
if (record.TeardownInProgress)
throw new InvalidOperationException(
@ -2730,8 +2802,11 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
if (record.WorldEntity is not null)
_ = RequireProjectionKey(record);
_entityObjects.CompleteProjectionRetirement(
record.Canonical);
if (completeCanonical)
{
_entityObjects.CompleteProjectionRetirement(
record.Canonical);
}
record.AnimationRuntime = null;
record.EffectProfile = null;