feat(runtime): own initial create residence transaction

This commit is contained in:
Erik 2026-08-01 19:35:08 +02:00
parent 74103f75b5
commit 38fd4b8dc9
10 changed files with 3031 additions and 17 deletions

View file

@ -102,6 +102,25 @@ public sealed class PhysicsTimestampGate
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(
ushort instance,
ushort movement,

View file

@ -20,6 +20,12 @@ public sealed class InboundPhysicsStateController
public bool TryGetSnapshot(uint guid, out WorldSession.EntitySpawn 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)
{
if (!_gates.TryGetValue(incoming.Guid, out PhysicsTimestampGate? gate))

View file

@ -47,6 +47,10 @@ public sealed class RuntimeEntityDirectory
public InboundCreateResult AcceptCreate(WorldSession.EntitySpawn incoming) =>
_inbound.AcceptCreate(incoming);
public CreateObjectTimestampDisposition PreviewCreateDisposition(
WorldSession.EntitySpawn incoming) =>
_inbound.PreviewCreateDisposition(incoming);
public bool TryDelete(
AcDream.Core.Net.Messages.DeleteObject.Parsed delete,
bool isLocalPlayer) =>

View file

@ -27,6 +27,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
int ContainerProjectionCount,
int EquipmentOwnerCount,
int PendingMoveCount,
int InitialCreateResidenceLeaseCount,
int StreamSubscriberCount,
int PlacementStreamSubscriberCount,
long StreamDispatchFailureCount,
@ -51,6 +52,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
&& ContainerProjectionCount == 0
&& EquipmentOwnerCount == 0
&& PendingMoveCount == 0
&& InitialCreateResidenceLeaseCount == 0
&& StreamSubscriberCount == 0
&& PlacementStreamSubscriberCount == 0
&& PendingDispatchCount == 0
@ -118,6 +120,9 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InventoryView = views.Inventory;
Events = new RuntimeEntityObjectEventStream(Entities, Objects);
Physics.SetPosition.BindEventStream(Events);
InitialCreateResidences = new RuntimeInitialCreateResidenceState(
Entities,
Physics.SetPosition);
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
@ -143,6 +148,9 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InventoryView = views.Inventory;
Events = new RuntimeEntityObjectEventStream(Entities, Objects);
Physics.SetPosition.BindEventStream(Events);
InitialCreateResidences = new RuntimeInitialCreateResidenceState(
Entities,
Physics.SetPosition);
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
@ -168,6 +176,9 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InventoryView = views.Inventory;
Events = new RuntimeEntityObjectEventStream(Entities, Objects);
Physics.SetPosition.BindEventStream(Events);
InitialCreateResidences = new RuntimeInitialCreateResidenceState(
Entities,
Physics.SetPosition);
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
@ -180,10 +191,14 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
public IRuntimeInventoryView InventoryView { get; }
public RuntimeEntityObjectEventStream Events { get; }
public RuntimePlacementProjectionChannel Placements { get; }
internal RuntimeInitialCreateResidenceState InitialCreateResidences
{ get; }
public RuntimeEntityObjectOwnershipSnapshot CaptureOwnership()
{
ParentAttachmentState parents = Entities.ParentAttachments;
RuntimeInitialCreateResidenceOwnershipSnapshot initialResidence =
InitialCreateResidences.CaptureOwnership();
return new RuntimeEntityObjectOwnershipSnapshot(
Entities.Count,
Entities.PendingTeardownCount,
@ -198,6 +213,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Objects.ContainerProjectionCount,
Objects.EquipmentOwnerCount,
Objects.PendingMoveCount,
initialResidence.ActiveLeaseCount
+ initialResidence.PendingAdoptionCount,
Events.SubscriberCount,
Events.PlacementSubscriberCount,
Events.DispatchFailureCount,
@ -215,6 +232,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
EnsureNotDisposed();
Events.BindContext(generation, frameNumber);
Placements.BindGeneration(generation);
InitialCreateResidences.BindGeneration(generation);
}
/// <summary>
@ -225,7 +243,28 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
/// </summary>
public RuntimeEntityRegistrationResult RegisterEntity(
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();
if (_sessionClearInProgress)
@ -233,6 +272,18 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
throw new InvalidOperationException(
"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);
if (result.Disposition
@ -256,11 +307,16 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
incoming.Guid,
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(
retained,
result.Snapshot,
refreshPosition: true);
Entities.AdvanceCreateAuthority(retained);
refreshPosition: !beginInitialResidence);
if (!beginInitialResidence)
Entities.AdvanceCreateAuthority(retained);
PublishEntity(RuntimeEntityChange.Updated, retained);
if (!IsCurrentOperation(
incoming.Guid,
@ -291,7 +347,24 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
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);
if (!InitializeAcceptedCreateResidence(
recovered,
result,
beginInitialResidence,
isLocalPlayer))
{
throw FailInitialResidenceRegistration(
recovered,
publishDeleted: false);
}
PublishEntity(RuntimeEntityChange.Registered, recovered);
if (!IsCurrentOperation(
incoming.Guid,
@ -366,6 +439,16 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
}
RuntimeEntityRecord canonical = Entities.AddActive(result.Snapshot);
if (!InitializeAcceptedCreateResidence(
canonical,
result,
beginInitialResidence,
isLocalPlayer))
{
throw FailInitialResidenceRegistration(
canonical,
publishDeleted: false);
}
try
{
PublishEntity(RuntimeEntityChange.Registered, canonical);
@ -446,10 +529,14 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(canonical);
RuntimePlacementCancellationReceipt cancellation =
RuntimePlacementCancellationReceipt initialCancellation =
ForgetInitialCreateResidence(canonical);
RuntimePlacementCancellationReceipt ordinaryCancellation =
Physics.SetPosition.Forget(
canonical,
releasePreparedMover: true);
RuntimePlacementCancellationReceipt cancellation =
PreferCancellation(initialCancellation, ordinaryCancellation);
Physics.CollisionReports.Forget(canonical);
Physics.RemoveSpatialProjection(canonical);
Entities.SetRemoteMotion(canonical, null);
@ -540,10 +627,14 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
}
Entities.RefreshSnapshot(canonical, accepted);
RuntimePlacementCancellationReceipt initialCancellation =
ForgetInitialCreateResidence(canonical);
Entities.AdvancePositionAuthority(canonical);
Physics.CollisionReports.LeaveWorld(canonical);
RuntimePlacementCancellationReceipt cancellation =
RuntimePlacementCancellationReceipt ordinaryCancellation =
Physics.SetPosition.Forget(canonical);
RuntimePlacementCancellationReceipt cancellation =
PreferCancellation(initialCancellation, ordinaryCancellation);
Entities.SuspendObjectClock(canonical);
Entities.SetFullCell(canonical, 0u, 0u);
Entities.ParentAttachments.EndChildProjection(update.Guid);
@ -631,8 +722,12 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
return false;
}
RuntimePlacementCancellationReceipt cancellation =
RuntimePlacementCancellationReceipt initialCancellation =
ForgetInitialCreateResidence(canonical);
RuntimePlacementCancellationReceipt ordinaryCancellation =
Physics.SetPosition.Forget(canonical);
RuntimePlacementCancellationReceipt cancellation =
PreferCancellation(initialCancellation, ordinaryCancellation);
Physics.CollisionReports.LeaveWorld(canonical);
Entities.SuspendObjectClock(canonical);
Entities.SetFullCell(canonical, 0u, 0u);
@ -788,6 +883,25 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
out AcceptedPhysicsTimestamps timestamps)
{
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(
update.Guid,
out RuntimeEntityRecord beforeCanonical);
@ -825,9 +939,47 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
};
}
RuntimePlacementCancellationReceipt cancellation = acceptedPosition
? Physics.SetPosition.Forget(canonical)
: default;
// Initial CreateObject residence is one immutable transaction. A
// 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(
canonical,
snapshot,
@ -903,6 +1055,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
if (!Entities.IsCurrent(canonical))
return false;
RuntimePlacementCancellationReceipt cancellation =
ForgetInitialCreateResidence(canonical);
Physics.CollisionReports.LeaveWorld(canonical);
Entities.SuspendObjectClock(canonical);
Entities.SetFullCell(canonical, 0u, 0u);
@ -911,7 +1065,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
canonical,
() => acknowledgeProjection?.Invoke(canonical),
RuntimeEntityChange.Withdrawn,
() => canonical.SpatialAuthorityVersion == spatialVersion);
() => canonical.SpatialAuthorityVersion == spatialVersion,
cancellation);
}
public bool CommitChildNoDraw(
@ -981,11 +1136,17 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
&& active.Incarnation == delete.InstanceSequence
&& Entities.RemoveActive(active))
{
RuntimePlacementCancellationReceipt initialCancellation =
ForgetInitialCreateResidence(active);
Physics.CollisionReports.Forget(active);
RuntimePlacementCancellationReceipt cancellation =
RuntimePlacementCancellationReceipt ordinaryCancellation =
Physics.SetPosition.Forget(
active,
releasePreparedMover: true);
RuntimePlacementCancellationReceipt cancellation =
PreferCancellation(
initialCancellation,
ordinaryCancellation);
retiredCanonical = active;
Entities.RetainTeardown(active);
Physics.SetPosition.PublishCancellation(cancellation);
@ -1058,6 +1219,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
_sessionClearInProgress = true;
RuntimeEntityRecord[] active = Entities.ActiveRecords.ToArray();
InitialCreateResidences.Clear();
Physics.CollisionReports.LeaveWorldBatch(active);
Physics.ResetSessionPhysics();
Entities.BeginSessionClear();
@ -1193,10 +1355,14 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
}
Entities.RefreshSnapshot(canonical, accepted);
RuntimePlacementCancellationReceipt initialCancellation =
ForgetInitialCreateResidence(canonical);
Entities.AdvancePositionAuthority(canonical);
Physics.CollisionReports.LeaveWorld(canonical);
RuntimePlacementCancellationReceipt cancellation =
RuntimePlacementCancellationReceipt ordinaryCancellation =
Physics.SetPosition.Forget(canonical);
RuntimePlacementCancellationReceipt cancellation =
PreferCancellation(initialCancellation, ordinaryCancellation);
ulong positionVersion = canonical.PositionAuthorityVersion;
ulong spatialVersion = canonical.SpatialAuthorityVersion;
return AcknowledgeProjectionAndPublish(
@ -1251,6 +1417,108 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
private void EnsureNotDisposed() =>
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(
Exception? first,
Exception? second) =>

View file

@ -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);
}
}

View file

@ -180,6 +180,9 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
| PhysicsSetPositionFlags.Slide
| PhysicsSetPositionFlags.SendPositionEvent;
internal static bool IsValidCreateWirePosition(
in CreateObject.ServerPosition position) => ValidPosition(position);
internal static RuntimeAuthoritativePositionRoute ClassifyCreate(
in RuntimeCreatePositionRouteRequest request)
{
@ -205,7 +208,10 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
0u,
UnparentBeforeRouting: 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,
StopInterpolating: false,
ConstrainAfterRouting: false,
@ -294,7 +300,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
{
return new RuntimeAuthoritativePositionRoute(
request.Authority,
RuntimeAuthoritativePositionDisposition.SetPosition,
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
operation,
AuthoritativeTeleportFlags,
placement,

View file

@ -250,6 +250,8 @@ internal readonly record struct RuntimeSetPositionOwnershipSnapshot(
int UnboundDeferredCellOrderCount,
int PreparedMoverCount,
int MoverPreparationAuthorityCount,
int PlacementCompletionWatchCount,
int AcknowledgedPlacementCompletionCount,
int CollisionPrefixQuiescenceCount,
int PendingQuiescenceProjectionCount)
{
@ -276,6 +278,8 @@ internal readonly record struct RuntimeSetPositionOwnershipSnapshot(
&& UnboundDeferredCellOrderCount == 0
&& PreparedMoverCount == 0
&& MoverPreparationAuthorityCount == 0
&& PlacementCompletionWatchCount == 0
&& AcknowledgedPlacementCompletionCount == 0
&& CollisionPrefixQuiescenceCount == 0
&& PendingQuiescenceProjectionCount == 0;
}
@ -414,6 +418,10 @@ internal sealed class RuntimeSetPositionState : IDisposable
_preparedMovers = [];
private readonly Dictionary<RuntimeEntityKey, MoverPreparationAuthority>
_moverPreparationAuthorities = [];
private readonly HashSet<RuntimeEntityPlacementToken>
_placementCompletionWatches = [];
private readonly Dictionary<RuntimeEntityPlacementToken,
RuntimePlacementProjectionToken> _acknowledgedPlacementCompletions = [];
private readonly Dictionary<uint, CollisionPrefixQuiescence>
_collisionPrefixQuiescence = [];
private readonly LinkedList<RuntimeEntityKey> _expiredLostCells = [];
@ -477,11 +485,15 @@ internal sealed class RuntimeSetPositionState : IDisposable
_unboundDeferredCellOrder.Count,
_preparedMovers.Count,
_moverPreparationAuthorities.Count,
_placementCompletionWatches.Count,
_acknowledgedPlacementCompletions.Count,
_collisionPrefixQuiescence.Count,
pendingQuiescenceProjections);
}
internal int PendingProjectionCount => _pendingProjection.Count;
internal bool CanBeginAuthoredPlacementSequence =>
_nextOperationId != ulong.MaxValue;
internal bool IsCollisionPrefixQuiescing(uint landblockId) =>
_collisionPrefixQuiescence.ContainsKey(
@ -812,6 +824,111 @@ internal sealed class RuntimeSetPositionState : IDisposable
portal,
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(
RuntimeEntityRecord record,
PhysicsBody body,
@ -853,6 +970,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
record.Snapshot.Physics?.Position ?? record.Snapshot.Position;
if (record.Key is not { } key
|| !_entities.IsCurrent(record)
|| HasRetainedCompletion(key)
|| record.PositionAuthorityVersion
!= expectedPositionAuthorityVersion
|| (captureMoverPreparationAuthority
@ -947,6 +1065,17 @@ internal sealed class RuntimeSetPositionState : IDisposable
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(
in RuntimeEntityPlacementToken token,
in RuntimeSetPositionMoverPreparation preparation,
@ -2206,6 +2335,12 @@ internal sealed class RuntimeSetPositionState : IDisposable
operation.ProjectionSequence = 0UL;
if (pending.Kind is RuntimePlacementProjectionKind.Place)
{
if (_placementCompletionWatches.Remove(operation.Token))
{
_acknowledgedPlacementCompletions.Add(
operation.Token,
pending.Token);
}
operation.Stage = RuntimeEntityPlacementStage.AwaitingCommitAcknowledgement;
_moverPreparationAuthorities.Remove(operation.Key);
return _operations.Remove(operation.Key);
@ -3041,6 +3176,8 @@ internal sealed class RuntimeSetPositionState : IDisposable
_lostDeadlineNodeIndex.Clear();
_preparedMovers.Clear();
_moverPreparationAuthorities.Clear();
_placementCompletionWatches.Clear();
_acknowledgedPlacementCompletions.Clear();
_collisionPrefixQuiescence.Clear();
_pendingProjection.Clear();
_expiredLostCells.Clear();
@ -3859,6 +3996,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
CancelExactLostKey(key);
if (!_operations.Remove(key, out Operation? operation))
return false;
ForgetPlacementCompletionCore(operation.Token);
_moverPreparationAuthorities.Remove(key);
UnindexDeferred(operation);
if (!preserveLostFamily && cancelLostFamily)
@ -3929,6 +4067,15 @@ internal sealed class RuntimeSetPositionState : IDisposable
: default;
}
private void ForgetPlacementCompletionCore(
in RuntimeEntityPlacementToken token)
{
if (!token.IsValid)
return;
_placementCompletionWatches.Remove(token);
_acknowledgedPlacementCompletions.Remove(token);
}
private void IndexDeferred(Operation operation)
{
if (operation.ExactCellId == 0u

View file

@ -118,6 +118,35 @@ public class PhysicsTimestampGateTests
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]
public void SameGenerationCreateObject_MixedChannelsAreAcceptedIndependently()
{

File diff suppressed because it is too large Load diff

View file

@ -48,7 +48,7 @@ public sealed class RuntimeAuthoritativePositionRouteClassifierTests
[Theory]
[InlineData(RuntimeCreateResidenceKind.Parented)]
[InlineData(RuntimeCreateResidenceKind.PickedUp)]
internal void NewParentedOrPickedUpCreate_LeavesWorldAndAwaitsPosition(
internal void NewParentedOrPickedUpCreate_RemainsCelllessAndAwaitsPosition(
RuntimeCreateResidenceKind residence)
{
RuntimeAuthoritativePositionRoute route =
@ -62,7 +62,7 @@ public sealed class RuntimeAuthoritativePositionRouteClassifierTests
Assert.Equal(RuntimeAuthoritativePositionDisposition.AwaitFreshPosition,
route.Disposition);
Assert.True(route.LeaveWorld);
Assert.False(route.LeaveWorld);
Assert.False(route.PerformsSetPosition);
}
@ -294,7 +294,7 @@ public sealed class RuntimeAuthoritativePositionRouteClassifierTests
RuntimeAuthoritativePositionRoute route = ClassifyLocal(
Authority(ushort.MaxValue, 0));
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPosition,
Assert.Equal(RuntimeAuthoritativePositionDisposition.SetPositionSimple,
route.Disposition);
Assert.Equal(0x1012u, (uint)route.SetPositionFlags);
Assert.True(route.UnparentBeforeRouting);