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
428
src/AcDream.Runtime/Entities/RuntimeEntityDirectory.cs
Normal file
428
src/AcDream.Runtime/Entities/RuntimeEntityDirectory.cs
Normal file
|
|
@ -0,0 +1,428 @@
|
|||
using AcDream.Core.Net;
|
||||
using AcDream.Core.Physics;
|
||||
|
||||
namespace AcDream.Runtime.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// The single update-thread authority for live server GUIDs, incarnations,
|
||||
/// accepted wire snapshots, teardown tombstones, and runtime-local identity.
|
||||
/// It owns no graphical state and can run without loading App or a backend.
|
||||
/// </summary>
|
||||
public sealed class RuntimeEntityDirectory
|
||||
{
|
||||
public const uint FirstLocalEntityId = 1_000_000u;
|
||||
public const uint LastLocalEntityId = 0x3FFF_FFFFu;
|
||||
|
||||
private readonly InboundPhysicsStateController _inbound = new();
|
||||
private readonly Dictionary<uint, RuntimeEntityRecord> _activeByGuid = new();
|
||||
private readonly Dictionary<(uint Guid, ushort Incarnation), RuntimeEntityRecord>
|
||||
_teardownByIncarnation = new();
|
||||
private readonly Dictionary<uint, RuntimeEntityRecord> _byLocalId = new();
|
||||
private readonly Dictionary<uint, ulong> _lifetimeMutationByGuid = new();
|
||||
private uint _nextLocalEntityId;
|
||||
|
||||
public RuntimeEntityDirectory(uint firstLocalEntityId = FirstLocalEntityId)
|
||||
{
|
||||
if (firstLocalEntityId is < FirstLocalEntityId or > LastLocalEntityId)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(firstLocalEntityId),
|
||||
$"Live entity ids must stay in 0x{FirstLocalEntityId:X8}..0x{LastLocalEntityId:X8}.");
|
||||
}
|
||||
|
||||
_nextLocalEntityId = firstLocalEntityId;
|
||||
}
|
||||
|
||||
public int Count => _activeByGuid.Count;
|
||||
public int PendingTeardownCount => _teardownByIncarnation.Count;
|
||||
public int ClaimedLocalIdCount => _byLocalId.Count;
|
||||
public ulong SessionLifetimeVersion { get; private set; }
|
||||
public IReadOnlyCollection<RuntimeEntityRecord> ActiveRecords => _activeByGuid.Values;
|
||||
public IReadOnlyCollection<RuntimeEntityRecord> TeardownRecords =>
|
||||
_teardownByIncarnation.Values;
|
||||
public IReadOnlyDictionary<uint, WorldSession.EntitySpawn> Snapshots => _inbound.Snapshots;
|
||||
public ParentAttachmentState ParentAttachments { get; } = new();
|
||||
|
||||
public InboundCreateResult AcceptCreate(WorldSession.EntitySpawn incoming) =>
|
||||
_inbound.AcceptCreate(incoming);
|
||||
|
||||
public bool TryDelete(
|
||||
AcDream.Core.Net.Messages.DeleteObject.Parsed delete,
|
||||
bool isLocalPlayer) =>
|
||||
_inbound.TryDelete(delete, isLocalPlayer);
|
||||
|
||||
public bool TryGetSnapshot(uint guid, out WorldSession.EntitySpawn spawn) =>
|
||||
_inbound.TryGetSnapshot(guid, out spawn);
|
||||
|
||||
public bool TryGetActive(uint guid, out RuntimeEntityRecord record) =>
|
||||
_activeByGuid.TryGetValue(guid, out record!);
|
||||
|
||||
public bool IsCurrent(RuntimeEntityRecord record) =>
|
||||
_activeByGuid.TryGetValue(record.ServerGuid, out RuntimeEntityRecord? current)
|
||||
&& ReferenceEquals(current, record);
|
||||
|
||||
public bool TryGetByLocalId(uint localEntityId, out RuntimeEntityRecord record) =>
|
||||
_byLocalId.TryGetValue(localEntityId, out record!);
|
||||
|
||||
public bool TryGetTeardown(
|
||||
uint guid,
|
||||
ushort incarnation,
|
||||
out RuntimeEntityRecord record) =>
|
||||
_teardownByIncarnation.TryGetValue((guid, incarnation), out record!);
|
||||
|
||||
public RuntimeEntityRecord AddActive(WorldSession.EntitySpawn snapshot)
|
||||
{
|
||||
if (_activeByGuid.ContainsKey(snapshot.Guid))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{snapshot.Guid:X8} already has an active incarnation.");
|
||||
}
|
||||
|
||||
var record = new RuntimeEntityRecord(snapshot);
|
||||
_activeByGuid.Add(snapshot.Guid, record);
|
||||
return record;
|
||||
}
|
||||
|
||||
public bool RemoveActive(uint guid, out RuntimeEntityRecord? record) =>
|
||||
_activeByGuid.Remove(guid, out record);
|
||||
|
||||
public bool RemoveActive(RuntimeEntityRecord expected)
|
||||
{
|
||||
if (!IsCurrent(expected))
|
||||
return false;
|
||||
return _activeByGuid.Remove(expected.ServerGuid);
|
||||
}
|
||||
|
||||
public void RetainTeardown(RuntimeEntityRecord record)
|
||||
{
|
||||
var key = (record.ServerGuid, record.Incarnation);
|
||||
if (_teardownByIncarnation.TryGetValue(
|
||||
key,
|
||||
out RuntimeEntityRecord? retained)
|
||||
&& !ReferenceEquals(retained, record))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity teardown tombstone collision for 0x{record.ServerGuid:X8} generation {record.Incarnation}.");
|
||||
}
|
||||
|
||||
_teardownByIncarnation[key] = record;
|
||||
}
|
||||
|
||||
public void ReleaseTeardown(RuntimeEntityRecord record)
|
||||
{
|
||||
var key = (record.ServerGuid, record.Incarnation);
|
||||
if (_teardownByIncarnation.TryGetValue(
|
||||
key,
|
||||
out RuntimeEntityRecord? retained)
|
||||
&& ReferenceEquals(retained, record))
|
||||
{
|
||||
_teardownByIncarnation.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPendingTeardown(uint serverGuid)
|
||||
{
|
||||
foreach ((uint Guid, ushort Incarnation) key in _teardownByIncarnation.Keys)
|
||||
{
|
||||
if (key.Guid == serverGuid)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public uint ClaimLocalId(RuntimeEntityRecord record)
|
||||
{
|
||||
if (!IsKnown(record))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A local id can only be claimed for an active or retained incarnation.");
|
||||
}
|
||||
|
||||
if (record.LocalEntityId is { } existing)
|
||||
return existing;
|
||||
|
||||
uint start = _nextLocalEntityId;
|
||||
do
|
||||
{
|
||||
uint candidate = _nextLocalEntityId;
|
||||
_nextLocalEntityId = candidate == LastLocalEntityId
|
||||
? FirstLocalEntityId
|
||||
: candidate + 1u;
|
||||
if (_byLocalId.ContainsKey(candidate))
|
||||
continue;
|
||||
|
||||
_byLocalId.Add(candidate, record);
|
||||
record.LocalEntityId = candidate;
|
||||
return candidate;
|
||||
}
|
||||
while (_nextLocalEntityId != start);
|
||||
|
||||
throw new InvalidOperationException("The live entity id namespace is exhausted.");
|
||||
}
|
||||
|
||||
public bool ReleaseLocalId(RuntimeEntityRecord record)
|
||||
{
|
||||
if (record.LocalEntityId is not { } localId)
|
||||
return false;
|
||||
if (_byLocalId.TryGetValue(localId, out RuntimeEntityRecord? retained)
|
||||
&& ReferenceEquals(retained, record))
|
||||
{
|
||||
_byLocalId.Remove(localId);
|
||||
}
|
||||
|
||||
record.LocalEntityId = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public ulong AdvanceLifetimeMutation(uint serverGuid)
|
||||
{
|
||||
ulong next = _lifetimeMutationByGuid.GetValueOrDefault(serverGuid) + 1UL;
|
||||
_lifetimeMutationByGuid[serverGuid] = next;
|
||||
return next;
|
||||
}
|
||||
|
||||
public ulong CurrentLifetimeMutation(uint serverGuid) =>
|
||||
_lifetimeMutationByGuid.GetValueOrDefault(serverGuid);
|
||||
|
||||
public void BeginSessionClear()
|
||||
{
|
||||
SessionLifetimeVersion++;
|
||||
_lifetimeMutationByGuid.Clear();
|
||||
ParentAttachments.Clear();
|
||||
_inbound.Clear();
|
||||
}
|
||||
|
||||
public bool CompleteSessionClearIfConverged()
|
||||
{
|
||||
if (_activeByGuid.Count != 0 || _teardownByIncarnation.Count != 0)
|
||||
return false;
|
||||
|
||||
_byLocalId.Clear();
|
||||
ParentAttachments.Clear();
|
||||
_inbound.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RefreshSnapshot(
|
||||
RuntimeEntityRecord record,
|
||||
WorldSession.EntitySpawn accepted,
|
||||
bool refreshPosition = false)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.Snapshot = accepted;
|
||||
record.RefreshDerivedState(refreshPosition);
|
||||
}
|
||||
|
||||
public void AdvanceCreateAuthority(RuntimeEntityRecord record)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.AdvanceCreateAuthority();
|
||||
}
|
||||
|
||||
public void AdvancePositionAuthority(RuntimeEntityRecord record)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.AdvancePositionAuthority();
|
||||
}
|
||||
|
||||
public void AdvanceVectorAuthority(RuntimeEntityRecord record)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.AdvanceVectorAuthority();
|
||||
}
|
||||
|
||||
public void AdvanceMovementAuthority(RuntimeEntityRecord record)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.AdvanceMovementAuthority();
|
||||
}
|
||||
|
||||
public void AdvanceObjDescAuthority(RuntimeEntityRecord record)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.AdvanceObjDescAuthority();
|
||||
}
|
||||
|
||||
public RetailPhysicsStateTransition ApplyRawPhysicsState(
|
||||
RuntimeEntityRecord record,
|
||||
uint rawState)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
return record.ApplyRawPhysicsState(rawState);
|
||||
}
|
||||
|
||||
public bool TryDequeueStateTransition(
|
||||
RuntimeEntityRecord record,
|
||||
out RetailPhysicsStateTransition transition)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
return record.TryDequeueStateTransition(out transition);
|
||||
}
|
||||
|
||||
public void SetChildNoDraw(RuntimeEntityRecord record, bool noDraw)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.SetChildNoDraw(noDraw);
|
||||
}
|
||||
|
||||
public void SuspendObjectClock(RuntimeEntityRecord record)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.SuspendObjectClock();
|
||||
}
|
||||
|
||||
public void ResetObjectClockForEnterWorld(RuntimeEntityRecord record, bool isStatic)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.ResetObjectClockForEnterWorld(isStatic);
|
||||
}
|
||||
|
||||
public void SetFullCell(
|
||||
RuntimeEntityRecord record,
|
||||
uint fullCellId,
|
||||
uint canonicalLandblockId)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.FullCellId = fullCellId;
|
||||
record.CanonicalLandblockId = canonicalLandblockId;
|
||||
}
|
||||
|
||||
public void SetFinalPhysicsState(
|
||||
RuntimeEntityRecord record,
|
||||
PhysicsStateFlags state)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.FinalPhysicsState = state;
|
||||
}
|
||||
|
||||
public void SetHasPartArray(RuntimeEntityRecord record, bool value)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.HasPartArray = value;
|
||||
}
|
||||
|
||||
public void SetPhysicsBody(
|
||||
RuntimeEntityRecord record,
|
||||
AcDream.Core.Physics.PhysicsBody? body)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.PhysicsBody = body;
|
||||
}
|
||||
|
||||
public void SetPhysicsBodyAcquisitionInProgress(
|
||||
RuntimeEntityRecord record,
|
||||
bool value)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.PhysicsBodyAcquisitionInProgress = value;
|
||||
}
|
||||
|
||||
public void SetPhysicsHost(
|
||||
RuntimeEntityRecord record,
|
||||
AcDream.Core.Physics.Motion.IPhysicsObjHost? host)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.PhysicsHost = host;
|
||||
}
|
||||
|
||||
public void SetDeleteAcceptedForTeardown(
|
||||
RuntimeEntityRecord record,
|
||||
bool value)
|
||||
{
|
||||
EnsureKnown(record);
|
||||
record.DeleteAcceptedForTeardown = value;
|
||||
}
|
||||
|
||||
public bool TryApplyObjDesc(
|
||||
AcDream.Core.Net.Messages.ObjDescEvent.Parsed update,
|
||||
out WorldSession.EntitySpawn accepted) =>
|
||||
_inbound.TryApplyObjDesc(update, out accepted);
|
||||
|
||||
public bool TryApplyPickup(
|
||||
AcDream.Core.Net.Messages.PickupEvent.Parsed update,
|
||||
out WorldSession.EntitySpawn accepted) =>
|
||||
_inbound.TryApplyPickup(update, out accepted);
|
||||
|
||||
public bool TryApplyCreateParent(
|
||||
CreateParentUpdate update,
|
||||
out WorldSession.EntitySpawn accepted) =>
|
||||
_inbound.TryApplyCreateParent(update, out accepted);
|
||||
|
||||
public bool TryApplyParent(
|
||||
AcDream.Core.Net.Messages.ParentEvent.Parsed update,
|
||||
out WorldSession.EntitySpawn accepted) =>
|
||||
_inbound.TryApplyParent(update, out accepted);
|
||||
|
||||
public bool TryCommitParent(
|
||||
uint childGuid,
|
||||
uint parentGuid,
|
||||
uint parentLocation,
|
||||
uint placementId,
|
||||
ushort positionSequence,
|
||||
out WorldSession.EntitySpawn accepted) =>
|
||||
_inbound.TryCommitParent(
|
||||
childGuid,
|
||||
parentGuid,
|
||||
parentLocation,
|
||||
placementId,
|
||||
positionSequence,
|
||||
out accepted);
|
||||
|
||||
public bool TryApplyMotion(
|
||||
WorldSession.EntityMotionUpdate update,
|
||||
bool retainPayload,
|
||||
out WorldSession.EntitySpawn accepted,
|
||||
out AcceptedPhysicsTimestamps timestamps) =>
|
||||
_inbound.TryApplyMotion(update, retainPayload, out accepted, out timestamps);
|
||||
|
||||
public bool TryApplyVector(
|
||||
AcDream.Core.Net.Messages.VectorUpdate.Parsed update,
|
||||
out WorldSession.EntitySpawn accepted) =>
|
||||
_inbound.TryApplyVector(update, out accepted);
|
||||
|
||||
public bool TryApplyState(
|
||||
AcDream.Core.Net.Messages.SetState.Parsed update,
|
||||
out WorldSession.EntitySpawn accepted) =>
|
||||
_inbound.TryApplyState(update, out accepted);
|
||||
|
||||
public bool TryApplyPosition(
|
||||
WorldSession.EntityPositionUpdate update,
|
||||
bool isLocalPlayer,
|
||||
System.Numerics.Quaternion? forcePositionRotation,
|
||||
System.Numerics.Vector3? currentLocalVelocity,
|
||||
out PositionTimestampDisposition disposition,
|
||||
out WorldSession.EntitySpawn accepted,
|
||||
out AcceptedPhysicsTimestamps timestamps) =>
|
||||
_inbound.TryApplyPosition(
|
||||
update,
|
||||
isLocalPlayer,
|
||||
forcePositionRotation,
|
||||
currentLocalVelocity,
|
||||
out disposition,
|
||||
out accepted,
|
||||
out timestamps);
|
||||
|
||||
public bool IsFreshTeleportStart(uint guid, ushort teleportSequence) =>
|
||||
_inbound.IsFreshTeleportStart(guid, teleportSequence);
|
||||
|
||||
private bool IsKnown(RuntimeEntityRecord record)
|
||||
{
|
||||
if (IsCurrent(record))
|
||||
return true;
|
||||
return _teardownByIncarnation.TryGetValue(
|
||||
(record.ServerGuid, record.Incarnation),
|
||||
out RuntimeEntityRecord? retained)
|
||||
&& ReferenceEquals(retained, record);
|
||||
}
|
||||
|
||||
private void EnsureKnown(RuntimeEntityRecord record)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(record);
|
||||
if (!IsKnown(record))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} is no longer owned by the directory.");
|
||||
}
|
||||
}
|
||||
}
|
||||
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