refactor(runtime): close simulation ownership

Move remote-motion construction, CreateObject vector initialization, final simulation-component retirement, and the combined J5 ownership ledger into Runtime. Delete App compatibility views and moved-state reconstruction while preserving the existing graphical projection and retail update order.
This commit is contained in:
Erik 2026-07-26 15:53:31 +02:00
parent c30a3efeb0
commit cdee7a4b49
57 changed files with 1013 additions and 410 deletions

View file

@ -223,33 +223,27 @@ public sealed class LiveEntityRecord
public PhysicsBody? PhysicsBody
{
get => Canonical.PhysicsBody;
internal set => _directory.SetPhysicsBody(Canonical, value);
}
internal bool PhysicsBodyAcquisitionInProgress
{
get => Canonical.PhysicsBodyAcquisitionInProgress;
set => _directory.SetPhysicsBodyAcquisitionInProgress(Canonical, value);
}
public ILiveEntityAnimationRuntime? AnimationRuntime { get; internal set; }
public ILiveEntityRemoteMotionRuntime? RemoteMotionRuntime
public IRuntimeRemoteMotion? RemoteMotionRuntime
{
get => Canonical.RemoteMotion;
internal set => _directory.SetRemoteMotion(Canonical, value);
}
internal bool RemoteMotionBindingInProgress
{
get => Canonical.RemoteMotionBindingInProgress;
set => _directory.SetRemoteMotionBindingInProgress(Canonical, value);
}
public AcDream.Core.Physics.Motion.IPhysicsObjHost? PhysicsHost
{
get => Canonical.PhysicsHost;
internal set => _directory.SetPhysicsHost(Canonical, value);
}
internal bool RequiresRemotePlacementRuntime
{
get => Canonical.RequiresRemotePlacementRuntime;
set => _directory.SetRequiresRemotePlacementRuntime(Canonical, value);
}
public IRuntimeProjectile? ProjectileRuntime => Canonical.Projectile;
public ILiveEntityEffectProfile? EffectProfile { get; internal set; }
@ -1389,7 +1383,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
ProjectionIsCurrent);
}
public void SetRemoteMotionRuntime(uint serverGuid, ILiveEntityRemoteMotionRuntime runtime)
internal void SetRemoteMotionRuntime(uint serverGuid, IRuntimeRemoteMotion runtime)
{
ArgumentNullException.ThrowIfNull(runtime);
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record))
@ -1442,7 +1436,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
public bool TryGetRemoteMotionRuntime(
uint serverGuid,
out ILiveEntityRemoteMotionRuntime runtime)
out IRuntimeRemoteMotion runtime)
{
if (_projections.TryGetCurrent(
serverGuid,
@ -1457,7 +1451,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
return false;
}
public bool ClearRemoteMotionRuntime(uint serverGuid)
internal bool ClearRemoteMotionRuntime(uint serverGuid)
{
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|| record.RemoteMotionRuntime is null)
@ -1468,6 +1462,26 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
return true;
}
internal RemoteMotion GetOrCreateRemoteMotionRuntime(uint serverGuid)
{
if (!_projections.TryGetCurrent(
serverGuid,
out LiveEntityRecord? record))
{
throw new InvalidOperationException(
$"Cannot acquire remote motion before live entity 0x{serverGuid:X8} exists.");
}
bool ProjectionIsCurrent() =>
_projections.TryGetCurrent(
serverGuid,
out LiveEntityRecord? current)
&& ReferenceEquals(current, record);
return _physics.GetOrCreateRemoteMotion(
record.Canonical,
ProjectionIsCurrent);
}
internal IRuntimeProjectile BindProjectileRuntime(
uint serverGuid,
PhysicsBody body,
@ -1874,27 +1888,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
destination.Add(key.LocalEntityId);
}
/// <summary>
/// Host side of retail <c>CPhysicsObj::set_active(1)</c> for a movement
/// manager owned by this exact incarnation. Static is a required no-op.
/// </summary>
internal bool TryActivateOrdinaryObject(
uint serverGuid,
ILiveEntityRemoteMotionRuntime runtime)
{
ArgumentNullException.ThrowIfNull(runtime);
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|| !ReferenceEquals(record.RemoteMotionRuntime, runtime)
|| (record.FinalPhysicsState & PhysicsStateFlags.Static) != 0)
{
return false;
}
record.ObjectClock.Activate();
runtime.Body.TransientState |= TransientStateFlags.Active;
return true;
}
/// <summary>
/// Commits retail <c>CPhysicsObj::set_velocity</c> (0x005113F0) to the
/// canonical body for this exact incarnation. The body and the retained
@ -1941,41 +1934,19 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
{
ArgumentNullException.ThrowIfNull(record);
ArgumentNullException.ThrowIfNull(body);
if (!IsFinite(velocity)
|| (angularVelocity is { } omega && !IsFinite(omega))
|| !double.IsFinite(currentTime)
|| !_projections.TryGetCurrent(
bool ProjectionIsCurrent() =>
_projections.TryGetCurrent(
record.ServerGuid,
out LiveEntityRecord? current)
|| !ReferenceEquals(current, record)
|| !ReferenceEquals(current.PhysicsBody, body))
{
return false;
}
bool wasBodyActive =
(body.TransientState & TransientStateFlags.Active) != 0;
body.set_velocity(velocity);
if ((current.FinalPhysicsState & PhysicsStateFlags.Static) != 0)
{
// PhysicsBody models set_velocity's leaf write and therefore sets
// Active unconditionally. Retail CPhysicsObj::set_active(1) is a
// no-op for Static, so restore the exact prior bit here.
if (!wasBodyActive)
body.TransientState &= ~TransientStateFlags.Active;
}
else
{
bool clockReactivated = current.ObjectClock.Activate();
// PhysicsBody.LastUpdateTime remains only the compatibility clock
// for older absolute-time helpers; the record clock is canonical.
if (clockReactivated || !wasBodyActive)
body.LastUpdateTime = currentTime;
}
if (angularVelocity is { } acceptedOmega)
body.Omega = acceptedOmega;
return true;
&& ReferenceEquals(current, record)
&& ReferenceEquals(current.PhysicsBody, body);
return _physics.TryCommitAuthoritativeVector(
record.Canonical,
body,
velocity,
angularVelocity,
currentTime,
ProjectionIsCurrent);
}
/// <summary>
@ -2007,7 +1978,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
internal bool IsCurrentSpatialRemoteMotion(
LiveEntityRecord record,
ILiveEntityRemoteMotionRuntime runtime) =>
IRuntimeRemoteMotion runtime) =>
IsCurrentRecord(record)
&& _physics.IsSpatialRemote(record.Canonical, runtime)
&& HasSpatialRuntimeProjection(record);
@ -2546,11 +2517,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
body.TransientState &= ~TransientStateFlags.Active;
}
private static bool IsFinite(Vector3 value) =>
float.IsFinite(value.X)
&& float.IsFinite(value.Y)
&& float.IsFinite(value.Z);
private void PublishProjectionVisibilityChanged(LiveEntityRecord record, bool visible)
{
Delegate[] subscribers = ProjectionVisibilityChanged?.GetInvocationList()
@ -2728,16 +2694,11 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
}
if (record.WorldEntity is not null)
{
_ = RequireProjectionKey(record);
_directory.ReleaseLocalId(record.Canonical);
}
_entityObjects.CompleteProjectionRetirement(
record.Canonical);
record.AnimationRuntime = null;
record.RemoteMotionRuntime = null;
record.PhysicsHost = null;
record.RequiresRemotePlacementRuntime = false;
record.PhysicsBody = null;
record.EffectProfile = null;
record.IsSpatiallyProjected = false;
record.IsSpatiallyVisible = false;

View file

@ -133,36 +133,3 @@ public sealed class LiveEntityAnimationRuntimeView<TAnimation>
throw new NotSupportedException();
}
}
/// <summary>Storage-free typed view over remote-motion components.</summary>
public sealed class LiveEntityRemoteMotionRuntimeView<TRemoteMotion>
where TRemoteMotion : class, ILiveEntityRemoteMotionRuntime
{
private readonly ILiveEntityRuntimeSource _runtime;
internal LiveEntityRemoteMotionRuntimeView(ILiveEntityRuntimeSource runtime) =>
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
public TRemoteMotion this[uint serverGuid]
{
set => (_runtime.Current
?? throw new InvalidOperationException("Live entity runtime is not initialized."))
.SetRemoteMotionRuntime(serverGuid, value);
}
public bool TryGetValue(uint serverGuid, out TRemoteMotion motion)
{
if (_runtime.Current?.TryGetRemoteMotionRuntime(serverGuid, out var found) == true
&& found is TRemoteMotion typed)
{
motion = typed;
return true;
}
motion = null!;
return false;
}
public bool Remove(uint serverGuid) =>
_runtime.Current?.ClearRemoteMotionRuntime(serverGuid) == true;
}