acdream/src/AcDream.App/Rendering/LiveEntityAnimationState.cs
Erik 833520253a fix(animation): harden presentation phase handoffs
Own every borrowed PartArray frame across callbacks, version presentation state across appearance rebinding, reject recursive phase consumption, and preserve static MotionDone when only a stale visual pose is discarded.
2026-07-21 10:01:25 +02:00

89 lines
3.4 KiB
C#

using System.Numerics;
using AcDream.App.World;
using AcDream.Core.Physics;
using AcDream.Core.Physics.Motion;
using AcDream.Core.World;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Types;
namespace AcDream.App.Rendering;
/// <summary>
/// Incarnation-owned live <c>CPartArray</c> presentation state. Time, root
/// motion, and hooks remain owned by <see cref="LiveEntityAnimationScheduler"/>;
/// this component retains the final part-presentation channels only.
/// </summary>
internal sealed class LiveEntityAnimationState : ILiveEntityAnimationRuntime
{
public required WorldEntity Entity;
WorldEntity ILiveEntityAnimationRuntime.Entity => Entity;
uint ILiveEntityAnimationRuntime.CurrentMotion => Sequencer?.CurrentMotion ?? 0u;
public required Setup Setup;
public required Animation Animation;
public required int LowFrame;
public required int HighFrame;
public required float Framerate;
public required float Scale;
public required IReadOnlyList<LiveAnimationPartTemplate> PartTemplate;
public required IReadOnlyList<bool> PartAvailability;
public float CurrFrame;
public AnimationSequencer? Sequencer;
public IReadOnlyList<PartTransform>? PreparedSequenceFrames;
public bool SequenceAdvancedBeforeAnimationPass;
public readonly Frame RootMotionScratch = new();
public readonly MotionDeltaFrame RootMotionDeltaScratch = new();
public readonly List<PartTransform> SequenceFramesScratch = new();
public readonly List<PartTransform> ScheduleFramesScratch = new();
/// <summary>Reusable compact drawable channel, consumed before draw.</summary>
public readonly List<MeshRef> MeshRefsScratch = new();
/// <summary>Reusable indexed rigid/effect channel.</summary>
public readonly List<Matrix4x4> EffectPartPosesScratch = new();
/// <summary>
/// Indexed visible transforms retained separately from the rigid channel
/// because Setup DefaultScale affects geometry but not attachments.
/// </summary>
public readonly List<Matrix4x4> VisualPartPosesScratch = new();
public bool PresentationPosesInitialized;
public ulong PresentationRevision { get; private set; } = 1UL;
public double LastSequenceDiagnosticTime;
public double LastPartDiagnosticTime;
public void InvalidatePresentationPoses()
{
PresentationRevision++;
if (PresentationRevision == 0UL)
PresentationRevision++;
PresentationPosesInitialized = false;
VisualPartPosesScratch.Clear();
EffectPartPosesScratch.Clear();
}
public IReadOnlyList<PartTransform> CaptureSequenceFrames(
IReadOnlyList<PartTransform> source)
{
ArgumentNullException.ThrowIfNull(source);
SequenceFramesScratch.Clear();
for (int i = 0; i < source.Count; i++)
SequenceFramesScratch.Add(source[i]);
return SequenceFramesScratch;
}
public IReadOnlyList<PartTransform> CaptureScheduleFrames(
IReadOnlyList<PartTransform> source)
{
ArgumentNullException.ThrowIfNull(source);
ScheduleFramesScratch.Clear();
for (int i = 0; i < source.Count; i++)
ScheduleFramesScratch.Add(source[i]);
return ScheduleFramesScratch;
}
}
internal readonly record struct LiveAnimationPartTemplate(
uint GfxObjId,
IReadOnlyDictionary<uint, uint>? SurfaceOverrides,
bool IsDrawable);