fix(rendering): bound portal resource lifetime

Separate logical ownership, render publication, and GPU retirement across live entities, landblocks, particles, textures, mesh arenas, portal/UI teardown, and per-frame scratch storage. Add bounded DAT/texture caches, upload budgets, three-frame fence retirement, exact-incarnation appearance reconciliation, frame pacing, and extensive lifetime conformance coverage.\n\nThe seven-destination connected route now cuts peak working/private memory roughly in half, returns Caul to 125-153 FPS locally, and produces no WER or AMD reset.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-18 21:35:16 +02:00
parent 3971997689
commit 749e8ceeb1
225 changed files with 29107 additions and 3914 deletions

View file

@ -37,6 +37,12 @@ public sealed class EntityEffectController : IAnimationHookSink
private readonly Dictionary<uint, Queue<PendingEffect>> _pendingByServerGuid = new();
private readonly Dictionary<uint, WorldEntity> _staticOwners = new();
private readonly HashSet<uint> _syntheticOwners = new();
private readonly HashSet<LiveEntityRecord> _dirtyLiveOwners =
new(ReferenceEqualityComparer.Instance);
private readonly List<LiveEntityRecord> _dirtyLiveOwnerOrder = [];
private readonly List<LiveEntityRecord> _dirtyLiveOwnerSnapshot = [];
private readonly List<uint> _readyGuidSnapshot = [];
private uint _posePublishLocalId;
private Action<string>? _diagnosticSink = Console.WriteLine;
public EntityEffectController(
@ -58,6 +64,8 @@ public sealed class EntityEffectController : IAnimationHookSink
_ownerUnregistered = ownerUnregistered ?? (_ => { });
_ownerSoundTableChanged = ownerSoundTableChanged ?? ((_, _) => { });
_runner.DiagnosticSink = message => _diagnosticSink?.Invoke(message);
_poses.EffectPoseChanged += OnEffectPoseChanged;
_liveEntities.ProjectionVisibilityChanged += OnProjectionVisibilityChanged;
}
public Action<string>? DiagnosticSink
@ -68,6 +76,7 @@ public sealed class EntityEffectController : IAnimationHookSink
public int PendingPacketCount => _pendingByServerGuid.Values.Sum(queue => queue.Count);
public int ReadyOwnerCount => _profilesByLocalId.Count;
internal int LastPoseRefreshOwnerVisitCount { get; private set; }
public void HandleDirect(PlayPhysicsScript message)
{
@ -222,7 +231,9 @@ public sealed class EntityEffectController : IAnimationHookSink
public void ClearNetworkState()
{
foreach (uint serverGuid in _readyGenerationByServerGuid.Keys.ToArray())
_readyGuidSnapshot.Clear();
_readyGuidSnapshot.AddRange(_readyGenerationByServerGuid.Keys);
foreach (uint serverGuid in _readyGuidSnapshot)
{
if (_liveEntities.TryGetLocalEntityId(serverGuid, out uint localId))
{
@ -233,20 +244,54 @@ public sealed class EntityEffectController : IAnimationHookSink
}
_readyGenerationByServerGuid.Clear();
_pendingByServerGuid.Clear();
_dirtyLiveOwners.Clear();
_dirtyLiveOwnerOrder.Clear();
_dirtyLiveOwnerSnapshot.Clear();
}
/// <summary>Refreshes every live root after animation/movement.</summary>
/// <summary>
/// Publishes only roots dirtied by movement, animation, cell projection,
/// or an authoritative position update. The queue is detached before
/// callbacks run, so a callback-produced mutation is retained for the
/// following update rather than invalidating this traversal.
/// </summary>
public void RefreshLiveOwnerPoses()
{
foreach (uint serverGuid in _readyGenerationByServerGuid.Keys.ToArray())
LastPoseRefreshOwnerVisitCount = 0;
if (_dirtyLiveOwnerOrder.Count == 0)
return;
_dirtyLiveOwnerSnapshot.Clear();
_dirtyLiveOwnerSnapshot.AddRange(_dirtyLiveOwnerOrder);
_dirtyLiveOwnerOrder.Clear();
_dirtyLiveOwners.Clear();
foreach (LiveEntityRecord record in _dirtyLiveOwnerSnapshot)
{
if (!TryGetReadyLocalId(serverGuid, out uint localId))
if (!_liveEntities.TryGetRecord(record.ServerGuid, out LiveEntityRecord current)
|| !ReferenceEquals(current, record)
|| !TryGetReadyLocalId(record.ServerGuid, out uint localId)
|| record.WorldEntity?.Id != localId)
{
continue;
RefreshLiveAnchor(serverGuid, localId);
TryReplayPending(serverGuid, localId);
}
LastPoseRefreshOwnerVisitCount++;
RefreshLiveAnchor(record.ServerGuid, localId);
TryReplayPending(record.ServerGuid, localId);
}
}
/// <summary>
/// Marks an accepted authoritative root mutation. The record reference,
/// rather than only its server GUID, isolates a queued update from later
/// delete/recreate reuse of that GUID.
/// </summary>
public void MarkLiveOwnerPoseDirty(uint serverGuid)
{
if (_liveEntities.TryGetRecord(serverGuid, out LiveEntityRecord record))
MarkLiveOwnerPoseDirty(record);
}
public bool PlayDirect(uint ownerLocalId, uint scriptDid)
{
if (!CanStartOwner(ownerLocalId))
@ -411,6 +456,37 @@ public sealed class EntityEffectController : IAnimationHookSink
return false;
}
private void OnEffectPoseChanged(uint localId)
{
if (_posePublishLocalId == localId)
return;
if (_liveEntities.TryGetServerGuid(localId, out uint serverGuid)
&& _liveEntities.TryGetRecord(serverGuid, out LiveEntityRecord record))
{
MarkLiveOwnerPoseDirty(record);
}
}
private void OnProjectionVisibilityChanged(LiveEntityRecord record, bool visible)
{
if (visible)
MarkLiveOwnerPoseDirty(record);
}
private void MarkLiveOwnerPoseDirty(LiveEntityRecord record)
{
if (!_readyGenerationByServerGuid.TryGetValue(
record.ServerGuid,
out ushort readyGeneration)
|| readyGeneration != record.Generation
|| !_dirtyLiveOwners.Add(record))
{
return;
}
_dirtyLiveOwnerOrder.Add(record);
}
private void RefreshLiveAnchor(uint serverGuid, uint localId)
{
if (_liveEntities.TryGetRecord(serverGuid, out LiveEntityRecord record)
@ -423,7 +499,18 @@ public sealed class EntityEffectController : IAnimationHookSink
// bookkeeping Position would collapse a weapon effect to the
// parent's origin.
if (record.ProjectionKind is LiveEntityProjectionKind.World)
_poses.UpdateRoot(entity);
{
uint previousPublish = _posePublishLocalId;
_posePublishLocalId = localId;
try
{
_poses.UpdateRoot(entity);
}
finally
{
_posePublishLocalId = previousPublish;
}
}
Vector3 anchor = _poses.TryGetRootPose(localId, out Matrix4x4 rootWorld)
? rootWorld.Translation