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;
|
||||
}
|
||||
|
||||
/// <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,
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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.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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue