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

@ -32,7 +32,29 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
bool HasLastStreamDispatchFailure,
int PendingDispatchCount,
bool IsDispatching,
bool IsSessionClearInProgress);
bool IsSessionClearInProgress,
bool IsDisposed)
{
public bool IsConverged =>
IsDisposed
&& ActiveEntityCount == 0
&& TeardownEntityCount == 0
&& ClaimedLocalIdCount == 0
&& AcceptedSnapshotCount == 0
&& UnresolvedParentRelationCount == 0
&& StagedParentRelationCount == 0
&& RecoveryParentRelationCount == 0
&& CommittedParentRelationCount == 0
&& ObjectCount == 0
&& ContainerCount == 0
&& ContainerProjectionCount == 0
&& EquipmentOwnerCount == 0
&& PendingMoveCount == 0
&& StreamSubscriberCount == 0
&& PendingDispatchCount == 0
&& !IsDispatching
&& !IsSessionClearInProgress;
}
/// <summary>
/// One exact DeleteObject acceptance issued by a
@ -140,7 +162,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Events.LastDispatchFailure is not null,
Events.PendingDispatchCount,
Events.IsDispatching,
_sessionClearInProgress);
_sessionClearInProgress,
_disposed);
}
public void BindEventContext(
@ -355,17 +378,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Entities.RetainTeardown(canonical);
try
{
Physics.RemoveSpatialProjection(canonical);
Entities.SetRemoteMotion(canonical, null);
Entities.SetRemoteMotionBindingInProgress(canonical, false);
Entities.SetProjectile(canonical, null);
Entities.SetProjectileBindingInProgress(canonical, false);
Entities.SetRequiresRemotePlacementRuntime(canonical, false);
Entities.SetPhysicsHost(canonical, null);
Entities.SetPhysicsBody(canonical, null);
Entities.SetPhysicsBodyAcquisitionInProgress(canonical, false);
Entities.SetHasPartArray(canonical, false);
Entities.ReleaseLocalId(canonical);
CompleteProjectionRetirement(canonical);
}
finally
{
@ -379,6 +392,30 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
}
}
/// <summary>
/// Completes the presentation acknowledgement for one retained
/// incarnation. Runtime retires every presentation-independent component
/// and releases its local identity only after the graphical host has
/// finished using those exact components for ExitWorld teardown.
/// </summary>
internal void CompleteProjectionRetirement(
RuntimeEntityRecord canonical)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(canonical);
Physics.RemoveSpatialProjection(canonical);
Entities.SetRemoteMotion(canonical, null);
Entities.SetRemoteMotionBindingInProgress(canonical, false);
Entities.SetProjectile(canonical, null);
Entities.SetProjectileBindingInProgress(canonical, false);
Entities.SetRequiresRemotePlacementRuntime(canonical, false);
Entities.SetPhysicsHost(canonical, null);
Entities.SetPhysicsBody(canonical, null);
Entities.SetPhysicsBodyAcquisitionInProgress(canonical, false);
Entities.SetHasPartArray(canonical, false);
Entities.ReleaseLocalId(canonical);
}
/// <summary>
/// Applies the retained-object half of an accepted CreateObject while the
/// exact canonical incarnation and its integration version remain current.

View file

@ -85,6 +85,7 @@ internal sealed class RuntimeEntityObjectViews
: IRuntimeEntityView
{
public int Count => owner.Count;
public int MaterializedCount => owner.ClaimedLocalIdCount;
public bool TryGet(
uint serverGuid,

View file

@ -22,6 +22,8 @@ public interface IRuntimeEntityView
{
int Count { get; }
int MaterializedCount { get; }
bool TryGet(uint serverGuid, out RuntimeEntitySnapshot entity);
/// <summary>

View file

@ -12,7 +12,19 @@ public readonly record struct RuntimePhysicsOwnershipSnapshot(
int CollisionAdmissionCount,
int CollisionGenerationCount,
bool OwnsProductionDataCache,
bool IsDisposed);
bool IsDisposed)
{
public bool IsConverged =>
IsDisposed
&& LandblockCount == 0
&& RetainedShadowRegistrationCount == 0
&& SpatialRootCount == 0
&& SpatialRemoteCount == 0
&& SpatialProjectileCount == 0
&& CollisionAdmissionCount == 0
&& CollisionGenerationCount == 0
&& OwnsProductionDataCache;
}
public readonly record struct RuntimePhysicsCellCommit(
RuntimeEntityRecord Record,
@ -437,6 +449,8 @@ public sealed class RuntimePhysicsState : IDisposable
record,
expectedPlacementContract
|| runtime is IRuntimeRemotePlacement);
if (expectedBody is null)
InitializeNewPhysicsBody(record, candidateBody);
Entities.SetRemoteMotion(record, runtime);
Entities.SetPhysicsBody(record, candidateBody);
candidateBody.State = record.FinalPhysicsState;
@ -452,6 +466,52 @@ public sealed class RuntimePhysicsState : IDisposable
}
}
/// <summary>
/// Constructs the one built-in remote-motion component for an exact
/// canonical incarnation. A graphical host may attach animation and pose
/// sinks afterward, but cannot construct or replace simulation state.
/// </summary>
internal RemoteMotion GetOrCreateRemoteMotion(
RuntimeEntityRecord record,
Func<bool>? externalOwnerValid = null)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(record);
EnsureCurrent(record);
if (record.RemoteMotion is { } retained)
{
return retained as RemoteMotion
?? throw new InvalidOperationException(
$"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} owns a non-production remote-motion component.");
}
var created = new RemoteMotion(record.PhysicsBody);
created.Movement.ActivatePhysicsObject = () =>
TryActivateOrdinaryObject(record, created);
SetRemoteMotion(record, created, externalOwnerValid);
return created;
}
internal bool TryActivateOrdinaryObject(
RuntimeEntityRecord record,
IRuntimeRemoteMotion runtime,
Func<bool>? externalOwnerValid = null)
{
ArgumentNullException.ThrowIfNull(record);
ArgumentNullException.ThrowIfNull(runtime);
if (!Entities.IsCurrent(record)
|| !ReferenceEquals(record.RemoteMotion, runtime)
|| (record.FinalPhysicsState & PhysicsStateFlags.Static) != 0
|| !(externalOwnerValid?.Invoke() ?? true))
{
return false;
}
record.ObjectClock.Activate();
runtime.Body.TransientState |= TransientStateFlags.Active;
return true;
}
/// <summary>
/// Acquires the one retail <c>CPhysicsObj</c> body for an exact accepted
/// object incarnation. Factories may cross a host boundary, so ownership
@ -499,6 +559,7 @@ public sealed class RuntimePhysicsState : IDisposable
$"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} acquired two physics bodies within one incarnation.");
}
InitializeNewPhysicsBody(record, candidate);
Entities.SetPhysicsBody(record, candidate);
candidate.State = record.FinalPhysicsState;
SynchronizeBodyActiveState(record);
@ -905,6 +966,7 @@ public sealed class RuntimePhysicsState : IDisposable
{
if (_disposed)
return;
Engine.Clear();
_spatialRemotes.Clear();
_spatialProjectiles.Clear();
_spatialRoots.Clear();
@ -1039,4 +1101,22 @@ public sealed class RuntimePhysicsState : IDisposable
else
body.TransientState &= ~TransientStateFlags.Active;
}
/// <summary>
/// Retail <c>CPhysicsObj::set_description</c> (0x00514F40) installs the
/// accepted CreateObject velocity and omega once, when the incarnation's
/// CPhysicsObj is first acquired. Later SetState never replays them.
/// </summary>
private static void InitializeNewPhysicsBody(
RuntimeEntityRecord record,
PhysicsBody body)
{
body.State = record.FinalPhysicsState;
if (record.Snapshot.Physics is not { } physics)
return;
if (physics.Velocity is { } velocity && IsFinite(velocity))
body.set_velocity(velocity);
if (physics.AngularVelocity is { } omega && IsFinite(omega))
body.Omega = omega;
}
}

View file

@ -0,0 +1,44 @@
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Physics;
namespace AcDream.Runtime;
/// <summary>
/// One allocation-free terminal ownership ledger for the complete J5
/// presentation-independent simulation graph. It owns no mutable state.
/// Graphical and no-window hosts inspect the same canonical owners.
/// </summary>
public readonly record struct RuntimeSimulationOwnershipSnapshot(
RuntimeEntityObjectOwnershipSnapshot EntityObjects,
RuntimePhysicsOwnershipSnapshot Physics,
RuntimeGameplayOwnershipSnapshot Gameplay)
{
public bool IsConverged =>
EntityObjects.IsConverged
&& Physics.IsConverged
&& Gameplay.IsConverged;
}
public static class RuntimeSimulationOwnership
{
public static RuntimeSimulationOwnershipSnapshot Capture(
RuntimeEntityObjectLifetime entityObjects,
RuntimeInventoryState inventory,
RuntimeCharacterState character,
RuntimeCommunicationState communication,
RuntimeActionState actions,
RuntimeLocalPlayerMovementState movement)
{
ArgumentNullException.ThrowIfNull(entityObjects);
return new RuntimeSimulationOwnershipSnapshot(
entityObjects.CaptureOwnership(),
entityObjects.Physics.CaptureOwnership(),
RuntimeGameplayOwnership.Capture(
inventory,
character,
communication,
actions,
movement));
}
}