feat(runtime): atomically replace collision generations
This commit is contained in:
parent
99bf1751bb
commit
9b0f59bd1b
21 changed files with 2435 additions and 408 deletions
|
|
@ -106,8 +106,7 @@ internal readonly record struct RuntimeCollisionPrefixMutationPermission(
|
|||
RuntimeCollisionPrefixQuiescenceToken Quiescence,
|
||||
ImmutableArray<RuntimePlacementProjectionToken> Withdrawals)
|
||||
{
|
||||
internal bool IsValid => Quiescence.IsValid
|
||||
&& !Withdrawals.IsDefault;
|
||||
internal bool IsValid => Quiescence.IsValid;
|
||||
}
|
||||
|
||||
internal readonly record struct RuntimeCollisionEvaluationAuthority(
|
||||
|
|
@ -373,8 +372,9 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
{ get; } = [];
|
||||
internal bool ResidentsParked { get; set; }
|
||||
internal bool PermissionIssued { get; set; }
|
||||
internal bool AbortReleaseInProgress { get; set; }
|
||||
internal ulong AbortRestoreGeneration { get; set; }
|
||||
internal bool ReleaseInProgress { get; set; }
|
||||
internal ulong ReleaseGeneration { get; set; }
|
||||
internal bool ReleaseGenerationReady { get; set; }
|
||||
}
|
||||
|
||||
private sealed class ContactCommitGuard(
|
||||
|
|
@ -504,10 +504,10 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
if (_collisionPrefixQuiescence.TryGetValue(
|
||||
prefix,
|
||||
out CollisionPrefixQuiescence? active)
|
||||
&& active.AbortReleaseInProgress)
|
||||
&& active.ReleaseInProgress)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Collision quiescence 0x{prefix:X8}/{active.Token.OperationId} is restoring its retained generation.");
|
||||
$"Collision quiescence 0x{prefix:X8}/{active.Token.OperationId} is releasing its retained residents.");
|
||||
}
|
||||
_collisionPrefixQuiescence.Remove(
|
||||
prefix,
|
||||
|
|
@ -592,7 +592,9 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
current.PermissionIssued = true;
|
||||
permission = new RuntimeCollisionPrefixMutationPermission(
|
||||
current.Token,
|
||||
current.RetainedWithdrawals.ToImmutableArray());
|
||||
current.RetainedWithdrawals.Count == 0
|
||||
? default
|
||||
: current.RetainedWithdrawals.ToImmutableArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -635,39 +637,93 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
return removedBeforePark;
|
||||
}
|
||||
|
||||
if (successorGeneration == 0UL
|
||||
|| !successorReady
|
||||
|| current.PendingWithdrawals.Count != 0)
|
||||
if (successorGeneration == 0UL)
|
||||
return false;
|
||||
|
||||
return AdvanceCollisionPrefixRelease(
|
||||
token,
|
||||
successorGeneration,
|
||||
successorReady,
|
||||
requireMutationPermission: false);
|
||||
}
|
||||
|
||||
internal bool CancelCollisionPrefixQuiescenceToUnavailable(
|
||||
in RuntimeCollisionPrefixQuiescenceToken token)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
return AdvanceCollisionPrefixRelease(
|
||||
token,
|
||||
generation: 0UL,
|
||||
ready: false,
|
||||
requireMutationPermission: false);
|
||||
}
|
||||
|
||||
internal bool ReleaseCollisionPrefixAfterMutation(
|
||||
in RuntimeCollisionPrefixQuiescenceToken token,
|
||||
ulong activeGeneration,
|
||||
bool ready)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
return AdvanceCollisionPrefixRelease(
|
||||
token,
|
||||
activeGeneration,
|
||||
ready,
|
||||
requireMutationPermission: true);
|
||||
}
|
||||
|
||||
private bool AdvanceCollisionPrefixRelease(
|
||||
in RuntimeCollisionPrefixQuiescenceToken token,
|
||||
ulong generation,
|
||||
bool ready,
|
||||
bool requireMutationPermission)
|
||||
{
|
||||
if ((generation == 0UL && ready)
|
||||
|| !TryGetCurrentQuiescence(
|
||||
token,
|
||||
out CollisionPrefixQuiescence? state))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!current.AbortReleaseInProgress)
|
||||
CollisionPrefixQuiescence current = state!;
|
||||
if (current.PendingWithdrawals.Count != 0)
|
||||
return false;
|
||||
if (requireMutationPermission
|
||||
&& !current.PermissionIssued
|
||||
&& !current.ReleaseInProgress)
|
||||
{
|
||||
current.AbortReleaseInProgress = true;
|
||||
current.AbortRestoreGeneration = successorGeneration;
|
||||
current.PermissionIssued = false;
|
||||
RebindQuiescedDeferredOperations(
|
||||
token,
|
||||
successorGeneration,
|
||||
ready: true);
|
||||
return false;
|
||||
}
|
||||
else if (current.AbortRestoreGeneration != successorGeneration)
|
||||
if (!current.ReleaseInProgress)
|
||||
{
|
||||
current.ReleaseInProgress = true;
|
||||
current.ReleaseGeneration = generation;
|
||||
current.ReleaseGenerationReady = ready;
|
||||
current.PermissionIssued = false;
|
||||
}
|
||||
else if (current.ReleaseGeneration != generation
|
||||
|| current.ReleaseGenerationReady != ready)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// A re-entrant/network placement may have joined the still-closed
|
||||
// prefix after abort release started. Transfer every exact newcomer
|
||||
// on each poll before deciding the barrier can open.
|
||||
RebindQuiescedDeferredOperations(
|
||||
token,
|
||||
successorGeneration,
|
||||
ready: true);
|
||||
// prefix after release started. Transfer every exact newcomer on each
|
||||
// poll before deciding the barrier can open.
|
||||
if (_operations.Count != 0)
|
||||
{
|
||||
RebindQuiescedDeferredOperations(
|
||||
token,
|
||||
ready ? generation : 0UL,
|
||||
ready,
|
||||
releaseUnavailable: !ready);
|
||||
}
|
||||
|
||||
if (current.PendingRestorePlacements.Count != 0
|
||||
|| HasQuiescedDeferredOperations(token.LandblockPrefix))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool removed = _collisionPrefixQuiescence.Remove(
|
||||
token.LandblockPrefix);
|
||||
if (removed)
|
||||
|
|
@ -675,19 +731,6 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
return removed;
|
||||
}
|
||||
|
||||
internal bool CompleteCollisionPrefixQuiescence(
|
||||
in RuntimeCollisionPrefixMutationPermission permission)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
if (!IsCollisionPrefixMutationPermissionCurrent(permission))
|
||||
return false;
|
||||
bool removed = _collisionPrefixQuiescence.Remove(
|
||||
permission.Quiescence.LandblockPrefix);
|
||||
if (removed)
|
||||
_physics.AdvanceCollisionQuiescenceAuthority();
|
||||
return removed;
|
||||
}
|
||||
|
||||
internal void BindEventStream(RuntimeEntityObjectEventStream events)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
|
|
@ -770,6 +813,34 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
portal,
|
||||
captureMoverPreparationAuthority: true);
|
||||
|
||||
internal void PrepareDormantLocalActivationOwnership(
|
||||
RuntimeEntityRecord record,
|
||||
PhysicsBody body,
|
||||
in RuntimeEntityPlacementToken token)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
ArgumentNullException.ThrowIfNull(body);
|
||||
if (!token.IsValid
|
||||
|| record.Key != token.Entity
|
||||
|| !_operations.TryGetValue(token.Entity, out Operation? operation)
|
||||
|| operation.Token != token
|
||||
|| operation.Stage is not RuntimeEntityPlacementStage
|
||||
.AwaitingPreparation
|
||||
|| !ReferenceEquals(operation.Record, record)
|
||||
|| record.PhysicsBody is not null
|
||||
|| !IsCurrent(operation)
|
||||
|| body.InWorld
|
||||
|| (body.TransientState & TransientStateFlags.Active) != 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Dormant local activation must bind to the exact current placement owner.");
|
||||
}
|
||||
|
||||
operation.Body = body;
|
||||
operation.DormantLocalActivation = true;
|
||||
}
|
||||
|
||||
private RuntimeEntityPlacementToken BeginAcceptedPlacementCore(
|
||||
RuntimeEntityRecord record,
|
||||
ulong expectedPositionAuthorityVersion,
|
||||
|
|
@ -2531,6 +2602,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
uint prefix,
|
||||
bool includeOutdoorCells)
|
||||
{
|
||||
if (_physics.SpatialRootCount == 0)
|
||||
return false;
|
||||
var roots = new List<RuntimeEntityRecord>();
|
||||
_physics.CopySpatialRootsTo(roots);
|
||||
for (int index = 0; index < roots.Count; index++)
|
||||
|
|
@ -2586,7 +2659,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
uint prefix = state.Token.LandblockPrefix;
|
||||
foreach (Operation operation in _operations.Values)
|
||||
{
|
||||
if (operation.WakeableLostCell)
|
||||
if (operation.WakeableLostCell || operation.DormantLocalActivation)
|
||||
continue;
|
||||
if (PlacementTouchesPrefix(operation.Command.Physics, prefix)
|
||||
|| ResultTouchesPrefix(operation.Result, prefix)
|
||||
|
|
@ -2703,7 +2776,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
|| !_collisionPrefixQuiescence.TryGetValue(
|
||||
operation.CollisionPrefix,
|
||||
out CollisionPrefixQuiescence? state)
|
||||
|| !state.AbortReleaseInProgress)
|
||||
|| !state.ReleaseInProgress
|
||||
|| !state.ReleaseGenerationReady)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -2745,21 +2819,40 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
private void RebindQuiescedDeferredOperations(
|
||||
in RuntimeCollisionPrefixQuiescenceToken token,
|
||||
ulong successorGeneration,
|
||||
bool ready)
|
||||
bool ready,
|
||||
bool releaseUnavailable = false)
|
||||
{
|
||||
foreach (Operation operation in _operations.Values.ToArray())
|
||||
{
|
||||
bool unavailableAfterReadyCommit = ready
|
||||
&& operation.CollisionQuiescenceHeld
|
||||
&& operation.CollisionGeneration == 0UL
|
||||
&& operation.CollisionPrefix == token.LandblockPrefix;
|
||||
if (!operation.WakeableLostCell
|
||||
|| operation.CollisionGeneration != token.CollisionGeneration
|
||||
|| operation.CollisionPrefix != token.LandblockPrefix)
|
||||
|| !operation.CollisionQuiescenceHeld
|
||||
|| operation.CollisionPrefix != token.LandblockPrefix
|
||||
|| (operation.CollisionGeneration
|
||||
!= token.CollisionGeneration
|
||||
&& !unavailableAfterReadyCommit))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
UnindexDeferred(operation);
|
||||
operation.CollisionGeneration = successorGeneration;
|
||||
ulong reboundGeneration = releaseUnavailable
|
||||
|| unavailableAfterReadyCommit
|
||||
? 0UL
|
||||
: successorGeneration;
|
||||
operation.CollisionGeneration = reboundGeneration;
|
||||
operation.CollisionGenerationReady = ready
|
||||
&& successorGeneration != 0UL;
|
||||
if (successorGeneration != 0UL)
|
||||
&& reboundGeneration != 0UL;
|
||||
if (releaseUnavailable || unavailableAfterReadyCommit)
|
||||
{
|
||||
operation.CollisionQuiescenceHeld = false;
|
||||
operation.Stage = operation.RequiresPreparation
|
||||
? RuntimeEntityPlacementStage.AwaitingPreparation
|
||||
: RuntimeEntityPlacementStage.AwaitingCell;
|
||||
}
|
||||
if (reboundGeneration != 0UL)
|
||||
IndexDeferred(operation);
|
||||
else
|
||||
IndexUnboundDeferred(operation);
|
||||
|
|
@ -3073,26 +3166,27 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
operation.Command.Physics,
|
||||
out CollisionPrefixQuiescence? blocking))
|
||||
{
|
||||
if (blocking!.AbortReleaseInProgress
|
||||
if (blocking!.ReleaseInProgress
|
||||
&& blocking.ReleaseGenerationReady
|
||||
&& operation.CollisionQuiescenceHeld
|
||||
&& operation.CollisionPrefix
|
||||
== blocking.Token.LandblockPrefix)
|
||||
{
|
||||
// The old collision generation remains active. Keep the
|
||||
// admission barrier closed to new commands while this exact
|
||||
// parked operation restores and its Place receipt drains.
|
||||
// The selected collision generation is active. Keep the
|
||||
// admission barrier closed while this exact parked operation
|
||||
// restores and its Place receipt drains.
|
||||
restoringQuiescence = blocking.Token;
|
||||
}
|
||||
else
|
||||
{
|
||||
UnindexDeferred(operation);
|
||||
operation.CollisionPrefix = blocking.Token.LandblockPrefix;
|
||||
operation.CollisionGeneration = blocking.Token.CollisionGeneration;
|
||||
operation.CollisionGenerationReady = false;
|
||||
operation.CollisionQuiescenceHeld = true;
|
||||
operation.Stage = RuntimeEntityPlacementStage.QuiescenceHeld;
|
||||
IndexDeferred(operation);
|
||||
return;
|
||||
UnindexDeferred(operation);
|
||||
operation.CollisionPrefix = blocking.Token.LandblockPrefix;
|
||||
operation.CollisionGeneration = blocking.Token.CollisionGeneration;
|
||||
operation.CollisionGenerationReady = false;
|
||||
operation.CollisionQuiescenceHeld = true;
|
||||
operation.Stage = RuntimeEntityPlacementStage.QuiescenceHeld;
|
||||
IndexDeferred(operation);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue