feat(runtime): atomically replace collision generations
This commit is contained in:
parent
99bf1751bb
commit
9b0f59bd1b
21 changed files with 2435 additions and 408 deletions
|
|
@ -1053,8 +1053,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
|||
_sessionClearInProgress = true;
|
||||
RuntimeEntityRecord[] active = Entities.ActiveRecords.ToArray();
|
||||
Physics.CollisionReports.LeaveWorldBatch(active);
|
||||
Physics.SetPosition.ResetSession();
|
||||
Physics.CollisionReports.ResetSession();
|
||||
Physics.ResetSessionPhysics();
|
||||
Entities.BeginSessionClear();
|
||||
foreach (RuntimeEntityRecord canonical in active)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -388,6 +388,14 @@ internal sealed class RuntimeLocalPlayerPhysicsPublicationState : IDisposable
|
|||
return RuntimeLocalPlayerPhysicsPublicationStatus.RejectedAuthority;
|
||||
}
|
||||
|
||||
// Seal the exact SetPosition owner before the irreversible no-fail
|
||||
// suffix. Any internal invariant failure therefore leaves every
|
||||
// canonical body/controller owner untouched.
|
||||
_physics.SetPosition.PrepareDormantLocalActivationOwnership(
|
||||
candidate.Record,
|
||||
candidate.Body,
|
||||
candidate.PreparedActivation.Token.Placement);
|
||||
|
||||
// All validation is complete. The remaining stores are callback-free,
|
||||
// non-allocating, and cannot fail on this single Runtime update thread.
|
||||
// The controller remains RuntimeOwnedDormant; the subsequent world
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ public readonly record struct RuntimePhysicsOwnershipSnapshot(
|
|||
bool IsCollisionReportDispatching,
|
||||
int CollisionPrefixQuiescenceCount,
|
||||
int PendingCollisionPrefixProjectionCount,
|
||||
int CollisionPrefixMutationCount,
|
||||
int CommittedCollisionPrefixMutationCount,
|
||||
int CollisionAdmissionCount,
|
||||
int CollisionGenerationCount,
|
||||
bool OwnsProductionDataCache,
|
||||
|
|
@ -75,6 +77,8 @@ public readonly record struct RuntimePhysicsOwnershipSnapshot(
|
|||
&& !IsCollisionReportDispatching
|
||||
&& CollisionPrefixQuiescenceCount == 0
|
||||
&& PendingCollisionPrefixProjectionCount == 0
|
||||
&& CollisionPrefixMutationCount == 0
|
||||
&& CommittedCollisionPrefixMutationCount == 0
|
||||
&& CollisionAdmissionCount == 0
|
||||
&& CollisionGenerationCount == 0
|
||||
&& OwnsProductionDataCache;
|
||||
|
|
@ -100,11 +104,13 @@ public sealed class RuntimeCollisionAdmission
|
|||
internal RuntimeCollisionAdmission(
|
||||
RuntimePhysicsState owner,
|
||||
uint landblockId,
|
||||
ulong generation)
|
||||
ulong generation,
|
||||
ulong previousGeneration)
|
||||
{
|
||||
Owner = owner;
|
||||
LandblockId = landblockId;
|
||||
Generation = generation;
|
||||
PreviousGeneration = previousGeneration;
|
||||
}
|
||||
|
||||
internal RuntimePhysicsState Owner { get; }
|
||||
|
|
@ -112,6 +118,32 @@ public sealed class RuntimeCollisionAdmission
|
|||
internal bool Completed { get; set; }
|
||||
public uint LandblockId { get; }
|
||||
public ulong Generation { get; }
|
||||
internal ulong PreviousGeneration { get; }
|
||||
}
|
||||
|
||||
internal enum RuntimeCollisionPrefixMutationKind : byte
|
||||
{
|
||||
Activation,
|
||||
Demotion,
|
||||
Withdrawal,
|
||||
}
|
||||
|
||||
internal sealed class RuntimeCollisionPrefixMutation
|
||||
{
|
||||
internal required RuntimeCollisionPrefixMutationKind Kind { get; init; }
|
||||
internal required uint LandblockId { get; init; }
|
||||
internal required ulong PreviousGeneration { get; init; }
|
||||
internal required ulong TargetGeneration { get; init; }
|
||||
internal ulong InvalidatedGeneration { get; init; }
|
||||
internal required RuntimeCollisionPrefixQuiescenceToken Quiescence
|
||||
{ get; init; }
|
||||
internal RuntimeCollisionAdmission? Admission { get; init; }
|
||||
internal PreparedLandblockCollisionGeneration? Prepared { get; init; }
|
||||
internal RuntimeCollisionPrefixMutationPermission Permission { get; set; }
|
||||
internal bool EngineMutationCommitted { get; set; }
|
||||
internal bool CancellationRequested { get; set; }
|
||||
internal bool WasResident { get; set; }
|
||||
internal bool Ready { get; set; }
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeCollisionAcknowledgement(
|
||||
|
|
@ -120,11 +152,23 @@ public readonly record struct RuntimeCollisionAcknowledgement(
|
|||
bool WasResident,
|
||||
bool Ready);
|
||||
|
||||
public readonly record struct RuntimeCollisionMutationResult(
|
||||
RuntimeCollisionAcknowledgement Acknowledgement,
|
||||
bool Completed)
|
||||
{
|
||||
public uint LandblockId => Acknowledgement.LandblockId;
|
||||
public ulong Generation => Acknowledgement.Generation;
|
||||
public bool WasResident => Acknowledgement.WasResident;
|
||||
public bool Ready => Acknowledgement.Ready;
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeCollisionGenerationCommit(
|
||||
RuntimeCollisionAcknowledgement Acknowledgement,
|
||||
uint[] DirtyRetainedOwnerIds)
|
||||
uint[] DirtyRetainedOwnerIds,
|
||||
bool EngineCommitted,
|
||||
bool Completed)
|
||||
{
|
||||
public bool Committed => Acknowledgement.Ready;
|
||||
public bool Committed => Completed;
|
||||
}
|
||||
|
||||
public readonly record struct RuntimeCollisionGenerationCommitted(
|
||||
|
|
@ -1045,6 +1089,8 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
_collisionAdmissions = new();
|
||||
private readonly Dictionary<uint, PreparedLandblockCollisionGeneration>
|
||||
_preparedCollisionGenerations = new();
|
||||
private readonly Dictionary<uint, RuntimeCollisionPrefixMutation>
|
||||
_collisionPrefixMutations = new();
|
||||
private readonly CollisionOwnerMutationJournal _collisionOwnerJournal = new();
|
||||
private readonly Dictionary<uint, List<PreparedLandblockCollisionGeneration>>
|
||||
_collisionOwnerSubscribers = new();
|
||||
|
|
@ -1175,6 +1221,9 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
collisionReports.IsDispatching,
|
||||
setPosition.CollisionPrefixQuiescenceCount,
|
||||
setPosition.PendingQuiescenceProjectionCount,
|
||||
_collisionPrefixMutations.Count,
|
||||
_collisionPrefixMutations.Values.Count(
|
||||
mutation => mutation.EngineMutationCommitted),
|
||||
_collisionAdmissions.Count,
|
||||
_collisionGenerations.Count,
|
||||
ReferenceEquals(Engine.DataCache, DataCache),
|
||||
|
|
@ -1907,6 +1956,29 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
_spatialRoots.Clear();
|
||||
}
|
||||
|
||||
internal void ResetSessionPhysics()
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
// GameRuntime serializes reset after the host update loop has stopped.
|
||||
// Reset may therefore run on the lifecycle/disposal thread rather than
|
||||
// the retired generation's update thread. It clears every mutation
|
||||
// owner before releasing affinity so the next generation can bind its
|
||||
// own update thread without admitting concurrent mutation.
|
||||
foreach ((_, PreparedLandblockCollisionGeneration prepared) in
|
||||
_preparedCollisionGenerations)
|
||||
{
|
||||
prepared.Dispose();
|
||||
}
|
||||
_preparedCollisionGenerations.Clear();
|
||||
_collisionPrefixMutations.Clear();
|
||||
_collisionAdmissions.Clear();
|
||||
SetPosition.ResetSession();
|
||||
CollisionReports.ResetSession();
|
||||
TrimCollisionOwnerJournal();
|
||||
AdvanceCollisionWorldAuthority();
|
||||
Volatile.Write(ref _collisionMutationThreadId, 0);
|
||||
}
|
||||
|
||||
internal RuntimeCollisionPrefixQuiescenceToken
|
||||
BeginCollisionPrefixQuiescence(
|
||||
uint landblockId,
|
||||
|
|
@ -1957,25 +2029,29 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
successorReady);
|
||||
}
|
||||
|
||||
internal bool CompleteCollisionPrefixQuiescence(
|
||||
in RuntimeCollisionPrefixMutationPermission permission)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
EnsureCollisionMutationThread();
|
||||
return SetPosition.CompleteCollisionPrefixQuiescence(permission);
|
||||
}
|
||||
|
||||
public RuntimeCollisionAdmission BeginCollisionAdmission(
|
||||
uint landblockId)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
EnsureCollisionMutationThread();
|
||||
uint canonical = CanonicalLandblock(landblockId);
|
||||
if (_collisionPrefixMutations.ContainsKey(canonical))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Collision prefix 0x{canonical:X8} is still completing its previous mutation transaction.");
|
||||
}
|
||||
ulong currentGeneration = _collisionGenerations.TryGetValue(
|
||||
canonical,
|
||||
out ulong current)
|
||||
? current
|
||||
: 0UL;
|
||||
ulong previousGeneration = currentGeneration;
|
||||
AdvanceCollisionWorldAuthority();
|
||||
if (_collisionAdmissions.Remove(
|
||||
canonical,
|
||||
out RuntimeCollisionAdmission? superseded))
|
||||
{
|
||||
previousGeneration = superseded.PreviousGeneration;
|
||||
SetPosition.CancelCollisionGeneration(
|
||||
canonical,
|
||||
superseded.Generation);
|
||||
|
|
@ -1986,16 +2062,13 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
prepared.Dispose();
|
||||
}
|
||||
}
|
||||
ulong generation = _collisionGenerations.TryGetValue(
|
||||
canonical,
|
||||
out ulong current)
|
||||
? checked(current + 1UL)
|
||||
: 1UL;
|
||||
ulong generation = checked(currentGeneration + 1UL);
|
||||
_collisionGenerations[canonical] = generation;
|
||||
var admission = new RuntimeCollisionAdmission(
|
||||
this,
|
||||
canonical,
|
||||
generation);
|
||||
generation,
|
||||
previousGeneration);
|
||||
_collisionAdmissions[canonical] = admission;
|
||||
SetPosition.BeginCollisionGeneration(canonical, generation);
|
||||
return admission;
|
||||
|
|
@ -2047,7 +2120,7 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
/// collision world is never withdrawn. A stale receipt may dispose its
|
||||
/// own staging storage but cannot invalidate a newer admission.
|
||||
/// </summary>
|
||||
internal void CancelCollisionGeneration(
|
||||
internal bool CancelCollisionGeneration(
|
||||
RuntimeCollisionAdmission admission,
|
||||
PreparedLandblockCollisionGeneration? prepared = null)
|
||||
{
|
||||
|
|
@ -2067,6 +2140,38 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
nameof(prepared));
|
||||
}
|
||||
|
||||
if (_collisionPrefixMutations.TryGetValue(
|
||||
admission.LandblockId,
|
||||
out RuntimeCollisionPrefixMutation? mutation))
|
||||
{
|
||||
if (mutation.Kind is not RuntimeCollisionPrefixMutationKind.Activation
|
||||
|| !ReferenceEquals(mutation.Admission, admission)
|
||||
|| (prepared is not null
|
||||
&& !ReferenceEquals(mutation.Prepared, prepared)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (mutation.EngineMutationCommitted)
|
||||
return AdvanceCommittedActivation(mutation).Committed;
|
||||
|
||||
mutation.CancellationRequested = true;
|
||||
bool previousReady = mutation.PreviousGeneration != 0UL
|
||||
&& Engine.IsLandblockTerrainResident(
|
||||
mutation.LandblockId);
|
||||
bool released = mutation.PreviousGeneration == 0UL
|
||||
? SetPosition.CancelCollisionPrefixQuiescenceToUnavailable(
|
||||
mutation.Quiescence)
|
||||
: CancelCollisionPrefixQuiescence(
|
||||
mutation.Quiescence,
|
||||
mutation.PreviousGeneration,
|
||||
previousReady);
|
||||
if (!released)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_collisionPrefixMutations.Remove(mutation.LandblockId);
|
||||
}
|
||||
|
||||
prepared?.Dispose();
|
||||
if (prepared is not null
|
||||
&& _preparedCollisionGenerations.TryGetValue(
|
||||
|
|
@ -2090,6 +2195,7 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
AdvanceCollisionWorldAuthority();
|
||||
}
|
||||
TrimCollisionOwnerJournal();
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void StageCollisionAssets(
|
||||
|
|
@ -2261,8 +2367,29 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
RuntimeCollisionAdmission admission,
|
||||
PreparedLandblockCollisionGeneration prepared)
|
||||
{
|
||||
ValidateAdmission(admission);
|
||||
EnsureNotDisposed();
|
||||
EnsureCollisionMutationThread();
|
||||
ArgumentNullException.ThrowIfNull(admission);
|
||||
ArgumentNullException.ThrowIfNull(prepared);
|
||||
|
||||
if (_collisionPrefixMutations.TryGetValue(
|
||||
admission.LandblockId,
|
||||
out RuntimeCollisionPrefixMutation? pending))
|
||||
{
|
||||
if (pending.Kind is not RuntimeCollisionPrefixMutationKind.Activation
|
||||
|| !ReferenceEquals(pending.Admission, admission)
|
||||
|| !ReferenceEquals(pending.Prepared, prepared))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A different collision-prefix mutation owns this landblock.");
|
||||
}
|
||||
if (pending.EngineMutationCommitted)
|
||||
return AdvanceCommittedActivation(pending);
|
||||
if (pending.CancellationRequested)
|
||||
return PendingActivation(pending);
|
||||
}
|
||||
|
||||
ValidateAdmission(admission);
|
||||
ValidatePreparedGeneration(admission, prepared);
|
||||
if (!admission.AssetsPrepared)
|
||||
{
|
||||
|
|
@ -2292,8 +2419,62 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
admission.Generation,
|
||||
Engine.IsLandblockTerrainResident(admission.LandblockId),
|
||||
Ready: false),
|
||||
Array.Empty<uint>());
|
||||
Array.Empty<uint>(),
|
||||
EngineCommitted: false,
|
||||
Completed: false);
|
||||
}
|
||||
|
||||
RuntimeCollisionPrefixMutation mutation;
|
||||
if (!_collisionPrefixMutations.TryGetValue(
|
||||
admission.LandblockId,
|
||||
out mutation!))
|
||||
{
|
||||
RuntimeCollisionPrefixQuiescenceToken token =
|
||||
BeginCollisionPrefixQuiescence(
|
||||
admission.LandblockId,
|
||||
admission.Generation,
|
||||
includeOutdoorCells: true);
|
||||
mutation = new RuntimeCollisionPrefixMutation
|
||||
{
|
||||
Kind = RuntimeCollisionPrefixMutationKind.Activation,
|
||||
LandblockId = admission.LandblockId,
|
||||
PreviousGeneration = admission.PreviousGeneration,
|
||||
TargetGeneration = admission.Generation,
|
||||
Quiescence = token,
|
||||
Admission = admission,
|
||||
Prepared = prepared,
|
||||
WasResident = Engine.IsLandblockTerrainResident(
|
||||
admission.LandblockId),
|
||||
};
|
||||
_collisionPrefixMutations.Add(admission.LandblockId, mutation);
|
||||
|
||||
// The first poll deliberately closes the prefix and marks its
|
||||
// resident-parking pass complete, even when the prefix is empty.
|
||||
// A later poll alone may consume mutation permission; affected
|
||||
// residents also require their exact Withdraw acknowledgements.
|
||||
}
|
||||
|
||||
if (!TryAcquireCollisionPrefixMutationPermission(
|
||||
mutation.Quiescence,
|
||||
out RuntimeCollisionPrefixMutationPermission permission))
|
||||
{
|
||||
return PendingActivation(mutation);
|
||||
}
|
||||
mutation.Permission = permission;
|
||||
|
||||
// Parking a live owner writes to the canonical shadow journal. The
|
||||
// prepared replacement must be resealed against that exact journal
|
||||
// tail before permission can be consumed.
|
||||
if (!IsCollisionPrefixMutationPermissionCurrent(permission)
|
||||
|| !prepared.IsOwnerMutationReconciliationCurrent
|
||||
|| !prepared.IsReadyForActivation
|
||||
|| HasOlderPreparedGeneration(prepared)
|
||||
|| prepared.HasPendingCommittedRebase
|
||||
|| prepared.HasPendingRetirement)
|
||||
{
|
||||
return PendingActivation(mutation);
|
||||
}
|
||||
|
||||
PhysicsEngine.PreparedPhysicsEngineLandblock replacement =
|
||||
prepared.TakeSealedReplacement();
|
||||
bool suppressOwnerJournal = _suppressCollisionOwnerJournal;
|
||||
|
|
@ -2313,20 +2494,70 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
if (later.Sequence > prepared.Sequence)
|
||||
later.EnqueueCommittedRebase(replacement);
|
||||
}
|
||||
admission.Completed = true;
|
||||
_collisionAdmissions.Remove(admission.LandblockId);
|
||||
_preparedCollisionGenerations.Remove(admission.LandblockId);
|
||||
prepared.MarkCommitted();
|
||||
TrimCollisionOwnerJournal();
|
||||
var acknowledgement = new RuntimeCollisionAcknowledgement(
|
||||
admission.LandblockId,
|
||||
admission.Generation,
|
||||
Engine.IsLandblockTerrainResident(admission.LandblockId),
|
||||
Ready: Engine.IsLandblockTerrainResident(admission.LandblockId));
|
||||
mutation.EngineMutationCommitted = true;
|
||||
mutation.Ready = Engine.IsLandblockTerrainResident(
|
||||
admission.LandblockId);
|
||||
mutation.WasResident = mutation.Ready;
|
||||
SetPosition.CommitCollisionGeneration(
|
||||
acknowledgement.LandblockId,
|
||||
acknowledgement.Generation,
|
||||
acknowledgement.Ready);
|
||||
mutation.LandblockId,
|
||||
mutation.TargetGeneration,
|
||||
mutation.Ready);
|
||||
RuntimeCollisionGenerationCommit completed =
|
||||
AdvanceCommittedActivation(mutation);
|
||||
return completed;
|
||||
}
|
||||
|
||||
private RuntimeCollisionGenerationCommit PendingActivation(
|
||||
RuntimeCollisionPrefixMutation mutation) => new(
|
||||
new RuntimeCollisionAcknowledgement(
|
||||
mutation.LandblockId,
|
||||
mutation.TargetGeneration,
|
||||
mutation.WasResident,
|
||||
Ready: mutation.EngineMutationCommitted && mutation.Ready),
|
||||
Array.Empty<uint>(),
|
||||
EngineCommitted: mutation.EngineMutationCommitted,
|
||||
Completed: false);
|
||||
|
||||
private RuntimeCollisionGenerationCommit AdvanceCommittedActivation(
|
||||
RuntimeCollisionPrefixMutation mutation)
|
||||
{
|
||||
if (!mutation.EngineMutationCommitted)
|
||||
throw new InvalidOperationException(
|
||||
"Collision activation cannot release before its engine transaction commits.");
|
||||
|
||||
bool completed = SetPosition.ReleaseCollisionPrefixAfterMutation(
|
||||
mutation.Quiescence,
|
||||
mutation.TargetGeneration,
|
||||
mutation.Ready);
|
||||
var acknowledgement = new RuntimeCollisionAcknowledgement(
|
||||
mutation.LandblockId,
|
||||
mutation.TargetGeneration,
|
||||
mutation.WasResident,
|
||||
mutation.Ready);
|
||||
if (!completed)
|
||||
{
|
||||
return new RuntimeCollisionGenerationCommit(
|
||||
acknowledgement,
|
||||
Array.Empty<uint>(),
|
||||
EngineCommitted: true,
|
||||
Completed: false);
|
||||
}
|
||||
|
||||
RuntimeCollisionAdmission admission = mutation.Admission
|
||||
?? throw new InvalidOperationException(
|
||||
"Collision activation lost its admission owner.");
|
||||
admission.Completed = true;
|
||||
if (_collisionAdmissions.TryGetValue(
|
||||
mutation.LandblockId,
|
||||
out RuntimeCollisionAdmission? current)
|
||||
&& ReferenceEquals(current, admission))
|
||||
{
|
||||
_collisionAdmissions.Remove(mutation.LandblockId);
|
||||
}
|
||||
_collisionPrefixMutations.Remove(mutation.LandblockId);
|
||||
TrimCollisionOwnerJournal();
|
||||
PublishCollisionGenerationCommitted(
|
||||
new RuntimeCollisionGenerationCommitted(
|
||||
acknowledgement.LandblockId,
|
||||
|
|
@ -2334,67 +2565,179 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
acknowledgement.Ready));
|
||||
return new RuntimeCollisionGenerationCommit(
|
||||
acknowledgement,
|
||||
Array.Empty<uint>());
|
||||
Array.Empty<uint>(),
|
||||
EngineCommitted: true,
|
||||
Completed: true);
|
||||
}
|
||||
|
||||
public RuntimeCollisionAcknowledgement DemoteCollisionToTerrain(
|
||||
private static RuntimeCollisionMutationResult PendingRetirement(
|
||||
RuntimeCollisionPrefixMutation mutation) => new(
|
||||
new RuntimeCollisionAcknowledgement(
|
||||
mutation.LandblockId,
|
||||
mutation.TargetGeneration,
|
||||
mutation.WasResident,
|
||||
Ready: mutation.EngineMutationCommitted && mutation.Ready),
|
||||
Completed: false);
|
||||
|
||||
private void CommitCollisionInvalidation(
|
||||
RuntimeCollisionPrefixMutation mutation)
|
||||
{
|
||||
_collisionGenerations[mutation.LandblockId] =
|
||||
mutation.TargetGeneration;
|
||||
SetPosition.CancelCollisionGeneration(
|
||||
mutation.LandblockId,
|
||||
mutation.InvalidatedGeneration);
|
||||
if (_collisionAdmissions.TryGetValue(
|
||||
mutation.LandblockId,
|
||||
out RuntimeCollisionAdmission? currentAdmission)
|
||||
&& ReferenceEquals(currentAdmission, mutation.Admission))
|
||||
{
|
||||
_collisionAdmissions.Remove(mutation.LandblockId);
|
||||
}
|
||||
if (_preparedCollisionGenerations.TryGetValue(
|
||||
mutation.LandblockId,
|
||||
out PreparedLandblockCollisionGeneration? currentPrepared)
|
||||
&& ReferenceEquals(currentPrepared, mutation.Prepared))
|
||||
{
|
||||
_preparedCollisionGenerations.Remove(mutation.LandblockId);
|
||||
currentPrepared.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public RuntimeCollisionMutationResult DemoteCollisionToTerrain(
|
||||
uint landblockId)
|
||||
=> AdvanceCollisionRetirementMutation(
|
||||
landblockId,
|
||||
RuntimeCollisionPrefixMutationKind.Demotion);
|
||||
|
||||
public RuntimeCollisionMutationResult WithdrawCollision(
|
||||
uint landblockId)
|
||||
=> AdvanceCollisionRetirementMutation(
|
||||
landblockId,
|
||||
RuntimeCollisionPrefixMutationKind.Withdrawal);
|
||||
|
||||
private RuntimeCollisionMutationResult AdvanceCollisionRetirementMutation(
|
||||
uint landblockId,
|
||||
RuntimeCollisionPrefixMutationKind kind)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
EnsureCollisionMutationThread();
|
||||
uint canonical = CanonicalLandblock(landblockId);
|
||||
bool resident = Engine.IsLandblockTerrainResident(canonical);
|
||||
InvalidateCollisionAdmission(canonical);
|
||||
bool suppressOwnerJournal = _suppressCollisionOwnerJournal;
|
||||
_suppressCollisionOwnerJournal = true;
|
||||
try
|
||||
{
|
||||
Engine.DemoteLandblockToTerrain(canonical);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressCollisionOwnerJournal = suppressOwnerJournal;
|
||||
}
|
||||
foreach ((_, PreparedLandblockCollisionGeneration prepared) in
|
||||
_preparedCollisionGenerations)
|
||||
{
|
||||
prepared.RecordDemotion(canonical);
|
||||
}
|
||||
return new RuntimeCollisionAcknowledgement(
|
||||
canonical,
|
||||
_collisionGenerations[canonical],
|
||||
resident,
|
||||
Ready: Engine.IsLandblockTerrainResident(canonical));
|
||||
}
|
||||
if (canonical == 0u)
|
||||
throw new ArgumentOutOfRangeException(nameof(landblockId));
|
||||
if (kind is RuntimeCollisionPrefixMutationKind.Activation)
|
||||
throw new ArgumentOutOfRangeException(nameof(kind));
|
||||
|
||||
public RuntimeCollisionAcknowledgement WithdrawCollision(
|
||||
uint landblockId)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
EnsureCollisionMutationThread();
|
||||
uint canonical = CanonicalLandblock(landblockId);
|
||||
bool resident = Engine.IsLandblockTerrainResident(canonical);
|
||||
InvalidateCollisionAdmission(canonical);
|
||||
bool suppressOwnerJournal = _suppressCollisionOwnerJournal;
|
||||
_suppressCollisionOwnerJournal = true;
|
||||
try
|
||||
if (!_collisionPrefixMutations.TryGetValue(
|
||||
canonical,
|
||||
out RuntimeCollisionPrefixMutation? mutation))
|
||||
{
|
||||
Engine.RemoveLandblock(canonical);
|
||||
ulong currentGeneration = _collisionGenerations.TryGetValue(
|
||||
canonical,
|
||||
out ulong current)
|
||||
? current
|
||||
: 0UL;
|
||||
RuntimeCollisionAdmission? admission =
|
||||
_collisionAdmissions.GetValueOrDefault(canonical);
|
||||
ulong previousGeneration = admission?.PreviousGeneration
|
||||
?? currentGeneration;
|
||||
ulong invalidatedGeneration = admission is not null
|
||||
? admission.Generation
|
||||
: checked(currentGeneration + 1UL);
|
||||
ulong targetGeneration = checked(
|
||||
Math.Max(currentGeneration, invalidatedGeneration) + 1UL);
|
||||
SetPosition.BeginCollisionGeneration(
|
||||
canonical,
|
||||
targetGeneration);
|
||||
RuntimeCollisionPrefixQuiescenceToken token =
|
||||
BeginCollisionPrefixQuiescence(
|
||||
canonical,
|
||||
targetGeneration,
|
||||
includeOutdoorCells:
|
||||
kind is RuntimeCollisionPrefixMutationKind.Withdrawal);
|
||||
mutation = new RuntimeCollisionPrefixMutation
|
||||
{
|
||||
Kind = kind,
|
||||
LandblockId = canonical,
|
||||
PreviousGeneration = previousGeneration,
|
||||
InvalidatedGeneration = invalidatedGeneration,
|
||||
TargetGeneration = targetGeneration,
|
||||
Quiescence = token,
|
||||
Admission = admission,
|
||||
Prepared = _preparedCollisionGenerations.GetValueOrDefault(
|
||||
canonical),
|
||||
WasResident = Engine.IsLandblockTerrainResident(canonical),
|
||||
};
|
||||
_collisionPrefixMutations.Add(canonical, mutation);
|
||||
}
|
||||
finally
|
||||
if (mutation.Kind != kind)
|
||||
{
|
||||
_suppressCollisionOwnerJournal = suppressOwnerJournal;
|
||||
throw new InvalidOperationException(
|
||||
"A different collision-prefix mutation owns this landblock.");
|
||||
}
|
||||
foreach ((_, PreparedLandblockCollisionGeneration prepared) in
|
||||
_preparedCollisionGenerations)
|
||||
|
||||
if (!mutation.EngineMutationCommitted)
|
||||
{
|
||||
prepared.RecordWithdrawal(canonical);
|
||||
if (!TryAcquireCollisionPrefixMutationPermission(
|
||||
mutation.Quiescence,
|
||||
out RuntimeCollisionPrefixMutationPermission permission)
|
||||
|| !IsCollisionPrefixMutationPermissionCurrent(permission))
|
||||
{
|
||||
return PendingRetirement(mutation);
|
||||
}
|
||||
mutation.Permission = permission;
|
||||
CommitCollisionInvalidation(mutation);
|
||||
|
||||
bool suppressOwnerJournal = _suppressCollisionOwnerJournal;
|
||||
_suppressCollisionOwnerJournal = true;
|
||||
try
|
||||
{
|
||||
if (kind is RuntimeCollisionPrefixMutationKind.Demotion)
|
||||
Engine.DemoteLandblockToTerrain(canonical);
|
||||
else
|
||||
Engine.RemoveLandblock(canonical);
|
||||
AdvanceCollisionWorldAuthority();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressCollisionOwnerJournal = suppressOwnerJournal;
|
||||
}
|
||||
foreach ((_, PreparedLandblockCollisionGeneration prepared) in
|
||||
_preparedCollisionGenerations)
|
||||
{
|
||||
if (kind is RuntimeCollisionPrefixMutationKind.Demotion)
|
||||
prepared.RecordDemotion(canonical);
|
||||
else
|
||||
prepared.RecordWithdrawal(canonical);
|
||||
}
|
||||
mutation.EngineMutationCommitted = true;
|
||||
mutation.Ready = kind is RuntimeCollisionPrefixMutationKind.Demotion
|
||||
&& Engine.IsLandblockTerrainResident(canonical);
|
||||
if (mutation.Ready)
|
||||
{
|
||||
SetPosition.CommitCollisionGeneration(
|
||||
canonical,
|
||||
mutation.TargetGeneration,
|
||||
ready: true);
|
||||
}
|
||||
}
|
||||
return new RuntimeCollisionAcknowledgement(
|
||||
canonical,
|
||||
_collisionGenerations[canonical],
|
||||
resident,
|
||||
Ready: false);
|
||||
|
||||
bool completed = SetPosition.ReleaseCollisionPrefixAfterMutation(
|
||||
mutation.Quiescence,
|
||||
mutation.TargetGeneration,
|
||||
mutation.Ready);
|
||||
if (completed)
|
||||
{
|
||||
_collisionPrefixMutations.Remove(canonical);
|
||||
TrimCollisionOwnerJournal();
|
||||
}
|
||||
return new RuntimeCollisionMutationResult(
|
||||
new RuntimeCollisionAcknowledgement(
|
||||
canonical,
|
||||
mutation.TargetGeneration,
|
||||
mutation.WasResident,
|
||||
mutation.Ready),
|
||||
completed);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -2419,6 +2762,7 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
_spatialRemotes.Clear();
|
||||
_spatialProjectiles.Clear();
|
||||
_spatialRoots.Clear();
|
||||
_collisionPrefixMutations.Clear();
|
||||
_collisionAdmissions.Clear();
|
||||
_collisionGenerations.Clear();
|
||||
CellCommitted = null;
|
||||
|
|
@ -2570,35 +2914,6 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private void InvalidateCollisionAdmission(uint landblockId)
|
||||
{
|
||||
AdvanceCollisionWorldAuthority();
|
||||
ulong currentGeneration = _collisionGenerations.TryGetValue(
|
||||
landblockId,
|
||||
out ulong current)
|
||||
? current
|
||||
: 0UL;
|
||||
ulong invalidatedGeneration = _collisionAdmissions.TryGetValue(
|
||||
landblockId,
|
||||
out RuntimeCollisionAdmission? admission)
|
||||
? admission.Generation
|
||||
: checked(currentGeneration + 1UL);
|
||||
ulong generation = checked(
|
||||
Math.Max(currentGeneration, invalidatedGeneration) + 1UL);
|
||||
_collisionGenerations[landblockId] = generation;
|
||||
SetPosition.CancelCollisionGeneration(
|
||||
landblockId,
|
||||
invalidatedGeneration);
|
||||
_collisionAdmissions.Remove(landblockId);
|
||||
if (_preparedCollisionGenerations.Remove(
|
||||
landblockId,
|
||||
out PreparedLandblockCollisionGeneration? prepared))
|
||||
{
|
||||
prepared.Dispose();
|
||||
}
|
||||
TrimCollisionOwnerJournal();
|
||||
}
|
||||
|
||||
internal bool TryPrepareSpatialRootAdmission(RuntimeEntityRecord record)
|
||||
{
|
||||
EnsureNotDisposed();
|
||||
|
|
@ -2960,7 +3275,13 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
{
|
||||
return;
|
||||
}
|
||||
subscribers.Remove(prepared);
|
||||
for (int index = 0; index < subscribers.Count; index++)
|
||||
{
|
||||
if (!ReferenceEquals(subscribers[index], prepared))
|
||||
continue;
|
||||
subscribers.RemoveAt(index);
|
||||
break;
|
||||
}
|
||||
if (subscribers.Count == 0)
|
||||
_collisionOwnerSubscribers.Remove(ownerId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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