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:
parent
3971997689
commit
749e8ceeb1
225 changed files with 29107 additions and 3914 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue