refactor(world): extract live entity hydration
This commit is contained in:
parent
27dcd0ecb7
commit
d10c5f2d54
17 changed files with 4310 additions and 1269 deletions
1119
src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs
Normal file
1119
src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -36,7 +36,7 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
private ParentAttachmentState Relations => _liveEntities.ParentAttachments;
|
||||
|
||||
/// <summary>Raised after the attached projection is fully registered.</summary>
|
||||
public event Action<uint>? EntityReady;
|
||||
internal event Action<LiveEntityReadyCandidate>? EntityReady;
|
||||
/// <summary>Raised after an attached projection has its composed pose.</summary>
|
||||
public event Action<uint>? ProjectionPoseReady;
|
||||
/// <summary>Raised when an attached projection leaves its cell presentation.</summary>
|
||||
|
|
@ -448,14 +448,46 @@ public sealed class EquippedChildRenderController : IDisposable
|
|||
// CPhysicsObj::set_hidden (0x00514C60).
|
||||
_liveEntities.SetAttachedChildNoDraw(childGuid, noDraw: true);
|
||||
}
|
||||
ulong readyCreateIntegrationVersion = childRecord.CreateIntegrationVersion;
|
||||
PublishChildPose(entity, parentWorld, parentEntity.ParentCellId, pose);
|
||||
Console.WriteLine(
|
||||
$"equipment: attached child=0x{childGuid:X8} parent=0x{pending.ParentGuid:X8} " +
|
||||
$"location={parentLocation} placement={placement}");
|
||||
Relations.MarkProjected(pending, candidateKind);
|
||||
ProjectionPoseReady?.Invoke(childGuid);
|
||||
EntityReady?.Invoke(childGuid);
|
||||
return true;
|
||||
return PublishEntityReadyExact(
|
||||
_liveEntities,
|
||||
childRecord,
|
||||
readyCreateIntegrationVersion,
|
||||
entity,
|
||||
EntityReady);
|
||||
}
|
||||
|
||||
internal static bool PublishEntityReadyExact(
|
||||
LiveEntityRuntime runtime,
|
||||
LiveEntityRecord expectedRecord,
|
||||
ulong expectedCreateIntegrationVersion,
|
||||
WorldEntity expectedEntity,
|
||||
Action<LiveEntityReadyCandidate>? publish)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(runtime);
|
||||
ArgumentNullException.ThrowIfNull(expectedRecord);
|
||||
ArgumentNullException.ThrowIfNull(expectedEntity);
|
||||
if (!runtime.IsCurrentCreateIntegration(
|
||||
expectedRecord,
|
||||
expectedCreateIntegrationVersion)
|
||||
|| !ReferenceEquals(expectedRecord.WorldEntity, expectedEntity))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
publish?.Invoke(new LiveEntityReadyCandidate(
|
||||
expectedRecord,
|
||||
expectedCreateIntegrationVersion));
|
||||
return runtime.IsCurrentCreateIntegration(
|
||||
expectedRecord,
|
||||
expectedCreateIntegrationVersion)
|
||||
&& ReferenceEquals(expectedRecord.WorldEntity, expectedEntity);
|
||||
}
|
||||
|
||||
private void PublishChildPose(
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,105 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,26 @@ internal static class SpawnMotionInitializer
|
|||
return sequencer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replays description-time MovementData into the same retained PartArray
|
||||
/// sequencer. This is used only when a fresher same-incarnation CreateObject
|
||||
/// interrupted initial hydration before the object crossed its ready edge;
|
||||
/// completed objects consume the packet through SetObjectMovement instead.
|
||||
/// </summary>
|
||||
public static void Reinitialize(
|
||||
AnimationSequencer sequencer,
|
||||
MotionTable motionTable,
|
||||
CreateObject.ServerMotionState? wireState)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(sequencer);
|
||||
ArgumentNullException.ThrowIfNull(motionTable);
|
||||
Plan plan = ResolvePlan(motionTable, wireState);
|
||||
sequencer.Reset();
|
||||
sequencer.InitializeState();
|
||||
sequencer.SetCycle(plan.Style, plan.Motion);
|
||||
sequencer.Manager.HandleEnterWorld();
|
||||
}
|
||||
|
||||
internal static Plan ResolvePlan(
|
||||
MotionTable motionTable,
|
||||
CreateObject.ServerMotionState? wireState)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue