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>
335 lines
11 KiB
C#
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);
|
|
}
|