acdream/src/AcDream.App/Rendering/LiveEntityCreateSupersessionRecovery.cs

105 lines
3.9 KiB
C#

using AcDream.App.World;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using DatReaderWriter.DBObjs;
namespace AcDream.App.Rendering;
/// <summary>
/// Completes a fresher same-incarnation CreateObject after it interrupted an
/// active projection transaction. Logical entity/resource ownership is
/// retained; the exact current appearance is republished, the plugin/current
/// snapshot is refreshed, and the retained animation owner is synchronized in
/// that order. Every potentially reentrant stage is guarded by the monotonic
/// Create-integration authority.
/// </summary>
internal static class LiveEntityCreateSupersessionRecovery
{
public static bool TryApply(
LiveEntityRuntime runtime,
LiveEntityRecord expectedRecord,
ulong expectedCreateIntegrationVersion,
Func<LiveEntityAppearanceUpdateState?> captureAppearance,
Func<LiveEntityAppearanceUpdateState, bool> publishAppearance,
Action<LiveEntityAppearanceUpdateState> publishCurrentSnapshot,
Action<LiveEntityAppearanceUpdateState> synchronizeAnimation)
{
ArgumentNullException.ThrowIfNull(runtime);
ArgumentNullException.ThrowIfNull(expectedRecord);
ArgumentNullException.ThrowIfNull(captureAppearance);
ArgumentNullException.ThrowIfNull(publishAppearance);
ArgumentNullException.ThrowIfNull(publishCurrentSnapshot);
ArgumentNullException.ThrowIfNull(synchronizeAnimation);
if (!runtime.IsCurrentCreateIntegration(
expectedRecord,
expectedCreateIntegrationVersion)
|| captureAppearance() is not { } visualUpdate
|| !runtime.IsCurrentCreateIntegration(
expectedRecord,
expectedCreateIntegrationVersion)
|| !publishAppearance(visualUpdate)
|| !runtime.IsCurrentCreateIntegration(
expectedRecord,
expectedCreateIntegrationVersion))
{
return false;
}
publishCurrentSnapshot(visualUpdate);
if (!runtime.IsCurrentCreateIntegration(
expectedRecord,
expectedCreateIntegrationVersion))
{
return false;
}
synchronizeAnimation(visualUpdate);
return runtime.IsCurrentCreateIntegration(
expectedRecord,
expectedCreateIntegrationVersion);
}
}
/// <summary>
/// Reconciles the exceptional case where a fresher same-incarnation Create
/// interrupts initial PartArray construction. Completed owners already route
/// MovementData through SetObjectMovement and therefore retain their exact
/// sequencer, dispatch sink, and paired manager/interpreter queues.
/// </summary>
internal static class LiveEntityCreateAnimationSynchronization
{
public static bool TrySynchronizeInterruptedInitialOwner(
LiveEntityRecord record,
LiveEntityAnimationState animation,
Animation? canonicalAnimation,
int canonicalLowFrame,
int canonicalHighFrame,
float canonicalFramerate,
MotionTable? motionTable,
CreateObject.ServerMotionState? wireState)
{
ArgumentNullException.ThrowIfNull(record);
ArgumentNullException.ThrowIfNull(animation);
if (record.InitialHydrationCompleted)
return false;
if (canonicalAnimation is not null)
{
animation.Animation = canonicalAnimation;
animation.LowFrame = canonicalLowFrame;
animation.HighFrame = canonicalHighFrame;
animation.Framerate = canonicalFramerate;
animation.CurrFrame = canonicalLowFrame;
}
if (animation.Sequencer is { } sequencer && motionTable is not null)
{
SpawnMotionInitializer.Reinitialize(
sequencer,
motionTable,
wireState);
}
return true;
}
}