refactor(runtime): own projectile simulation
Move projectile component identity, prediction invalidation, spatial worksets, authoritative corrections, and the retail physics step into AcDream.Runtime. Keep App as the DAT-shape and presentation adapter so ACE outcomes and visible behavior remain unchanged.
This commit is contained in:
parent
fce8e7b18e
commit
2aee33569f
19 changed files with 1255 additions and 335 deletions
|
|
@ -20,12 +20,6 @@ public interface ILiveEntityAnimationRuntime
|
|||
uint CurrentMotion { get; }
|
||||
}
|
||||
|
||||
/// <summary>Projectile physics state owned by a live object.</summary>
|
||||
public interface ILiveEntityProjectileRuntime
|
||||
{
|
||||
PhysicsBody Body { get; }
|
||||
}
|
||||
|
||||
/// <summary>Marker for the DAT-driven effect profile added by the effect phase.</summary>
|
||||
public interface ILiveEntityEffectProfile { }
|
||||
|
||||
|
|
@ -257,7 +251,7 @@ public sealed class LiveEntityRecord
|
|||
get => Canonical.RequiresRemotePlacementRuntime;
|
||||
set => _directory.SetRequiresRemotePlacementRuntime(Canonical, value);
|
||||
}
|
||||
public ILiveEntityProjectileRuntime? ProjectileRuntime { get; internal set; }
|
||||
public IRuntimeProjectile? ProjectileRuntime => Canonical.Projectile;
|
||||
public ILiveEntityEffectProfile? EffectProfile { get; internal set; }
|
||||
public bool ResourcesRegistered { get; internal set; }
|
||||
public bool IsSpatiallyProjected { get; internal set; }
|
||||
|
|
@ -396,7 +390,6 @@ 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, ILiveEntityProjectileRuntime> _spatialProjectiles = new();
|
||||
private readonly List<RuntimeEntityRecord> _spatialRootCanonicalScratch = new();
|
||||
private readonly List<RuntimeEntityRecord> _spatialRemoteCanonicalScratch = new();
|
||||
private bool _isClearing;
|
||||
|
|
@ -477,7 +470,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
}
|
||||
internal int SpatialAnimationRuntimeCount => _spatialAnimations.Count;
|
||||
internal int SpatialRemoteMotionRuntimeCount => _physics.SpatialRemoteCount;
|
||||
internal int SpatialProjectileRuntimeCount => _spatialProjectiles.Count;
|
||||
internal int SpatialProjectileRuntimeCount => _physics.SpatialProjectileCount;
|
||||
internal int SpatialRootObjectCount => _physics.SpatialRootCount;
|
||||
public ParentAttachmentState ParentAttachments => _directory.ParentAttachments;
|
||||
|
||||
|
|
@ -1475,9 +1468,13 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
return true;
|
||||
}
|
||||
|
||||
public void SetProjectileRuntime(uint serverGuid, ILiveEntityProjectileRuntime runtime)
|
||||
internal IRuntimeProjectile BindProjectileRuntime(
|
||||
uint serverGuid,
|
||||
PhysicsBody body,
|
||||
ProjectileCollisionSphere collisionSphere,
|
||||
Func<bool>? externalOwnerValid = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(runtime);
|
||||
ArgumentNullException.ThrowIfNull(body);
|
||||
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|
||||
|| record.WorldEntity is null)
|
||||
{
|
||||
|
|
@ -1487,24 +1484,28 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
if (record.PhysicsBodyAcquisitionInProgress && record.PhysicsBody is null)
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} cannot bind projectile physics during physics-body acquisition.");
|
||||
PhysicsBody candidateBody = runtime.Body;
|
||||
if (record.PhysicsBody is { } canonicalBody
|
||||
&& !ReferenceEquals(canonicalBody, candidateBody))
|
||||
&& !ReferenceEquals(canonicalBody, body))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} cannot replace its canonical physics body within one incarnation.");
|
||||
}
|
||||
|
||||
record.ProjectileRuntime = runtime;
|
||||
record.PhysicsBody = candidateBody;
|
||||
candidateBody.State = record.FinalPhysicsState;
|
||||
SynchronizePhysicsBodyActiveState(record);
|
||||
IRuntimeProjectile runtime = _physics.BindProjectile(
|
||||
record.Canonical,
|
||||
body,
|
||||
collisionSphere,
|
||||
() => IsCurrentRecord(record)
|
||||
&& record.WorldEntity is { } entity
|
||||
&& record.LocalEntityId == entity.Id
|
||||
&& (externalOwnerValid?.Invoke() ?? true));
|
||||
RefreshSpatialRuntimeIndexes(record);
|
||||
return runtime;
|
||||
}
|
||||
|
||||
public bool TryGetProjectileRuntime(
|
||||
uint serverGuid,
|
||||
out ILiveEntityProjectileRuntime runtime)
|
||||
out IRuntimeProjectile runtime)
|
||||
{
|
||||
if (_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|
||||
&& record.ProjectileRuntime is { } found)
|
||||
|
|
@ -1523,7 +1524,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
|| record.ProjectileRuntime is null)
|
||||
return false;
|
||||
|
||||
record.ProjectileRuntime = null;
|
||||
_physics.ClearProjectile(record.Canonical);
|
||||
RefreshSpatialRuntimeIndexes(record);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -2021,11 +2022,11 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
ArgumentNullException.ThrowIfNull(destination);
|
||||
destination.Clear();
|
||||
|
||||
foreach ((RuntimeEntityKey key, ILiveEntityProjectileRuntime runtime)
|
||||
in _spatialProjectiles)
|
||||
_physics.CopySpatialProjectilesTo(_spatialRootCanonicalScratch);
|
||||
for (int i = 0; i < _spatialRootCanonicalScratch.Count; i++)
|
||||
{
|
||||
if (_projections.TryGet(key, out LiveEntityRecord? record)
|
||||
&& ReferenceEquals(record.ProjectileRuntime, runtime)
|
||||
RuntimeEntityRecord canonical = _spatialRootCanonicalScratch[i];
|
||||
if (_projections.TryGet(canonical, out LiveEntityRecord? record)
|
||||
&& HasSpatialRuntimeProjection(record))
|
||||
{
|
||||
destination.Add(record);
|
||||
|
|
@ -2035,12 +2036,9 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
|
||||
internal bool IsCurrentSpatialProjectile(
|
||||
LiveEntityRecord record,
|
||||
ILiveEntityProjectileRuntime runtime) =>
|
||||
IRuntimeProjectile runtime) =>
|
||||
IsCurrentRecord(record)
|
||||
&& ReferenceEquals(record.ProjectileRuntime, runtime)
|
||||
&& record.ProjectionKey is { } key
|
||||
&& _spatialProjectiles.TryGetValue(key, out var indexed)
|
||||
&& ReferenceEquals(indexed, runtime)
|
||||
&& _physics.IsSpatialProjectile(record.Canonical, runtime)
|
||||
&& HasSpatialRuntimeProjection(record);
|
||||
|
||||
public bool IsHidden(uint serverGuid) =>
|
||||
|
|
@ -2459,17 +2457,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
}
|
||||
}
|
||||
|
||||
if (spatial && record.ProjectileRuntime is { } projectile)
|
||||
{
|
||||
_spatialProjectiles[key] = projectile;
|
||||
}
|
||||
else if (current
|
||||
|| (record.ProjectileRuntime is { } retainedProjectile
|
||||
&& _spatialProjectiles.TryGetValue(key, out var indexedProjectile)
|
||||
&& ReferenceEquals(indexedProjectile, retainedProjectile)))
|
||||
{
|
||||
_spatialProjectiles.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveSpatialRuntimeIndexes(LiveEntityRecord record)
|
||||
|
|
@ -2486,11 +2473,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
_spatialAnimations.Remove(key);
|
||||
}
|
||||
|
||||
if (_spatialProjectiles.TryGetValue(key, out var indexedProjectile)
|
||||
&& ReferenceEquals(indexedProjectile, record.ProjectileRuntime))
|
||||
{
|
||||
_spatialProjectiles.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSpatialVisibilityChanged(RuntimeEntityKey key, bool visible)
|
||||
|
|
@ -2681,7 +2663,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
}
|
||||
|
||||
_spatialAnimations.Clear();
|
||||
_spatialProjectiles.Clear();
|
||||
_physics.ClearSpatialWorksets();
|
||||
_projections.ClearConverged();
|
||||
_spatial.ClearLiveEntityLifetimeState();
|
||||
|
|
@ -2717,6 +2698,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
// a retryable tombstone until every external cleanup acknowledges
|
||||
// success.
|
||||
RemoveSpatialRuntimeIndexes(record);
|
||||
_physics.ClearProjectile(record.Canonical);
|
||||
|
||||
if (!record.RuntimeComponentsTeardownCompleted)
|
||||
{
|
||||
|
|
@ -2756,7 +2738,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
|
|||
record.PhysicsHost = null;
|
||||
record.RequiresRemotePlacementRuntime = false;
|
||||
record.PhysicsBody = null;
|
||||
record.ProjectileRuntime = null;
|
||||
record.EffectProfile = null;
|
||||
record.IsSpatiallyProjected = false;
|
||||
record.IsSpatiallyVisible = false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue