refactor(runtime): publish canonical entity object deltas

Issue stable Runtime identities at canonical registration, publish entity and inventory commits through one generation-stamped synchronous stream, and make graphical adapters borrow the same direct views and events as a no-window host. Preserve exact projection teardown and retail mutation order while removing App-side event reconstruction.

Make the hard-recenter ordering fixture independent of the production two-millisecond frame budget so its injected-failure gate is deterministic.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-26 06:42:13 +02:00
parent d3e96ff912
commit ce3ac310d9
23 changed files with 2352 additions and 666 deletions

View file

@ -1,9 +1,17 @@
using AcDream.Core.Items;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
namespace AcDream.Runtime.Entities;
public readonly record struct RuntimeEntityRegistrationResult(
InboundCreateResult Inbound,
RuntimeEntityRecord? Canonical,
bool LogicalRegistrationCreated,
bool ReplacedExistingGeneration,
Exception? PriorGenerationCleanupFailure = null);
/// <summary>
/// One exact DeleteObject acceptance issued by a
/// <see cref="RuntimeEntityObjectLifetime"/>. The graphical host retires the
@ -14,15 +22,21 @@ public sealed class RuntimeEntityDeleteAcceptance
{
internal RuntimeEntityDeleteAcceptance(
RuntimeEntityObjectLifetime owner,
DeleteObject.Parsed delete)
DeleteObject.Parsed delete,
RuntimeEntityRecord? retiredCanonical,
bool removeRetainedObject)
{
Owner = owner;
Delete = delete;
RetiredCanonical = retiredCanonical;
RemoveRetainedObject = removeRetainedObject;
}
internal RuntimeEntityObjectLifetime Owner { get; }
internal bool Completed { get; set; }
public DeleteObject.Parsed Delete { get; }
public RuntimeEntityRecord? RetiredCanonical { get; }
public bool RemoveRetainedObject { get; }
}
/// <summary>
@ -30,17 +44,253 @@ public sealed class RuntimeEntityDeleteAcceptance
/// retained object lifetimes. Graphical and direct hosts borrow these exact
/// instances; they never allocate a second directory or object table.
/// </summary>
public sealed class RuntimeEntityObjectLifetime
public sealed class RuntimeEntityObjectLifetime : IDisposable
{
private bool _sessionClearInProgress;
private bool _disposed;
public RuntimeEntityObjectLifetime(
uint firstLocalEntityId = RuntimeEntityDirectory.FirstLocalEntityId)
{
Entities = new RuntimeEntityDirectory(firstLocalEntityId);
Objects = new ClientObjectTable();
var views = new RuntimeEntityObjectViews(Entities, Objects);
EntityView = views.Entities;
InventoryView = views.Inventory;
Events = new RuntimeEntityObjectEventStream(Entities, Objects);
}
public RuntimeEntityDirectory Entities { get; }
public ClientObjectTable Objects { get; }
public IRuntimeEntityView EntityView { get; }
public IRuntimeInventoryView InventoryView { get; }
public RuntimeEntityObjectEventStream Events { get; }
public void BindEventContext(
Func<RuntimeGenerationToken> generation,
Func<ulong> frameNumber)
{
EnsureNotDisposed();
Events.BindContext(generation, frameNumber);
}
/// <summary>
/// Owns the presentation-free half of retail's CreateObject lifetime
/// transaction. An attached graphical host may synchronously retire the
/// displaced projection through <paramref name="retirePriorProjection"/>;
/// a direct host omits it and Runtime retires canonical-only state.
/// </summary>
public RuntimeEntityRegistrationResult RegisterEntity(
WorldSession.EntitySpawn incoming,
Func<RuntimeEntityRecord, Exception?>? retirePriorProjection = null)
{
EnsureNotDisposed();
if (_sessionClearInProgress)
{
throw new InvalidOperationException(
"A Runtime entity cannot register while its session lifetime is clearing.");
}
InboundCreateResult result = Entities.AcceptCreate(incoming);
if (result.Disposition
is CreateObjectTimestampDisposition.StaleGeneration)
{
return new RuntimeEntityRegistrationResult(
result,
Canonical: null,
LogicalRegistrationCreated: false,
ReplacedExistingGeneration: false);
}
ulong sessionVersion = Entities.SessionLifetimeVersion;
ulong operationVersion =
Entities.AdvanceLifetimeMutation(incoming.Guid);
if (result.Disposition
is CreateObjectTimestampDisposition.ExistingGeneration)
{
if (Entities.TryGetActive(
incoming.Guid,
out RuntimeEntityRecord retained))
{
Entities.RefreshSnapshot(
retained,
result.Snapshot,
refreshPosition: true);
Entities.AdvanceCreateAuthority(retained);
PublishEntity(RuntimeEntityChange.Updated, retained);
if (!IsCurrentOperation(
incoming.Guid,
retained,
sessionVersion,
operationVersion))
{
return SupersededRegistration(
incoming.Guid,
replacedExistingGeneration: false);
}
return new RuntimeEntityRegistrationResult(
result,
retained,
LogicalRegistrationCreated: false,
ReplacedExistingGeneration: false);
}
if (Entities.TryGetTeardown(
incoming.Guid,
result.Snapshot.InstanceSequence,
out _))
{
return new RuntimeEntityRegistrationResult(
result,
Canonical: null,
LogicalRegistrationCreated: false,
ReplacedExistingGeneration: false);
}
RuntimeEntityRecord recovered = Entities.AddActive(result.Snapshot);
PublishEntity(RuntimeEntityChange.Registered, recovered);
if (!IsCurrentOperation(
incoming.Guid,
recovered,
sessionVersion,
operationVersion))
{
return SupersededRegistration(
incoming.Guid,
replacedExistingGeneration: false);
}
return new RuntimeEntityRegistrationResult(
result,
recovered,
LogicalRegistrationCreated: true,
ReplacedExistingGeneration: false);
}
bool replaced = Entities.RemoveActive(
incoming.Guid,
out RuntimeEntityRecord? prior);
if (result.Disposition
is CreateObjectTimestampDisposition.NewGeneration)
{
Entities.ParentAttachments.EndGeneration(
incoming.Guid,
result.Snapshot.InstanceSequence);
}
Exception? cleanupFailure = null;
if (prior is not null)
{
try
{
PublishEntity(RuntimeEntityChange.Deleted, prior);
}
catch (Exception error)
{
cleanupFailure = error;
}
Exception? projectionFailure = retirePriorProjection is null
? RetireCanonicalOnly(prior)
: retirePriorProjection(prior);
cleanupFailure = Combine(cleanupFailure, projectionFailure);
}
if (Entities.SessionLifetimeVersion != sessionVersion
|| Entities.CurrentLifetimeMutation(incoming.Guid)
!= operationVersion)
{
if (cleanupFailure is not null)
{
throw new AggregateException(
$"Prior incarnation of live entity 0x{incoming.Guid:X8} failed teardown while its incoming replacement was superseded.",
cleanupFailure);
}
return new RuntimeEntityRegistrationResult(
SupersededCreateResult(),
Entities.TryGetActive(
incoming.Guid,
out RuntimeEntityRecord current)
? current
: null,
LogicalRegistrationCreated: false,
ReplacedExistingGeneration: replaced);
}
RuntimeEntityRecord canonical = Entities.AddActive(result.Snapshot);
try
{
PublishEntity(RuntimeEntityChange.Registered, canonical);
}
catch (Exception error)
{
if (cleanupFailure is not null)
{
throw new AggregateException(
$"Live entity 0x{incoming.Guid:X8} registered after prior cleanup and commit observers failed.",
cleanupFailure,
error);
}
throw;
}
if (!IsCurrentOperation(
incoming.Guid,
canonical,
sessionVersion,
operationVersion))
{
if (cleanupFailure is not null)
{
throw new AggregateException(
$"Prior incarnation of live entity 0x{incoming.Guid:X8} failed teardown while its committed replacement was superseded.",
cleanupFailure);
}
return SupersededRegistration(
incoming.Guid,
replaced);
}
return new RuntimeEntityRegistrationResult(
result,
canonical,
LogicalRegistrationCreated: true,
ReplacedExistingGeneration: replaced,
cleanupFailure);
}
/// <summary>
/// Completes a displaced incarnation that never crossed the graphical
/// projection acquisition edge.
/// </summary>
public Exception? RetireCanonicalOnly(RuntimeEntityRecord canonical)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(canonical);
try
{
Entities.RetainTeardown(canonical);
try
{
Entities.SetPhysicsHost(canonical, null);
Entities.SetPhysicsBody(canonical, null);
Entities.SetPhysicsBodyAcquisitionInProgress(canonical, false);
Entities.SetHasPartArray(canonical, false);
Entities.ReleaseLocalId(canonical);
}
finally
{
Entities.ReleaseTeardown(canonical);
}
return null;
}
catch (Exception error)
{
return error;
}
}
/// <summary>
/// Applies the retained-object half of an accepted CreateObject while the
@ -54,6 +304,7 @@ public sealed class RuntimeEntityObjectLifetime
WorldSession.EntitySpawn spawn,
bool replaceGeneration)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(canonical);
if (canonical.ServerGuid != spawn.Guid
|| canonical.Incarnation != spawn.InstanceSequence)
@ -75,24 +326,423 @@ public sealed class RuntimeEntityObjectLifetime
&& IsCurrent();
}
public bool TryApplyObjDesc(
ObjDescEvent.Parsed update,
Action<RuntimeEntityRecord>? acknowledgeProjection,
out WorldSession.EntitySpawn accepted)
{
EnsureNotDisposed();
bool applied = Entities.TryApplyObjDesc(update, out accepted);
if (!applied
|| !Entities.TryGetActive(
update.Guid,
out RuntimeEntityRecord canonical))
{
return applied;
}
Entities.RefreshSnapshot(canonical, accepted);
Entities.AdvanceObjDescAuthority(canonical);
acknowledgeProjection?.Invoke(canonical);
if (!Entities.IsCurrent(canonical))
return false;
PublishEntity(RuntimeEntityChange.Updated, canonical);
return Entities.IsCurrent(canonical);
}
public bool TryApplyPickup(
PickupEvent.Parsed update,
Action<RuntimeEntityRecord>? acknowledgeProjection,
out WorldSession.EntitySpawn accepted)
{
EnsureNotDisposed();
bool applied = Entities.TryApplyPickup(update, out accepted);
if (!applied
|| !Entities.TryGetActive(
update.Guid,
out RuntimeEntityRecord canonical))
{
return applied;
}
Entities.RefreshSnapshot(canonical, accepted);
Entities.AdvancePositionAuthority(canonical);
Entities.SuspendObjectClock(canonical);
Entities.SetFullCell(canonical, 0u, 0u);
Entities.ParentAttachments.EndChildProjection(update.Guid);
acknowledgeProjection?.Invoke(canonical);
if (!Entities.IsCurrent(canonical))
return false;
PublishEntity(RuntimeEntityChange.Withdrawn, canonical);
return Entities.IsCurrent(canonical);
}
public bool TryApplyCreateParent(
CreateParentUpdate update,
Action<RuntimeEntityRecord>? acknowledgeProjection,
out WorldSession.EntitySpawn accepted)
{
EnsureNotDisposed();
bool applied = Entities.TryApplyCreateParent(update, out accepted);
return CommitPositionChannelUpdate(
applied,
update.ChildGuid,
accepted,
acknowledgeProjection);
}
public bool TryApplyParent(
ParentEvent.Parsed update,
Action<RuntimeEntityRecord>? acknowledgeProjection,
out WorldSession.EntitySpawn accepted)
{
EnsureNotDisposed();
bool applied = Entities.TryApplyParent(update, out accepted);
return CommitPositionChannelUpdate(
applied,
update.ChildGuid,
accepted,
acknowledgeProjection);
}
public bool TryCommitParent(
ParentAttachmentRelation relation,
Action<RuntimeEntityRecord>? acknowledgeProjection,
out WorldSession.EntitySpawn accepted)
{
EnsureNotDisposed();
bool committed = Entities.TryCommitParent(
relation.ChildGuid,
relation.ParentGuid,
relation.ParentLocation,
relation.PlacementId,
relation.ChildPositionSequence,
out accepted);
if (!committed
|| !Entities.TryGetActive(
relation.ChildGuid,
out RuntimeEntityRecord canonical))
{
return committed;
}
Entities.RefreshSnapshot(canonical, accepted);
acknowledgeProjection?.Invoke(canonical);
if (!Entities.IsCurrent(canonical))
return false;
PublishEntity(RuntimeEntityChange.Updated, canonical);
return Entities.IsCurrent(canonical);
}
public bool CommitAcceptedParentCellless(
RuntimeEntityRecord canonical,
ulong positionAuthorityVersion,
Action<RuntimeEntityRecord>? acknowledgeProjection)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(canonical);
if (!Entities.IsCurrent(canonical)
|| canonical.PositionAuthorityVersion != positionAuthorityVersion)
{
return false;
}
Entities.SuspendObjectClock(canonical);
Entities.SetFullCell(canonical, 0u, 0u);
acknowledgeProjection?.Invoke(canonical);
if (!Entities.IsCurrent(canonical)
|| canonical.PositionAuthorityVersion != positionAuthorityVersion)
{
return false;
}
PublishEntity(RuntimeEntityChange.Withdrawn, canonical);
return Entities.IsCurrent(canonical)
&& canonical.PositionAuthorityVersion == positionAuthorityVersion;
}
public bool TryApplyMotion(
WorldSession.EntityMotionUpdate update,
bool retainPayload,
Action<RuntimeEntityRecord>? acknowledgeProjection,
out WorldSession.EntitySpawn accepted,
out AcceptedPhysicsTimestamps timestamps)
{
EnsureNotDisposed();
bool applied = Entities.TryApplyMotion(
update,
retainPayload,
out accepted,
out timestamps);
if (Entities.TryGetSnapshot(
update.Guid,
out WorldSession.EntitySpawn snapshot)
&& Entities.TryGetActive(
update.Guid,
out RuntimeEntityRecord canonical))
{
Entities.RefreshSnapshot(canonical, snapshot);
if (applied && retainPayload)
Entities.AdvanceMovementAuthority(canonical);
acknowledgeProjection?.Invoke(canonical);
if (applied && Entities.IsCurrent(canonical))
PublishEntity(RuntimeEntityChange.Updated, canonical);
if (applied && !Entities.IsCurrent(canonical))
return false;
}
return applied;
}
public bool TryApplyVector(
VectorUpdate.Parsed update,
Action<RuntimeEntityRecord>? acknowledgeProjection,
out WorldSession.EntitySpawn accepted)
{
EnsureNotDisposed();
bool applied = Entities.TryApplyVector(update, out accepted);
if (!applied
|| !Entities.TryGetActive(
update.Guid,
out RuntimeEntityRecord canonical))
{
return applied;
}
Entities.RefreshSnapshot(canonical, accepted);
Entities.AdvanceVectorAuthority(canonical);
acknowledgeProjection?.Invoke(canonical);
if (!Entities.IsCurrent(canonical))
return false;
PublishEntity(RuntimeEntityChange.Updated, canonical);
return Entities.IsCurrent(canonical);
}
public bool TryApplyState(
SetState.Parsed update,
Action<RuntimeEntityRecord, RetailPhysicsStateTransition>?
acknowledgeProjection,
out WorldSession.EntitySpawn accepted,
out RetailPhysicsStateTransition transition)
{
EnsureNotDisposed();
bool applied = Entities.TryApplyState(update, out accepted);
transition = default;
if (!applied
|| !Entities.TryGetActive(
update.Guid,
out RuntimeEntityRecord canonical))
{
return applied;
}
Entities.RefreshSnapshot(canonical, accepted);
transition = Entities.ApplyRawPhysicsState(
canonical,
update.PhysicsState);
acknowledgeProjection?.Invoke(canonical, transition);
if (!Entities.IsCurrent(canonical))
return false;
PublishEntity(
transition.HiddenTransition
is RetailHiddenTransition.BecameHidden
? RuntimeEntityChange.Hidden
: RuntimeEntityChange.Updated,
canonical);
return Entities.IsCurrent(canonical);
}
public bool TryApplyPosition(
WorldSession.EntityPositionUpdate update,
bool isLocalPlayer,
System.Numerics.Quaternion? forcePositionRotation,
System.Numerics.Vector3? currentLocalVelocity,
bool projectionRequiresTeleportHook,
Action<RuntimeEntityRecord>? acknowledgeProjection,
out PositionTimestampDisposition disposition,
out WorldSession.EntitySpawn accepted,
out AcceptedPhysicsTimestamps timestamps)
{
EnsureNotDisposed();
bool hadCanonical = Entities.TryGetActive(
update.Guid,
out RuntimeEntityRecord beforeCanonical);
uint beforeCell = beforeCanonical?.FullCellId ?? 0u;
bool wasCellless = hadCanonical && beforeCell == 0u;
bool known = Entities.TryApplyPosition(
update,
isLocalPlayer,
forcePositionRotation,
currentLocalVelocity,
out disposition,
out accepted,
out timestamps);
if (!known
|| !Entities.TryGetSnapshot(
update.Guid,
out WorldSession.EntitySpawn snapshot)
|| !Entities.TryGetActive(
update.Guid,
out RuntimeEntityRecord canonical))
{
return known;
}
bool acceptedPosition =
disposition is not PositionTimestampDisposition.Rejected;
if (disposition is PositionTimestampDisposition.Apply)
{
timestamps = timestamps with
{
TeleportHookRequired =
timestamps.TeleportAdvanced
|| wasCellless
|| projectionRequiresTeleportHook,
};
}
Entities.RefreshSnapshot(
canonical,
snapshot,
refreshPosition: acceptedPosition);
if (acceptedPosition
&& ReferenceEquals(canonical, beforeCanonical))
{
Entities.AdvancePositionAuthority(canonical);
Entities.ParentAttachments.EndChildProjection(update.Guid);
}
acknowledgeProjection?.Invoke(canonical);
if (!Entities.IsCurrent(canonical))
return false;
if (acceptedPosition)
{
PublishEntity(
beforeCell != canonical.FullCellId
? RuntimeEntityChange.Rebucketed
: RuntimeEntityChange.Updated,
canonical);
}
return Entities.IsCurrent(canonical);
}
public bool CommitRebucket(
RuntimeEntityRecord canonical,
uint fullCellId,
uint canonicalLandblockId,
Action<RuntimeEntityRecord>? acknowledgeProjection = null)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(canonical);
if (!Entities.IsCurrent(canonical))
return false;
uint previous = canonical.FullCellId;
Entities.SetFullCell(
canonical,
fullCellId,
canonicalLandblockId);
acknowledgeProjection?.Invoke(canonical);
if (!Entities.IsCurrent(canonical))
return false;
if (previous != fullCellId)
PublishEntity(RuntimeEntityChange.Rebucketed, canonical);
return Entities.IsCurrent(canonical);
}
public bool CommitWithdrawal(
RuntimeEntityRecord canonical,
Action<RuntimeEntityRecord>? acknowledgeProjection = null)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(canonical);
if (!Entities.IsCurrent(canonical))
return false;
Entities.SuspendObjectClock(canonical);
Entities.SetFullCell(canonical, 0u, 0u);
acknowledgeProjection?.Invoke(canonical);
if (!Entities.IsCurrent(canonical))
return false;
PublishEntity(RuntimeEntityChange.Withdrawn, canonical);
return Entities.IsCurrent(canonical);
}
public bool CommitChildNoDraw(
RuntimeEntityRecord canonical,
bool noDraw,
Action<RuntimeEntityRecord>? acknowledgeProjection = null)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(canonical);
if (!Entities.IsCurrent(canonical))
return false;
Entities.SetChildNoDraw(canonical, noDraw);
acknowledgeProjection?.Invoke(canonical);
if (!Entities.IsCurrent(canonical))
return false;
PublishEntity(RuntimeEntityChange.Updated, canonical);
return Entities.IsCurrent(canonical);
}
public bool RetireAfterProjectionAcquisitionFailure(
RuntimeEntityRecord canonical)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(canonical);
if (!Entities.RemoveActive(canonical))
return false;
Entities.AdvanceLifetimeMutation(canonical.ServerGuid);
PublishEntity(RuntimeEntityChange.Deleted, canonical);
return true;
}
/// <summary>
/// Accepts the exact DeleteObject generation without yet publishing the
/// retained-object removal. This split lets the graphical host retire the
/// active identity first, matching the established reentrant callback
/// order, while Runtime remains the only freshness authority.
/// Accepts and retires the exact canonical DeleteObject generation without
/// yet publishing the retained-object removal. This split lets a graphical
/// host tear down the exact retired projection before completing the
/// retained-object mutation while Runtime remains the only freshness,
/// identity, and canonical-lifetime authority.
/// </summary>
public bool TryAcceptDelete(
DeleteObject.Parsed delete,
bool isLocalPlayer,
bool removeRetainedObject,
out RuntimeEntityDeleteAcceptance acceptance)
{
EnsureNotDisposed();
if (!Entities.TryDelete(delete, isLocalPlayer))
{
acceptance = null!;
return false;
}
acceptance = new RuntimeEntityDeleteAcceptance(this, delete);
Entities.AdvanceLifetimeMutation(delete.Guid);
Entities.ParentAttachments.DeleteGeneration(
delete.Guid,
delete.InstanceSequence);
RuntimeEntityRecord? retiredCanonical = null;
if (Entities.TryGetActive(
delete.Guid,
out RuntimeEntityRecord active)
&& active.Incarnation == delete.InstanceSequence
&& Entities.RemoveActive(active))
{
retiredCanonical = active;
PublishEntity(
removeRetainedObject
? RuntimeEntityChange.Deleted
: RuntimeEntityChange.Withdrawn,
active);
}
acceptance = new RuntimeEntityDeleteAcceptance(
this,
delete,
retiredCanonical,
removeRetainedObject);
return true;
}
@ -104,6 +754,7 @@ public sealed class RuntimeEntityObjectLifetime
public void CompleteAcceptedDelete(
RuntimeEntityDeleteAcceptance acceptance)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(acceptance);
if (!ReferenceEquals(acceptance.Owner, this))
{
@ -117,19 +768,132 @@ public sealed class RuntimeEntityObjectLifetime
}
acceptance.Completed = true;
ObjectTableWiring.ApplyEntityDelete(Objects, acceptance.Delete);
if (acceptance.RemoveRetainedObject)
ObjectTableWiring.ApplyEntityDelete(Objects, acceptance.Delete);
}
/// <summary>
/// Applies a delete accepted by the dormant exact-incarnation owner after
/// the active Runtime directory correctly reports no live record.
/// </summary>
public void ApplyAcceptedDormantDelete(DeleteObject.Parsed delete) =>
public void ApplyAcceptedDormantDelete(DeleteObject.Parsed delete)
{
EnsureNotDisposed();
ObjectTableWiring.ApplyEntityDelete(Objects, delete);
}
/// <summary>
/// Clears retained object state at the Runtime-owned reset stage. App
/// projection teardown remains a later acknowledged stage.
/// </summary>
public void ClearObjects() => Objects.Clear();
public void ClearObjects()
{
EnsureNotDisposed();
Objects.Clear();
}
public IReadOnlyList<RuntimeEntityRecord> BeginSessionClear()
{
EnsureNotDisposed();
if (_sessionClearInProgress)
return Array.Empty<RuntimeEntityRecord>();
_sessionClearInProgress = true;
Entities.BeginSessionClear();
RuntimeEntityRecord[] retired = Entities.ActiveRecords.ToArray();
foreach (RuntimeEntityRecord canonical in retired)
{
if (!Entities.RemoveActive(canonical))
continue;
PublishEntity(RuntimeEntityChange.Deleted, canonical);
}
return retired;
}
public bool CompleteSessionClearIfConverged()
{
EnsureNotDisposed();
if (!_sessionClearInProgress
|| !Entities.CompleteSessionClearIfConverged())
{
return false;
}
_sessionClearInProgress = false;
return true;
}
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
Events.Dispose();
}
private bool CommitPositionChannelUpdate(
bool applied,
uint guid,
WorldSession.EntitySpawn accepted,
Action<RuntimeEntityRecord>? acknowledgeProjection)
{
if (!applied
|| !Entities.TryGetActive(
guid,
out RuntimeEntityRecord canonical))
{
return applied;
}
Entities.RefreshSnapshot(canonical, accepted);
Entities.AdvancePositionAuthority(canonical);
acknowledgeProjection?.Invoke(canonical);
if (!Entities.IsCurrent(canonical))
return false;
PublishEntity(RuntimeEntityChange.Updated, canonical);
return Entities.IsCurrent(canonical);
}
private void PublishEntity(
RuntimeEntityChange change,
RuntimeEntityRecord canonical) =>
Events.PublishEntity(change, canonical);
private void EnsureNotDisposed() =>
ObjectDisposedException.ThrowIf(_disposed, this);
private static Exception? Combine(
Exception? first,
Exception? second) =>
first is null
? second
: second is null
? first
: new AggregateException(first, second);
private static InboundCreateResult SupersededCreateResult() => new(
CreateObjectTimestampDisposition.StaleGeneration,
default,
null,
default);
private bool IsCurrentOperation(
uint guid,
RuntimeEntityRecord canonical,
ulong sessionVersion,
ulong operationVersion) =>
Entities.SessionLifetimeVersion == sessionVersion
&& Entities.CurrentLifetimeMutation(guid) == operationVersion
&& Entities.IsCurrent(canonical);
private RuntimeEntityRegistrationResult SupersededRegistration(
uint guid,
bool replacedExistingGeneration) =>
new(
SupersededCreateResult(),
Entities.TryGetActive(guid, out RuntimeEntityRecord current)
? current
: null,
LogicalRegistrationCreated: false,
ReplacedExistingGeneration: replacedExistingGeneration);
}