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
|
|
@ -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++)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue