feat(runtime): atomically replace collision generations

This commit is contained in:
Erik 2026-08-01 17:33:34 +02:00
parent 99bf1751bb
commit 9b0f59bd1b
21 changed files with 2435 additions and 408 deletions

View file

@ -70,15 +70,46 @@ public sealed class LandblockPhysicsPublication : IDisposable
internal int RefloodCursor { get; set; }
internal bool RefloodCommitted { get; set; }
internal bool SealCommitted { get; set; }
internal bool EngineMutationCommitted { get; set; }
/// <summary>
/// The last Runtime mutation poll was nonterminal. This includes exact
/// placement acknowledgements, collision-report/shadow dispatch debt, and
/// Runtime's deliberate first quiescence poll.
/// </summary>
internal bool RuntimeMutationPending { get; set; }
internal bool BeginCommitted { get; set; }
internal bool CompletionCommitted { get; set; }
internal bool CancellationRequested { get; private set; }
public void Dispose()
{
if (!CompletionCommitted)
Physics.CancelCollisionGeneration(
CollisionAdmission,
PreparedGeneration);
if (!TryCancel())
{
throw new InvalidOperationException(
"Collision publication cancellation is waiting for exact placement acknowledgements and must remain retained.");
}
}
internal bool TryCancel()
{
if (CompletionCommitted)
return true;
CancellationRequested = true;
for (int poll = 0; poll < 2; poll++)
{
if (Physics.CancelCollisionGeneration(
CollisionAdmission,
PreparedGeneration))
{
return true;
}
if (Physics.CaptureOwnership()
.PendingCollisionPrefixProjectionCount != 0)
{
return false;
}
}
return false;
}
public uint LandblockId => Build.Landblock.LandblockId;
@ -246,9 +277,14 @@ public sealed class LandblockPhysicsPublisher
publication.SetupObjectIds);
return publication;
}
catch
catch (Exception publicationError)
{
_physics.CancelCollisionGeneration(collisionAdmission, prepared);
if (!_physics.CancelCollisionGeneration(collisionAdmission, prepared))
{
throw new AggregateException(
"Collision preparation failed and its pre-engine cancellation did not converge.",
publicationError);
}
throw;
}
}
@ -416,7 +452,7 @@ public sealed class LandblockPhysicsPublisher
/// presentation pipeline supplies the static-presentation owner callback
/// that preserves per-entity light-before-collision order.
/// </summary>
public void CompletePublication(
public bool CompletePublication(
LandblockPhysicsPublication publication,
Action<WorldEntity>? beforeStaticCollision = null)
{
@ -426,7 +462,29 @@ public sealed class LandblockPhysicsPublisher
"Physics publication cannot complete before its prefix commits.");
while (!AdvanceCompleteOne(publication, beforeStaticCollision))
{
if (publication.RuntimeMutationPending)
{
if (!CanContinueMutationSynchronously())
return false;
// The activation transaction deliberately closes its exact
// quiescence boundary on one poll and transfers the engine on
// the next. This synchronous compatibility API may consume
// that finite internal suffix; the frame-budgeted pipeline
// always yields on the first nonterminal commit below.
publication.RuntimeMutationPending = false;
}
}
return true;
}
internal bool CanContinueMutationSynchronously()
{
RuntimePhysicsOwnershipSnapshot ownership = _physics.CaptureOwnership();
return ownership.PendingCollisionPrefixProjectionCount == 0
&& ownership.PendingCollisionReportCount == 0
&& ownership.PendingCollisionSetPositionDispatchCount == 0
&& ownership.PendingShadowSetPositionDispatchCount == 0
&& !ownership.IsCollisionReportDispatching;
}
/// <summary>
@ -438,11 +496,15 @@ public sealed class LandblockPhysicsPublisher
Action<WorldEntity>? beforeStaticCollision = null)
{
ValidateReceipt(publication);
if (publication.CancellationRequested)
throw new InvalidOperationException(
"A cancelled collision publication cannot resume.");
if (!publication.BeginCommitted)
throw new InvalidOperationException(
"Physics publication cannot complete before its prefix commits.");
if (publication.CompletionCommitted)
return true;
publication.RuntimeMutationPending = false;
long started = Stopwatch.GetTimestamp();
LoadedLandblock landblock = publication.Build.Landblock;
@ -567,12 +629,20 @@ public sealed class LandblockPhysicsPublisher
_physics.CommitCollisionGeneration(
publication.CollisionAdmission,
publication.PreparedGeneration);
if (!commit.Committed)
publication.EngineMutationCommitted |= commit.EngineCommitted;
if (!commit.Completed)
{
// Runtime coalesces post-seal arrivals in its owner journal.
// Resume that seal tail rather than restarting the
// completed generation-wide capture/reflood pass.
publication.SealCommitted = false;
publication.RuntimeMutationPending = true;
if (!commit.EngineCommitted)
{
// Runtime coalesces post-seal arrivals in its owner
// journal. Resume that seal tail rather than restarting
// the completed generation-wide capture/reflood pass.
publication.SealCommitted = false;
}
// Once EngineCommitted is true the replacement is canonical
// and cannot be resealed or rolled back. Later frames poll
// only the exact resident-restore acknowledgement suffix.
_completePublishTicks += Stopwatch.GetTimestamp() - started;
return false;
}
@ -587,16 +657,54 @@ public sealed class LandblockPhysicsPublisher
return publication.CompletionCommitted;
}
public void DemoteToTerrain(uint landblockId)
public bool DemoteToTerrain(uint landblockId)
{
_physics.DemoteCollisionToTerrain(landblockId);
_demotionCount++;
for (int poll = 0; poll < 2; poll++)
{
if (AdvanceDemotion(landblockId))
return true;
if (_physics.CaptureOwnership()
.PendingCollisionPrefixProjectionCount != 0)
{
return false;
}
}
return false;
}
public void RemoveLandblock(uint landblockId)
internal bool AdvanceDemotion(uint landblockId)
{
_physics.WithdrawCollision(landblockId);
RuntimeCollisionMutationResult result =
_physics.DemoteCollisionToTerrain(landblockId);
if (!result.Completed)
return false;
_demotionCount++;
return true;
}
public bool RemoveLandblock(uint landblockId)
{
for (int poll = 0; poll < 2; poll++)
{
if (AdvanceRemoval(landblockId))
return true;
if (_physics.CaptureOwnership()
.PendingCollisionPrefixProjectionCount != 0)
{
return false;
}
}
return false;
}
internal bool AdvanceRemoval(uint landblockId)
{
RuntimeCollisionMutationResult result =
_physics.WithdrawCollision(landblockId);
if (!result.Completed)
return false;
_fullRemovalCount++;
return true;
}
private void PublishCell(
@ -1066,7 +1174,8 @@ public sealed class LandblockPhysicsPublisher
"The physics publication receipt belongs to another publisher.",
nameof(publication));
}
if (!publication.CompletionCommitted)
if (!publication.CompletionCommitted
&& !publication.EngineMutationCommitted)
{
ObjectDisposedException.ThrowIf(
publication.PreparedGeneration.IsDisposed,

View file

@ -217,14 +217,34 @@ public sealed class LandblockPresentationPipeline
/// <summary>
/// Cancels retained publication receipts during a generation reset. A
/// collision receipt owns only its private staging world until activation,
/// so cancellation cannot withdraw or partially replace the active world.
/// pre-engine receipt restores its exact prior generation. A receipt that
/// already transferred the engine generation remains retained until its
/// canonical post-engine placement acknowledgement suffix completes; the
/// committed transfer is never rolled back.
/// </summary>
internal void CancelPendingPublications()
internal bool CancelPendingPublications()
{
foreach (PublicationTransaction transaction in _publications.Values)
transaction.PhysicsPublication?.Dispose();
_publications.Clear();
if (_publications.Count == 0)
return true;
LandblockStreamResult[] pending = [.. _publications.Keys];
bool completed = true;
for (int index = 0; index < pending.Length; index++)
{
LandblockStreamResult result = pending[index];
if (!_publications.TryGetValue(
result,
out PublicationTransaction? transaction))
{
continue;
}
bool cancelled = transaction.PhysicsPublication is not { } physics
|| physics.TryCancel();
if (cancelled)
_publications.Remove(result);
else
completed = false;
}
return completed && _publications.Count == 0;
}
public void ResumePublication(LandblockStreamResult result)
@ -247,10 +267,15 @@ public sealed class LandblockPresentationPipeline
return Advance(result, transaction, meter, ensureProgress);
}
public void AdvanceRetirements() => _retirements.Advance();
public void AdvanceRetirements()
{
_retirements.Advance();
}
public void AdvanceRetirements(StreamingWorkMeter meter) =>
public void AdvanceRetirements(StreamingWorkMeter meter)
{
_retirements.Advance(meter);
}
internal void AdvancePriorityRetirement(
uint landblockId,
@ -261,14 +286,26 @@ public sealed class LandblockPresentationPipeline
{
_retirements.BeginFull(landblockId);
if (_retirements.UsesBudgetedSteps)
{
_retirements.Advance();
if (_retirements.IsPending(landblockId)
&& (_physicsPublisher?.CanContinueMutationSynchronously()
?? true))
_retirements.Advance();
}
}
public void BeginNearLayerRetirement(uint landblockId)
{
_retirements.BeginNearLayer(landblockId);
if (_retirements.UsesBudgetedSteps)
{
_retirements.Advance();
if (_retirements.IsPending(landblockId)
&& (_physicsPublisher?.CanContinueMutationSynchronously()
?? true))
_retirements.Advance();
}
}
internal void EnqueueFullRetirement(uint landblockId) =>
@ -754,6 +791,17 @@ public sealed class LandblockPresentationPipeline
{
return new LandblockPublicationAdvance(false, progressed);
}
if (transaction.PhysicsPublication.RuntimeMutationPending)
{
if (meter is not null
|| !_physicsPublisher
.CanContinueMutationSynchronously())
{
return new LandblockPublicationAdvance(
false,
progressed);
}
}
}
while (!transaction.StaticPublication.CompletionCommitted)
{

View file

@ -73,22 +73,20 @@ public sealed class LandblockPresentationRetirementOwner
static entity => entity.ServerGuid == 0,
_staticPresentation.RemovePluginProjection);
if (!ticket.RunOnce(
LandblockRetirementStage.Physics,
() => ticket.Kind == LandblockRetirementKind.Full
? _physics.AdvanceRemoval(ticket.LandblockId)
: _physics.AdvanceDemotion(ticket.LandblockId)))
{
return;
}
if (ticket.Kind == LandblockRetirementKind.Full)
{
ticket.RunOnce(
LandblockRetirementStage.Terrain,
() => _render.RemoveTerrain(ticket.LandblockId));
}
ticket.RunOnce(
LandblockRetirementStage.Physics,
() =>
{
if (ticket.Kind == LandblockRetirementKind.Full)
_physics.RemoveLandblock(ticket.LandblockId);
else
_physics.DemoteToTerrain(ticket.LandblockId);
});
ticket.RunOnce(
LandblockRetirementStage.CellVisibility,
() => _render.RemoveCellVisibility(ticket.LandblockId));
@ -128,13 +126,9 @@ public sealed class LandblockPresentationRetirementOwner
LandblockRetirementStage.Physics =>
ticket.RunOnceStep(
LandblockRetirementStage.Physics,
() =>
{
if (ticket.Kind == LandblockRetirementKind.Full)
_physics.RemoveLandblock(ticket.LandblockId);
else
_physics.DemoteToTerrain(ticket.LandblockId);
}),
() => ticket.Kind == LandblockRetirementKind.Full
? _physics.AdvanceRemoval(ticket.LandblockId)
: _physics.AdvanceDemotion(ticket.LandblockId)),
LandblockRetirementStage.CellVisibility =>
ticket.RunOnceStep(
LandblockRetirementStage.CellVisibility,

View file

@ -12,8 +12,8 @@ public enum LandblockRetirementStage : ushort
EntityLighting = 1 << 3,
EntityTranslucency = 1 << 4,
PluginProjection = 1 << 5,
Terrain = 1 << 6,
Physics = 1 << 7,
Physics = 1 << 6,
Terrain = 1 << 7,
CellVisibility = 1 << 8,
BuildingRegistry = 1 << 9,
EnvironmentCells = 1 << 10,
@ -24,6 +24,7 @@ internal enum LandblockRetirementOperationResult : byte
{
NoWork,
Progressed,
Pending,
Failed,
}
@ -77,6 +78,28 @@ public sealed class LandblockRetirementTicket
}
}
public bool RunOnce(LandblockRetirementStage stage, Func<bool> operation)
{
ValidateSingleStage(stage);
ArgumentNullException.ThrowIfNull(operation);
if ((CompletedStages & stage) != 0)
return true;
try
{
if (!operation())
return false;
CompletedStages |= stage;
_failures.Remove(stage);
return true;
}
catch (Exception error)
{
_failures[stage] = error;
return false;
}
}
internal LandblockRetirementOperationResult RunOnceStep(
LandblockRetirementStage stage,
Action operation)
@ -100,6 +123,31 @@ public sealed class LandblockRetirementTicket
}
}
internal LandblockRetirementOperationResult RunOnceStep(
LandblockRetirementStage stage,
Func<bool> operation)
{
ValidateSingleStage(stage);
ArgumentNullException.ThrowIfNull(operation);
if ((CompletedStages & stage) != 0)
return LandblockRetirementOperationResult.NoWork;
try
{
if (!operation())
return LandblockRetirementOperationResult.Pending;
CompletedStages |= stage;
_failures.Remove(stage);
return LandblockRetirementOperationResult.Progressed;
}
catch (Exception error)
{
_failures[stage] = error;
return LandblockRetirementOperationResult.Failed;
}
}
public bool RunForEachEntity(
LandblockRetirementStage stage,
Func<WorldEntity, bool> predicate,
@ -815,6 +863,8 @@ public sealed class LandblockRetirementCoordinator
}
meter.Complete();
if (result == LandblockRetirementOperationResult.Pending)
return BudgetedAdvanceResult.Yielded;
return BudgetedAdvanceResult.Progressed;
}

View file

@ -1362,19 +1362,23 @@ public sealed class StreamingController
{
if (!transaction.PendingPublicationsCleared)
{
bool cancelled = false;
if (!TryRunStreamingWork(
meter,
new StreamingWorkCost(EntityOperations: 1),
"recenter-cancel-publications",
() =>
{
_presentation.CancelPendingPublications();
transaction.PendingPublicationsCleared = true;
cancelled =
_presentation.CancelPendingPublications();
return true;
}))
{
return false;
}
transaction.PendingPublicationsCleared = cancelled;
if (!cancelled)
return false;
}
if (!TryRunStreamingWork(
meter,
@ -1519,19 +1523,23 @@ public sealed class StreamingController
{
if (!transaction.PendingPublicationsCleared)
{
bool cancelled = false;
if (!TryRunStreamingWork(
meter,
new StreamingWorkCost(EntityOperations: 1),
"reload-cancel-publications",
() =>
{
_presentation.CancelPendingPublications();
transaction.PendingPublicationsCleared = true;
cancelled =
_presentation.CancelPendingPublications();
return true;
}))
{
return false;
}
transaction.PendingPublicationsCleared = cancelled;
if (!cancelled)
return false;
}
if (!TryRunStreamingWork(
meter,