refactor(runtime): own per-session physics simulation
Move the sole PhysicsEngine, production cache, collision admissions, canonical bodies and hosts, remote components, ordinary/remote worksets, simulation, cell commits, and shadow synchronization under RuntimeEntityObjectLifetime. Keep App as the prepared-asset, animation-input, and render-projection adapter while preserving the named-retail update and collision order. Add exact-incarnation, object-clock, callback-reentrancy, GUID-reuse, two-runtime isolation, source ownership, collision publication, and graphical projection coverage. Release build and the complete 8,588-test solution pass. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
0dc3bfdeff
commit
7e6033d0ad
39 changed files with 3685 additions and 1722 deletions
|
|
@ -20,65 +20,6 @@ public interface ILiveEntityAnimationRuntime
|
|||
uint CurrentMotion { get; }
|
||||
}
|
||||
|
||||
/// <summary>Remote motion state owned by a live object.</summary>
|
||||
public interface ILiveEntityRemoteMotionRuntime
|
||||
{
|
||||
PhysicsBody Body { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional seam for a remote-motion component that reads the exact
|
||||
/// incarnation's canonical movement-manager host from
|
||||
/// <see cref="LiveEntityRecord"/>.
|
||||
/// </summary>
|
||||
public interface ILiveEntityPhysicsHostConsumer
|
||||
{
|
||||
void BindPhysicsHost(Func<AcDream.Core.Physics.Motion.IPhysicsObjHost?> read);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Atomic composition seam for a component that consumes both canonical host
|
||||
/// and cell identity. Implementations validate every delegate before
|
||||
/// publishing any of them, so a failed bind leaves the component reusable.
|
||||
/// </summary>
|
||||
public interface ILiveEntityCanonicalRuntimeConsumer
|
||||
{
|
||||
void BindCanonicalRuntime(
|
||||
Func<AcDream.Core.Physics.Motion.IPhysicsObjHost?> readPhysicsHost,
|
||||
Func<uint> readCell,
|
||||
Action<uint> writeCell);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional seam for a remote-motion component that consumes the canonical
|
||||
/// full-cell identity owned by <see cref="LiveEntityRecord"/>. The runtime
|
||||
/// binds this for every production remote, regardless of whether projectile
|
||||
/// classification arrives before or after the MovementManager.
|
||||
/// </summary>
|
||||
public interface ILiveEntityCanonicalCellConsumer
|
||||
{
|
||||
void BindCanonicalCell(Func<uint> read, Action<uint> write);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote CPhysicsObj state required to finish a retail MoveOrTeleport
|
||||
/// placement. A same-incarnation runtime replacement may wrap the same body,
|
||||
/// but it must preserve this complete placement contract.
|
||||
/// </summary>
|
||||
public interface ILiveEntityRemotePlacementRuntime :
|
||||
ILiveEntityRemoteMotionRuntime,
|
||||
ILiveEntityCanonicalCellConsumer
|
||||
{
|
||||
uint CellId { get; set; }
|
||||
bool Airborne { get; set; }
|
||||
Vector3 LastServerPosition { get; set; }
|
||||
double LastServerPositionTime { get; set; }
|
||||
Vector3 LastShadowSyncPosition { get; set; }
|
||||
Quaternion LastShadowSyncOrientation { get; set; }
|
||||
void HitGround();
|
||||
void LeaveGround();
|
||||
}
|
||||
|
||||
/// <summary>Projectile physics state owned by a live object.</summary>
|
||||
public interface ILiveEntityProjectileRuntime
|
||||
{
|
||||
|
|
@ -296,14 +237,26 @@ public sealed class LiveEntityRecord
|
|||
set => _directory.SetPhysicsBodyAcquisitionInProgress(Canonical, value);
|
||||
}
|
||||
public ILiveEntityAnimationRuntime? AnimationRuntime { get; internal set; }
|
||||
public ILiveEntityRemoteMotionRuntime? RemoteMotionRuntime { get; internal set; }
|
||||
internal bool RemoteMotionBindingInProgress { get; set; }
|
||||
public ILiveEntityRemoteMotionRuntime? RemoteMotionRuntime
|
||||
{
|
||||
get => Canonical.RemoteMotion;
|
||||
internal set => _directory.SetRemoteMotion(Canonical, value);
|
||||
}
|
||||
internal bool RemoteMotionBindingInProgress
|
||||
{
|
||||
get => Canonical.RemoteMotionBindingInProgress;
|
||||
set => _directory.SetRemoteMotionBindingInProgress(Canonical, value);
|
||||
}
|
||||
public AcDream.Core.Physics.Motion.IPhysicsObjHost? PhysicsHost
|
||||
{
|
||||
get => Canonical.PhysicsHost;
|
||||
internal set => _directory.SetPhysicsHost(Canonical, value);
|
||||
}
|
||||
internal bool RequiresRemotePlacementRuntime { get; set; }
|
||||
internal bool RequiresRemotePlacementRuntime
|
||||
{
|
||||
get => Canonical.RequiresRemotePlacementRuntime;
|
||||
set => _directory.SetRequiresRemotePlacementRuntime(Canonical, value);
|
||||
}
|
||||
public ILiveEntityProjectileRuntime? ProjectileRuntime { get; internal set; }
|
||||
public ILiveEntityEffectProfile? EffectProfile { get; internal set; }
|
||||
public bool ResourcesRegistered { get; internal set; }
|
||||
|
|
@ -435,6 +388,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
private readonly ILiveEntityRuntimeComponentLifecycle _runtimeComponentLifecycle;
|
||||
private readonly RuntimeEntityObjectLifetime _entityObjects;
|
||||
private readonly RuntimeEntityDirectory _directory;
|
||||
private readonly RuntimePhysicsState _physics;
|
||||
private readonly LiveEntityProjectionStore _projections;
|
||||
// Logical components survive retail's 25-second leave-visibility lifetime.
|
||||
// Frame loops must not scan those retained owners. These component-specific
|
||||
|
|
@ -442,12 +396,9 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
// projection. Hidden and Frozen remain members: their state controls what
|
||||
// each retail update path advances after the O(active) lookup.
|
||||
private readonly Dictionary<RuntimeEntityKey, ILiveEntityAnimationRuntime> _spatialAnimations = new();
|
||||
private readonly Dictionary<RuntimeEntityKey, ILiveEntityRemoteMotionRuntime> _spatialRemoteMotion = new();
|
||||
private readonly Dictionary<RuntimeEntityKey, ILiveEntityProjectileRuntime> _spatialProjectiles = new();
|
||||
// Retail CPhysics::UseTime walks the ordinary object table, not a render-
|
||||
// animation component table. This index is the loaded/cell-backed root
|
||||
// workset; component-specific indexes remain lookup accelerators only.
|
||||
private readonly Dictionary<RuntimeEntityKey, LiveEntityRecord> _spatialRootObjects = new();
|
||||
private readonly List<RuntimeEntityRecord> _spatialRootCanonicalScratch = new();
|
||||
private readonly List<RuntimeEntityRecord> _spatialRemoteCanonicalScratch = new();
|
||||
private bool _isClearing;
|
||||
private bool _sessionClearPendingFinalization;
|
||||
private bool _isRegisteringResources;
|
||||
|
|
@ -492,8 +443,10 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
_entityObjects = entityObjects
|
||||
?? throw new ArgumentNullException(nameof(entityObjects));
|
||||
_directory = _entityObjects.Entities;
|
||||
_physics = _entityObjects.Physics;
|
||||
_projections = new LiveEntityProjectionStore(_directory);
|
||||
_spatial.LiveProjectionVisibilityChanged += OnSpatialVisibilityChanged;
|
||||
_physics.CellCommitted += OnRuntimePhysicsCellCommitted;
|
||||
}
|
||||
|
||||
public int Count => _directory.Count;
|
||||
|
|
@ -506,6 +459,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
_projections.VisibleRecords;
|
||||
internal IReadOnlyCollection<RuntimeEntityRecord> CanonicalRecords =>
|
||||
_directory.ActiveRecords;
|
||||
internal RuntimePhysicsState Physics => _physics;
|
||||
public IReadOnlyDictionary<uint, WorldSession.EntitySpawn> Snapshots => _directory.Snapshots;
|
||||
internal int AnimationRuntimeCount
|
||||
{
|
||||
|
|
@ -522,9 +476,9 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
}
|
||||
}
|
||||
internal int SpatialAnimationRuntimeCount => _spatialAnimations.Count;
|
||||
internal int SpatialRemoteMotionRuntimeCount => _spatialRemoteMotion.Count;
|
||||
internal int SpatialRemoteMotionRuntimeCount => _physics.SpatialRemoteCount;
|
||||
internal int SpatialProjectileRuntimeCount => _spatialProjectiles.Count;
|
||||
internal int SpatialRootObjectCount => _spatialRootObjects.Count;
|
||||
internal int SpatialRootObjectCount => _physics.SpatialRootCount;
|
||||
public ParentAttachmentState ParentAttachments => _directory.ParentAttachments;
|
||||
|
||||
internal bool TryGetCanonical(
|
||||
|
|
@ -798,10 +752,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
RuntimeEntityKey key = RequireProjectionKey(record);
|
||||
bool wasProjected = record.IsSpatiallyProjected;
|
||||
bool wasVisible = record.IsSpatiallyVisible;
|
||||
bool wasOrdinaryRoot = _spatialRootObjects.TryGetValue(
|
||||
key,
|
||||
out LiveEntityRecord? indexedRoot)
|
||||
&& ReferenceEquals(indexedRoot, record);
|
||||
bool wasOrdinaryRoot = _physics.IsSpatialRoot(record.Canonical);
|
||||
ulong projectionOperation = ++record.ProjectionMutationVersion;
|
||||
// GpuWorldState reports an intermediate false/true pair while moving
|
||||
// between two loaded buckets. Suppress those implementation details
|
||||
|
|
@ -936,10 +887,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
return false;
|
||||
|
||||
RuntimeEntityKey key = RequireProjectionKey(record);
|
||||
bool wasOrdinaryRoot = _spatialRootObjects.TryGetValue(
|
||||
key,
|
||||
out LiveEntityRecord? rootRecord)
|
||||
&& ReferenceEquals(rootRecord, record);
|
||||
bool wasOrdinaryRoot = _physics.IsSpatialRoot(record.Canonical);
|
||||
ulong objectClockEpoch = record.ObjectClockEpoch;
|
||||
ulong projectionOperation = ++record.ProjectionMutationVersion;
|
||||
Exception? spatialNotificationFailure = null;
|
||||
|
|
@ -1436,43 +1384,16 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
throw new InvalidOperationException(
|
||||
$"Cannot acquire physics body before live entity 0x{serverGuid:X8} is materialized.");
|
||||
}
|
||||
if (record.PhysicsBody is { } retained)
|
||||
return retained;
|
||||
if (record.PhysicsBodyAcquisitionInProgress)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} physics-body acquisition is already in progress.");
|
||||
}
|
||||
|
||||
record.PhysicsBodyAcquisitionInProgress = true;
|
||||
try
|
||||
{
|
||||
PhysicsBody candidate = factory(record)
|
||||
?? throw new InvalidOperationException("Physics-body factory returned null.");
|
||||
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? current)
|
||||
|| !ReferenceEquals(current, record)
|
||||
|| current.WorldEntity is null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} changed incarnation during physics-body acquisition.");
|
||||
}
|
||||
if (record.PhysicsBody is { } concurrentlyBound)
|
||||
{
|
||||
if (ReferenceEquals(concurrentlyBound, candidate))
|
||||
return concurrentlyBound;
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} acquired two physics bodies within one incarnation.");
|
||||
}
|
||||
|
||||
record.PhysicsBody = candidate;
|
||||
candidate.State = record.FinalPhysicsState;
|
||||
SynchronizePhysicsBodyActiveState(record);
|
||||
return candidate;
|
||||
}
|
||||
finally
|
||||
{
|
||||
record.PhysicsBodyAcquisitionInProgress = false;
|
||||
}
|
||||
bool ProjectionIsCurrent() =>
|
||||
_projections.TryGetCurrent(
|
||||
serverGuid,
|
||||
out LiveEntityRecord? current)
|
||||
&& ReferenceEquals(current, record)
|
||||
&& current.WorldEntity is not null;
|
||||
return _physics.GetOrCreatePhysicsBody(
|
||||
record.Canonical,
|
||||
_ => factory(record),
|
||||
ProjectionIsCurrent);
|
||||
}
|
||||
|
||||
public void SetRemoteMotionRuntime(uint serverGuid, ILiveEntityRemoteMotionRuntime runtime)
|
||||
|
|
@ -1480,122 +1401,17 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
ArgumentNullException.ThrowIfNull(runtime);
|
||||
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record))
|
||||
throw new InvalidOperationException($"Cannot bind remote motion before live entity 0x{serverGuid:X8} exists.");
|
||||
if (record.RemoteMotionBindingInProgress)
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} remote-motion binding is already in progress.");
|
||||
PhysicsBody candidateBody = runtime.Body
|
||||
?? throw new InvalidOperationException("A remote-motion runtime returned no physics body.");
|
||||
if (ReferenceEquals(record.RemoteMotionRuntime, runtime))
|
||||
{
|
||||
if (!ReferenceEquals(record.PhysicsBody, candidateBody))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} remote motion changed its canonical physics body.");
|
||||
}
|
||||
// Idempotent publication is important when motion and vector
|
||||
// packets converge on the same runtime in one update turn. The
|
||||
// component seams are incarnation-bound and must never be rebound.
|
||||
candidateBody.State = record.FinalPhysicsState;
|
||||
SynchronizePhysicsBodyActiveState(record);
|
||||
RefreshSpatialRuntimeIndexes(record);
|
||||
return;
|
||||
}
|
||||
if (record.PhysicsBodyAcquisitionInProgress && record.PhysicsBody is null)
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} cannot bind remote motion during physics-body acquisition.");
|
||||
if (record.PhysicsBody is { } canonicalBody
|
||||
&& !ReferenceEquals(canonicalBody, candidateBody))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} cannot replace its canonical physics body within one incarnation.");
|
||||
}
|
||||
if (record.RequiresRemotePlacementRuntime
|
||||
&& runtime is not ILiveEntityRemotePlacementRuntime)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} cannot discard its remote placement contract within one incarnation.");
|
||||
}
|
||||
// Bind every possibly-throwing exact-incarnation seam before
|
||||
// publishing any canonical record/index state. An already-bound
|
||||
// component from an older GUID generation must fail without poisoning
|
||||
// the replacement record. The callback is arbitrary App code, so the
|
||||
// exact record, authority epoch, and expected owners are revalidated
|
||||
// before commit just like resource/physics-body acquisition.
|
||||
LiveEntityRecord incarnation = record;
|
||||
ulong sessionVersion = _directory.SessionLifetimeVersion;
|
||||
ulong lifetimeVersion = CurrentLifetimeMutation(serverGuid);
|
||||
PhysicsBody? expectedBody = record.PhysicsBody;
|
||||
ILiveEntityRemoteMotionRuntime? expectedRuntime = record.RemoteMotionRuntime;
|
||||
bool expectedPlacementContract = record.RequiresRemotePlacementRuntime;
|
||||
Func<AcDream.Core.Physics.Motion.IPhysicsObjHost?> readPhysicsHost = () =>
|
||||
_projections.TryGetCurrent(serverGuid, out var current)
|
||||
&& ReferenceEquals(current, incarnation)
|
||||
&& ReferenceEquals(current.RemoteMotionRuntime, runtime)
|
||||
? current.PhysicsHost
|
||||
: null;
|
||||
// Reads remain bound to the exact CPhysicsObj incarnation through its
|
||||
// exit_world notifications. Only writes require active ownership.
|
||||
Func<uint> readCell = () => incarnation.FullCellId;
|
||||
Action<uint> writeCell = cellId =>
|
||||
{
|
||||
if (cellId != 0
|
||||
&& _projections.TryGetCurrent(serverGuid, out var current)
|
||||
&& ReferenceEquals(current, incarnation)
|
||||
&& ReferenceEquals(current.RemoteMotionRuntime, runtime))
|
||||
{
|
||||
RebucketLiveEntity(serverGuid, cellId);
|
||||
}
|
||||
};
|
||||
|
||||
record.RemoteMotionBindingInProgress = true;
|
||||
try
|
||||
{
|
||||
if (runtime is ILiveEntityCanonicalRuntimeConsumer canonicalConsumer)
|
||||
{
|
||||
canonicalConsumer.BindCanonicalRuntime(
|
||||
readPhysicsHost,
|
||||
readCell,
|
||||
writeCell);
|
||||
}
|
||||
else
|
||||
{
|
||||
bool consumesHost = runtime is ILiveEntityPhysicsHostConsumer;
|
||||
bool consumesCell = runtime is ILiveEntityCanonicalCellConsumer;
|
||||
if (consumesHost && consumesCell)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A remote runtime that consumes both canonical host and cell identity must bind them atomically.");
|
||||
}
|
||||
if (runtime is ILiveEntityPhysicsHostConsumer hostConsumer)
|
||||
hostConsumer.BindPhysicsHost(readPhysicsHost);
|
||||
if (runtime is ILiveEntityCanonicalCellConsumer cellConsumer)
|
||||
cellConsumer.BindCanonicalCell(readCell, writeCell);
|
||||
}
|
||||
|
||||
if (_directory.SessionLifetimeVersion != sessionVersion
|
||||
|| CurrentLifetimeMutation(serverGuid) != lifetimeVersion
|
||||
|| !_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? current)
|
||||
|| !ReferenceEquals(current, incarnation)
|
||||
|| !ReferenceEquals(current.PhysicsBody, expectedBody)
|
||||
|| !ReferenceEquals(current.RemoteMotionRuntime, expectedRuntime)
|
||||
|| current.RequiresRemotePlacementRuntime != expectedPlacementContract)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} changed incarnation or component ownership during remote-motion binding.");
|
||||
}
|
||||
|
||||
record.RequiresRemotePlacementRuntime |=
|
||||
runtime is ILiveEntityRemotePlacementRuntime;
|
||||
record.RemoteMotionRuntime = runtime;
|
||||
record.PhysicsBody = candidateBody;
|
||||
candidateBody.State = record.FinalPhysicsState;
|
||||
SynchronizePhysicsBodyActiveState(record);
|
||||
RefreshSpatialRuntimeIndexes(record);
|
||||
}
|
||||
finally
|
||||
{
|
||||
record.RemoteMotionBindingInProgress = false;
|
||||
}
|
||||
_physics.SetRemoteMotion(
|
||||
record.Canonical,
|
||||
runtime,
|
||||
() => CurrentLifetimeMutation(serverGuid) == lifetimeVersion
|
||||
&& _projections.TryGetCurrent(
|
||||
serverGuid,
|
||||
out LiveEntityRecord? current)
|
||||
&& ReferenceEquals(current, record));
|
||||
SynchronizePhysicsBodyActiveState(record);
|
||||
RefreshSpatialRuntimeIndexes(record);
|
||||
}
|
||||
|
||||
public void InstallPhysicsHost(
|
||||
|
|
@ -1605,26 +1421,23 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
ArgumentNullException.ThrowIfNull(expectedRecord);
|
||||
ArgumentNullException.ThrowIfNull(host);
|
||||
uint serverGuid = expectedRecord.ServerGuid;
|
||||
if (host.Id != serverGuid)
|
||||
throw new ArgumentException("A physics host must match its live entity GUID.", nameof(host));
|
||||
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|
||||
|| !ReferenceEquals(record, expectedRecord))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} changed incarnation during physics-host installation.");
|
||||
}
|
||||
if (record.PhysicsHost is not null)
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} already owns its incarnation-stable physics host.");
|
||||
record.PhysicsHost = host;
|
||||
bool ProjectionIsCurrent() =>
|
||||
_projections.TryGetCurrent(
|
||||
serverGuid,
|
||||
out LiveEntityRecord? current)
|
||||
&& ReferenceEquals(current, expectedRecord);
|
||||
_physics.InstallPhysicsHost(
|
||||
expectedRecord.Canonical,
|
||||
host,
|
||||
ProjectionIsCurrent);
|
||||
}
|
||||
|
||||
public bool TryGetPhysicsHost(
|
||||
uint serverGuid,
|
||||
out AcDream.Core.Physics.Motion.IPhysicsObjHost host)
|
||||
{
|
||||
if (_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|
||||
&& record.PhysicsHost is { } existing)
|
||||
if (_projections.TryGetCurrent(serverGuid, out _)
|
||||
&& _physics.TryGetPhysicsHost(serverGuid, out var existing))
|
||||
{
|
||||
host = existing;
|
||||
return true;
|
||||
|
|
@ -1656,7 +1469,8 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|
||||
|| record.RemoteMotionRuntime is null)
|
||||
return false;
|
||||
record.RemoteMotionRuntime = null;
|
||||
if (!_physics.ClearRemoteMotion(record.Canonical))
|
||||
return false;
|
||||
RefreshSpatialRuntimeIndexes(record);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1977,10 +1791,16 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
{
|
||||
ArgumentNullException.ThrowIfNull(destination);
|
||||
destination.Clear();
|
||||
foreach ((RuntimeEntityKey key, LiveEntityRecord indexed) in _spatialRootObjects)
|
||||
_physics.CopySpatialRootsTo(_spatialRootCanonicalScratch);
|
||||
for (int index = 0;
|
||||
index < _spatialRootCanonicalScratch.Count;
|
||||
index++)
|
||||
{
|
||||
if (_projections.TryGet(key, out LiveEntityRecord? current)
|
||||
&& ReferenceEquals(current, indexed)
|
||||
RuntimeEntityRecord canonical =
|
||||
_spatialRootCanonicalScratch[index];
|
||||
if (_projections.TryGet(
|
||||
canonical,
|
||||
out LiveEntityRecord? current)
|
||||
&& HasSpatialRuntimeProjection(current))
|
||||
{
|
||||
destination.Add(current);
|
||||
|
|
@ -1990,9 +1810,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
|
||||
internal bool IsCurrentSpatialRootObject(LiveEntityRecord record) =>
|
||||
IsCurrentRecord(record)
|
||||
&& record.ProjectionKey is { } key
|
||||
&& _spatialRootObjects.TryGetValue(key, out var indexed)
|
||||
&& ReferenceEquals(indexed, record)
|
||||
&& _physics.IsSpatialRoot(record.Canonical)
|
||||
&& HasSpatialRuntimeProjection(record);
|
||||
|
||||
internal bool IsCurrentSpatialAnimation(
|
||||
|
|
@ -2169,12 +1987,16 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
{
|
||||
ArgumentNullException.ThrowIfNull(destination);
|
||||
destination.Clear();
|
||||
|
||||
foreach ((RuntimeEntityKey key, ILiveEntityRemoteMotionRuntime runtime)
|
||||
in _spatialRemoteMotion)
|
||||
_physics.CopySpatialRemotesTo(_spatialRemoteCanonicalScratch);
|
||||
for (int index = 0;
|
||||
index < _spatialRemoteCanonicalScratch.Count;
|
||||
index++)
|
||||
{
|
||||
if (_projections.TryGet(key, out LiveEntityRecord? record)
|
||||
&& ReferenceEquals(record.RemoteMotionRuntime, runtime)
|
||||
RuntimeEntityRecord canonical =
|
||||
_spatialRemoteCanonicalScratch[index];
|
||||
if (_projections.TryGet(
|
||||
canonical,
|
||||
out LiveEntityRecord? record)
|
||||
&& HasSpatialRuntimeProjection(record))
|
||||
{
|
||||
destination.Add(record);
|
||||
|
|
@ -2186,10 +2008,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
LiveEntityRecord record,
|
||||
ILiveEntityRemoteMotionRuntime runtime) =>
|
||||
IsCurrentRecord(record)
|
||||
&& ReferenceEquals(record.RemoteMotionRuntime, runtime)
|
||||
&& record.ProjectionKey is { } key
|
||||
&& _spatialRemoteMotion.TryGetValue(key, out var indexed)
|
||||
&& ReferenceEquals(indexed, runtime)
|
||||
&& _physics.IsSpatialRemote(record.Canonical, runtime)
|
||||
&& HasSpatialRuntimeProjection(record);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -2623,16 +2442,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
return;
|
||||
}
|
||||
|
||||
if (spatial)
|
||||
{
|
||||
_spatialRootObjects[key] = record;
|
||||
}
|
||||
else if (current
|
||||
|| (_spatialRootObjects.TryGetValue(key, out var indexedRoot)
|
||||
&& ReferenceEquals(indexedRoot, record)))
|
||||
{
|
||||
_spatialRootObjects.Remove(key);
|
||||
}
|
||||
_physics.AcknowledgeSpatialProjection(record.Canonical, spatial);
|
||||
|
||||
if (record.WorldEntity is not null)
|
||||
{
|
||||
|
|
@ -2649,18 +2459,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
}
|
||||
}
|
||||
|
||||
if (spatial && record.RemoteMotionRuntime is { } remote)
|
||||
{
|
||||
_spatialRemoteMotion[key] = remote;
|
||||
}
|
||||
else if (current
|
||||
|| (record.RemoteMotionRuntime is { } retainedRemote
|
||||
&& _spatialRemoteMotion.TryGetValue(key, out var indexedRemote)
|
||||
&& ReferenceEquals(indexedRemote, retainedRemote)))
|
||||
{
|
||||
_spatialRemoteMotion.Remove(key);
|
||||
}
|
||||
|
||||
if (spatial && record.ProjectileRuntime is { } projectile)
|
||||
{
|
||||
_spatialProjectiles[key] = projectile;
|
||||
|
|
@ -2679,11 +2477,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
if (record.ProjectionKey is not { } key)
|
||||
return;
|
||||
|
||||
if (_spatialRootObjects.TryGetValue(key, out var indexedRoot)
|
||||
&& ReferenceEquals(indexedRoot, record))
|
||||
{
|
||||
_spatialRootObjects.Remove(key);
|
||||
}
|
||||
_physics.RemoveSpatialProjection(record.Canonical);
|
||||
|
||||
if (record.WorldEntity is not null
|
||||
&& _spatialAnimations.TryGetValue(key, out var indexedAnimation)
|
||||
|
|
@ -2692,12 +2486,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
_spatialAnimations.Remove(key);
|
||||
}
|
||||
|
||||
if (_spatialRemoteMotion.TryGetValue(key, out var indexedRemote)
|
||||
&& ReferenceEquals(indexedRemote, record.RemoteMotionRuntime))
|
||||
{
|
||||
_spatialRemoteMotion.Remove(key);
|
||||
}
|
||||
|
||||
if (_spatialProjectiles.TryGetValue(key, out var indexedProjectile)
|
||||
&& ReferenceEquals(indexedProjectile, record.ProjectileRuntime))
|
||||
{
|
||||
|
|
@ -2725,10 +2513,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
bool wasVisible = record.IsSpatiallyVisible;
|
||||
if (RequireProjectionKey(record) != key)
|
||||
return;
|
||||
bool wasOrdinaryRoot = _spatialRootObjects.TryGetValue(
|
||||
key,
|
||||
out LiveEntityRecord? indexedRoot)
|
||||
&& ReferenceEquals(indexedRoot, record);
|
||||
bool wasOrdinaryRoot = _physics.IsSpatialRoot(record.Canonical);
|
||||
record.IsSpatiallyVisible = visible;
|
||||
bool isOrdinaryRoot = record.ProjectionKind is LiveEntityProjectionKind.World
|
||||
&& record.IsSpatiallyProjected
|
||||
|
|
@ -2749,6 +2534,25 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
PublishProjectionVisibilityChanged(record, visible);
|
||||
}
|
||||
|
||||
private void OnRuntimePhysicsCellCommitted(
|
||||
RuntimePhysicsCellCommit commit)
|
||||
{
|
||||
if (commit.Record.SpatialAuthorityVersion
|
||||
!= commit.SpatialAuthorityVersion
|
||||
|| !_directory.IsCurrent(commit.Record)
|
||||
|| !_projections.TryGet(
|
||||
commit.Record,
|
||||
out LiveEntityRecord? projection)
|
||||
|| projection.WorldEntity is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RebucketLiveEntity(
|
||||
commit.Record.ServerGuid,
|
||||
commit.FullCellId);
|
||||
}
|
||||
|
||||
private static void SynchronizePhysicsBodyActiveState(
|
||||
LiveEntityRecord record)
|
||||
{
|
||||
|
|
@ -2877,9 +2681,8 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
}
|
||||
|
||||
_spatialAnimations.Clear();
|
||||
_spatialRemoteMotion.Clear();
|
||||
_spatialProjectiles.Clear();
|
||||
_spatialRootObjects.Clear();
|
||||
_physics.ClearSpatialWorksets();
|
||||
_projections.ClearConverged();
|
||||
_spatial.ClearLiveEntityLifetimeState();
|
||||
_sessionClearPendingFinalization = false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue