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:
parent
c30a3efeb0
commit
cdee7a4b49
57 changed files with 1013 additions and 410 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ internal sealed class RuntimeEntityObjectViews
|
|||
: IRuntimeEntityView
|
||||
{
|
||||
public int Count => owner.Count;
|
||||
public int MaterializedCount => owner.ClaimedLocalIdCount;
|
||||
|
||||
public bool TryGet(
|
||||
uint serverGuid,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ public interface IRuntimeEntityView
|
|||
{
|
||||
int Count { get; }
|
||||
|
||||
int MaterializedCount { get; }
|
||||
|
||||
bool TryGet(uint serverGuid, out RuntimeEntitySnapshot entity);
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
src/AcDream.Runtime/RuntimeSimulationOwnership.cs
Normal file
44
src/AcDream.Runtime/RuntimeSimulationOwnership.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue