feat(runtime): atomically replace collision generations
This commit is contained in:
parent
99bf1751bb
commit
9b0f59bd1b
21 changed files with 2435 additions and 408 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue