acdream/src/AcDream.App/Physics/RemotePhysicsUpdater.cs
Erik 7e6033d0ad 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>
2026-07-26 13:39:57 +02:00

335 lines
11 KiB
C#

using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.World;
using AcDream.Core.Physics;
using AcDream.Core.Physics.Motion;
using AcDream.Core.World;
using AcDream.Runtime.Physics;
namespace AcDream.App.Physics;
/// <summary>
/// Graphical adapter for Runtime's presentation-free remote-object physics.
/// It supplies DAT-derived shape dimensions and animation callbacks, then
/// projects the committed root snapshot into the retained App entity.
/// </summary>
internal sealed class RemotePhysicsUpdater
{
private readonly RuntimeRemotePhysicsUpdater _runtime;
private readonly Func<uint, WorldEntity, (float Radius, float Height)>
_getSetupCylinder;
private readonly Action<uint, LiveEntityAnimationState, RemoteMotion, Vector3>
_applyServerControlledVelocityCycle;
private readonly List<LiveEntityRecord> _spatialRemoteSnapshot = new();
internal RemotePhysicsUpdater(
RuntimePhysicsState physics,
Func<uint, WorldEntity, (float Radius, float Height)> getSetupCylinder,
Action<uint, LiveEntityAnimationState, RemoteMotion, Vector3>
applyServerControlledVelocityCycle)
{
_runtime = new RuntimeRemotePhysicsUpdater(
physics ?? throw new ArgumentNullException(nameof(physics)));
_getSetupCylinder = getSetupCylinder
?? throw new ArgumentNullException(nameof(getSetupCylinder));
_applyServerControlledVelocityCycle =
applyServerControlledVelocityCycle
?? throw new ArgumentNullException(
nameof(applyServerControlledVelocityCycle));
}
public void TickHiddenEntities(
LiveEntityRuntime liveEntities,
uint localPlayerServerGuid,
float dt,
Action<WorldEntity> publishRootPose,
Action<uint, AnimationSequencer>? processAnimationHooks = null,
Action<uint>? markPartPoseDirty = null)
{
ArgumentNullException.ThrowIfNull(liveEntities);
ArgumentNullException.ThrowIfNull(publishRootPose);
Vector3? playerPosition = null;
if (localPlayerServerGuid != 0
&& liveEntities.TryGetWorldEntity(
localPlayerServerGuid,
out WorldEntity playerEntity))
{
playerPosition = playerEntity.Position;
}
liveEntities.CopySpatialRemoteMotionRecordsTo(
_spatialRemoteSnapshot);
foreach (LiveEntityRecord record in _spatialRemoteSnapshot)
{
if (record.ServerGuid == localPlayerServerGuid
|| (record.FinalPhysicsState & PhysicsStateFlags.Hidden) == 0
|| record.RemoteMotionRuntime is not RemoteMotion remote
|| !liveEntities.IsCurrentSpatialRemoteMotion(record, remote)
|| record.WorldEntity is not { } entity)
{
continue;
}
LiveEntityAnimationState? animation =
record.AnimationRuntime as LiveEntityAnimationState;
RetailObjectActivityResult activity =
RetailObjectActivityGate.Evaluate(
record.ObjectClock,
remote.Body,
liveEntities.GetRootObjectClockDisposition(
record.ServerGuid)
is RetailObjectClockDisposition.Advance,
record.HasPartArray,
(record.FinalPhysicsState
& PhysicsStateFlags.Static) != 0,
entity.Position,
playerPosition,
dt);
if (activity is not RetailObjectActivityResult.Active)
continue;
AnimationSequencer? sequencer = animation?.Sequencer;
ulong objectClockEpoch = record.ObjectClockEpoch;
RetailObjectQuantumBatch batch = record.ObjectClock.Advance(dt);
for (int qi = 0; qi < batch.Count; qi++)
{
if (!IsCurrent(
liveEntities,
record,
entity,
remote,
objectClockEpoch)
|| !TickHidden(
remote,
entity,
batch.GetQuantum(qi),
sequencer?.Manager,
processAnimationHooks,
sequencer,
liveEntities,
record,
objectClockEpoch))
{
break;
}
}
if (batch.Count > 0
&& IsCurrent(
liveEntities,
record,
entity,
remote,
objectClockEpoch))
{
markPartPoseDirty?.Invoke(record.ServerGuid);
publishRootPose(entity);
}
}
}
public bool Tick(
RemoteMotion remote,
LiveEntityAnimationState animation,
float dt,
MotionDeltaFrame rootMotionLocalFrame,
int liveCenterX,
int liveCenterY,
Action<uint, AnimationSequencer>? processAnimationHooks = null,
LiveEntityRuntime? ownerRuntime = null,
LiveEntityRecord? ownerRecord = null,
ulong ownerClockEpoch = 0)
{
ArgumentNullException.ThrowIfNull(animation);
return Tick(
remote,
animation.Entity,
animation.Scale,
animation.Sequencer,
animation,
dt,
rootMotionLocalFrame,
liveCenterX,
liveCenterY,
processAnimationHooks,
ownerRuntime,
ownerRecord,
ownerClockEpoch);
}
public bool Tick(
RemoteMotion remote,
WorldEntity entity,
float objectScale,
AnimationSequencer? sequencer,
LiveEntityAnimationState? animationForVelocityCycle,
float dt,
MotionDeltaFrame rootMotionLocalFrame,
int liveCenterX,
int liveCenterY,
Action<uint, AnimationSequencer>? processAnimationHooks = null,
LiveEntityRuntime? ownerRuntime = null,
LiveEntityRecord? ownerRecord = null,
ulong ownerClockEpoch = 0)
{
ArgumentNullException.ThrowIfNull(remote);
ArgumentNullException.ThrowIfNull(entity);
if (ownerRuntime is null
|| ownerRecord is null)
{
return false;
}
bool OwnerValid() => IsCurrent(
ownerRuntime,
ownerRecord,
entity,
remote,
ownerClockEpoch);
var (radius, height) =
_getSetupCylinder(ownerRecord.ServerGuid, entity);
Action<Vector3>? staleCycle =
animationForVelocityCycle is null
? null
: velocity => _applyServerControlledVelocityCycle(
ownerRecord.ServerGuid,
animationForVelocityCycle,
remote,
velocity);
return _runtime.Tick(
ownerRecord.Canonical,
remote,
objectScale,
sequencer,
dt,
ownerClockEpoch,
rootMotionLocalFrame,
radius,
height,
liveCenterX,
liveCenterY,
processAnimationHooks,
staleCycle,
snapshot =>
{
if (!OwnerValid())
return false;
entity.SetPosition(snapshot.Position);
entity.ParentCellId = snapshot.FullCellId;
entity.Rotation = snapshot.Orientation;
return OwnerValid();
},
OwnerValid);
}
public bool TickHidden(
RemoteMotion remote,
WorldEntity entity,
float dt,
MotionTableManager? partArrayHandleMovement = null,
Action<uint, AnimationSequencer>? processAnimationHooks = null,
AnimationSequencer? sequencer = null,
LiveEntityRuntime? ownerRuntime = null,
LiveEntityRecord? ownerRecord = null,
ulong ownerClockEpoch = 0)
{
ArgumentNullException.ThrowIfNull(remote);
ArgumentNullException.ThrowIfNull(entity);
if (ownerRuntime is null
|| ownerRecord is null)
{
return false;
}
bool OwnerValid() => IsCurrent(
ownerRuntime,
ownerRecord,
entity,
remote,
ownerClockEpoch);
var (radius, height) =
_getSetupCylinder(ownerRecord.ServerGuid, entity);
return _runtime.TickHidden(
ownerRecord.Canonical,
remote,
dt,
ownerClockEpoch,
radius,
height,
partArrayHandleMovement,
processAnimationHooks,
sequencer,
snapshot =>
{
if (!OwnerValid())
return false;
entity.SetPosition(snapshot.Position);
entity.ParentCellId = snapshot.FullCellId;
entity.Rotation = snapshot.Orientation;
return OwnerValid();
},
OwnerValid);
}
public void SyncRemoteShadowToBody(
uint entityId,
IRuntimeRemotePlacement remote,
int liveCenterX,
int liveCenterY,
uint? authoritativeCellId = null) =>
_runtime.SyncRemoteShadowToBody(
entityId,
remote,
liveCenterX,
liveCenterY,
authoritativeCellId);
public void SyncRemoteShadowToBody(
uint entityId,
PhysicsBody body,
int liveCenterX,
int liveCenterY,
uint authoritativeCellId) =>
_runtime.SyncRemoteShadowToBody(
entityId,
body,
liveCenterX,
liveCenterY,
authoritativeCellId);
internal static bool ShouldSynchronizeShadowPose(
Vector3 currentPosition,
Quaternion currentOrientation,
Vector3 lastPosition,
Quaternion lastOrientation) =>
RuntimeRemotePhysicsUpdater.ShouldSynchronizeShadowPose(
currentPosition,
currentOrientation,
lastPosition,
lastOrientation);
internal static bool ShouldSynchronizeShadow(
bool cellChanged,
Vector3 currentPosition,
Quaternion currentOrientation,
Vector3 lastPosition,
Quaternion lastOrientation) =>
RuntimeRemotePhysicsUpdater.ShouldSynchronizeShadow(
cellChanged,
currentPosition,
currentOrientation,
lastPosition,
lastOrientation);
private static bool IsCurrent(
LiveEntityRuntime runtime,
LiveEntityRecord record,
WorldEntity entity,
RemoteMotion remote,
ulong objectClockEpoch) =>
record.ObjectClockEpoch == objectClockEpoch
&& runtime.IsCurrentSpatialRemoteMotion(record, remote)
&& ReferenceEquals(record.WorldEntity, entity);
}