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:
parent
d3e96ff912
commit
ce3ac310d9
23 changed files with 2352 additions and 666 deletions
|
|
@ -3,6 +3,7 @@ using AcDream.Core.Net;
|
|||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using System.Numerics;
|
||||
using System.Runtime.ExceptionServices;
|
||||
|
|
@ -377,29 +378,13 @@ public sealed class LiveEntityRecord
|
|||
internal RetailPhysicsStateTransition ApplyRawPhysicsState(uint rawState) =>
|
||||
_directory.ApplyRawPhysicsState(Canonical, rawState);
|
||||
|
||||
internal void AdvancePositionAuthority() =>
|
||||
_directory.AdvancePositionAuthority(Canonical);
|
||||
|
||||
internal void AdvanceVectorAuthority() =>
|
||||
_directory.AdvanceVectorAuthority(Canonical);
|
||||
|
||||
internal void AdvanceMovementAuthority() =>
|
||||
_directory.AdvanceMovementAuthority(Canonical);
|
||||
|
||||
internal void AdvanceObjDescAuthority()
|
||||
internal void NoteObjDescProjectionSynchronization()
|
||||
{
|
||||
_directory.AdvanceObjDescAuthority(Canonical);
|
||||
AppearanceProjectionSynchronizationPending = true;
|
||||
if (AppearanceHydrationInProgress)
|
||||
AppearanceHydrationRetryRequested = true;
|
||||
}
|
||||
|
||||
internal void AdvanceCreateAuthority() =>
|
||||
_directory.AdvanceCreateAuthority(Canonical);
|
||||
|
||||
internal void SetChildNoDraw(bool noDraw) =>
|
||||
_directory.SetChildNoDraw(Canonical, noDraw);
|
||||
|
||||
/// <summary>
|
||||
/// Cell used when a streamed landblock asks live objects to restore their
|
||||
/// spatial projection. Once materialized, the canonical runtime cell wins;
|
||||
|
|
@ -582,150 +567,26 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
: "A live entity cannot register from inside atomic resource registration.");
|
||||
}
|
||||
|
||||
InboundCreateResult result = _directory.AcceptCreate(incoming);
|
||||
if (result.Disposition is CreateObjectTimestampDisposition.StaleGeneration)
|
||||
return new LiveEntityRegistrationResult(
|
||||
result,
|
||||
Canonical: null,
|
||||
Projection: null,
|
||||
LogicalRegistrationCreated: false,
|
||||
ReplacedExistingGeneration: false);
|
||||
ulong sessionVersion = _directory.SessionLifetimeVersion;
|
||||
ulong operationVersion = AdvanceLifetimeMutation(incoming.Guid);
|
||||
|
||||
if (result.Disposition is CreateObjectTimestampDisposition.ExistingGeneration)
|
||||
RuntimeEntityRegistrationResult registration =
|
||||
_entityObjects.RegisterEntity(
|
||||
incoming,
|
||||
RetirePriorProjection);
|
||||
RuntimeEntityRecord? canonical = registration.Canonical;
|
||||
LiveEntityRecord? projection = canonical is null
|
||||
? null
|
||||
: _projections.GetCurrentOrDefault(canonical.ServerGuid);
|
||||
if (projection is not null
|
||||
&& ReferenceEquals(projection.Canonical, canonical))
|
||||
{
|
||||
if (_directory.TryGetActive(
|
||||
incoming.Guid,
|
||||
out RuntimeEntityRecord retainedCanonical))
|
||||
{
|
||||
_directory.RefreshSnapshot(
|
||||
retainedCanonical,
|
||||
result.Snapshot,
|
||||
refreshPosition: true);
|
||||
_directory.AdvanceCreateAuthority(retainedCanonical);
|
||||
_projections.TryGet(
|
||||
retainedCanonical,
|
||||
out LiveEntityRecord? retainedProjection);
|
||||
if (retainedProjection is not null)
|
||||
RefreshSpatialRuntimeIndexes(retainedProjection);
|
||||
return new LiveEntityRegistrationResult(
|
||||
result,
|
||||
retainedCanonical,
|
||||
retainedProjection,
|
||||
LogicalRegistrationCreated: false,
|
||||
ReplacedExistingGeneration: false);
|
||||
}
|
||||
|
||||
// A failed materialization rollback retains this exact incarnation
|
||||
// as a teardown tombstone. Do not create a second active record
|
||||
// with the same (GUID, INSTANCE_TS) while its partial resources are
|
||||
// still converging; a later retransmit can recover after teardown.
|
||||
if (_directory.TryGetTeardown(
|
||||
incoming.Guid,
|
||||
result.Snapshot.InstanceSequence,
|
||||
out _))
|
||||
{
|
||||
return new LiveEntityRegistrationResult(
|
||||
result,
|
||||
Canonical: null,
|
||||
Projection: null,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
// Defensive repair for a caller that cleared only the logical map.
|
||||
// Normal session teardown clears both owners together.
|
||||
RuntimeEntityRecord recoveredCanonical =
|
||||
_directory.AddActive(result.Snapshot);
|
||||
return new LiveEntityRegistrationResult(
|
||||
result,
|
||||
recoveredCanonical,
|
||||
Projection: null,
|
||||
LogicalRegistrationCreated: true,
|
||||
ReplacedExistingGeneration: false);
|
||||
RefreshSpatialRuntimeIndexes(projection);
|
||||
}
|
||||
|
||||
bool replaced = _directory.RemoveActive(
|
||||
incoming.Guid,
|
||||
out RuntimeEntityRecord? oldCanonical);
|
||||
LiveEntityRecord? old = null;
|
||||
if (oldCanonical is not null
|
||||
&& _projections.TryGet(oldCanonical, out LiveEntityRecord? projected))
|
||||
{
|
||||
old = projected;
|
||||
if (!_projections.RemoveActive(old))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Exact App projection for 0x{incoming.Guid:X8}/{old.Generation} could not be retired.");
|
||||
}
|
||||
}
|
||||
if (result.Disposition is CreateObjectTimestampDisposition.NewGeneration)
|
||||
ParentAttachments.EndGeneration(incoming.Guid, result.Snapshot.InstanceSequence);
|
||||
|
||||
Exception? tearDownFailure = null;
|
||||
if (old is not null)
|
||||
{
|
||||
RetainTeardownRecord(old);
|
||||
_logicalTeardownDepth++;
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
TearDownRecord(old);
|
||||
ReleaseTeardownRecord(old);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
tearDownFailure = error;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logicalTeardownDepth--;
|
||||
}
|
||||
}
|
||||
else if (oldCanonical is not null)
|
||||
{
|
||||
TearDownCanonicalOnly(oldCanonical);
|
||||
}
|
||||
|
||||
// Resource callbacks are arbitrary App integration code. Any nested
|
||||
// create, accepted delete, or session reset advances this epoch. The
|
||||
// outer packet is then superseded even when the nested action left no
|
||||
// record/snapshot (delete/reset); reinstalling it would resurrect an
|
||||
// incarnation after an accepted terminal event.
|
||||
if (_directory.SessionLifetimeVersion != sessionVersion
|
||||
|| CurrentLifetimeMutation(incoming.Guid) != operationVersion)
|
||||
{
|
||||
if (tearDownFailure is not null)
|
||||
{
|
||||
throw new AggregateException(
|
||||
$"Prior incarnation of live entity 0x{incoming.Guid:X8} failed teardown while its incoming replacement was superseded.",
|
||||
tearDownFailure);
|
||||
}
|
||||
|
||||
return new LiveEntityRegistrationResult(
|
||||
SupersededCreateResult(),
|
||||
_directory.TryGetActive(
|
||||
incoming.Guid,
|
||||
out RuntimeEntityRecord currentCanonical)
|
||||
? currentCanonical
|
||||
: null,
|
||||
_projections.GetCurrentOrDefault(incoming.Guid),
|
||||
false,
|
||||
replaced,
|
||||
tearDownFailure);
|
||||
}
|
||||
|
||||
RuntimeEntityRecord canonical = _directory.AddActive(result.Snapshot);
|
||||
return new LiveEntityRegistrationResult(
|
||||
result,
|
||||
registration.Inbound,
|
||||
canonical,
|
||||
Projection: null,
|
||||
true,
|
||||
replaced,
|
||||
tearDownFailure);
|
||||
projection,
|
||||
registration.LogicalRegistrationCreated,
|
||||
registration.ReplacedExistingGeneration,
|
||||
registration.PriorGenerationCleanupFailure);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -785,7 +646,9 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
bool createdSidecar = false;
|
||||
if (!_projections.TryGet(expectedCanonical, out record))
|
||||
{
|
||||
uint localId = _directory.ClaimLocalId(expectedCanonical);
|
||||
_ = expectedCanonical.LocalEntityId
|
||||
?? throw new InvalidOperationException(
|
||||
"Runtime must issue entity identity before App projection.");
|
||||
try
|
||||
{
|
||||
record = _projections.AddMaterializing(expectedCanonical);
|
||||
|
|
@ -800,7 +663,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
record.ProjectionKey = null;
|
||||
record = null;
|
||||
}
|
||||
_directory.ReleaseLocalId(expectedCanonical);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
@ -833,7 +695,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
{
|
||||
_projections.RemoveActive(record);
|
||||
record.ProjectionKey = null;
|
||||
_directory.ReleaseLocalId(expectedCanonical);
|
||||
record = null;
|
||||
}
|
||||
throw;
|
||||
|
|
@ -870,14 +731,13 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
{
|
||||
_projections.RemoveActive(record);
|
||||
record.ProjectionKey = null;
|
||||
_directory.ReleaseLocalId(expectedCanonical);
|
||||
record.WorldEntity = null;
|
||||
record = null;
|
||||
throw;
|
||||
}
|
||||
|
||||
if (_directory.IsCurrent(expectedCanonical))
|
||||
_directory.RemoveActive(expectedCanonical);
|
||||
_entityObjects.RetireAfterProjectionAcquisitionFailure(
|
||||
expectedCanonical);
|
||||
if (_projections.RemoveActive(record))
|
||||
{
|
||||
RetainTeardownRecord(record);
|
||||
|
|
@ -983,11 +843,24 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
bool visible = _spatial.IsLiveEntityProjectionResident(key);
|
||||
record.IsSpatiallyVisible = visible;
|
||||
RefreshPresentation(record);
|
||||
if ((spatialCellOrLandblockId & 0xFFFFu) != 0xFFFFu)
|
||||
record.FullCellId = spatialCellOrLandblockId;
|
||||
record.CanonicalLandblockId = spatialCellOrLandblockId == 0
|
||||
uint committedFullCell =
|
||||
(spatialCellOrLandblockId & 0xFFFFu) != 0xFFFFu
|
||||
? spatialCellOrLandblockId
|
||||
: record.FullCellId;
|
||||
uint committedLandblock = spatialCellOrLandblockId == 0
|
||||
? 0u
|
||||
: (spatialCellOrLandblockId & 0xFFFF0000u) | 0xFFFFu;
|
||||
if (!_entityObjects.CommitRebucket(
|
||||
record.Canonical,
|
||||
committedFullCell,
|
||||
committedLandblock))
|
||||
{
|
||||
ThrowAfterCommittedProjectionChange(
|
||||
serverGuid,
|
||||
spatialNotificationFailure,
|
||||
runtimeNotificationFailure: null);
|
||||
return false;
|
||||
}
|
||||
bool isOrdinaryRoot = record.ProjectionKind is LiveEntityProjectionKind.World
|
||||
&& record.IsSpatiallyProjected
|
||||
&& record.IsSpatiallyVisible
|
||||
|
|
@ -1182,7 +1055,12 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
return false;
|
||||
}
|
||||
|
||||
ClearWorldCell(serverGuid);
|
||||
if (!_entityObjects.CommitWithdrawal(
|
||||
record.Canonical,
|
||||
AcknowledgeCelllessCanonicalCommit))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
record.WorldEntity.ParentCellId = 0u;
|
||||
return WithdrawLiveEntityProjection(serverGuid);
|
||||
}
|
||||
|
|
@ -1218,10 +1096,9 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
if (!_entityObjects.TryAcceptDelete(
|
||||
delete,
|
||||
isLocalPlayer,
|
||||
removeRetainedObject,
|
||||
out acceptedDelete))
|
||||
return false;
|
||||
AdvanceLifetimeMutation(delete.Guid);
|
||||
ParentAttachments.DeleteGeneration(delete.Guid, delete.InstanceSequence);
|
||||
|
||||
// End active gameplay identity before arbitrary callbacks so a
|
||||
// newer generation can be accepted re-entrantly, but retain the
|
||||
|
|
@ -1229,11 +1106,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
// succeeded. A throwing resource callback can then be retried by
|
||||
// the same accepted Delete instead of orphaning its owner.
|
||||
LiveEntityRecord? retainedRegistrationFailure = record;
|
||||
if (_directory.TryGetActive(
|
||||
delete.Guid,
|
||||
out RuntimeEntityRecord activeCanonical)
|
||||
&& activeCanonical.Incarnation == delete.InstanceSequence
|
||||
&& _directory.RemoveActive(activeCanonical))
|
||||
if (acceptedDelete.RetiredCanonical is { } activeCanonical)
|
||||
{
|
||||
if (_projections.TryGet(
|
||||
activeCanonical,
|
||||
|
|
@ -1266,16 +1139,13 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
{
|
||||
if (!retryingAcceptedDelete)
|
||||
{
|
||||
if (removeRetainedObject)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
_entityObjects.CompleteAcceptedDelete(acceptedDelete!);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
(failures ??= new List<Exception>()).Add(error);
|
||||
}
|
||||
_entityObjects.CompleteAcceptedDelete(acceptedDelete!);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
(failures ??= new List<Exception>()).Add(error);
|
||||
}
|
||||
|
||||
try
|
||||
|
|
@ -1872,95 +1742,44 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
_directory.TryGetSnapshot(guid, out spawn);
|
||||
|
||||
public bool TryApplyObjDesc(ObjDescEvent.Parsed update, out WorldSession.EntitySpawn accepted)
|
||||
{
|
||||
bool applied = _directory.TryApplyObjDesc(update, out accepted);
|
||||
if (applied
|
||||
&& _directory.TryGetActive(
|
||||
update.Guid,
|
||||
out RuntimeEntityRecord canonical))
|
||||
{
|
||||
_directory.RefreshSnapshot(canonical, accepted);
|
||||
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
|
||||
record.AdvanceObjDescAuthority();
|
||||
else
|
||||
_directory.AdvanceObjDescAuthority(canonical);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
=> _entityObjects.TryApplyObjDesc(
|
||||
update,
|
||||
canonical =>
|
||||
{
|
||||
if (_projections.TryGet(
|
||||
canonical,
|
||||
out LiveEntityRecord? record))
|
||||
{
|
||||
record.NoteObjDescProjectionSynchronization();
|
||||
}
|
||||
},
|
||||
out accepted);
|
||||
|
||||
public bool TryApplyPickup(PickupEvent.Parsed update, out WorldSession.EntitySpawn accepted)
|
||||
{
|
||||
bool applied = _directory.TryApplyPickup(update, out accepted);
|
||||
if (applied
|
||||
&& _directory.TryGetActive(
|
||||
update.Guid,
|
||||
out RuntimeEntityRecord canonical))
|
||||
{
|
||||
_directory.RefreshSnapshot(canonical, accepted);
|
||||
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
|
||||
record.AdvancePositionAuthority();
|
||||
else
|
||||
_directory.AdvancePositionAuthority(canonical);
|
||||
ClearWorldCell(canonical);
|
||||
ParentAttachments.EndChildProjection(update.Guid);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
=> _entityObjects.TryApplyPickup(
|
||||
update,
|
||||
AcknowledgeCelllessCanonicalCommit,
|
||||
out accepted);
|
||||
|
||||
public bool TryApplyCreateParent(CreateParentUpdate update, out WorldSession.EntitySpawn accepted)
|
||||
{
|
||||
bool applied = _directory.TryApplyCreateParent(update, out accepted);
|
||||
if (applied
|
||||
&& _directory.TryGetActive(
|
||||
update.ChildGuid,
|
||||
out RuntimeEntityRecord canonical))
|
||||
{
|
||||
_directory.RefreshSnapshot(canonical, accepted);
|
||||
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
|
||||
record.AdvancePositionAuthority();
|
||||
else
|
||||
_directory.AdvancePositionAuthority(canonical);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
=> _entityObjects.TryApplyCreateParent(
|
||||
update,
|
||||
acknowledgeProjection: null,
|
||||
out accepted);
|
||||
|
||||
public bool TryApplyParent(ParentEvent.Parsed update, out WorldSession.EntitySpawn accepted)
|
||||
{
|
||||
bool applied = _directory.TryApplyParent(update, out accepted);
|
||||
if (applied
|
||||
&& _directory.TryGetActive(
|
||||
update.ChildGuid,
|
||||
out RuntimeEntityRecord canonical))
|
||||
{
|
||||
_directory.RefreshSnapshot(canonical, accepted);
|
||||
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
|
||||
record.AdvancePositionAuthority();
|
||||
else
|
||||
_directory.AdvancePositionAuthority(canonical);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
=> _entityObjects.TryApplyParent(
|
||||
update,
|
||||
acknowledgeProjection: null,
|
||||
out accepted);
|
||||
|
||||
internal bool CommitStagedParent(
|
||||
ParentAttachmentRelation relation,
|
||||
out WorldSession.EntitySpawn accepted)
|
||||
{
|
||||
bool committed = _directory.TryCommitParent(
|
||||
relation.ChildGuid,
|
||||
relation.ParentGuid,
|
||||
relation.ParentLocation,
|
||||
relation.PlacementId,
|
||||
relation.ChildPositionSequence,
|
||||
=> _entityObjects.TryCommitParent(
|
||||
relation,
|
||||
acknowledgeProjection: null,
|
||||
out accepted);
|
||||
if (committed
|
||||
&& _directory.TryGetActive(
|
||||
relation.ChildGuid,
|
||||
out RuntimeEntityRecord canonical))
|
||||
{
|
||||
_directory.RefreshSnapshot(canonical, accepted);
|
||||
}
|
||||
return committed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commits retail <c>CPhysicsObj::set_parent</c>'s cell-less edge only
|
||||
|
|
@ -1973,25 +1792,20 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
ulong positionAuthorityVersion)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
if (!IsCurrentPositionAuthority(record, positionAuthorityVersion))
|
||||
return false;
|
||||
ClearWorldCell(record.ServerGuid);
|
||||
return IsCurrentPositionAuthority(record, positionAuthorityVersion);
|
||||
return _entityObjects.CommitAcceptedParentCellless(
|
||||
record.Canonical,
|
||||
positionAuthorityVersion,
|
||||
AcknowledgeCelllessCanonicalCommit);
|
||||
}
|
||||
|
||||
internal bool CommitAcceptedParentCellless(
|
||||
RuntimeEntityRecord canonical,
|
||||
ulong positionAuthorityVersion)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(canonical);
|
||||
if (!_directory.IsCurrent(canonical)
|
||||
|| canonical.PositionAuthorityVersion != positionAuthorityVersion)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ClearWorldCell(canonical);
|
||||
return _directory.IsCurrent(canonical)
|
||||
&& canonical.PositionAuthorityVersion == positionAuthorityVersion;
|
||||
return _entityObjects.CommitAcceptedParentCellless(
|
||||
canonical,
|
||||
positionAuthorityVersion,
|
||||
AcknowledgeCelllessCanonicalCommit);
|
||||
}
|
||||
|
||||
public bool TryApplyMotion(
|
||||
|
|
@ -1999,43 +1813,18 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
bool retainPayload,
|
||||
out WorldSession.EntitySpawn accepted,
|
||||
out AcceptedPhysicsTimestamps timestamps)
|
||||
{
|
||||
bool applied = _directory.TryApplyMotion(update, retainPayload, out accepted, out timestamps);
|
||||
if (_directory.TryGetSnapshot(update.Guid, out WorldSession.EntitySpawn snapshot)
|
||||
&& _directory.TryGetActive(
|
||||
update.Guid,
|
||||
out RuntimeEntityRecord canonical))
|
||||
{
|
||||
_directory.RefreshSnapshot(canonical, snapshot);
|
||||
}
|
||||
if (applied
|
||||
&& retainPayload
|
||||
&& _directory.TryGetActive(update.Guid, out canonical))
|
||||
{
|
||||
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
|
||||
record.AdvanceMovementAuthority();
|
||||
else
|
||||
_directory.AdvanceMovementAuthority(canonical);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
=> _entityObjects.TryApplyMotion(
|
||||
update,
|
||||
retainPayload,
|
||||
acknowledgeProjection: null,
|
||||
out accepted,
|
||||
out timestamps);
|
||||
|
||||
public bool TryApplyVector(VectorUpdate.Parsed update, out WorldSession.EntitySpawn accepted)
|
||||
{
|
||||
bool applied = _directory.TryApplyVector(update, out accepted);
|
||||
if (applied
|
||||
&& _directory.TryGetActive(
|
||||
update.Guid,
|
||||
out RuntimeEntityRecord canonical))
|
||||
{
|
||||
_directory.RefreshSnapshot(canonical, accepted);
|
||||
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
|
||||
record.AdvanceVectorAuthority();
|
||||
else
|
||||
_directory.AdvanceVectorAuthority(canonical);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
=> _entityObjects.TryApplyVector(
|
||||
update,
|
||||
acknowledgeProjection: null,
|
||||
out accepted);
|
||||
|
||||
public bool TryApplyState(SetState.Parsed update, out WorldSession.EntitySpawn accepted)
|
||||
=> TryApplyState(update, out accepted, out _);
|
||||
|
|
@ -2044,23 +1833,19 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
SetState.Parsed update,
|
||||
out WorldSession.EntitySpawn accepted,
|
||||
out RetailPhysicsStateTransition transition)
|
||||
{
|
||||
bool applied = _directory.TryApplyState(update, out accepted);
|
||||
transition = default;
|
||||
if (applied
|
||||
&& _directory.TryGetActive(
|
||||
update.Guid,
|
||||
out RuntimeEntityRecord canonical))
|
||||
{
|
||||
_directory.RefreshSnapshot(canonical, accepted);
|
||||
transition = _directory.ApplyRawPhysicsState(
|
||||
canonical,
|
||||
update.PhysicsState);
|
||||
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
|
||||
RefreshPresentation(record);
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
=> _entityObjects.TryApplyState(
|
||||
update,
|
||||
(canonical, _) =>
|
||||
{
|
||||
if (_projections.TryGet(
|
||||
canonical,
|
||||
out LiveEntityRecord? record))
|
||||
{
|
||||
RefreshPresentation(record);
|
||||
}
|
||||
},
|
||||
out accepted,
|
||||
out transition);
|
||||
|
||||
/// <summary>
|
||||
/// Retail parent Hidden directly toggles each attached child's NoDraw bit
|
||||
|
|
@ -2070,9 +1855,10 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
{
|
||||
if (!_projections.TryGetCurrent(childServerGuid, out LiveEntityRecord? record))
|
||||
return false;
|
||||
record.SetChildNoDraw(noDraw);
|
||||
RefreshPresentation(record);
|
||||
return true;
|
||||
return _entityObjects.CommitChildNoDraw(
|
||||
record.Canonical,
|
||||
noDraw,
|
||||
_ => RefreshPresentation(record));
|
||||
}
|
||||
|
||||
public bool TryApplyPosition(
|
||||
|
|
@ -2084,62 +1870,26 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
out WorldSession.EntitySpawn accepted,
|
||||
out AcceptedPhysicsTimestamps timestamps)
|
||||
{
|
||||
bool hadCanonical = _directory.TryGetActive(
|
||||
update.Guid,
|
||||
out RuntimeEntityRecord? beforeCanonical);
|
||||
LiveEntityRecord? beforePosition = null;
|
||||
if (hadCanonical)
|
||||
_projections.TryGet(beforeCanonical!, out beforePosition);
|
||||
bool wasCellLess = hadCanonical
|
||||
&& (beforeCanonical!.FullCellId == 0
|
||||
|| beforePosition is null
|
||||
|| !beforePosition.IsSpatiallyProjected
|
||||
|| !beforePosition.IsSpatiallyVisible);
|
||||
bool known = _directory.TryApplyPosition(
|
||||
bool projectionRequiresTeleportHook =
|
||||
_directory.TryGetActive(
|
||||
update.Guid,
|
||||
out RuntimeEntityRecord canonical)
|
||||
&& (canonical.FullCellId == 0u
|
||||
|| !_projections.TryGet(
|
||||
canonical,
|
||||
out LiveEntityRecord? projection)
|
||||
|| !projection.IsSpatiallyProjected
|
||||
|| !projection.IsSpatiallyVisible);
|
||||
return _entityObjects.TryApplyPosition(
|
||||
update,
|
||||
isLocalPlayer,
|
||||
forcePositionRotation,
|
||||
currentLocalVelocity,
|
||||
projectionRequiresTeleportHook,
|
||||
acknowledgeProjection: null,
|
||||
out disposition,
|
||||
out accepted,
|
||||
out timestamps);
|
||||
if (known && _directory.TryGetSnapshot(update.Guid, out WorldSession.EntitySpawn snapshot))
|
||||
{
|
||||
bool acceptedPosition = disposition is not PositionTimestampDisposition.Rejected;
|
||||
if (disposition is PositionTimestampDisposition.Apply)
|
||||
{
|
||||
timestamps = timestamps with
|
||||
{
|
||||
TeleportHookRequired = timestamps.TeleportAdvanced || wasCellLess,
|
||||
};
|
||||
}
|
||||
if (_directory.TryGetActive(
|
||||
update.Guid,
|
||||
out RuntimeEntityRecord acceptedCanonical))
|
||||
{
|
||||
_directory.RefreshSnapshot(
|
||||
acceptedCanonical,
|
||||
snapshot,
|
||||
refreshPosition: acceptedPosition);
|
||||
if (acceptedPosition
|
||||
&& ReferenceEquals(acceptedCanonical, beforeCanonical))
|
||||
{
|
||||
if (_projections.TryGet(
|
||||
acceptedCanonical,
|
||||
out LiveEntityRecord? acceptedRecord)
|
||||
&& ReferenceEquals(acceptedRecord, beforePosition))
|
||||
{
|
||||
acceptedRecord.AdvancePositionAuthority();
|
||||
}
|
||||
else
|
||||
{
|
||||
_directory.AdvancePositionAuthority(acceptedCanonical);
|
||||
}
|
||||
ParentAttachments.EndChildProjection(update.Guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
return known;
|
||||
}
|
||||
|
||||
public bool IsFreshTeleportStart(uint localPlayerGuid, ushort teleportSequence) =>
|
||||
|
|
@ -2669,15 +2419,13 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
|
||||
_isClearing = true;
|
||||
_sessionClearPendingFinalization = true;
|
||||
_directory.BeginSessionClear();
|
||||
IReadOnlyList<RuntimeEntityRecord> retiredCanonicals =
|
||||
_entityObjects.BeginSessionClear();
|
||||
List<Exception>? failures = null;
|
||||
try
|
||||
{
|
||||
foreach (RuntimeEntityRecord canonical in
|
||||
_directory.ActiveRecords.ToArray())
|
||||
foreach (RuntimeEntityRecord canonical in retiredCanonicals)
|
||||
{
|
||||
if (!_directory.RemoveActive(canonical))
|
||||
continue;
|
||||
if (_projections.TryGet(
|
||||
canonical,
|
||||
out LiveEntityRecord? record))
|
||||
|
|
@ -2796,12 +2544,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
_directory.RefreshSnapshot(canonical, accepted, refreshPosition);
|
||||
}
|
||||
|
||||
private static InboundCreateResult SupersededCreateResult() => new(
|
||||
CreateObjectTimestampDisposition.StaleGeneration,
|
||||
default,
|
||||
null,
|
||||
default);
|
||||
|
||||
private ulong AdvanceLifetimeMutation(uint serverGuid)
|
||||
=> _directory.AdvanceLifetimeMutation(serverGuid);
|
||||
|
||||
|
|
@ -2841,37 +2583,18 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
|
||||
private void ClearWorldCell(uint guid)
|
||||
private void AcknowledgeCelllessCanonicalCommit(
|
||||
RuntimeEntityRecord canonical)
|
||||
{
|
||||
if (!_directory.TryGetActive(guid, out RuntimeEntityRecord canonical))
|
||||
if (!_directory.IsCurrent(canonical)
|
||||
|| !_projections.TryGet(
|
||||
canonical,
|
||||
out LiveEntityRecord? record))
|
||||
{
|
||||
return;
|
||||
ClearWorldCell(canonical);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearWorldCell(RuntimeEntityRecord canonical)
|
||||
{
|
||||
if (!_directory.IsCurrent(canonical))
|
||||
return;
|
||||
if (!_projections.TryGet(canonical, out LiveEntityRecord? record))
|
||||
{
|
||||
_directory.SetFullCell(canonical, 0, 0);
|
||||
return;
|
||||
}
|
||||
bool wasOrdinaryRoot = record.ProjectionKey is { } key
|
||||
&& _spatialRootObjects.TryGetValue(
|
||||
key,
|
||||
out LiveEntityRecord? indexedRoot)
|
||||
&& ReferenceEquals(indexedRoot, record);
|
||||
// Pickup/Parent is retail's leave-world edge. Suspend the canonical
|
||||
// object clock while the record is still indexed as a root; clearing
|
||||
// FullCellId first removes that evidence and makes the later projection
|
||||
// withdrawal look like a duplicate non-root edge.
|
||||
if (wasOrdinaryRoot)
|
||||
{
|
||||
record.SuspendObjectClock();
|
||||
SynchronizePhysicsBodyActiveState(record);
|
||||
}
|
||||
_directory.SetFullCell(canonical, 0, 0);
|
||||
SynchronizePhysicsBodyActiveState(record);
|
||||
RefreshSpatialRuntimeIndexes(record);
|
||||
}
|
||||
|
||||
|
|
@ -3091,27 +2814,50 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
_projections.RetainTeardown(record);
|
||||
}
|
||||
|
||||
private void TearDownCanonicalOnly(RuntimeEntityRecord canonical)
|
||||
private Exception? RetirePriorProjection(
|
||||
RuntimeEntityRecord canonical)
|
||||
{
|
||||
// A canonical-only incarnation has never crossed the App projection
|
||||
// acquisition edge, so it owns no renderer/effect/animation suffix.
|
||||
// Retain it briefly only so Runtime's validated mutators can converge
|
||||
// any presentation-free physics state before the tombstone vanishes.
|
||||
_directory.RetainTeardown(canonical);
|
||||
if (!_projections.TryGet(
|
||||
canonical,
|
||||
out LiveEntityRecord? projection))
|
||||
{
|
||||
return _entityObjects.RetireCanonicalOnly(canonical);
|
||||
}
|
||||
|
||||
if (!_projections.RemoveActive(projection))
|
||||
{
|
||||
return new InvalidOperationException(
|
||||
$"Exact App projection for 0x{canonical.ServerGuid:X8}/{canonical.Incarnation} could not be retired.");
|
||||
}
|
||||
|
||||
RetainTeardownRecord(projection);
|
||||
_logicalTeardownDepth++;
|
||||
try
|
||||
{
|
||||
_directory.SetPhysicsHost(canonical, null);
|
||||
_directory.SetPhysicsBody(canonical, null);
|
||||
_directory.SetPhysicsBodyAcquisitionInProgress(canonical, false);
|
||||
_directory.SetHasPartArray(canonical, false);
|
||||
_directory.ReleaseLocalId(canonical);
|
||||
try
|
||||
{
|
||||
TearDownRecord(projection);
|
||||
ReleaseTeardownRecord(projection);
|
||||
return null;
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_directory.ReleaseTeardown(canonical);
|
||||
_logicalTeardownDepth--;
|
||||
}
|
||||
}
|
||||
|
||||
private void TearDownCanonicalOnly(RuntimeEntityRecord canonical)
|
||||
{
|
||||
Exception? failure = _entityObjects.RetireCanonicalOnly(canonical);
|
||||
if (failure is not null)
|
||||
ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
|
||||
private void ReleaseTeardownRecord(LiveEntityRecord record)
|
||||
{
|
||||
_projections.ReleaseTeardown(record);
|
||||
|
|
@ -3125,7 +2871,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
private void CompleteSessionClearIfConverged()
|
||||
{
|
||||
if (!_sessionClearPendingFinalization
|
||||
|| !_directory.CompleteSessionClearIfConverged())
|
||||
|| !_entityObjects.CompleteSessionClearIfConverged())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue