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

@ -307,6 +307,74 @@ public class LandblockStreamerTests
Assert.NotEqual(testThreadId, loaderThreadId.Value);
}
[Fact]
public async Task DisposeAndConcurrentDisposeWaitForInFlightLoad()
{
using var entered = new ManualResetEventSlim();
using var release = new ManualResetEventSlim();
var streamer = new LandblockStreamer(loadLandblock: _ =>
{
entered.Set();
release.Wait();
return null;
});
try
{
streamer.Start();
streamer.EnqueueLoad(0x12340000u);
Assert.True(entered.Wait(TimeSpan.FromSeconds(2)));
Task firstDispose = Task.Run(streamer.Dispose);
Task secondDispose = Task.Run(streamer.Dispose);
await Task.Delay(50);
Assert.False(firstDispose.IsCompleted);
Assert.False(secondDispose.IsCompleted);
release.Set();
await Task.WhenAll(firstDispose, secondDispose).WaitAsync(TimeSpan.FromSeconds(2));
}
finally
{
release.Set();
streamer.Dispose();
}
}
[Fact]
public void DisposeRejectsEveryEnqueueKind()
{
var streamer = new LandblockStreamer(loadLandblock: _ => null);
streamer.Start();
streamer.Dispose();
Assert.Throws<ObjectDisposedException>(() => streamer.EnqueueLoad(0x12340000u));
Assert.Throws<ObjectDisposedException>(() => streamer.EnqueueUnload(0x12340000u));
Assert.Throws<ObjectDisposedException>(streamer.ClearPendingLoads);
}
[Fact]
public async Task ConcurrentStartAndDisposeLeaveAClosedStreamer()
{
for (int iteration = 0; iteration < 20; iteration++)
{
var streamer = new LandblockStreamer(loadLandblock: _ => null);
Exception? startFailure = null;
Task start = Task.Run(() =>
{
try { streamer.Start(); }
catch (ObjectDisposedException ex) { startFailure = ex; }
});
Task dispose = Task.Run(streamer.Dispose);
await Task.WhenAll(start, dispose).WaitAsync(TimeSpan.FromSeconds(2));
Assert.True(startFailure is null or ObjectDisposedException);
Assert.Throws<ObjectDisposedException>(() => streamer.EnqueueLoad(0x12340000u));
streamer.Dispose();
}
}
private static async Task<LandblockStreamResult> DrainFirstAsync(LandblockStreamer streamer)
{
for (int i = 0; i < SpinMaxIterations; i++)