refactor(world): extract live appearance and parenting

Move ObjDesc, Parent, Pickup, child-ready, and retained projection recovery into LiveEntityHydrationController while preserving retail timestamp and leave-world semantics. Pin ready publication to exact projection authority and restore attached descendant chains synchronously.
This commit is contained in:
Erik 2026-07-21 18:10:07 +02:00
parent 40352d5a7a
commit fe5514967c
9 changed files with 1098 additions and 199 deletions

View file

@ -201,6 +201,7 @@ public sealed class LiveEntityRecord
? 0UL
: 1UL;
MovementAuthorityVersion = 1UL;
ObjDescAuthorityVersion = 1UL;
FinalPhysicsState = RetailPhysicsStateTransitions.ConstructorState;
ApplyRawPhysicsState(RawPhysicsState);
}
@ -258,6 +259,7 @@ public sealed class LiveEntityRecord
internal ulong VectorAuthorityVersion { get; private set; }
internal ulong VelocityAuthorityVersion { get; private set; }
internal ulong MovementAuthorityVersion { get; private set; }
internal ulong ObjDescAuthorityVersion { get; private set; }
/// <summary>
/// Advances for every accepted CreateObject affecting this incarnation,
/// including a fresher same-INSTANCE_TS retransmit. App integration uses
@ -284,6 +286,15 @@ public sealed class LiveEntityRecord
internal bool ProjectionHydrationInProgress { get; set; }
internal ulong ProjectionHydrationCreateVersion { get; set; }
internal bool ProjectionHydrationRetryRequested { get; set; }
/// <summary>
/// A renderer mesh transaction can be re-entered by a newer accepted
/// ObjDesc. Keep that independent projection obligation on the exact
/// incarnation until the newest visual description publishes.
/// </summary>
internal bool AppearanceProjectionSynchronizationPending { get; set; }
internal bool AppearanceHydrationInProgress { get; set; }
internal ulong AppearanceHydrationVersion { get; set; }
internal bool AppearanceHydrationRetryRequested { get; set; }
public LiveEntityProjectionKind ProjectionKind { get; internal set; }
internal bool RuntimeComponentsTeardownCompleted { get; set; }
internal LiveEntityTeardownPlan? RuntimeComponentTeardownPlan { get; set; }
@ -338,6 +349,14 @@ public sealed class LiveEntityRecord
VelocityAuthorityVersion++;
}
internal void AdvanceObjDescAuthority()
{
ObjDescAuthorityVersion++;
AppearanceProjectionSynchronizationPending = true;
if (AppearanceHydrationInProgress)
AppearanceHydrationRetryRequested = true;
}
internal void AdvanceCreateAuthority()
{
PositionAuthorityVersion++;
@ -345,6 +364,7 @@ public sealed class LiveEntityRecord
VectorAuthorityVersion++;
VelocityAuthorityVersion++;
MovementAuthorityVersion++;
ObjDescAuthorityVersion++;
CreateIntegrationVersion++;
}
@ -1547,7 +1567,12 @@ public sealed class LiveEntityRuntime
public bool TryApplyObjDesc(ObjDescEvent.Parsed update, out WorldSession.EntitySpawn accepted)
{
bool applied = _inbound.TryApplyObjDesc(update, out accepted);
if (applied) RefreshRecord(update.Guid, accepted);
if (applied)
{
RefreshRecord(update.Guid, accepted);
if (_recordsByGuid.TryGetValue(update.Guid, out LiveEntityRecord? record))
record.AdvanceObjDescAuthority();
}
return applied;
}
@ -1805,6 +1830,13 @@ public sealed class LiveEntityRuntime
&& ReferenceEquals(current, record)
&& current.MovementAuthorityVersion == authorityVersion;
internal bool IsCurrentObjDescAuthority(
LiveEntityRecord record,
ulong authorityVersion) =>
_recordsByGuid.TryGetValue(record.ServerGuid, out LiveEntityRecord? current)
&& ReferenceEquals(current, record)
&& current.ObjDescAuthorityVersion == authorityVersion;
/// <summary>
/// Copies the canonical loaded root-object workset used by retail
/// <c>CPhysics::UseTime</c>. The record reference is the incarnation token;
@ -2156,6 +2188,63 @@ public sealed class LiveEntityRuntime
return retry;
}
internal bool TryBeginAppearanceHydration(
LiveEntityRecord expectedRecord,
ulong expectedObjDescAuthorityVersion)
{
ArgumentNullException.ThrowIfNull(expectedRecord);
if (!expectedRecord.AppearanceProjectionSynchronizationPending
|| !IsCurrentObjDescAuthority(
expectedRecord,
expectedObjDescAuthorityVersion))
{
return false;
}
expectedRecord.AppearanceProjectionSynchronizationPending = true;
if (expectedRecord.AppearanceHydrationInProgress)
{
expectedRecord.AppearanceHydrationRetryRequested = true;
return false;
}
expectedRecord.AppearanceHydrationInProgress = true;
expectedRecord.AppearanceHydrationVersion =
expectedObjDescAuthorityVersion;
expectedRecord.AppearanceHydrationRetryRequested = false;
return true;
}
internal bool EndAppearanceHydration(LiveEntityRecord expectedRecord)
{
ArgumentNullException.ThrowIfNull(expectedRecord);
bool retry = IsCurrentRecord(expectedRecord)
&& (expectedRecord.AppearanceHydrationRetryRequested
|| expectedRecord.ObjDescAuthorityVersion
!= expectedRecord.AppearanceHydrationVersion);
expectedRecord.AppearanceHydrationInProgress = false;
expectedRecord.AppearanceHydrationVersion = 0UL;
expectedRecord.AppearanceHydrationRetryRequested = false;
return retry;
}
internal bool TryCompleteAppearanceProjectionSynchronization(
LiveEntityRecord expectedRecord,
ulong expectedObjDescAuthorityVersion)
{
ArgumentNullException.ThrowIfNull(expectedRecord);
if (!expectedRecord.AppearanceProjectionSynchronizationPending
|| !IsCurrentObjDescAuthority(
expectedRecord,
expectedObjDescAuthorityVersion))
{
return false;
}
expectedRecord.AppearanceProjectionSynchronizationPending = false;
return true;
}
internal bool IsCurrentCreateIntegration(
LiveEntityRecord expectedRecord,
ulong expectedCreateIntegrationVersion) =>
@ -2722,6 +2811,10 @@ public sealed class LiveEntityRuntime
record.ProjectionHydrationInProgress = false;
record.ProjectionHydrationCreateVersion = 0UL;
record.ProjectionHydrationRetryRequested = false;
record.AppearanceProjectionSynchronizationPending = false;
record.AppearanceHydrationInProgress = false;
record.AppearanceHydrationVersion = 0UL;
record.AppearanceHydrationRetryRequested = false;
record.WorldEntity = null;
}
finally