refactor(runtime): own canonical entity and object lifetime

Introduce one presentation-free RuntimeEntityObjectLifetime for the exact entity directory and ClientObjectTable. Make GameWindow, graphical projections, retained UI, interaction, session routing, create/delete integration, and reset borrow that owner while preserving synchronous retail ordering, dormant retention, and retry semantics.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-26 05:54:46 +02:00
parent d9bf4c4960
commit 5ef8b5371d
40 changed files with 712 additions and 170 deletions

View file

@ -1,7 +1,7 @@
using AcDream.App.Input;
using AcDream.Core.Items;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Runtime.Entities;
namespace AcDream.App.World;
@ -17,7 +17,7 @@ internal interface ILiveEntityPruneSink
internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
{
private readonly LiveEntityRuntime _runtime;
private readonly ClientObjectTable _objects;
private readonly RuntimeEntityObjectLifetime _entityObjects;
private readonly ILiveEntityTeardownCoordinator _teardown;
private readonly ILocalPlayerIdentitySource _identity;
private readonly DormantLiveEntityStore _dormant;
@ -25,14 +25,15 @@ internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
public LiveEntityDeletionController(
LiveEntityRuntime runtime,
ClientObjectTable objects,
RuntimeEntityObjectLifetime entityObjects,
ILiveEntityTeardownCoordinator teardown,
ILocalPlayerIdentitySource identity,
DormantLiveEntityStore? dormant = null,
Action<string>? diagnostic = null)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
_entityObjects = entityObjects
?? throw new ArgumentNullException(nameof(entityObjects));
_teardown = teardown ?? throw new ArgumentNullException(nameof(teardown));
_identity = identity ?? throw new ArgumentNullException(nameof(identity));
_dormant = dormant ?? new DormantLiveEntityStore();
@ -56,11 +57,10 @@ internal sealed class LiveEntityDeletionController : ILiveEntityPruneSink
bool removed = _runtime.UnregisterLiveEntity(
delete,
isLocalPlayer: false,
beforeTeardown: () =>
ObjectTableWiring.ApplyEntityDelete(_objects, delete));
removeRetainedObject: true);
bool removedDormant = _dormant.RemoveExact(delete);
if (!removed && removedDormant)
ObjectTableWiring.ApplyEntityDelete(_objects, delete);
_entityObjects.ApplyAcceptedDormantDelete(delete);
if (removed)
{
_dormant.RemoveExact(delete);

View file

@ -1,6 +1,5 @@
using AcDream.App.Rendering;
using AcDream.App.Input;
using AcDream.Core.Items;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.World;
@ -169,7 +168,7 @@ internal interface ILiveEntityWorldOriginCoordinator
internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoadedSink
{
private readonly LiveEntityRuntime _runtime;
private readonly ClientObjectTable _objects;
private readonly RuntimeEntityObjectLifetime _entityObjects;
private readonly object _datLock;
private readonly ILiveEntityProjectionMaterializer _materializer;
private readonly ILiveEntityRelationshipProjection _relationships;
@ -193,7 +192,7 @@ internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoaded
public LiveEntityHydrationController(
LiveEntityRuntime runtime,
ClientObjectTable objects,
RuntimeEntityObjectLifetime entityObjects,
object datLock,
ILiveEntityProjectionMaterializer materializer,
ILiveEntityRelationshipProjection relationships,
@ -207,7 +206,8 @@ internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoaded
Action<string>? diagnostic = null)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
_entityObjects = entityObjects
?? throw new ArgumentNullException(nameof(entityObjects));
_datLock = datLock ?? throw new ArgumentNullException(nameof(datLock));
_materializer = materializer ?? throw new ArgumentNullException(nameof(materializer));
_relationships = relationships ?? throw new ArgumentNullException(nameof(relationships));
@ -285,15 +285,13 @@ internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoaded
if (_runtime.IsCurrentCreateIntegration(
canonical,
createIntegrationVersion)
&& ObjectTableWiring.ApplyEntitySpawn(
_objects,
&& _entityObjects.ApplyAcceptedSpawn(
canonical,
createIntegrationVersion,
spawn,
replaceGeneration: result.Disposition is
AcDream.Core.Physics.CreateObjectTimestampDisposition.NewGeneration
|| dormantDisposition is DormantCreateDisposition.NewGeneration,
accepting: () => _runtime.IsCurrentCreateIntegration(
canonical,
createIntegrationVersion))
|| dormantDisposition is DormantCreateDisposition.NewGeneration)
&& _runtime.IsCurrentCreateIntegration(
canonical,
createIntegrationVersion))

View file

@ -448,6 +448,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
private readonly GpuWorldState _spatial;
private readonly ILiveEntityResourceLifecycle _resources;
private readonly ILiveEntityRuntimeComponentLifecycle _runtimeComponentLifecycle;
private readonly RuntimeEntityObjectLifetime _entityObjects;
private readonly RuntimeEntityDirectory _directory;
private readonly LiveEntityProjectionStore _projections;
// Logical components survive retail's 25-second leave-visibility lifetime.
@ -471,12 +472,12 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
public LiveEntityRuntime(
GpuWorldState spatial,
ILiveEntityResourceLifecycle resources,
uint firstLocalEntityId = FirstLiveEntityId)
RuntimeEntityObjectLifetime entityObjects)
: this(
spatial,
resources,
NullLiveEntityRuntimeComponentLifecycle.Instance,
firstLocalEntityId)
entityObjects)
{
}
@ -484,12 +485,12 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
GpuWorldState spatial,
ILiveEntityResourceLifecycle resources,
Action<LiveEntityRecord> tearDownRuntimeComponents,
uint firstLocalEntityId = FirstLiveEntityId)
RuntimeEntityObjectLifetime entityObjects)
: this(
spatial,
resources,
new DelegateLiveEntityRuntimeComponentLifecycle(tearDownRuntimeComponents),
firstLocalEntityId)
entityObjects)
{
}
@ -497,13 +498,15 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
GpuWorldState spatial,
ILiveEntityResourceLifecycle resources,
ILiveEntityRuntimeComponentLifecycle? runtimeComponentLifecycle,
uint firstLocalEntityId = FirstLiveEntityId)
RuntimeEntityObjectLifetime entityObjects)
{
_spatial = spatial ?? throw new ArgumentNullException(nameof(spatial));
_resources = resources ?? throw new ArgumentNullException(nameof(resources));
_runtimeComponentLifecycle = runtimeComponentLifecycle
?? throw new ArgumentNullException(nameof(runtimeComponentLifecycle));
_directory = new RuntimeEntityDirectory(firstLocalEntityId);
_entityObjects = entityObjects
?? throw new ArgumentNullException(nameof(entityObjects));
_directory = _entityObjects.Entities;
_projections = new LiveEntityProjectionStore(_directory);
_spatial.LiveProjectionVisibilityChanged += OnSpatialVisibilityChanged;
}
@ -1187,6 +1190,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
public bool UnregisterLiveEntity(
DeleteObject.Parsed delete,
bool isLocalPlayer,
bool removeRetainedObject = false,
Action? beforeTeardown = null)
{
if (_isRegisteringResources)
@ -1208,9 +1212,13 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
&& (record.DeleteAcceptedForTeardown
|| _isClearing
|| _sessionClearPendingFinalization);
RuntimeEntityDeleteAcceptance? acceptedDelete = null;
if (!retryingAcceptedDelete)
{
if (!_directory.TryDelete(delete, isLocalPlayer))
if (!_entityObjects.TryAcceptDelete(
delete,
isLocalPlayer,
out acceptedDelete))
return false;
AdvanceLifetimeMutation(delete.Guid);
ParentAttachments.DeleteGeneration(delete.Guid, delete.InstanceSequence);
@ -1258,6 +1266,18 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
{
if (!retryingAcceptedDelete)
{
if (removeRetainedObject)
{
try
{
_entityObjects.CompleteAcceptedDelete(acceptedDelete!);
}
catch (Exception error)
{
(failures ??= new List<Exception>()).Add(error);
}
}
try
{
beforeTeardown?.Invoke();