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

@ -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();