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

@ -1,5 +1,6 @@
using DatReaderWriter;
using DatReaderWriter.DBObjs;
using AcDream.Core.Content;
using DatReaderWriter.Types;
using AcDream.Core.Physics;
@ -15,7 +16,7 @@ public static class LandblockLoader
/// Load a single landblock (heightmap + static objects) from the dats.
/// </summary>
/// <returns>Null if the landblock is missing from the cell dat.</returns>
public static LoadedLandblock? Load(DatCollection dats, uint landblockId)
public static LoadedLandblock? Load(IDatObjectSource dats, uint landblockId)
{
var block = dats.Get<LandBlock>(landblockId);
if (block is null)
@ -40,7 +41,7 @@ public static class LandblockLoader
var result = new List<WorldEntity>(info.Objects.Count + info.Buildings.Count);
// When landblockId is non-zero, namespace stab Ids globally:
// 0xC0XXYY00 + n, where XX = lbX byte, YY = lbY byte
// 0xCXXYYIII, where XX = lbX, YY = lbY, III = 12-bit counter
// distinct from the 0x8XXYYIII scenery and 0x4XXYYIII interior
// namespaces in GameWindow.cs. The 0xC0 top byte distinguishes stabs.
//
@ -48,20 +49,21 @@ public static class LandblockLoader
// legacy starting-from-1 monotonic Ids — compatible with their assertions
// which check uniqueness within a single landblock.
//
// The low byte reserves 0 for the namespace base and 1..255 for
// entities. Never wrap into the Y byte: a collision here would corrupt
// every renderer/physics/effect table keyed by WorldEntity.Id.
uint stabIdBase = landblockId == 0
? 0u
: 0xC0000000u | ((landblockId >> 24) & 0xFFu) << 16 | ((landblockId >> 16) & 0xFFu) << 8;
uint nextId = stabIdBase == 0 ? 1u : stabIdBase + 1u;
// The 12-bit allocator fails before crossing into the adjacent
// landblock's Y range; no wrapped id can corrupt renderer, physics, or
// effect tables keyed by WorldEntity.Id.
uint landblockX = (landblockId >> 24) & 0xFFu;
uint landblockY = (landblockId >> 16) & 0xFFu;
uint nextId = landblockId == 0 ? 1u : 0u;
uint AllocateId()
{
if (stabIdBase != 0 && nextId > stabIdBase + 0xFFu)
throw new InvalidDataException(
$"Landblock 0x{landblockId:X8} exceeds the 255-entry static stab id namespace.");
return nextId++;
if (landblockId == 0)
return nextId++;
return LandblockStaticEntityIdAllocator.Allocate(
landblockX,
landblockY,
ref nextId);
}
foreach (var stab in info.Objects)