refactor(animation): extract live PartArray presenter

Move final live part composition, exact-incarnation schedule handoff, MotionDone binding, and presentation diagnostics out of GameWindow while preserving the retail frame order. Reject stale schedule and completion ABA at the new owner boundary.

Co-Authored-By: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-21 09:37:02 +02:00
parent 9760ff5a8d
commit f004ac5562
14 changed files with 963 additions and 438 deletions

View file

@ -5,7 +5,6 @@ using AcDream.Core.Physics;
using AcDream.Core.Physics.Motion;
using AcDream.Core.World;
using DatReaderWriter.Types;
using AnimatedEntity = AcDream.App.Rendering.GameWindow.AnimatedEntity;
using RemoteMotion = AcDream.App.Rendering.GameWindow.RemoteMotion;
namespace AcDream.App.Rendering;
@ -67,7 +66,7 @@ internal sealed class LiveEntityAnimationScheduler
bool localHiddenPartPoseDirty,
int liveCenterX,
int liveCenterY,
Action<uint, AnimatedEntity>? prepareAnimation = null)
Action<LiveEntityRecord, LiveEntityAnimationState>? prepareAnimation = null)
{
_schedules.Clear();
LiveEntityRuntime? runtime = _liveEntities();
@ -83,14 +82,12 @@ internal sealed class LiveEntityAnimationScheduler
continue;
}
AnimatedEntity? animation = record.AnimationRuntime as AnimatedEntity;
LiveEntityAnimationState? animation = record.AnimationRuntime as LiveEntityAnimationState;
RemoteMotion? remote = record.RemoteMotionRuntime as RemoteMotion;
ProjectileController.Runtime? projectile =
record.ProjectileRuntime as ProjectileController.Runtime;
ulong objectClockEpoch = record.ObjectClockEpoch;
if (animation is not null)
prepareAnimation?.Invoke(record.ServerGuid, animation);
if (!IsCurrent(
runtime,
record,
@ -101,6 +98,22 @@ internal sealed class LiveEntityAnimationScheduler
objectClockEpoch))
continue;
if (animation is not null)
{
prepareAnimation?.Invoke(record, animation);
if (!IsCurrent(
runtime,
record,
entity,
animation,
remote,
projectile,
objectClockEpoch))
{
continue;
}
}
LiveEntityAnimationSchedule schedule = AdvanceRecord(
runtime,
record,
@ -126,7 +139,14 @@ internal sealed class LiveEntityAnimationScheduler
projectile,
objectClockEpoch))
{
_schedules[entity.Id] = schedule;
_schedules[entity.Id] = schedule with
{
Record = record,
Entity = entity,
Animation = animation,
ObjectClockEpoch = objectClockEpoch,
ProjectionMutationVersion = record.ProjectionMutationVersion,
};
}
}
@ -137,7 +157,7 @@ internal sealed class LiveEntityAnimationScheduler
LiveEntityRuntime runtime,
LiveEntityRecord record,
WorldEntity entity,
AnimatedEntity? animation,
LiveEntityAnimationState? animation,
RemoteMotion? remote,
ProjectileController.Runtime? projectile,
float elapsedSeconds,
@ -472,7 +492,7 @@ internal sealed class LiveEntityAnimationScheduler
LiveEntityRuntime runtime,
LiveEntityRecord record,
WorldEntity entity,
AnimatedEntity? animation,
LiveEntityAnimationState? animation,
RemoteMotion? remote,
ProjectileController.Runtime? projectile,
ulong objectClockEpoch) =>
@ -506,4 +526,9 @@ internal sealed class LiveEntityAnimationScheduler
internal readonly record struct LiveEntityAnimationSchedule(
IReadOnlyList<PartTransform>? SequenceFrames,
float LegacyAdvanceSeconds,
bool ComposeParts);
bool ComposeParts,
LiveEntityRecord? Record = null,
WorldEntity? Entity = null,
LiveEntityAnimationState? Animation = null,
ulong ObjectClockEpoch = 0UL,
ulong ProjectionMutationVersion = 0UL);