feat(runtime): own initial create residence transaction
This commit is contained in:
parent
74103f75b5
commit
38fd4b8dc9
10 changed files with 3031 additions and 17 deletions
|
|
@ -102,6 +102,25 @@ public sealed class PhysicsTimestampGate
|
||||||
return CreateObjectTimestampDisposition.ExistingGeneration;
|
return CreateObjectTimestampDisposition.ExistingGeneration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Classifies an incoming CreateObject generation without consuming any
|
||||||
|
/// channel timestamp. Runtime uses this to preflight only admissions which
|
||||||
|
/// can create or replace a canonical incarnation; equal/stale packets must
|
||||||
|
/// still reach the normal per-channel retail gates unchanged.
|
||||||
|
/// </summary>
|
||||||
|
public CreateObjectTimestampDisposition PreviewCreateObject(
|
||||||
|
ushort instance)
|
||||||
|
{
|
||||||
|
if (!_seeded)
|
||||||
|
return CreateObjectTimestampDisposition.InitialGeneration;
|
||||||
|
ushort currentInstance = _timestamps[Instance];
|
||||||
|
if (IsNewer(currentInstance, instance))
|
||||||
|
return CreateObjectTimestampDisposition.NewGeneration;
|
||||||
|
if (IsNewer(instance, currentInstance))
|
||||||
|
return CreateObjectTimestampDisposition.StaleGeneration;
|
||||||
|
return CreateObjectTimestampDisposition.ExistingGeneration;
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryAcceptMovementEvent(
|
public bool TryAcceptMovementEvent(
|
||||||
ushort instance,
|
ushort instance,
|
||||||
ushort movement,
|
ushort movement,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,12 @@ public sealed class InboundPhysicsStateController
|
||||||
public bool TryGetSnapshot(uint guid, out WorldSession.EntitySpawn spawn) =>
|
public bool TryGetSnapshot(uint guid, out WorldSession.EntitySpawn spawn) =>
|
||||||
_snapshots.TryGetValue(guid, out spawn);
|
_snapshots.TryGetValue(guid, out spawn);
|
||||||
|
|
||||||
|
public CreateObjectTimestampDisposition PreviewCreateDisposition(
|
||||||
|
WorldSession.EntitySpawn incoming) =>
|
||||||
|
_gates.TryGetValue(incoming.Guid, out PhysicsTimestampGate? gate)
|
||||||
|
? gate.PreviewCreateObject(incoming.InstanceSequence)
|
||||||
|
: CreateObjectTimestampDisposition.InitialGeneration;
|
||||||
|
|
||||||
public InboundCreateResult AcceptCreate(WorldSession.EntitySpawn incoming)
|
public InboundCreateResult AcceptCreate(WorldSession.EntitySpawn incoming)
|
||||||
{
|
{
|
||||||
if (!_gates.TryGetValue(incoming.Guid, out PhysicsTimestampGate? gate))
|
if (!_gates.TryGetValue(incoming.Guid, out PhysicsTimestampGate? gate))
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,10 @@ public sealed class RuntimeEntityDirectory
|
||||||
public InboundCreateResult AcceptCreate(WorldSession.EntitySpawn incoming) =>
|
public InboundCreateResult AcceptCreate(WorldSession.EntitySpawn incoming) =>
|
||||||
_inbound.AcceptCreate(incoming);
|
_inbound.AcceptCreate(incoming);
|
||||||
|
|
||||||
|
public CreateObjectTimestampDisposition PreviewCreateDisposition(
|
||||||
|
WorldSession.EntitySpawn incoming) =>
|
||||||
|
_inbound.PreviewCreateDisposition(incoming);
|
||||||
|
|
||||||
public bool TryDelete(
|
public bool TryDelete(
|
||||||
AcDream.Core.Net.Messages.DeleteObject.Parsed delete,
|
AcDream.Core.Net.Messages.DeleteObject.Parsed delete,
|
||||||
bool isLocalPlayer) =>
|
bool isLocalPlayer) =>
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
|
||||||
int ContainerProjectionCount,
|
int ContainerProjectionCount,
|
||||||
int EquipmentOwnerCount,
|
int EquipmentOwnerCount,
|
||||||
int PendingMoveCount,
|
int PendingMoveCount,
|
||||||
|
int InitialCreateResidenceLeaseCount,
|
||||||
int StreamSubscriberCount,
|
int StreamSubscriberCount,
|
||||||
int PlacementStreamSubscriberCount,
|
int PlacementStreamSubscriberCount,
|
||||||
long StreamDispatchFailureCount,
|
long StreamDispatchFailureCount,
|
||||||
|
|
@ -51,6 +52,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
|
||||||
&& ContainerProjectionCount == 0
|
&& ContainerProjectionCount == 0
|
||||||
&& EquipmentOwnerCount == 0
|
&& EquipmentOwnerCount == 0
|
||||||
&& PendingMoveCount == 0
|
&& PendingMoveCount == 0
|
||||||
|
&& InitialCreateResidenceLeaseCount == 0
|
||||||
&& StreamSubscriberCount == 0
|
&& StreamSubscriberCount == 0
|
||||||
&& PlacementStreamSubscriberCount == 0
|
&& PlacementStreamSubscriberCount == 0
|
||||||
&& PendingDispatchCount == 0
|
&& PendingDispatchCount == 0
|
||||||
|
|
@ -118,6 +120,9 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
InventoryView = views.Inventory;
|
InventoryView = views.Inventory;
|
||||||
Events = new RuntimeEntityObjectEventStream(Entities, Objects);
|
Events = new RuntimeEntityObjectEventStream(Entities, Objects);
|
||||||
Physics.SetPosition.BindEventStream(Events);
|
Physics.SetPosition.BindEventStream(Events);
|
||||||
|
InitialCreateResidences = new RuntimeInitialCreateResidenceState(
|
||||||
|
Entities,
|
||||||
|
Physics.SetPosition);
|
||||||
Placements = new RuntimePlacementProjectionChannel(
|
Placements = new RuntimePlacementProjectionChannel(
|
||||||
Events,
|
Events,
|
||||||
Physics.SetPosition);
|
Physics.SetPosition);
|
||||||
|
|
@ -143,6 +148,9 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
InventoryView = views.Inventory;
|
InventoryView = views.Inventory;
|
||||||
Events = new RuntimeEntityObjectEventStream(Entities, Objects);
|
Events = new RuntimeEntityObjectEventStream(Entities, Objects);
|
||||||
Physics.SetPosition.BindEventStream(Events);
|
Physics.SetPosition.BindEventStream(Events);
|
||||||
|
InitialCreateResidences = new RuntimeInitialCreateResidenceState(
|
||||||
|
Entities,
|
||||||
|
Physics.SetPosition);
|
||||||
Placements = new RuntimePlacementProjectionChannel(
|
Placements = new RuntimePlacementProjectionChannel(
|
||||||
Events,
|
Events,
|
||||||
Physics.SetPosition);
|
Physics.SetPosition);
|
||||||
|
|
@ -168,6 +176,9 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
InventoryView = views.Inventory;
|
InventoryView = views.Inventory;
|
||||||
Events = new RuntimeEntityObjectEventStream(Entities, Objects);
|
Events = new RuntimeEntityObjectEventStream(Entities, Objects);
|
||||||
Physics.SetPosition.BindEventStream(Events);
|
Physics.SetPosition.BindEventStream(Events);
|
||||||
|
InitialCreateResidences = new RuntimeInitialCreateResidenceState(
|
||||||
|
Entities,
|
||||||
|
Physics.SetPosition);
|
||||||
Placements = new RuntimePlacementProjectionChannel(
|
Placements = new RuntimePlacementProjectionChannel(
|
||||||
Events,
|
Events,
|
||||||
Physics.SetPosition);
|
Physics.SetPosition);
|
||||||
|
|
@ -180,10 +191,14 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
public IRuntimeInventoryView InventoryView { get; }
|
public IRuntimeInventoryView InventoryView { get; }
|
||||||
public RuntimeEntityObjectEventStream Events { get; }
|
public RuntimeEntityObjectEventStream Events { get; }
|
||||||
public RuntimePlacementProjectionChannel Placements { get; }
|
public RuntimePlacementProjectionChannel Placements { get; }
|
||||||
|
internal RuntimeInitialCreateResidenceState InitialCreateResidences
|
||||||
|
{ get; }
|
||||||
|
|
||||||
public RuntimeEntityObjectOwnershipSnapshot CaptureOwnership()
|
public RuntimeEntityObjectOwnershipSnapshot CaptureOwnership()
|
||||||
{
|
{
|
||||||
ParentAttachmentState parents = Entities.ParentAttachments;
|
ParentAttachmentState parents = Entities.ParentAttachments;
|
||||||
|
RuntimeInitialCreateResidenceOwnershipSnapshot initialResidence =
|
||||||
|
InitialCreateResidences.CaptureOwnership();
|
||||||
return new RuntimeEntityObjectOwnershipSnapshot(
|
return new RuntimeEntityObjectOwnershipSnapshot(
|
||||||
Entities.Count,
|
Entities.Count,
|
||||||
Entities.PendingTeardownCount,
|
Entities.PendingTeardownCount,
|
||||||
|
|
@ -198,6 +213,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
Objects.ContainerProjectionCount,
|
Objects.ContainerProjectionCount,
|
||||||
Objects.EquipmentOwnerCount,
|
Objects.EquipmentOwnerCount,
|
||||||
Objects.PendingMoveCount,
|
Objects.PendingMoveCount,
|
||||||
|
initialResidence.ActiveLeaseCount
|
||||||
|
+ initialResidence.PendingAdoptionCount,
|
||||||
Events.SubscriberCount,
|
Events.SubscriberCount,
|
||||||
Events.PlacementSubscriberCount,
|
Events.PlacementSubscriberCount,
|
||||||
Events.DispatchFailureCount,
|
Events.DispatchFailureCount,
|
||||||
|
|
@ -215,6 +232,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
EnsureNotDisposed();
|
EnsureNotDisposed();
|
||||||
Events.BindContext(generation, frameNumber);
|
Events.BindContext(generation, frameNumber);
|
||||||
Placements.BindGeneration(generation);
|
Placements.BindGeneration(generation);
|
||||||
|
InitialCreateResidences.BindGeneration(generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -225,7 +243,28 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RuntimeEntityRegistrationResult RegisterEntity(
|
public RuntimeEntityRegistrationResult RegisterEntity(
|
||||||
WorldSession.EntitySpawn incoming,
|
WorldSession.EntitySpawn incoming,
|
||||||
Func<RuntimeEntityRecord, Exception?>? retirePriorProjection = null)
|
Func<RuntimeEntityRecord, Exception?>? retirePriorProjection = null) =>
|
||||||
|
RegisterEntityCore(
|
||||||
|
incoming,
|
||||||
|
beginInitialResidence: false,
|
||||||
|
isLocalPlayer: false,
|
||||||
|
retirePriorProjection);
|
||||||
|
|
||||||
|
internal RuntimeEntityRegistrationResult RegisterEntityWithInitialResidence(
|
||||||
|
WorldSession.EntitySpawn incoming,
|
||||||
|
bool isLocalPlayer,
|
||||||
|
Func<RuntimeEntityRecord, Exception?>? retirePriorProjection = null) =>
|
||||||
|
RegisterEntityCore(
|
||||||
|
incoming,
|
||||||
|
beginInitialResidence: true,
|
||||||
|
isLocalPlayer,
|
||||||
|
retirePriorProjection);
|
||||||
|
|
||||||
|
private RuntimeEntityRegistrationResult RegisterEntityCore(
|
||||||
|
WorldSession.EntitySpawn incoming,
|
||||||
|
bool beginInitialResidence,
|
||||||
|
bool isLocalPlayer,
|
||||||
|
Func<RuntimeEntityRecord, Exception?>? retirePriorProjection)
|
||||||
{
|
{
|
||||||
EnsureNotDisposed();
|
EnsureNotDisposed();
|
||||||
if (_sessionClearInProgress)
|
if (_sessionClearInProgress)
|
||||||
|
|
@ -233,6 +272,18 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
"A Runtime entity cannot register while its session lifetime is clearing.");
|
"A Runtime entity cannot register while its session lifetime is clearing.");
|
||||||
}
|
}
|
||||||
|
CreateObjectTimestampDisposition preview =
|
||||||
|
Entities.PreviewCreateDisposition(incoming);
|
||||||
|
bool requiresFreshResidenceAdmission = preview is
|
||||||
|
CreateObjectTimestampDisposition.InitialGeneration
|
||||||
|
or CreateObjectTimestampDisposition.NewGeneration;
|
||||||
|
if (beginInitialResidence
|
||||||
|
&& requiresFreshResidenceAdmission
|
||||||
|
&& !InitialCreateResidences.CanAcceptCreate(incoming))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"CreateObject 0x{incoming.Guid:X8} cannot acquire a structurally valid initial residence lease.");
|
||||||
|
}
|
||||||
|
|
||||||
InboundCreateResult result = Entities.AcceptCreate(incoming);
|
InboundCreateResult result = Entities.AcceptCreate(incoming);
|
||||||
if (result.Disposition
|
if (result.Disposition
|
||||||
|
|
@ -256,11 +307,16 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
incoming.Guid,
|
incoming.Guid,
|
||||||
out RuntimeEntityRecord retained))
|
out RuntimeEntityRecord retained))
|
||||||
{
|
{
|
||||||
|
// Existing-generation CreateObject contributes untimestamped
|
||||||
|
// description fields here. Position/Parent/Pickup/State/etc.
|
||||||
|
// remain separate freshness-gated events and must not churn a
|
||||||
|
// pending initial placement or re-preclaim its wire cell.
|
||||||
Entities.RefreshSnapshot(
|
Entities.RefreshSnapshot(
|
||||||
retained,
|
retained,
|
||||||
result.Snapshot,
|
result.Snapshot,
|
||||||
refreshPosition: true);
|
refreshPosition: !beginInitialResidence);
|
||||||
Entities.AdvanceCreateAuthority(retained);
|
if (!beginInitialResidence)
|
||||||
|
Entities.AdvanceCreateAuthority(retained);
|
||||||
PublishEntity(RuntimeEntityChange.Updated, retained);
|
PublishEntity(RuntimeEntityChange.Updated, retained);
|
||||||
if (!IsCurrentOperation(
|
if (!IsCurrentOperation(
|
||||||
incoming.Guid,
|
incoming.Guid,
|
||||||
|
|
@ -291,7 +347,24 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
ReplacedExistingGeneration: false);
|
ReplacedExistingGeneration: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (beginInitialResidence
|
||||||
|
&& !InitialCreateResidences.CanAcceptCreate(result.Snapshot))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Recovered CreateObject 0x{incoming.Guid:X8} cannot acquire a structurally valid initial residence lease.");
|
||||||
|
}
|
||||||
|
|
||||||
RuntimeEntityRecord recovered = Entities.AddActive(result.Snapshot);
|
RuntimeEntityRecord recovered = Entities.AddActive(result.Snapshot);
|
||||||
|
if (!InitializeAcceptedCreateResidence(
|
||||||
|
recovered,
|
||||||
|
result,
|
||||||
|
beginInitialResidence,
|
||||||
|
isLocalPlayer))
|
||||||
|
{
|
||||||
|
throw FailInitialResidenceRegistration(
|
||||||
|
recovered,
|
||||||
|
publishDeleted: false);
|
||||||
|
}
|
||||||
PublishEntity(RuntimeEntityChange.Registered, recovered);
|
PublishEntity(RuntimeEntityChange.Registered, recovered);
|
||||||
if (!IsCurrentOperation(
|
if (!IsCurrentOperation(
|
||||||
incoming.Guid,
|
incoming.Guid,
|
||||||
|
|
@ -366,6 +439,16 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimeEntityRecord canonical = Entities.AddActive(result.Snapshot);
|
RuntimeEntityRecord canonical = Entities.AddActive(result.Snapshot);
|
||||||
|
if (!InitializeAcceptedCreateResidence(
|
||||||
|
canonical,
|
||||||
|
result,
|
||||||
|
beginInitialResidence,
|
||||||
|
isLocalPlayer))
|
||||||
|
{
|
||||||
|
throw FailInitialResidenceRegistration(
|
||||||
|
canonical,
|
||||||
|
publishDeleted: false);
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PublishEntity(RuntimeEntityChange.Registered, canonical);
|
PublishEntity(RuntimeEntityChange.Registered, canonical);
|
||||||
|
|
@ -446,10 +529,14 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
{
|
{
|
||||||
EnsureNotDisposed();
|
EnsureNotDisposed();
|
||||||
ArgumentNullException.ThrowIfNull(canonical);
|
ArgumentNullException.ThrowIfNull(canonical);
|
||||||
RuntimePlacementCancellationReceipt cancellation =
|
RuntimePlacementCancellationReceipt initialCancellation =
|
||||||
|
ForgetInitialCreateResidence(canonical);
|
||||||
|
RuntimePlacementCancellationReceipt ordinaryCancellation =
|
||||||
Physics.SetPosition.Forget(
|
Physics.SetPosition.Forget(
|
||||||
canonical,
|
canonical,
|
||||||
releasePreparedMover: true);
|
releasePreparedMover: true);
|
||||||
|
RuntimePlacementCancellationReceipt cancellation =
|
||||||
|
PreferCancellation(initialCancellation, ordinaryCancellation);
|
||||||
Physics.CollisionReports.Forget(canonical);
|
Physics.CollisionReports.Forget(canonical);
|
||||||
Physics.RemoveSpatialProjection(canonical);
|
Physics.RemoveSpatialProjection(canonical);
|
||||||
Entities.SetRemoteMotion(canonical, null);
|
Entities.SetRemoteMotion(canonical, null);
|
||||||
|
|
@ -540,10 +627,14 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
Entities.RefreshSnapshot(canonical, accepted);
|
Entities.RefreshSnapshot(canonical, accepted);
|
||||||
|
RuntimePlacementCancellationReceipt initialCancellation =
|
||||||
|
ForgetInitialCreateResidence(canonical);
|
||||||
Entities.AdvancePositionAuthority(canonical);
|
Entities.AdvancePositionAuthority(canonical);
|
||||||
Physics.CollisionReports.LeaveWorld(canonical);
|
Physics.CollisionReports.LeaveWorld(canonical);
|
||||||
RuntimePlacementCancellationReceipt cancellation =
|
RuntimePlacementCancellationReceipt ordinaryCancellation =
|
||||||
Physics.SetPosition.Forget(canonical);
|
Physics.SetPosition.Forget(canonical);
|
||||||
|
RuntimePlacementCancellationReceipt cancellation =
|
||||||
|
PreferCancellation(initialCancellation, ordinaryCancellation);
|
||||||
Entities.SuspendObjectClock(canonical);
|
Entities.SuspendObjectClock(canonical);
|
||||||
Entities.SetFullCell(canonical, 0u, 0u);
|
Entities.SetFullCell(canonical, 0u, 0u);
|
||||||
Entities.ParentAttachments.EndChildProjection(update.Guid);
|
Entities.ParentAttachments.EndChildProjection(update.Guid);
|
||||||
|
|
@ -631,8 +722,12 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimePlacementCancellationReceipt cancellation =
|
RuntimePlacementCancellationReceipt initialCancellation =
|
||||||
|
ForgetInitialCreateResidence(canonical);
|
||||||
|
RuntimePlacementCancellationReceipt ordinaryCancellation =
|
||||||
Physics.SetPosition.Forget(canonical);
|
Physics.SetPosition.Forget(canonical);
|
||||||
|
RuntimePlacementCancellationReceipt cancellation =
|
||||||
|
PreferCancellation(initialCancellation, ordinaryCancellation);
|
||||||
Physics.CollisionReports.LeaveWorld(canonical);
|
Physics.CollisionReports.LeaveWorld(canonical);
|
||||||
Entities.SuspendObjectClock(canonical);
|
Entities.SuspendObjectClock(canonical);
|
||||||
Entities.SetFullCell(canonical, 0u, 0u);
|
Entities.SetFullCell(canonical, 0u, 0u);
|
||||||
|
|
@ -788,6 +883,25 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
out AcceptedPhysicsTimestamps timestamps)
|
out AcceptedPhysicsTimestamps timestamps)
|
||||||
{
|
{
|
||||||
EnsureNotDisposed();
|
EnsureNotDisposed();
|
||||||
|
RuntimeInitialCreateResidenceLease priorInitialResidence = default;
|
||||||
|
bool hadPendingInitialResidence =
|
||||||
|
Entities.TryGetActive(
|
||||||
|
update.Guid,
|
||||||
|
out RuntimeEntityRecord pendingCanonical)
|
||||||
|
&& InitialCreateResidences.TryGetTransaction(
|
||||||
|
pendingCanonical,
|
||||||
|
out priorInitialResidence);
|
||||||
|
if (hadPendingInitialResidence
|
||||||
|
&& !InitialCreateResidences.CanEnqueueAcceptedPosition(
|
||||||
|
pendingCanonical,
|
||||||
|
priorInitialResidence,
|
||||||
|
update))
|
||||||
|
{
|
||||||
|
disposition = PositionTimestampDisposition.Rejected;
|
||||||
|
accepted = default;
|
||||||
|
timestamps = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
bool hadCanonical = Entities.TryGetActive(
|
bool hadCanonical = Entities.TryGetActive(
|
||||||
update.Guid,
|
update.Guid,
|
||||||
out RuntimeEntityRecord beforeCanonical);
|
out RuntimeEntityRecord beforeCanonical);
|
||||||
|
|
@ -825,9 +939,47 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimePlacementCancellationReceipt cancellation = acceptedPosition
|
|
||||||
? Physics.SetPosition.Forget(canonical)
|
// Initial CreateObject residence is one immutable transaction. A
|
||||||
: default;
|
// Position packet accepted before its 0x11 placement completes is
|
||||||
|
// retained in the transaction's ordered continuation batch; it must
|
||||||
|
// not replace the initial mover operation, mutate the Runtime record
|
||||||
|
// used to prepare that operation, or escape through a host callback.
|
||||||
|
if (hadPendingInitialResidence)
|
||||||
|
{
|
||||||
|
if (!acceptedPosition)
|
||||||
|
return true;
|
||||||
|
if (!ReferenceEquals(canonical, pendingCanonical))
|
||||||
|
throw FailInitialResidenceRegistration(
|
||||||
|
canonical,
|
||||||
|
publishDeleted: true);
|
||||||
|
|
||||||
|
RuntimeInitialCreateResidenceLease retained =
|
||||||
|
InitialCreateResidences.EnqueueAcceptedPosition(
|
||||||
|
canonical,
|
||||||
|
priorInitialResidence,
|
||||||
|
update,
|
||||||
|
accepted,
|
||||||
|
disposition,
|
||||||
|
timestamps,
|
||||||
|
isLocalPlayer,
|
||||||
|
forcePositionRotation,
|
||||||
|
currentLocalVelocity,
|
||||||
|
projectionRequiresTeleportHook);
|
||||||
|
if (!retained.IsValid)
|
||||||
|
{
|
||||||
|
throw FailInitialResidenceRegistration(
|
||||||
|
canonical,
|
||||||
|
publishDeleted: true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimePlacementCancellationReceipt cancellation = default;
|
||||||
|
if (acceptedPosition)
|
||||||
|
{
|
||||||
|
cancellation = Physics.SetPosition.Forget(canonical);
|
||||||
|
}
|
||||||
Entities.RefreshSnapshot(
|
Entities.RefreshSnapshot(
|
||||||
canonical,
|
canonical,
|
||||||
snapshot,
|
snapshot,
|
||||||
|
|
@ -903,6 +1055,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
if (!Entities.IsCurrent(canonical))
|
if (!Entities.IsCurrent(canonical))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
RuntimePlacementCancellationReceipt cancellation =
|
||||||
|
ForgetInitialCreateResidence(canonical);
|
||||||
Physics.CollisionReports.LeaveWorld(canonical);
|
Physics.CollisionReports.LeaveWorld(canonical);
|
||||||
Entities.SuspendObjectClock(canonical);
|
Entities.SuspendObjectClock(canonical);
|
||||||
Entities.SetFullCell(canonical, 0u, 0u);
|
Entities.SetFullCell(canonical, 0u, 0u);
|
||||||
|
|
@ -911,7 +1065,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
canonical,
|
canonical,
|
||||||
() => acknowledgeProjection?.Invoke(canonical),
|
() => acknowledgeProjection?.Invoke(canonical),
|
||||||
RuntimeEntityChange.Withdrawn,
|
RuntimeEntityChange.Withdrawn,
|
||||||
() => canonical.SpatialAuthorityVersion == spatialVersion);
|
() => canonical.SpatialAuthorityVersion == spatialVersion,
|
||||||
|
cancellation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CommitChildNoDraw(
|
public bool CommitChildNoDraw(
|
||||||
|
|
@ -981,11 +1136,17 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
&& active.Incarnation == delete.InstanceSequence
|
&& active.Incarnation == delete.InstanceSequence
|
||||||
&& Entities.RemoveActive(active))
|
&& Entities.RemoveActive(active))
|
||||||
{
|
{
|
||||||
|
RuntimePlacementCancellationReceipt initialCancellation =
|
||||||
|
ForgetInitialCreateResidence(active);
|
||||||
Physics.CollisionReports.Forget(active);
|
Physics.CollisionReports.Forget(active);
|
||||||
RuntimePlacementCancellationReceipt cancellation =
|
RuntimePlacementCancellationReceipt ordinaryCancellation =
|
||||||
Physics.SetPosition.Forget(
|
Physics.SetPosition.Forget(
|
||||||
active,
|
active,
|
||||||
releasePreparedMover: true);
|
releasePreparedMover: true);
|
||||||
|
RuntimePlacementCancellationReceipt cancellation =
|
||||||
|
PreferCancellation(
|
||||||
|
initialCancellation,
|
||||||
|
ordinaryCancellation);
|
||||||
retiredCanonical = active;
|
retiredCanonical = active;
|
||||||
Entities.RetainTeardown(active);
|
Entities.RetainTeardown(active);
|
||||||
Physics.SetPosition.PublishCancellation(cancellation);
|
Physics.SetPosition.PublishCancellation(cancellation);
|
||||||
|
|
@ -1058,6 +1219,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
|
|
||||||
_sessionClearInProgress = true;
|
_sessionClearInProgress = true;
|
||||||
RuntimeEntityRecord[] active = Entities.ActiveRecords.ToArray();
|
RuntimeEntityRecord[] active = Entities.ActiveRecords.ToArray();
|
||||||
|
InitialCreateResidences.Clear();
|
||||||
Physics.CollisionReports.LeaveWorldBatch(active);
|
Physics.CollisionReports.LeaveWorldBatch(active);
|
||||||
Physics.ResetSessionPhysics();
|
Physics.ResetSessionPhysics();
|
||||||
Entities.BeginSessionClear();
|
Entities.BeginSessionClear();
|
||||||
|
|
@ -1193,10 +1355,14 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
Entities.RefreshSnapshot(canonical, accepted);
|
Entities.RefreshSnapshot(canonical, accepted);
|
||||||
|
RuntimePlacementCancellationReceipt initialCancellation =
|
||||||
|
ForgetInitialCreateResidence(canonical);
|
||||||
Entities.AdvancePositionAuthority(canonical);
|
Entities.AdvancePositionAuthority(canonical);
|
||||||
Physics.CollisionReports.LeaveWorld(canonical);
|
Physics.CollisionReports.LeaveWorld(canonical);
|
||||||
RuntimePlacementCancellationReceipt cancellation =
|
RuntimePlacementCancellationReceipt ordinaryCancellation =
|
||||||
Physics.SetPosition.Forget(canonical);
|
Physics.SetPosition.Forget(canonical);
|
||||||
|
RuntimePlacementCancellationReceipt cancellation =
|
||||||
|
PreferCancellation(initialCancellation, ordinaryCancellation);
|
||||||
ulong positionVersion = canonical.PositionAuthorityVersion;
|
ulong positionVersion = canonical.PositionAuthorityVersion;
|
||||||
ulong spatialVersion = canonical.SpatialAuthorityVersion;
|
ulong spatialVersion = canonical.SpatialAuthorityVersion;
|
||||||
return AcknowledgeProjectionAndPublish(
|
return AcknowledgeProjectionAndPublish(
|
||||||
|
|
@ -1251,6 +1417,108 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
||||||
private void EnsureNotDisposed() =>
|
private void EnsureNotDisposed() =>
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
|
internal bool TryGetInitialCreateResidence(
|
||||||
|
RuntimeEntityRecord canonical,
|
||||||
|
out RuntimeInitialCreateResidenceLease lease)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
return InitialCreateResidences.TryGetCurrent(canonical, out lease);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal RuntimeInitialCreateResidenceCompletionStatus
|
||||||
|
CompleteInitialCreateResidence(
|
||||||
|
RuntimeEntityRecord canonical,
|
||||||
|
in RuntimeInitialCreateResidenceToken token,
|
||||||
|
out RuntimeInitialCreateResidenceReceipt receipt)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
return InitialCreateResidences.Complete(
|
||||||
|
canonical,
|
||||||
|
token,
|
||||||
|
out receipt);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool AcknowledgeInitialCreateResidenceAdoption(
|
||||||
|
RuntimeEntityRecord canonical,
|
||||||
|
in RuntimeInitialCreateResidenceAdoptionToken token)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
return InitialCreateResidences.AcknowledgeAdoption(
|
||||||
|
canonical,
|
||||||
|
token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuntimePlacementCancellationReceipt ForgetInitialCreateResidence(
|
||||||
|
RuntimeEntityRecord canonical)
|
||||||
|
{
|
||||||
|
return InitialCreateResidences.Forget(
|
||||||
|
canonical,
|
||||||
|
out _,
|
||||||
|
out RuntimePlacementCancellationReceipt cancellation)
|
||||||
|
? cancellation
|
||||||
|
: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RuntimePlacementCancellationReceipt PreferCancellation(
|
||||||
|
in RuntimePlacementCancellationReceipt initial,
|
||||||
|
in RuntimePlacementCancellationReceipt ordinary) =>
|
||||||
|
initial.IsValid ? initial : ordinary;
|
||||||
|
|
||||||
|
private bool InitializeAcceptedCreateResidence(
|
||||||
|
RuntimeEntityRecord canonical,
|
||||||
|
in InboundCreateResult accepted,
|
||||||
|
bool beginInitialResidence,
|
||||||
|
bool isLocalPlayer)
|
||||||
|
{
|
||||||
|
if (!beginInitialResidence)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// The explicit cutover path separates accepted wire authority from
|
||||||
|
// committed residence before any observer can hydrate it. Failure to
|
||||||
|
// own the exact route is fail-closed; the caller removes the canonical
|
||||||
|
// record before publishing Registered/Updated.
|
||||||
|
if (canonical.PositionAuthorityVersion == 0UL)
|
||||||
|
Entities.AdvancePositionAuthority(canonical);
|
||||||
|
if (canonical.FullCellId != 0u)
|
||||||
|
Entities.SetFullCell(canonical, 0u, 0u);
|
||||||
|
RuntimeInitialCreateResidenceLease lease =
|
||||||
|
InitialCreateResidences.Begin(
|
||||||
|
canonical,
|
||||||
|
accepted,
|
||||||
|
isLocalPlayer);
|
||||||
|
return lease.IsValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Exception FailInitialResidenceRegistration(
|
||||||
|
RuntimeEntityRecord canonical,
|
||||||
|
bool publishDeleted)
|
||||||
|
{
|
||||||
|
if (!Entities.RemoveActive(canonical))
|
||||||
|
{
|
||||||
|
return new InvalidOperationException(
|
||||||
|
$"Initial residence for 0x{canonical.ServerGuid:X8} failed after its canonical incarnation was superseded.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Exception? failure = null;
|
||||||
|
if (publishDeleted)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PublishEntity(RuntimeEntityChange.Deleted, canonical);
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
failure = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
failure = Combine(failure, RetireCanonicalOnly(canonical));
|
||||||
|
var cause = new InvalidOperationException(
|
||||||
|
$"Initial residence for 0x{canonical.ServerGuid:X8} could not acquire its exact Runtime placement lease.");
|
||||||
|
return failure is null
|
||||||
|
? cause
|
||||||
|
: new AggregateException(cause, failure);
|
||||||
|
}
|
||||||
|
|
||||||
private static Exception? Combine(
|
private static Exception? Combine(
|
||||||
Exception? first,
|
Exception? first,
|
||||||
Exception? second) =>
|
Exception? second) =>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,743 @@
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Numerics;
|
||||||
|
using AcDream.Core.Net;
|
||||||
|
using AcDream.Core.Net.Messages;
|
||||||
|
using AcDream.Core.Physics;
|
||||||
|
using AcDream.Runtime.Physics;
|
||||||
|
|
||||||
|
namespace AcDream.Runtime.Entities;
|
||||||
|
|
||||||
|
internal readonly record struct RuntimeInitialCreateResidenceToken(
|
||||||
|
RuntimeEntityKey Entity,
|
||||||
|
ulong LeaseId,
|
||||||
|
ulong SessionLifetimeVersion,
|
||||||
|
ulong PositionAuthorityVersion,
|
||||||
|
ulong CreateIntegrationVersion,
|
||||||
|
ulong SourcePlacementCommitVersion)
|
||||||
|
{
|
||||||
|
internal bool IsValid => Entity.LocalEntityId != 0u
|
||||||
|
&& LeaseId != 0UL
|
||||||
|
&& PositionAuthorityVersion != 0UL
|
||||||
|
&& CreateIntegrationVersion != 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exact, presentation-free initial CreateObject residence authority. The
|
||||||
|
/// accepted wire frame remains on the canonical record while FullCell stays
|
||||||
|
/// zero until the authored Runtime SetPosition operation commits.
|
||||||
|
/// </summary>
|
||||||
|
internal readonly record struct RuntimeInitialCreateResidenceLease(
|
||||||
|
RuntimeInitialCreateResidenceToken Token,
|
||||||
|
RuntimeAuthoritativePositionRoute Route,
|
||||||
|
RuntimeEntityPlacementToken Placement,
|
||||||
|
ImmutableArray<RuntimeInitialCreateResidenceContinuation> Continuations)
|
||||||
|
{
|
||||||
|
internal bool IsValid => Token.IsValid
|
||||||
|
&& Route.Accepted
|
||||||
|
&& Route.Authority.Entity == Token.Entity
|
||||||
|
&& !Continuations.IsDefault
|
||||||
|
&& HasValidContinuationChain()
|
||||||
|
&& (!Route.PerformsSetPosition
|
||||||
|
|| Placement.IsValid
|
||||||
|
&& Placement.Entity == Token.Entity);
|
||||||
|
|
||||||
|
private bool HasValidContinuationChain()
|
||||||
|
{
|
||||||
|
ushort previousTeleport = Route.Authority.AcceptedTeleportSequence;
|
||||||
|
for (int index = 0; index < Continuations.Length; index++)
|
||||||
|
{
|
||||||
|
RuntimeInitialCreateResidenceContinuation continuation =
|
||||||
|
Continuations[index];
|
||||||
|
if (!continuation.IsValid
|
||||||
|
|| continuation.Sequence != (ulong)index + 1UL
|
||||||
|
|| continuation.InstanceSequence != Token.Entity.Incarnation
|
||||||
|
|| continuation.PositionAuthorityVersion
|
||||||
|
!= Token.PositionAuthorityVersion
|
||||||
|
|| continuation.PreviousTeleportSequence
|
||||||
|
!= previousTeleport)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
previousTeleport = continuation.AcceptedTeleportSequence;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// One accepted Position packet which arrived before the immutable initial
|
||||||
|
/// CreateObject admission completed. These records remain dormant until a
|
||||||
|
/// host adopts the completed batch; accepting them never replaces or mutates
|
||||||
|
/// the initial 0x11 SetPosition operation.
|
||||||
|
/// </summary>
|
||||||
|
internal readonly record struct RuntimeInitialCreateResidenceContinuation(
|
||||||
|
ulong Sequence,
|
||||||
|
RuntimePositionEntityKind EntityKind,
|
||||||
|
ushort InstanceSequence,
|
||||||
|
ushort PositionSequence,
|
||||||
|
ushort PreviousTeleportSequence,
|
||||||
|
ushort TeleportSequence,
|
||||||
|
ushort ForcePositionSequence,
|
||||||
|
ushort AcceptedTeleportSequence,
|
||||||
|
ushort AcceptedForcePositionSequence,
|
||||||
|
PositionTimestampDisposition TimestampDisposition,
|
||||||
|
CreateObject.ServerPosition AcceptedWirePosition,
|
||||||
|
Vector3? PositionPackVelocity,
|
||||||
|
Vector3? AcceptedVelocity,
|
||||||
|
Quaternion? ForcePositionRotation,
|
||||||
|
Vector3? CurrentLocalVelocity,
|
||||||
|
bool ProjectionRequiresTeleportHook,
|
||||||
|
uint PlacementFrame,
|
||||||
|
ulong PositionAuthorityVersion)
|
||||||
|
{
|
||||||
|
internal bool IsValid => Sequence != 0UL
|
||||||
|
&& EntityKind is RuntimePositionEntityKind.LocalPlayer
|
||||||
|
or RuntimePositionEntityKind.Remote
|
||||||
|
or RuntimePositionEntityKind.Projectile
|
||||||
|
&& PositionAuthorityVersion != 0UL
|
||||||
|
&& TimestampDisposition is PositionTimestampDisposition.Apply
|
||||||
|
or PositionTimestampDisposition.ForcePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly record struct RuntimeInitialCreateResidenceAdoptionToken(
|
||||||
|
RuntimeEntityKey Entity,
|
||||||
|
ulong LeaseId,
|
||||||
|
ulong AdoptionId,
|
||||||
|
ulong SessionLifetimeVersion,
|
||||||
|
ulong Revision)
|
||||||
|
{
|
||||||
|
internal bool IsValid => Entity.LocalEntityId != 0u
|
||||||
|
&& LeaseId != 0UL
|
||||||
|
&& AdoptionId != 0UL
|
||||||
|
&& Revision != 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum RuntimeInitialCreateResidenceCompletionStatus : byte
|
||||||
|
{
|
||||||
|
Completed,
|
||||||
|
PendingPlacement,
|
||||||
|
RejectedToken,
|
||||||
|
RejectedAuthority,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exact post-residence receipt. A local graphical or no-window host may run
|
||||||
|
/// the retail after-enter teleport suffix only when this receipt carries
|
||||||
|
/// <see cref="RuntimeTeleportHookPhase.AfterEnterWorld"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal readonly record struct RuntimeInitialCreateResidenceReceipt(
|
||||||
|
RuntimeInitialCreateResidenceToken Token,
|
||||||
|
RuntimeTeleportHookPhase TeleportHookPhase,
|
||||||
|
RuntimePlacementProjectionToken Projection,
|
||||||
|
uint FullCellId,
|
||||||
|
ulong PlacementCommitVersion,
|
||||||
|
RuntimeInitialCreateResidenceAdoptionToken Adoption,
|
||||||
|
ImmutableArray<RuntimeInitialCreateResidenceContinuation> Continuations);
|
||||||
|
|
||||||
|
internal readonly record struct RuntimeInitialCreateResidenceOwnershipSnapshot(
|
||||||
|
int ActiveLeaseCount,
|
||||||
|
int PendingAdoptionCount,
|
||||||
|
ulong LastLeaseId)
|
||||||
|
{
|
||||||
|
internal bool IsConverged => ActiveLeaseCount == 0
|
||||||
|
&& PendingAdoptionCount == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Owns only initial CreateObject residence leases. DAT lookup, body creation,
|
||||||
|
/// and presentation stay outside this owner; their immutable preparation is
|
||||||
|
/// submitted through the lease's canonical Runtime SetPosition token.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class RuntimeInitialCreateResidenceState
|
||||||
|
{
|
||||||
|
private sealed class Entry
|
||||||
|
{
|
||||||
|
internal required RuntimeEntityRecord Record { get; init; }
|
||||||
|
internal required RuntimeInitialCreateResidenceLease Lease { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class CompletedEntry
|
||||||
|
{
|
||||||
|
internal required RuntimeEntityRecord Record { get; init; }
|
||||||
|
internal required RuntimeInitialCreateResidenceLease Lease { get; set; }
|
||||||
|
internal required RuntimeInitialCreateResidenceReceipt Receipt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly RuntimeEntityDirectory _entities;
|
||||||
|
private readonly RuntimeSetPositionState _setPosition;
|
||||||
|
private readonly Dictionary<RuntimeEntityKey, Entry> _entries = [];
|
||||||
|
private readonly Dictionary<RuntimeEntityKey, CompletedEntry> _completed = [];
|
||||||
|
private Func<RuntimeGenerationToken>? _generation;
|
||||||
|
private ulong _nextLeaseId;
|
||||||
|
|
||||||
|
internal RuntimeInitialCreateResidenceState(
|
||||||
|
RuntimeEntityDirectory entities,
|
||||||
|
RuntimeSetPositionState setPosition)
|
||||||
|
{
|
||||||
|
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
|
||||||
|
_setPosition = setPosition
|
||||||
|
?? throw new ArgumentNullException(nameof(setPosition));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void BindGeneration(Func<RuntimeGenerationToken> generation)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(generation);
|
||||||
|
if (_generation is not null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"The initial Create residence generation source is already bound.");
|
||||||
|
}
|
||||||
|
_generation = generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool CanAcceptCreate(WorldSession.EntitySpawn incoming)
|
||||||
|
{
|
||||||
|
bool parented = (incoming.ParentGuid
|
||||||
|
?? incoming.Physics?.Parent?.Guid)
|
||||||
|
is not null and not 0u;
|
||||||
|
bool topLevel = !parented
|
||||||
|
&& incoming.Position is { LandblockId: not 0u };
|
||||||
|
return CurrentGeneration().Value != 0UL
|
||||||
|
&& _nextLeaseId != ulong.MaxValue
|
||||||
|
&& (!topLevel
|
||||||
|
|| _setPosition.CanBeginAuthoredPlacementSequence
|
||||||
|
&& RuntimeAuthoritativePositionRouteClassifier
|
||||||
|
.IsValidCreateWirePosition(
|
||||||
|
incoming.Position!.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal RuntimeInitialCreateResidenceLease Begin(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
in InboundCreateResult accepted,
|
||||||
|
bool isLocalPlayer)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(record);
|
||||||
|
if (!_entities.IsCurrent(record)
|
||||||
|
|| record.Key is not { } key
|
||||||
|
|| record.FullCellId != 0u
|
||||||
|
|| accepted.Snapshot.Guid != record.ServerGuid
|
||||||
|
|| accepted.Snapshot.InstanceSequence != record.Incarnation)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeGenerationToken generation = CurrentGeneration();
|
||||||
|
RuntimePositionEntityKind entityKind = isLocalPlayer
|
||||||
|
? RuntimePositionEntityKind.LocalPlayer
|
||||||
|
: (record.FinalPhysicsState & PhysicsStateFlags.Missile) != 0
|
||||||
|
? RuntimePositionEntityKind.Projectile
|
||||||
|
: RuntimePositionEntityKind.Remote;
|
||||||
|
WorldSession.EntitySpawn snapshot = accepted.Snapshot;
|
||||||
|
// PhysicsDesc parent ownership precedes its optional position frame in
|
||||||
|
// retail set_description. A parented Create is not a top-level world
|
||||||
|
// admission merely because ACE also supplied a position payload.
|
||||||
|
RuntimeCreateResidenceKind residence =
|
||||||
|
(snapshot.ParentGuid ?? snapshot.Physics?.Parent?.Guid)
|
||||||
|
is not null and not 0u
|
||||||
|
? RuntimeCreateResidenceKind.Parented
|
||||||
|
: snapshot.Position is { LandblockId: not 0u }
|
||||||
|
? RuntimeCreateResidenceKind.TopLevel
|
||||||
|
: RuntimeCreateResidenceKind.PickedUp;
|
||||||
|
var authority = new RuntimeAuthoritativePositionAuthority(
|
||||||
|
generation,
|
||||||
|
key,
|
||||||
|
record.PositionAuthorityVersion,
|
||||||
|
snapshot.PositionSequence,
|
||||||
|
accepted.Timestamps.Teleport,
|
||||||
|
accepted.Timestamps.Teleport,
|
||||||
|
PositionTimestampDisposition.Apply);
|
||||||
|
RuntimeAuthoritativePositionRoute route =
|
||||||
|
RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate(
|
||||||
|
new RuntimeCreatePositionRouteRequest(
|
||||||
|
authority,
|
||||||
|
entityKind,
|
||||||
|
residence,
|
||||||
|
snapshot.Position,
|
||||||
|
new RuntimePositionPlacementFacts(
|
||||||
|
record.FinalPhysicsState,
|
||||||
|
HasAuthoredMoverShape: snapshot.SetupTableId is not null)));
|
||||||
|
return Own(record, route);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal RuntimeInitialCreateResidenceLease EnqueueAcceptedPosition(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
in RuntimeInitialCreateResidenceLease prior,
|
||||||
|
WorldSession.EntityPositionUpdate update,
|
||||||
|
in WorldSession.EntitySpawn accepted,
|
||||||
|
PositionTimestampDisposition disposition,
|
||||||
|
in AcceptedPhysicsTimestamps timestamps,
|
||||||
|
bool isLocalPlayer,
|
||||||
|
Quaternion? forcePositionRotation,
|
||||||
|
Vector3? currentLocalVelocity,
|
||||||
|
bool projectionRequiresTeleportHook)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(record);
|
||||||
|
if (!_entities.IsCurrent(record)
|
||||||
|
|| record.Key is not { } key
|
||||||
|
|| update.Guid != record.ServerGuid
|
||||||
|
|| accepted.Guid != record.ServerGuid
|
||||||
|
|| disposition is PositionTimestampDisposition.Rejected)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeInitialCreateResidenceLease current;
|
||||||
|
Entry? active = null;
|
||||||
|
CompletedEntry? completed = null;
|
||||||
|
if (_entries.TryGetValue(key, out active)
|
||||||
|
&& ReferenceEquals(active.Record, record)
|
||||||
|
&& active.Lease.Token == prior.Token
|
||||||
|
&& IsCurrent(active))
|
||||||
|
{
|
||||||
|
current = active.Lease;
|
||||||
|
}
|
||||||
|
else if (_completed.TryGetValue(key, out completed)
|
||||||
|
&& ReferenceEquals(completed.Record, record)
|
||||||
|
&& completed.Lease.Token == prior.Token
|
||||||
|
&& IsCompletedCurrent(completed))
|
||||||
|
{
|
||||||
|
current = completed.Lease;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
if (current.Continuations.Length == int.MaxValue
|
||||||
|
|| completed is not null
|
||||||
|
&& completed.Receipt.Adoption.Revision == ulong.MaxValue)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
RuntimePositionEntityKind entityKind = isLocalPlayer
|
||||||
|
? RuntimePositionEntityKind.LocalPlayer
|
||||||
|
: (record.FinalPhysicsState & PhysicsStateFlags.Missile) != 0
|
||||||
|
? RuntimePositionEntityKind.Projectile
|
||||||
|
: RuntimePositionEntityKind.Remote;
|
||||||
|
CreateObject.ServerPosition acceptedPosition =
|
||||||
|
accepted.Position ?? update.Position;
|
||||||
|
ushort previousTeleport = current.Continuations.IsEmpty
|
||||||
|
? current.Route.Authority.AcceptedTeleportSequence
|
||||||
|
: current.Continuations[^1].AcceptedTeleportSequence;
|
||||||
|
ulong sequence = (ulong)current.Continuations.Length + 1UL;
|
||||||
|
var continuation = new RuntimeInitialCreateResidenceContinuation(
|
||||||
|
sequence,
|
||||||
|
entityKind,
|
||||||
|
update.InstanceSequence,
|
||||||
|
update.PositionSequence,
|
||||||
|
previousTeleport,
|
||||||
|
update.TeleportSequence,
|
||||||
|
update.ForcePositionSequence,
|
||||||
|
timestamps.Teleport,
|
||||||
|
timestamps.ForcePosition,
|
||||||
|
disposition,
|
||||||
|
acceptedPosition,
|
||||||
|
update.Velocity,
|
||||||
|
accepted.Physics?.Velocity,
|
||||||
|
forcePositionRotation,
|
||||||
|
currentLocalVelocity,
|
||||||
|
projectionRequiresTeleportHook,
|
||||||
|
accepted.PlacementId ?? 0u,
|
||||||
|
record.PositionAuthorityVersion);
|
||||||
|
if (!continuation.IsValid)
|
||||||
|
return default;
|
||||||
|
|
||||||
|
RuntimeInitialCreateResidenceLease revised = current with
|
||||||
|
{
|
||||||
|
Continuations = current.Continuations.Add(continuation),
|
||||||
|
};
|
||||||
|
if (active is not null)
|
||||||
|
{
|
||||||
|
active.Lease = revised;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completed!.Lease = revised;
|
||||||
|
RuntimeInitialCreateResidenceAdoptionToken revisedAdoption =
|
||||||
|
completed.Receipt.Adoption with
|
||||||
|
{
|
||||||
|
Revision = completed.Receipt.Adoption.Revision + 1UL,
|
||||||
|
};
|
||||||
|
completed.Receipt = completed.Receipt with
|
||||||
|
{
|
||||||
|
Adoption = revisedAdoption,
|
||||||
|
Continuations = revised.Continuations,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return revised;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool CanEnqueueAcceptedPosition(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
in RuntimeInitialCreateResidenceLease prior,
|
||||||
|
in WorldSession.EntityPositionUpdate update)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(record);
|
||||||
|
if (record.Key is not { } key
|
||||||
|
|| update.Guid != record.ServerGuid
|
||||||
|
|| !RuntimeAuthoritativePositionRouteClassifier
|
||||||
|
.IsValidCreateWirePosition(update.Position))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_entries.TryGetValue(key, out Entry? entry))
|
||||||
|
{
|
||||||
|
return ReferenceEquals(entry.Record, record)
|
||||||
|
&& entry.Lease.Token == prior.Token
|
||||||
|
&& IsCurrent(entry)
|
||||||
|
&& entry.Lease.Continuations.Length < int.MaxValue;
|
||||||
|
}
|
||||||
|
return _completed.TryGetValue(key, out CompletedEntry? completed)
|
||||||
|
&& ReferenceEquals(completed.Record, record)
|
||||||
|
&& completed.Lease.Token == prior.Token
|
||||||
|
&& IsCompletedCurrent(completed)
|
||||||
|
&& completed.Lease.Continuations.Length < int.MaxValue
|
||||||
|
&& completed.Receipt.Adoption.Revision < ulong.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TryGetTransaction(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
out RuntimeInitialCreateResidenceLease lease)
|
||||||
|
{
|
||||||
|
if (TryGetCurrent(record, out lease))
|
||||||
|
return true;
|
||||||
|
if (record.Key is { } key
|
||||||
|
&& _completed.TryGetValue(key, out CompletedEntry? completed)
|
||||||
|
&& ReferenceEquals(completed.Record, record))
|
||||||
|
{
|
||||||
|
if (IsCompletedCurrent(completed))
|
||||||
|
{
|
||||||
|
lease = completed.Lease;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Retire(completed);
|
||||||
|
}
|
||||||
|
lease = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuntimeInitialCreateResidenceLease Own(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
in RuntimeAuthoritativePositionRoute route)
|
||||||
|
{
|
||||||
|
RuntimeEntityKey key = record.Key!.Value;
|
||||||
|
if (_entries.ContainsKey(key)
|
||||||
|
|| _nextLeaseId == ulong.MaxValue)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
ulong leaseId = _nextLeaseId + 1UL;
|
||||||
|
|
||||||
|
RuntimeEntityPlacementToken placement = default;
|
||||||
|
if (route.PerformsSetPosition)
|
||||||
|
{
|
||||||
|
placement = _setPosition.TryBeginExclusiveAuthoredPlacement(
|
||||||
|
record,
|
||||||
|
record.PositionAuthorityVersion,
|
||||||
|
route.OperationKind);
|
||||||
|
if (!placement.IsValid)
|
||||||
|
return default;
|
||||||
|
if (!_setPosition.WatchPlacementCompletion(placement))
|
||||||
|
{
|
||||||
|
_ = _setPosition.ForgetExactPlacement(placement);
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!route.Accepted)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = new RuntimeInitialCreateResidenceToken(
|
||||||
|
key,
|
||||||
|
leaseId,
|
||||||
|
_entities.SessionLifetimeVersion,
|
||||||
|
record.PositionAuthorityVersion,
|
||||||
|
record.CreateIntegrationVersion,
|
||||||
|
record.PlacementCommitVersion);
|
||||||
|
var lease = new RuntimeInitialCreateResidenceLease(
|
||||||
|
token,
|
||||||
|
route,
|
||||||
|
placement,
|
||||||
|
ImmutableArray<RuntimeInitialCreateResidenceContinuation>.Empty);
|
||||||
|
_entries.Add(key, new Entry
|
||||||
|
{
|
||||||
|
Record = record,
|
||||||
|
Lease = lease,
|
||||||
|
});
|
||||||
|
_nextLeaseId = leaseId;
|
||||||
|
return lease;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TryGetCurrent(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
out RuntimeInitialCreateResidenceLease lease)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(record);
|
||||||
|
if (record.Key is { } key
|
||||||
|
&& _entries.TryGetValue(key, out Entry? entry)
|
||||||
|
&& ReferenceEquals(entry.Record, record))
|
||||||
|
{
|
||||||
|
if (IsCurrent(entry))
|
||||||
|
{
|
||||||
|
lease = entry.Lease;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Retire(entry);
|
||||||
|
}
|
||||||
|
lease = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal RuntimeInitialCreateResidenceCompletionStatus Complete(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
in RuntimeInitialCreateResidenceToken token,
|
||||||
|
out RuntimeInitialCreateResidenceReceipt receipt)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(record);
|
||||||
|
receipt = default;
|
||||||
|
if (token.IsValid
|
||||||
|
&& _completed.TryGetValue(token.Entity, out CompletedEntry? completed)
|
||||||
|
&& completed.Receipt.Token == token
|
||||||
|
&& ReferenceEquals(completed.Record, record))
|
||||||
|
{
|
||||||
|
if (IsCompletedCurrent(completed))
|
||||||
|
{
|
||||||
|
receipt = completed.Receipt;
|
||||||
|
return RuntimeInitialCreateResidenceCompletionStatus.Completed;
|
||||||
|
}
|
||||||
|
Retire(completed);
|
||||||
|
return RuntimeInitialCreateResidenceCompletionStatus
|
||||||
|
.RejectedAuthority;
|
||||||
|
}
|
||||||
|
if (!token.IsValid
|
||||||
|
|| !_entries.TryGetValue(token.Entity, out Entry? entry)
|
||||||
|
|| entry.Lease.Token != token
|
||||||
|
|| !ReferenceEquals(entry.Record, record))
|
||||||
|
{
|
||||||
|
return RuntimeInitialCreateResidenceCompletionStatus.RejectedToken;
|
||||||
|
}
|
||||||
|
if (!IsCurrent(entry))
|
||||||
|
{
|
||||||
|
Retire(entry);
|
||||||
|
return RuntimeInitialCreateResidenceCompletionStatus
|
||||||
|
.RejectedAuthority;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeInitialCreateResidenceLease lease = entry.Lease;
|
||||||
|
RuntimePlacementProjectionToken projection = default;
|
||||||
|
if (lease.Route.PerformsSetPosition)
|
||||||
|
{
|
||||||
|
if (_setPosition.IsPlacementCurrent(lease.Placement))
|
||||||
|
{
|
||||||
|
return RuntimeInitialCreateResidenceCompletionStatus
|
||||||
|
.PendingPlacement;
|
||||||
|
}
|
||||||
|
if (!_setPosition.TryPeekAcknowledgedPlacement(
|
||||||
|
lease.Placement,
|
||||||
|
out projection)
|
||||||
|
|| projection.Entity != token.Entity
|
||||||
|
|| projection.PositionAuthorityVersion
|
||||||
|
!= token.PositionAuthorityVersion
|
||||||
|
|| projection.SessionLifetimeVersion
|
||||||
|
!= token.SessionLifetimeVersion
|
||||||
|
|| projection.ExactCellId == 0u
|
||||||
|
|| projection.ExactCellId != record.FullCellId
|
||||||
|
|| projection.PlacementCommitVersion
|
||||||
|
<= token.SourcePlacementCommitVersion
|
||||||
|
|| projection.PlacementCommitVersion
|
||||||
|
!= record.PlacementCommitVersion)
|
||||||
|
{
|
||||||
|
Retire(entry);
|
||||||
|
return RuntimeInitialCreateResidenceCompletionStatus
|
||||||
|
.RejectedAuthority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (record.FullCellId != 0u)
|
||||||
|
{
|
||||||
|
Retire(entry);
|
||||||
|
return RuntimeInitialCreateResidenceCompletionStatus
|
||||||
|
.RejectedAuthority;
|
||||||
|
}
|
||||||
|
|
||||||
|
var adoption = new RuntimeInitialCreateResidenceAdoptionToken(
|
||||||
|
token.Entity,
|
||||||
|
token.LeaseId,
|
||||||
|
token.LeaseId,
|
||||||
|
token.SessionLifetimeVersion,
|
||||||
|
Revision: 1UL);
|
||||||
|
receipt = new RuntimeInitialCreateResidenceReceipt(
|
||||||
|
token,
|
||||||
|
lease.Route.TeleportHookPhase,
|
||||||
|
projection,
|
||||||
|
record.FullCellId,
|
||||||
|
record.PlacementCommitVersion,
|
||||||
|
adoption,
|
||||||
|
lease.Continuations);
|
||||||
|
_entries.Remove(token.Entity);
|
||||||
|
_completed.Add(token.Entity, new CompletedEntry
|
||||||
|
{
|
||||||
|
Record = record,
|
||||||
|
Lease = lease,
|
||||||
|
Receipt = receipt,
|
||||||
|
});
|
||||||
|
return RuntimeInitialCreateResidenceCompletionStatus.Completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool AcknowledgeAdoption(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
in RuntimeInitialCreateResidenceAdoptionToken token)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(record);
|
||||||
|
if (!token.IsValid)
|
||||||
|
return false;
|
||||||
|
if (!_completed.TryGetValue(token.Entity, out CompletedEntry? current)
|
||||||
|
|| !ReferenceEquals(current.Record, record)
|
||||||
|
|| current.Receipt.Adoption != token)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!IsCompletedCurrent(current))
|
||||||
|
{
|
||||||
|
Retire(current);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (current.Lease.Route.PerformsSetPosition
|
||||||
|
&& !_setPosition.ConsumeAcknowledgedPlacement(
|
||||||
|
current.Lease.Placement,
|
||||||
|
current.Receipt.Projection))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _completed.Remove(token.Entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool Forget(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
out RuntimeInitialCreateResidenceLease lease,
|
||||||
|
out RuntimePlacementCancellationReceipt cancellation)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(record);
|
||||||
|
cancellation = default;
|
||||||
|
if (record.Key is { } key
|
||||||
|
&& _entries.TryGetValue(key, out Entry? entry)
|
||||||
|
&& ReferenceEquals(entry.Record, record)
|
||||||
|
&& _entries.Remove(key))
|
||||||
|
{
|
||||||
|
lease = entry.Lease;
|
||||||
|
cancellation = _setPosition.ForgetExactPlacement(
|
||||||
|
lease.Placement);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (record.Key is { } completedKey
|
||||||
|
&& _completed.TryGetValue(
|
||||||
|
completedKey,
|
||||||
|
out CompletedEntry? completed)
|
||||||
|
&& ReferenceEquals(completed.Record, record)
|
||||||
|
&& _completed.Remove(completedKey))
|
||||||
|
{
|
||||||
|
lease = completed.Lease;
|
||||||
|
cancellation = _setPosition.ForgetExactPlacement(
|
||||||
|
lease.Placement);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
lease = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Clear()
|
||||||
|
{
|
||||||
|
Entry[] active = _entries.Values.ToArray();
|
||||||
|
CompletedEntry[] completed = _completed.Values.ToArray();
|
||||||
|
var cancellations = new RuntimePlacementCancellationReceipt[
|
||||||
|
active.Length + completed.Length];
|
||||||
|
_entries.Clear();
|
||||||
|
_completed.Clear();
|
||||||
|
int cancellationCount = 0;
|
||||||
|
foreach (Entry entry in active)
|
||||||
|
{
|
||||||
|
RuntimePlacementCancellationReceipt cancellation =
|
||||||
|
_setPosition.ForgetExactPlacement(
|
||||||
|
entry.Lease.Placement);
|
||||||
|
if (cancellation.IsValid)
|
||||||
|
cancellations[cancellationCount++] = cancellation;
|
||||||
|
}
|
||||||
|
foreach (CompletedEntry entry in completed)
|
||||||
|
{
|
||||||
|
RuntimePlacementCancellationReceipt cancellation =
|
||||||
|
_setPosition.ForgetExactPlacement(
|
||||||
|
entry.Lease.Placement);
|
||||||
|
if (cancellation.IsValid)
|
||||||
|
cancellations[cancellationCount++] = cancellation;
|
||||||
|
}
|
||||||
|
for (int index = 0; index < cancellationCount; index++)
|
||||||
|
{
|
||||||
|
_setPosition.PublishCancellation(cancellations[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal RuntimeInitialCreateResidenceOwnershipSnapshot CaptureOwnership() =>
|
||||||
|
new(_entries.Count, _completed.Count, _nextLeaseId);
|
||||||
|
|
||||||
|
private bool IsCurrent(Entry entry)
|
||||||
|
{
|
||||||
|
RuntimeInitialCreateResidenceToken token = entry.Lease.Token;
|
||||||
|
bool placementCurrent = !entry.Lease.Route.PerformsSetPosition
|
||||||
|
|| _setPosition.IsPlacementCompletionTracked(
|
||||||
|
entry.Lease.Placement);
|
||||||
|
return placementCurrent
|
||||||
|
&& _entities.IsCurrent(entry.Record)
|
||||||
|
&& entry.Record.Key == token.Entity
|
||||||
|
&& _entities.SessionLifetimeVersion
|
||||||
|
== token.SessionLifetimeVersion
|
||||||
|
&& entry.Record.PositionAuthorityVersion
|
||||||
|
== token.PositionAuthorityVersion
|
||||||
|
&& entry.Record.CreateIntegrationVersion
|
||||||
|
== token.CreateIntegrationVersion
|
||||||
|
&& entry.Lease.Route.Authority.Generation
|
||||||
|
== CurrentGeneration();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RuntimeGenerationToken CurrentGeneration()
|
||||||
|
{
|
||||||
|
return _generation?.Invoke() ?? default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsCompletedCurrent(CompletedEntry entry)
|
||||||
|
{
|
||||||
|
RuntimeInitialCreateResidenceReceipt receipt = entry.Receipt;
|
||||||
|
return _entities.IsCurrent(entry.Record)
|
||||||
|
&& entry.Record.Key == receipt.Token.Entity
|
||||||
|
&& _entities.SessionLifetimeVersion
|
||||||
|
== receipt.Token.SessionLifetimeVersion
|
||||||
|
&& entry.Record.PositionAuthorityVersion
|
||||||
|
== receipt.Token.PositionAuthorityVersion
|
||||||
|
&& entry.Record.CreateIntegrationVersion
|
||||||
|
== receipt.Token.CreateIntegrationVersion
|
||||||
|
&& entry.Record.FullCellId == receipt.FullCellId
|
||||||
|
&& entry.Record.PlacementCommitVersion
|
||||||
|
== receipt.PlacementCommitVersion
|
||||||
|
&& entry.Lease.Route.Authority.Generation
|
||||||
|
== CurrentGeneration()
|
||||||
|
&& receipt.Token.SessionLifetimeVersion
|
||||||
|
== receipt.Adoption.SessionLifetimeVersion
|
||||||
|
&& receipt.Token.LeaseId == receipt.Adoption.LeaseId
|
||||||
|
&& (!entry.Lease.Route.PerformsSetPosition
|
||||||
|
|| _setPosition.IsPlacementCompletionTracked(
|
||||||
|
entry.Lease.Placement));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Retire(Entry entry)
|
||||||
|
{
|
||||||
|
_entries.Remove(entry.Lease.Token.Entity);
|
||||||
|
RuntimePlacementCancellationReceipt cancellation =
|
||||||
|
_setPosition.ForgetExactPlacement(entry.Lease.Placement);
|
||||||
|
_setPosition.PublishCancellation(cancellation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Retire(CompletedEntry entry)
|
||||||
|
{
|
||||||
|
_completed.Remove(entry.Receipt.Token.Entity);
|
||||||
|
RuntimePlacementCancellationReceipt cancellation =
|
||||||
|
_setPosition.ForgetExactPlacement(entry.Lease.Placement);
|
||||||
|
_setPosition.PublishCancellation(cancellation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -180,6 +180,9 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
|
||||||
| PhysicsSetPositionFlags.Slide
|
| PhysicsSetPositionFlags.Slide
|
||||||
| PhysicsSetPositionFlags.SendPositionEvent;
|
| PhysicsSetPositionFlags.SendPositionEvent;
|
||||||
|
|
||||||
|
internal static bool IsValidCreateWirePosition(
|
||||||
|
in CreateObject.ServerPosition position) => ValidPosition(position);
|
||||||
|
|
||||||
internal static RuntimeAuthoritativePositionRoute ClassifyCreate(
|
internal static RuntimeAuthoritativePositionRoute ClassifyCreate(
|
||||||
in RuntimeCreatePositionRouteRequest request)
|
in RuntimeCreatePositionRouteRequest request)
|
||||||
{
|
{
|
||||||
|
|
@ -205,7 +208,10 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
|
||||||
0u,
|
0u,
|
||||||
UnparentBeforeRouting: false,
|
UnparentBeforeRouting: false,
|
||||||
ApplyPlacementFrameBeforeRouting: false,
|
ApplyPlacementFrameBeforeRouting: false,
|
||||||
LeaveWorld: true,
|
// A fresh cellless CreateObject has never entered world.
|
||||||
|
// Parent composition and later pickup events own their own
|
||||||
|
// callbacks; initial residence invents no withdrawal edge.
|
||||||
|
LeaveWorld: false,
|
||||||
TeleportHookPhase: RuntimeTeleportHookPhase.None,
|
TeleportHookPhase: RuntimeTeleportHookPhase.None,
|
||||||
StopInterpolating: false,
|
StopInterpolating: false,
|
||||||
ConstrainAfterRouting: false,
|
ConstrainAfterRouting: false,
|
||||||
|
|
@ -294,7 +300,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
|
||||||
{
|
{
|
||||||
return new RuntimeAuthoritativePositionRoute(
|
return new RuntimeAuthoritativePositionRoute(
|
||||||
request.Authority,
|
request.Authority,
|
||||||
RuntimeAuthoritativePositionDisposition.SetPosition,
|
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
||||||
operation,
|
operation,
|
||||||
AuthoritativeTeleportFlags,
|
AuthoritativeTeleportFlags,
|
||||||
placement,
|
placement,
|
||||||
|
|
|
||||||
|
|
@ -250,6 +250,8 @@ internal readonly record struct RuntimeSetPositionOwnershipSnapshot(
|
||||||
int UnboundDeferredCellOrderCount,
|
int UnboundDeferredCellOrderCount,
|
||||||
int PreparedMoverCount,
|
int PreparedMoverCount,
|
||||||
int MoverPreparationAuthorityCount,
|
int MoverPreparationAuthorityCount,
|
||||||
|
int PlacementCompletionWatchCount,
|
||||||
|
int AcknowledgedPlacementCompletionCount,
|
||||||
int CollisionPrefixQuiescenceCount,
|
int CollisionPrefixQuiescenceCount,
|
||||||
int PendingQuiescenceProjectionCount)
|
int PendingQuiescenceProjectionCount)
|
||||||
{
|
{
|
||||||
|
|
@ -276,6 +278,8 @@ internal readonly record struct RuntimeSetPositionOwnershipSnapshot(
|
||||||
&& UnboundDeferredCellOrderCount == 0
|
&& UnboundDeferredCellOrderCount == 0
|
||||||
&& PreparedMoverCount == 0
|
&& PreparedMoverCount == 0
|
||||||
&& MoverPreparationAuthorityCount == 0
|
&& MoverPreparationAuthorityCount == 0
|
||||||
|
&& PlacementCompletionWatchCount == 0
|
||||||
|
&& AcknowledgedPlacementCompletionCount == 0
|
||||||
&& CollisionPrefixQuiescenceCount == 0
|
&& CollisionPrefixQuiescenceCount == 0
|
||||||
&& PendingQuiescenceProjectionCount == 0;
|
&& PendingQuiescenceProjectionCount == 0;
|
||||||
}
|
}
|
||||||
|
|
@ -414,6 +418,10 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
||||||
_preparedMovers = [];
|
_preparedMovers = [];
|
||||||
private readonly Dictionary<RuntimeEntityKey, MoverPreparationAuthority>
|
private readonly Dictionary<RuntimeEntityKey, MoverPreparationAuthority>
|
||||||
_moverPreparationAuthorities = [];
|
_moverPreparationAuthorities = [];
|
||||||
|
private readonly HashSet<RuntimeEntityPlacementToken>
|
||||||
|
_placementCompletionWatches = [];
|
||||||
|
private readonly Dictionary<RuntimeEntityPlacementToken,
|
||||||
|
RuntimePlacementProjectionToken> _acknowledgedPlacementCompletions = [];
|
||||||
private readonly Dictionary<uint, CollisionPrefixQuiescence>
|
private readonly Dictionary<uint, CollisionPrefixQuiescence>
|
||||||
_collisionPrefixQuiescence = [];
|
_collisionPrefixQuiescence = [];
|
||||||
private readonly LinkedList<RuntimeEntityKey> _expiredLostCells = [];
|
private readonly LinkedList<RuntimeEntityKey> _expiredLostCells = [];
|
||||||
|
|
@ -477,11 +485,15 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
||||||
_unboundDeferredCellOrder.Count,
|
_unboundDeferredCellOrder.Count,
|
||||||
_preparedMovers.Count,
|
_preparedMovers.Count,
|
||||||
_moverPreparationAuthorities.Count,
|
_moverPreparationAuthorities.Count,
|
||||||
|
_placementCompletionWatches.Count,
|
||||||
|
_acknowledgedPlacementCompletions.Count,
|
||||||
_collisionPrefixQuiescence.Count,
|
_collisionPrefixQuiescence.Count,
|
||||||
pendingQuiescenceProjections);
|
pendingQuiescenceProjections);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal int PendingProjectionCount => _pendingProjection.Count;
|
internal int PendingProjectionCount => _pendingProjection.Count;
|
||||||
|
internal bool CanBeginAuthoredPlacementSequence =>
|
||||||
|
_nextOperationId != ulong.MaxValue;
|
||||||
|
|
||||||
internal bool IsCollisionPrefixQuiescing(uint landblockId) =>
|
internal bool IsCollisionPrefixQuiescing(uint landblockId) =>
|
||||||
_collisionPrefixQuiescence.ContainsKey(
|
_collisionPrefixQuiescence.ContainsKey(
|
||||||
|
|
@ -812,6 +824,111 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
||||||
portal,
|
portal,
|
||||||
captureMoverPreparationAuthority: true);
|
captureMoverPreparationAuthority: true);
|
||||||
|
|
||||||
|
internal bool IsPlacementCurrent(
|
||||||
|
in RuntimeEntityPlacementToken token)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
return token.IsValid
|
||||||
|
&& _operations.TryGetValue(token.Entity, out Operation? operation)
|
||||||
|
&& operation.Token == token
|
||||||
|
&& IsCurrent(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reserves the exact acknowledgement edge for a higher-level Runtime
|
||||||
|
/// transaction. Ordinary SetPosition operations retain no completion
|
||||||
|
/// history; only explicitly watched tokens survive operation retirement.
|
||||||
|
/// </summary>
|
||||||
|
internal bool WatchPlacementCompletion(
|
||||||
|
in RuntimeEntityPlacementToken token)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
return IsPlacementCurrent(token)
|
||||||
|
&& _placementCompletionWatches.Add(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsPlacementCompletionTracked(
|
||||||
|
in RuntimeEntityPlacementToken token)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
return token.IsValid
|
||||||
|
&& (IsPlacementCurrent(token)
|
||||||
|
&& _placementCompletionWatches.Contains(token)
|
||||||
|
|| _acknowledgedPlacementCompletions.ContainsKey(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TryPeekAcknowledgedPlacement(
|
||||||
|
in RuntimeEntityPlacementToken token,
|
||||||
|
out RuntimePlacementProjectionToken projection)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
if (token.IsValid
|
||||||
|
&& _acknowledgedPlacementCompletions.TryGetValue(
|
||||||
|
token,
|
||||||
|
out projection))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
projection = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool ConsumeAcknowledgedPlacement(
|
||||||
|
in RuntimeEntityPlacementToken token,
|
||||||
|
in RuntimePlacementProjectionToken expected)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
return token.IsValid
|
||||||
|
&& _acknowledgedPlacementCompletions.TryGetValue(
|
||||||
|
token,
|
||||||
|
out RuntimePlacementProjectionToken current)
|
||||||
|
&& current == expected
|
||||||
|
&& _acknowledgedPlacementCompletions.Remove(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ForgetPlacementCompletion(
|
||||||
|
in RuntimeEntityPlacementToken token)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
ForgetPlacementCompletionCore(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal RuntimePlacementCancellationReceipt ForgetExactPlacement(
|
||||||
|
in RuntimeEntityPlacementToken token)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
ForgetPlacementCompletionCore(token);
|
||||||
|
if (!token.IsValid
|
||||||
|
|| !_operations.TryGetValue(token.Entity, out Operation? operation)
|
||||||
|
|| operation.Token != token)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
return CancelCore(operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal RuntimeEntityPlacementToken TryBeginExclusiveAuthoredPlacement(
|
||||||
|
RuntimeEntityRecord record,
|
||||||
|
ulong expectedPositionAuthorityVersion,
|
||||||
|
RuntimeSetPositionOperationKind kind,
|
||||||
|
RuntimePortalPlacementAuthority portal = default)
|
||||||
|
{
|
||||||
|
EnsureNotDisposed();
|
||||||
|
ArgumentNullException.ThrowIfNull(record);
|
||||||
|
if (record.Key is not { } key
|
||||||
|
|| _operations.ContainsKey(key)
|
||||||
|
|| HasRetainedCompletion(key))
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
return BeginAcceptedPlacementCore(
|
||||||
|
record,
|
||||||
|
expectedPositionAuthorityVersion,
|
||||||
|
kind,
|
||||||
|
portal,
|
||||||
|
captureMoverPreparationAuthority: true);
|
||||||
|
}
|
||||||
|
|
||||||
internal void PrepareDormantLocalActivationOwnership(
|
internal void PrepareDormantLocalActivationOwnership(
|
||||||
RuntimeEntityRecord record,
|
RuntimeEntityRecord record,
|
||||||
PhysicsBody body,
|
PhysicsBody body,
|
||||||
|
|
@ -853,6 +970,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
||||||
record.Snapshot.Physics?.Position ?? record.Snapshot.Position;
|
record.Snapshot.Physics?.Position ?? record.Snapshot.Position;
|
||||||
if (record.Key is not { } key
|
if (record.Key is not { } key
|
||||||
|| !_entities.IsCurrent(record)
|
|| !_entities.IsCurrent(record)
|
||||||
|
|| HasRetainedCompletion(key)
|
||||||
|| record.PositionAuthorityVersion
|
|| record.PositionAuthorityVersion
|
||||||
!= expectedPositionAuthorityVersion
|
!= expectedPositionAuthorityVersion
|
||||||
|| (captureMoverPreparationAuthority
|
|| (captureMoverPreparationAuthority
|
||||||
|
|
@ -947,6 +1065,17 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
||||||
return IsCurrent(replacement) ? token : default;
|
return IsCurrent(replacement) ? token : default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool HasRetainedCompletion(RuntimeEntityKey key)
|
||||||
|
{
|
||||||
|
foreach (RuntimeEntityPlacementToken token
|
||||||
|
in _acknowledgedPlacementCompletions.Keys)
|
||||||
|
{
|
||||||
|
if (token.Entity == key)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
internal RuntimeSetPositionMoverPreparationStatus PrepareMover(
|
internal RuntimeSetPositionMoverPreparationStatus PrepareMover(
|
||||||
in RuntimeEntityPlacementToken token,
|
in RuntimeEntityPlacementToken token,
|
||||||
in RuntimeSetPositionMoverPreparation preparation,
|
in RuntimeSetPositionMoverPreparation preparation,
|
||||||
|
|
@ -2206,6 +2335,12 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
||||||
operation.ProjectionSequence = 0UL;
|
operation.ProjectionSequence = 0UL;
|
||||||
if (pending.Kind is RuntimePlacementProjectionKind.Place)
|
if (pending.Kind is RuntimePlacementProjectionKind.Place)
|
||||||
{
|
{
|
||||||
|
if (_placementCompletionWatches.Remove(operation.Token))
|
||||||
|
{
|
||||||
|
_acknowledgedPlacementCompletions.Add(
|
||||||
|
operation.Token,
|
||||||
|
pending.Token);
|
||||||
|
}
|
||||||
operation.Stage = RuntimeEntityPlacementStage.AwaitingCommitAcknowledgement;
|
operation.Stage = RuntimeEntityPlacementStage.AwaitingCommitAcknowledgement;
|
||||||
_moverPreparationAuthorities.Remove(operation.Key);
|
_moverPreparationAuthorities.Remove(operation.Key);
|
||||||
return _operations.Remove(operation.Key);
|
return _operations.Remove(operation.Key);
|
||||||
|
|
@ -3041,6 +3176,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
||||||
_lostDeadlineNodeIndex.Clear();
|
_lostDeadlineNodeIndex.Clear();
|
||||||
_preparedMovers.Clear();
|
_preparedMovers.Clear();
|
||||||
_moverPreparationAuthorities.Clear();
|
_moverPreparationAuthorities.Clear();
|
||||||
|
_placementCompletionWatches.Clear();
|
||||||
|
_acknowledgedPlacementCompletions.Clear();
|
||||||
_collisionPrefixQuiescence.Clear();
|
_collisionPrefixQuiescence.Clear();
|
||||||
_pendingProjection.Clear();
|
_pendingProjection.Clear();
|
||||||
_expiredLostCells.Clear();
|
_expiredLostCells.Clear();
|
||||||
|
|
@ -3859,6 +3996,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
||||||
CancelExactLostKey(key);
|
CancelExactLostKey(key);
|
||||||
if (!_operations.Remove(key, out Operation? operation))
|
if (!_operations.Remove(key, out Operation? operation))
|
||||||
return false;
|
return false;
|
||||||
|
ForgetPlacementCompletionCore(operation.Token);
|
||||||
_moverPreparationAuthorities.Remove(key);
|
_moverPreparationAuthorities.Remove(key);
|
||||||
UnindexDeferred(operation);
|
UnindexDeferred(operation);
|
||||||
if (!preserveLostFamily && cancelLostFamily)
|
if (!preserveLostFamily && cancelLostFamily)
|
||||||
|
|
@ -3929,6 +4067,15 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
||||||
: default;
|
: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ForgetPlacementCompletionCore(
|
||||||
|
in RuntimeEntityPlacementToken token)
|
||||||
|
{
|
||||||
|
if (!token.IsValid)
|
||||||
|
return;
|
||||||
|
_placementCompletionWatches.Remove(token);
|
||||||
|
_acknowledgedPlacementCompletions.Remove(token);
|
||||||
|
}
|
||||||
|
|
||||||
private void IndexDeferred(Operation operation)
|
private void IndexDeferred(Operation operation)
|
||||||
{
|
{
|
||||||
if (operation.ExactCellId == 0u
|
if (operation.ExactCellId == 0u
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,35 @@ public class PhysicsTimestampGateTests
|
||||||
Assert.True(gate.TryAcceptMovementEvent(4, 0x9011, 2));
|
Assert.True(gate.TryAcceptMovementEvent(4, 0x9011, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PreviewCreateObject_MatchesSeedDispositionWithoutMutation()
|
||||||
|
{
|
||||||
|
var gate = new PhysicsTimestampGate();
|
||||||
|
|
||||||
|
Assert.Equal(CreateObjectTimestampDisposition.InitialGeneration,
|
||||||
|
gate.PreviewCreateObject(instance: 7));
|
||||||
|
Assert.Equal(CreateObjectTimestampDisposition.InitialGeneration,
|
||||||
|
gate.SeedForCreateObject(10, 20, 30, 40, 50, 60, 70, 80, 7));
|
||||||
|
|
||||||
|
Assert.Equal(CreateObjectTimestampDisposition.ExistingGeneration,
|
||||||
|
gate.PreviewCreateObject(instance: 7));
|
||||||
|
Assert.Equal(CreateObjectTimestampDisposition.NewGeneration,
|
||||||
|
gate.PreviewCreateObject(instance: 8));
|
||||||
|
Assert.Equal(CreateObjectTimestampDisposition.StaleGeneration,
|
||||||
|
gate.PreviewCreateObject(instance: 6));
|
||||||
|
|
||||||
|
// Preview is observational: all timestamp channels still compare
|
||||||
|
// against the original instance-seven seed afterward.
|
||||||
|
Assert.True(gate.TryAcceptMovementEvent(
|
||||||
|
instance: 7,
|
||||||
|
movement: 21,
|
||||||
|
serverControlledMove: 60));
|
||||||
|
Assert.False(gate.TryAcceptMovementEvent(
|
||||||
|
instance: 8,
|
||||||
|
movement: 22,
|
||||||
|
serverControlledMove: 60));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SameGenerationCreateObject_MixedChannelsAreAcceptedIndependently()
|
public void SameGenerationCreateObject_MixedChannelsAreAcceptedIndependently()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -48,7 +48,7 @@ public sealed class RuntimeAuthoritativePositionRouteClassifierTests
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(RuntimeCreateResidenceKind.Parented)]
|
[InlineData(RuntimeCreateResidenceKind.Parented)]
|
||||||
[InlineData(RuntimeCreateResidenceKind.PickedUp)]
|
[InlineData(RuntimeCreateResidenceKind.PickedUp)]
|
||||||
internal void NewParentedOrPickedUpCreate_LeavesWorldAndAwaitsPosition(
|
internal void NewParentedOrPickedUpCreate_RemainsCelllessAndAwaitsPosition(
|
||||||
RuntimeCreateResidenceKind residence)
|
RuntimeCreateResidenceKind residence)
|
||||||
{
|
{
|
||||||
RuntimeAuthoritativePositionRoute route =
|
RuntimeAuthoritativePositionRoute route =
|
||||||
|
|
@ -62,7 +62,7 @@ public sealed class RuntimeAuthoritativePositionRouteClassifierTests
|
||||||
|
|
||||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.AwaitFreshPosition,
|
Assert.Equal(RuntimeAuthoritativePositionDisposition.AwaitFreshPosition,
|
||||||
route.Disposition);
|
route.Disposition);
|
||||||
Assert.True(route.LeaveWorld);
|
Assert.False(route.LeaveWorld);
|
||||||
Assert.False(route.PerformsSetPosition);
|
Assert.False(route.PerformsSetPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,7 +294,7 @@ public sealed class RuntimeAuthoritativePositionRouteClassifierTests
|
||||||
RuntimeAuthoritativePositionRoute route = ClassifyLocal(
|
RuntimeAuthoritativePositionRoute route = ClassifyLocal(
|
||||||
Authority(ushort.MaxValue, 0));
|
Authority(ushort.MaxValue, 0));
|
||||||
|
|
||||||
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPosition,
|
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
||||||
route.Disposition);
|
route.Disposition);
|
||||||
Assert.Equal(0x1012u, (uint)route.SetPositionFlags);
|
Assert.Equal(0x1012u, (uint)route.SetPositionFlags);
|
||||||
Assert.True(route.UnparentBeforeRouting);
|
Assert.True(route.UnparentBeforeRouting);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue