refactor(runtime): extract the live object frame

This commit is contained in:
Erik 2026-07-22 00:42:26 +02:00
parent 99a3e819c4
commit 4e4aac2c5a
27 changed files with 1217 additions and 371 deletions

View file

@ -1,4 +1,6 @@
using System.Numerics;
using AcDream.App.Input;
using AcDream.App.Rendering.Vfx;
using AcDream.App.Physics;
using AcDream.App.World;
using AcDream.Core.Physics;
@ -17,41 +19,38 @@ namespace AcDream.App.Rendering;
/// </summary>
internal sealed class LiveEntityAnimationScheduler
{
private readonly Func<LiveEntityRuntime?> _liveEntities;
private readonly Func<uint> _localPlayerGuid;
private readonly LiveEntityRuntime _liveEntities;
private readonly ILocalPlayerIdentitySource _localPlayer;
private readonly RemotePhysicsUpdater _remotePhysics;
private readonly LiveEntityOrdinaryPhysicsUpdater _ordinaryPhysics;
private readonly Func<ProjectileController?> _projectiles;
private readonly Action<WorldEntity> _publishRootPose;
private readonly Action<uint, AnimationSequencer> _captureAnimationHooks;
private readonly ProjectileController _projectiles;
private readonly IEntityRootPosePublisher _rootPoses;
private readonly IAnimationHookCaptureSink _animationHooks;
private readonly List<LiveEntityRecord> _rootSnapshot = new();
private readonly Dictionary<uint, LiveEntityAnimationSchedule> _schedules = new();
private readonly Frame _rootFrameScratch = new();
private readonly MotionDeltaFrame _rootDeltaScratch = new();
public LiveEntityAnimationScheduler(
Func<LiveEntityRuntime?> liveEntities,
Func<uint> localPlayerGuid,
LiveEntityRuntime liveEntities,
ILocalPlayerIdentitySource localPlayer,
RemotePhysicsUpdater remotePhysics,
LiveEntityOrdinaryPhysicsUpdater ordinaryPhysics,
Func<ProjectileController?> projectiles,
Action<WorldEntity> publishRootPose,
Action<uint, AnimationSequencer> captureAnimationHooks)
ProjectileController projectiles,
IEntityRootPosePublisher rootPoses,
IAnimationHookCaptureSink animationHooks)
{
_liveEntities = liveEntities
?? throw new ArgumentNullException(nameof(liveEntities));
_localPlayerGuid = localPlayerGuid
?? throw new ArgumentNullException(nameof(localPlayerGuid));
_localPlayer = localPlayer
?? throw new ArgumentNullException(nameof(localPlayer));
_remotePhysics = remotePhysics
?? throw new ArgumentNullException(nameof(remotePhysics));
_ordinaryPhysics = ordinaryPhysics
?? throw new ArgumentNullException(nameof(ordinaryPhysics));
_projectiles = projectiles
?? throw new ArgumentNullException(nameof(projectiles));
_publishRootPose = publishRootPose
?? throw new ArgumentNullException(nameof(publishRootPose));
_captureAnimationHooks = captureAnimationHooks
?? throw new ArgumentNullException(nameof(captureAnimationHooks));
_projectiles = projectiles ?? throw new ArgumentNullException(nameof(projectiles));
_rootPoses = rootPoses ?? throw new ArgumentNullException(nameof(rootPoses));
_animationHooks = animationHooks ?? throw new ArgumentNullException(nameof(animationHooks));
}
/// <summary>
@ -69,9 +68,7 @@ internal sealed class LiveEntityAnimationScheduler
Action<LiveEntityRecord, LiveEntityAnimationState>? prepareAnimation = null)
{
_schedules.Clear();
LiveEntityRuntime? runtime = _liveEntities();
if (runtime is null)
return _schedules;
LiveEntityRuntime runtime = _liveEntities;
runtime.CopySpatialRootObjectRecordsTo(_rootSnapshot);
foreach (LiveEntityRecord record in _rootSnapshot)
@ -180,7 +177,7 @@ internal sealed class LiveEntityAnimationScheduler
// PlayerMovementController owns this exact record clock and advances
// its PartArray before local collision. Consume that prepared pose;
// never tick the same clock or sequence a second time here.
if (serverGuid == _localPlayerGuid())
if (serverGuid == _localPlayer.ServerGuid)
{
IReadOnlyList<PartTransform>? prepared =
animation?.PreparedSequenceFrames;
@ -208,7 +205,7 @@ internal sealed class LiveEntityAnimationScheduler
// that are smaller than an admitted object quantum. Publish that
// current root independently of PartArray recomposition so local
// attached effects and lights never trail the rendered character.
_publishRootPose(entity);
_rootPoses.UpdateRoot(entity);
if (!IsCurrent(
runtime,
record,
@ -249,7 +246,7 @@ internal sealed class LiveEntityAnimationScheduler
IReadOnlyList<PartTransform>? frames = null;
float legacyElapsed = 0f;
bool completed = true;
ProjectileController? projectileController = _projectiles();
ProjectileController projectileController = _projectiles;
bool projectileHandlesMovement = projectile is not null
&& projectileController?.HandlesMovement(serverGuid) == true;
float objectScale = animation?.Scale
@ -275,7 +272,7 @@ internal sealed class LiveEntityAnimationScheduler
entity,
quantum,
sequencer?.Manager,
_captureAnimationHooks,
_animationHooks.Capture,
sequencer,
runtime,
record,
@ -289,7 +286,7 @@ internal sealed class LiveEntityAnimationScheduler
{
if (sequencer is not null)
{
_captureAnimationHooks(entity.Id, sequencer);
_animationHooks.Capture(entity.Id, sequencer);
if (!IsCurrent(
runtime,
record,
@ -355,7 +352,7 @@ internal sealed class LiveEntityAnimationScheduler
rootDelta,
liveCenterX,
liveCenterY,
_captureAnimationHooks,
_animationHooks.Capture,
runtime,
record,
objectClockEpoch))
@ -381,7 +378,7 @@ internal sealed class LiveEntityAnimationScheduler
liveCenterY,
objectClockEpoch,
sequencer,
_captureAnimationHooks))
_animationHooks.Capture))
{
completed = false;
break;
@ -405,7 +402,7 @@ internal sealed class LiveEntityAnimationScheduler
out projectileStep) == true;
if (!ordinaryBodyHandlesMovement && sequencer is not null)
_captureAnimationHooks(entity.Id, sequencer);
_animationHooks.Capture(entity.Id, sequencer);
if (!IsCurrent(runtime, record, entity, animation, remote, projectile, objectClockEpoch))
{
completed = false;
@ -443,7 +440,7 @@ internal sealed class LiveEntityAnimationScheduler
return default;
}
_publishRootPose(entity);
_rootPoses.UpdateRoot(entity);
if (!IsCurrent(runtime, record, entity, animation, remote, projectile, objectClockEpoch))
return default;