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

@ -2,7 +2,6 @@ using DatReaderWriter;
using DatReaderWriter.Enums;
using DatReaderWriter.Lib.IO;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
@ -58,9 +57,44 @@ public sealed class DatCollectionAdapter : IDatReaderWriter {
public string SourceDirectory => _dats.Options.DatDirectory ?? string.Empty;
public IDatDatabase Portal => _portal;
public IDatDatabase Cell => _cell;
public ReadOnlyDictionary<uint, IDatDatabase> CellRegions => _cellRegions;
public IDatDatabase HighRes => _highRes;
public IDatDatabase Language => _language;
public IDatDatabase Local => _language;
[return: MaybeNull]
public T Get<T>(uint fileId) where T : IDBObj =>
TryGet<T>(fileId, out var value) ? value : default;
public bool TryGet<T>(uint fileId, [MaybeNullWhen(false)] out T value) where T : IDBObj {
if (typeof(T) == typeof(DatReaderWriter.DBObjs.Iteration)) {
throw new Exception(
"Iteration is not a valid type to get from a dat file collection since it is used in all dat files. Use a specific dat like datCollection.Portal.Get<Iteration>()");
}
switch (_dats.TypeToDatFileType<T>()) {
case DatFileType.Cell:
return _cell.TryGet(fileId, out value);
case DatFileType.Portal:
return _portal.TryGet(fileId, out value)
|| _highRes.TryGet(fileId, out value);
case DatFileType.Local:
return _language.TryGet(fileId, out value);
default:
value = default;
return false;
}
}
public IEnumerable<uint> GetAllIdsOfType<T>() where T : IDBObj =>
_dats.TypeToDatFileType<T>() switch {
DatFileType.Cell => _cell.GetAllIdsOfType<T>(),
DatFileType.Portal => _portal.GetAllIdsOfType<T>()
.Concat(_highRes.GetAllIdsOfType<T>()),
DatFileType.Local => _language.GetAllIdsOfType<T>(),
_ => Array.Empty<uint>(),
};
// RegionFileMap is used by some WB internals but not by any acdream consumer.
public ReadOnlyDictionary<uint, uint> RegionFileMap =>
@ -122,8 +156,11 @@ public sealed class DatCollectionAdapter : IDatReaderWriter {
/// </summary>
public sealed class DatDatabaseWrapper : IDatDatabase {
private readonly DatDatabase _db;
private readonly ConcurrentDictionary<(Type, uint), IDBObj> _cache = new();
private readonly object _lock = new();
// One cache per database: a DatCollectionAdapter therefore retains at
// most 4 * 256 decoded entries and 4 * 64 MiB of estimated payload. The
// cache itself documents why estimated bytes are not a hard heap bound.
private readonly BoundedDatObjectCache _cache = new();
private readonly object _databaseLock = new();
public DatDatabaseWrapper(DatDatabase db) {
ArgumentNullException.ThrowIfNull(db);
@ -140,14 +177,19 @@ public sealed class DatDatabaseWrapper : IDatDatabase {
_db.GetAllIdsOfType<T>();
public bool TryGet<T>(uint fileId, [MaybeNullWhen(false)] out T value) where T : IDBObj {
if (_cache.TryGetValue((typeof(T), fileId), out var cached)) {
value = (T)cached;
if (_cache.TryGet(fileId, out value)) {
return true;
}
lock (_lock) {
lock (_databaseLock) {
// A different reader may have populated the cache while this
// caller waited for the serialized DatDatabase read path.
if (_cache.TryGet(fileId, out value)) {
return true;
}
if (_db.TryGet<T>(fileId, out value)) {
_cache.TryAdd((typeof(T), fileId), value);
value = _cache.GetOrAdd(fileId, value);
return true;
}
@ -168,13 +210,13 @@ public sealed class DatDatabaseWrapper : IDatDatabase {
}
public bool TryGetFileBytes(uint fileId, [MaybeNullWhen(false)] out byte[] value) {
lock (_lock) {
lock (_databaseLock) {
return _db.TryGetFileBytes(fileId, out value);
}
}
public bool TryGetFileBytes(uint fileId, ref byte[] bytes, out int bytesRead) {
lock (_lock) {
lock (_databaseLock) {
return _db.TryGetFileBytes(fileId, ref bytes, out bytesRead);
}
}