refactor(runtime): own canonical entity identity and incarnations
Introduce the presentation-free RuntimeEntityDirectory and RuntimeEntityRecord as the sole owners of server GUIDs, INSTANCE_TS incarnations, accepted snapshots, authority versions, retryable tombstones, session epochs, and local-ID allocation. Convert LiveEntityRuntime into an App projection sidecar host resolved through canonical record identity without a second GUID map. Preserve the existing synchronous and reentrant projection lifecycle, including retryable registration/delete/session teardown. Add Runtime-only identity/lifetime tests and App ownership guards. Validated by the Release solution build and 8,441 complete Release tests with five existing skips. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
f7442d13e9
commit
f46ddb5cdb
5 changed files with 1218 additions and 225 deletions
160
src/AcDream.Runtime/Entities/RuntimeEntityRecord.cs
Normal file
160
src/AcDream.Runtime/Entities/RuntimeEntityRecord.cs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
using AcDream.Core.Net;
|
||||
using AcDream.Core.Physics;
|
||||
|
||||
namespace AcDream.Runtime.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Stable identity issued by the canonical runtime directory. The local id is
|
||||
/// claimed when a graphical host first materializes the incarnation; a
|
||||
/// no-window host may claim it immediately.
|
||||
/// </summary>
|
||||
public readonly record struct RuntimeEntityKey(
|
||||
uint LocalEntityId,
|
||||
ushort Incarnation);
|
||||
|
||||
/// <summary>
|
||||
/// Presentation-free canonical state for one accepted server-object
|
||||
/// incarnation. App projection, animation, effects, visibility, and hydration
|
||||
/// state must never be stored here.
|
||||
/// </summary>
|
||||
public sealed class RuntimeEntityRecord
|
||||
{
|
||||
private readonly Queue<RetailPhysicsStateTransition> _pendingStateTransitions = new();
|
||||
|
||||
internal RuntimeEntityRecord(WorldSession.EntitySpawn snapshot)
|
||||
{
|
||||
ServerGuid = snapshot.Guid;
|
||||
Snapshot = snapshot;
|
||||
RefreshDerivedState();
|
||||
PositionAuthorityVersion = snapshot.Position is null ? 0UL : 1UL;
|
||||
VectorAuthorityVersion = snapshot.Physics is null ? 0UL : 1UL;
|
||||
VelocityAuthorityVersion = snapshot.Position is null
|
||||
&& snapshot.Physics is null
|
||||
? 0UL
|
||||
: 1UL;
|
||||
MovementAuthorityVersion = 1UL;
|
||||
ObjDescAuthorityVersion = 1UL;
|
||||
FinalPhysicsState = RetailPhysicsStateTransitions.ConstructorState;
|
||||
ApplyRawPhysicsState(RawPhysicsState);
|
||||
}
|
||||
|
||||
public uint ServerGuid { get; }
|
||||
public ushort Incarnation => Snapshot.InstanceSequence;
|
||||
public ushort Generation => Incarnation;
|
||||
public WorldSession.EntitySpawn Snapshot { get; internal set; }
|
||||
public uint? LocalEntityId { get; internal set; }
|
||||
public RuntimeEntityKey? Key => LocalEntityId is { } localId
|
||||
? new RuntimeEntityKey(localId, Incarnation)
|
||||
: null;
|
||||
public uint FullCellId { get; internal set; }
|
||||
public uint CanonicalLandblockId { get; internal set; }
|
||||
public uint RawPhysicsState { get; internal set; }
|
||||
public PhysicsStateFlags FinalPhysicsState { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Incarnation-stable retail <c>CPhysicsObj::update_time</c> owner.
|
||||
/// </summary>
|
||||
public RetailObjectQuantumClock ObjectClock { get; } = new();
|
||||
|
||||
public ulong ObjectClockEpoch { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True after this incarnation has successfully constructed its retail
|
||||
/// <c>CPartArray</c>. This is a simulation/lifetime fact, not a renderer
|
||||
/// object reference.
|
||||
/// </summary>
|
||||
public bool HasPartArray { get; internal set; }
|
||||
public PhysicsBody? PhysicsBody { get; internal set; }
|
||||
public bool PhysicsBodyAcquisitionInProgress { get; internal set; }
|
||||
public AcDream.Core.Physics.Motion.IPhysicsObjHost? PhysicsHost { get; internal set; }
|
||||
public ulong PositionAuthorityVersion { get; private set; }
|
||||
public ulong StateAuthorityVersion { get; private set; }
|
||||
public ulong VectorAuthorityVersion { get; private set; }
|
||||
public ulong VelocityAuthorityVersion { get; private set; }
|
||||
public ulong MovementAuthorityVersion { get; private set; }
|
||||
public ulong ObjDescAuthorityVersion { get; private set; }
|
||||
public ulong CreateIntegrationVersion { get; private set; } = 1UL;
|
||||
public bool DeleteAcceptedForTeardown { get; internal set; }
|
||||
|
||||
internal bool TryDequeueStateTransition(out RetailPhysicsStateTransition transition) =>
|
||||
_pendingStateTransitions.TryDequeue(out transition);
|
||||
|
||||
internal void SuspendObjectClock()
|
||||
{
|
||||
ObjectClock.Deactivate();
|
||||
ObjectClockEpoch++;
|
||||
}
|
||||
|
||||
internal void ResetObjectClockForEnterWorld(bool isStatic)
|
||||
{
|
||||
ObjectClock.ResetForEnterWorld(isStatic);
|
||||
ObjectClockEpoch++;
|
||||
}
|
||||
|
||||
internal RetailPhysicsStateTransition ApplyRawPhysicsState(uint rawState)
|
||||
{
|
||||
StateAuthorityVersion++;
|
||||
RawPhysicsState = rawState;
|
||||
RetailPhysicsStateTransition transition = RetailPhysicsStateTransitions.Apply(
|
||||
FinalPhysicsState,
|
||||
(PhysicsStateFlags)rawState);
|
||||
FinalPhysicsState = transition.FinalState;
|
||||
if (PhysicsBody is not null)
|
||||
PhysicsBody.State = FinalPhysicsState;
|
||||
_pendingStateTransitions.Enqueue(transition);
|
||||
return transition;
|
||||
}
|
||||
|
||||
internal void AdvancePositionAuthority()
|
||||
{
|
||||
PositionAuthorityVersion++;
|
||||
VelocityAuthorityVersion++;
|
||||
}
|
||||
|
||||
internal void AdvanceVectorAuthority()
|
||||
{
|
||||
VectorAuthorityVersion++;
|
||||
VelocityAuthorityVersion++;
|
||||
}
|
||||
|
||||
internal void AdvanceMovementAuthority()
|
||||
{
|
||||
MovementAuthorityVersion++;
|
||||
VelocityAuthorityVersion++;
|
||||
}
|
||||
|
||||
internal void AdvanceObjDescAuthority() => ObjDescAuthorityVersion++;
|
||||
|
||||
internal void AdvanceCreateAuthority()
|
||||
{
|
||||
PositionAuthorityVersion++;
|
||||
StateAuthorityVersion++;
|
||||
VectorAuthorityVersion++;
|
||||
VelocityAuthorityVersion++;
|
||||
MovementAuthorityVersion++;
|
||||
ObjDescAuthorityVersion++;
|
||||
CreateIntegrationVersion++;
|
||||
}
|
||||
|
||||
internal void SetChildNoDraw(bool noDraw)
|
||||
{
|
||||
FinalPhysicsState = noDraw
|
||||
? FinalPhysicsState | PhysicsStateFlags.NoDraw
|
||||
: FinalPhysicsState & ~PhysicsStateFlags.NoDraw;
|
||||
if (PhysicsBody is not null)
|
||||
PhysicsBody.State = FinalPhysicsState;
|
||||
}
|
||||
|
||||
internal void RefreshDerivedState(bool refreshPosition = true)
|
||||
{
|
||||
if (refreshPosition && Snapshot.Position is { } position)
|
||||
{
|
||||
FullCellId = position.LandblockId;
|
||||
CanonicalLandblockId = (FullCellId & 0xFFFF0000u) | 0xFFFFu;
|
||||
}
|
||||
|
||||
RawPhysicsState = Snapshot.Physics?.RawState
|
||||
?? Snapshot.PhysicsState
|
||||
?? 0u;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue