refactor(world): canonicalize live physics host ownership

This commit is contained in:
Erik 2026-07-21 14:05:34 +02:00
parent 5882b308c1
commit fcb66198fc
21 changed files with 1546 additions and 323 deletions

View file

@ -1,4 +1,3 @@
using AcDream.App.Rendering;
using AcDream.App.World;
namespace AcDream.App.Physics;
@ -22,7 +21,8 @@ namespace AcDream.App.Physics;
/// </para>
/// </summary>
internal sealed class RemoteMotion :
AcDream.App.World.ILiveEntityRemotePlacementRuntime
AcDream.App.World.ILiveEntityRemotePlacementRuntime,
AcDream.App.World.ILiveEntityCanonicalRuntimeConsumer
{
public AcDream.Core.Physics.PhysicsBody Body { get; }
AcDream.Core.Physics.PhysicsBody AcDream.App.World.ILiveEntityRemoteMotionRuntime.Body => Body;
@ -82,8 +82,13 @@ internal sealed class RemoteMotion :
// voyeur system (retail CPhysicsObj::target_manager). Replaces the
// AP-79 TrackedTarget* poll fields: the MoveToManager's set_target/
// clear_target/quantum seams route here, HandleTargetting ticks per
// frame, and OTHER entities resolve this one via GameWindow._physicsHosts.
public EntityPhysicsHost? Host;
// frame, and OTHER entities resolve this one through LiveEntityRuntime's
// exact-incarnation PhysicsHost slot.
private Func<AcDream.Core.Physics.Motion.IPhysicsObjHost?>? _physicsHostReader;
private bool _fullPhysicsHostBound;
public EntityPhysicsHost? Host => _fullPhysicsHostBound
? _physicsHostReader?.Invoke() as EntityPhysicsHost
: null;
/// <summary>
/// True while a server MoveToObject/MoveToPosition packet is the
/// active locomotion source. Retail runs these through MoveToManager
@ -299,9 +304,42 @@ internal sealed class RemoteMotion :
});
}
public void BindCanonicalRuntime(
Func<AcDream.Core.Physics.Motion.IPhysicsObjHost?> readPhysicsHost,
Func<uint> readCell,
Action<uint> writeCell)
{
ArgumentNullException.ThrowIfNull(readPhysicsHost);
ArgumentNullException.ThrowIfNull(readCell);
ArgumentNullException.ThrowIfNull(writeCell);
if (_physicsHostReader is not null
|| _canonicalCellReader is not null
|| _canonicalCellWriter is not null)
{
throw new InvalidOperationException("The remote canonical runtime context is already bound.");
}
// All validation precedes publication. Nothing below can throw, so a
// rejected context never leaves a half-bound reusable component.
_physicsHostReader = readPhysicsHost;
_canonicalCellReader = readCell;
_canonicalCellWriter = writeCell;
}
public void BindCanonicalCell(Func<uint> read, Action<uint> write)
{
_canonicalCellReader = read ?? throw new ArgumentNullException(nameof(read));
_canonicalCellWriter = write ?? throw new ArgumentNullException(nameof(write));
ArgumentNullException.ThrowIfNull(read);
ArgumentNullException.ThrowIfNull(write);
if (_canonicalCellReader is not null || _canonicalCellWriter is not null)
throw new InvalidOperationException("The remote canonical cell source is already bound.");
_canonicalCellReader = read;
_canonicalCellWriter = write;
}
public void MarkFullPhysicsHostBound()
{
if (_physicsHostReader is null)
throw new InvalidOperationException("The canonical physics-host source is not bound.");
_fullPhysicsHostBound = true;
}
}