refactor(runtime): extract the live object frame

This commit is contained in:
Erik 2026-07-22 00:42:26 +02:00
parent 99a3e819c4
commit 4e4aac2c5a
27 changed files with 1217 additions and 371 deletions

View file

@ -1,5 +1,7 @@
using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.World;
using AcDream.Content;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
@ -9,6 +11,29 @@ using RemoteMotion = AcDream.App.Physics.RemoteMotion;
namespace AcDream.App.Physics;
internal interface IProjectileSetupResolver
{
Setup? Resolve(uint setupId);
}
internal sealed class DatProjectileSetupResolver : IProjectileSetupResolver
{
private readonly IDatReaderWriter _dats;
private readonly object _datLock;
public DatProjectileSetupResolver(IDatReaderWriter dats, object datLock)
{
_dats = dats ?? throw new ArgumentNullException(nameof(dats));
_datLock = datLock ?? throw new ArgumentNullException(nameof(datLock));
}
public Setup? Resolve(uint setupId)
{
lock (_datLock)
return _dats.Get<Setup>(setupId);
}
}
/// <summary>
/// Update-thread presentation owner for live retail missiles. Logical identity,
/// generation, accepted network state, and teardown remain owned by
@ -56,26 +81,26 @@ internal sealed class ProjectileController
private readonly LiveEntityRuntime _liveEntities;
private readonly ProjectilePhysicsStepper _stepper;
private readonly ShadowObjectRegistry _shadows;
private readonly Func<uint, Setup?> _setupResolver;
private readonly Action<WorldEntity> _publishRootPose;
private readonly Func<(int X, int Y)> _liveCenterProvider;
private readonly IProjectileSetupResolver? _setupResolver;
private readonly IEntityRootPosePublisher? _rootPoses;
private readonly LiveWorldOriginState? _origin;
private readonly List<LiveEntityRecord> _spatialProjectileSnapshot = new();
private double _lastFiniteGameTime;
internal ProjectileController(
LiveEntityRuntime liveEntities,
PhysicsEngine physicsEngine,
Func<uint, Setup?>? setupResolver = null,
Action<WorldEntity>? publishRootPose = null,
Func<(int X, int Y)>? liveCenterProvider = null)
IProjectileSetupResolver? setupResolver = null,
IEntityRootPosePublisher? rootPoses = null,
LiveWorldOriginState? origin = null)
{
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
ArgumentNullException.ThrowIfNull(physicsEngine);
_stepper = new ProjectilePhysicsStepper(physicsEngine);
_shadows = physicsEngine.ShadowObjects;
_setupResolver = setupResolver ?? (_ => null);
_publishRootPose = publishRootPose ?? (_ => { });
_liveCenterProvider = liveCenterProvider ?? (() => (0, 0));
_setupResolver = setupResolver;
_rootPoses = rootPoses;
_origin = origin;
_liveEntities.ProjectionVisibilityChanged += OnProjectionVisibilityChanged;
}
@ -322,7 +347,7 @@ internal sealed class ProjectileController
{
SuspendOutsideWorld(runtime, entity.Id);
}
_publishRootPose(entity);
_rootPoses?.UpdateRoot(entity);
return true;
}
@ -472,7 +497,7 @@ internal sealed class ProjectileController
// A newer State packet can make an already materialized object a
// missile. TryBind adopts an existing MovementManager body so the
// live record still represents retail's one CPhysicsObj.
Setup? setup = _setupResolver(entity.SourceGfxObjOrSetupId);
Setup? setup = _setupResolver?.Resolve(entity.SourceGfxObjOrSetupId);
if (setup is null)
{
DiagnosticSink?.Invoke(
@ -617,7 +642,7 @@ internal sealed class ProjectileController
{
SuspendOutsideWorld(runtime, entity.Id);
}
_publishRootPose(entity);
_rootPoses?.UpdateRoot(entity);
return true;
}
@ -712,7 +737,7 @@ internal sealed class ProjectileController
record.FullCellId,
liveCenterX,
liveCenterY);
_publishRootPose(entity);
_rootPoses?.UpdateRoot(entity);
}
if (IsHidden(record))
@ -909,7 +934,7 @@ internal sealed class ProjectileController
current.FullCellId,
liveCenterX,
liveCenterY);
_publishRootPose(entity);
_rootPoses?.UpdateRoot(entity);
if (!IsCurrentQuantumIdentity(step))
return false;
}
@ -966,7 +991,8 @@ internal sealed class ProjectileController
// not lazily on the next simulated quantum: a zero-velocity or
// newly ordinary retained projectile may have no later projectile
// step from which to repair its shadow membership.
(int liveCenterX, int liveCenterY) = _liveCenterProvider();
int liveCenterX = _origin?.CenterX ?? 0;
int liveCenterY = _origin?.CenterY ?? 0;
ShadowPositionSynchronizer.Sync(
_shadows,
entity.Id,
@ -975,7 +1001,7 @@ internal sealed class ProjectileController
record.FullCellId,
liveCenterX,
liveCenterY);
_publishRootPose(entity);
_rootPoses?.UpdateRoot(entity);
return;
}