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
|
|
@ -81,6 +81,313 @@ public sealed class LandblockSpawnAdapterReadinessTests
|
|||
Assert.DoesNotContain(sharedGeometryId, meshes.ReferenceCounts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_BeforeCommitFailure_RetryAcquiresOnlyUnfinishedReferences()
|
||||
{
|
||||
const ulong first = 0x01000010ul;
|
||||
const ulong failed = 0x01000020ul;
|
||||
const ulong last = 0x01000030ul;
|
||||
var meshes = new FaultInjectingMeshAdapter();
|
||||
meshes.FailNext(ReferenceOperation.Increment, failed, committed: false);
|
||||
var adapter = new LandblockSpawnAdapter(meshes);
|
||||
LoadedLandblock landblock = MakeLandblock(0x1234FFFFu, (uint)first, (uint)failed, (uint)last);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => adapter.OnLandblockLoaded(landblock));
|
||||
|
||||
Assert.Equal(1, meshes.ReferenceCounts[first]);
|
||||
Assert.DoesNotContain(failed, meshes.ReferenceCounts);
|
||||
Assert.Equal(1, meshes.ReferenceCounts[last]);
|
||||
Assert.False(adapter.IsLandblockRenderReady(landblock.LandblockId));
|
||||
|
||||
adapter.OnLandblockLoaded(landblock);
|
||||
|
||||
Assert.Equal(1, meshes.ReferenceCounts[first]);
|
||||
Assert.Equal(1, meshes.ReferenceCounts[failed]);
|
||||
Assert.Equal(1, meshes.ReferenceCounts[last]);
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.Increment, first));
|
||||
Assert.Equal(2, meshes.CallCount(ReferenceOperation.Increment, failed));
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.Increment, last));
|
||||
Assert.True(adapter.IsLandblockRenderReady(landblock.LandblockId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_CommittedPreparedPinFailure_RetryDoesNotDoubleAcquire()
|
||||
{
|
||||
const ulong prepared = 0x2_0000_1234ul;
|
||||
var meshes = new FaultInjectingMeshAdapter();
|
||||
meshes.FailNext(ReferenceOperation.PinPrepared, prepared, committed: true);
|
||||
var adapter = new LandblockSpawnAdapter(meshes);
|
||||
LoadedLandblock landblock = MakeLandblock(0x1234FFFFu);
|
||||
|
||||
MeshReferenceMutationException error = Assert.Throws<MeshReferenceMutationException>(
|
||||
() => adapter.OnLandblockLoaded(landblock, new[] { prepared }));
|
||||
|
||||
Assert.True(error.MutationCommitted);
|
||||
Assert.Equal(1, meshes.ReferenceCounts[prepared]);
|
||||
|
||||
adapter.OnLandblockLoaded(landblock, new[] { prepared });
|
||||
|
||||
Assert.Equal(1, meshes.ReferenceCounts[prepared]);
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.PinPrepared, prepared));
|
||||
Assert.True(adapter.IsLandblockRenderReady(landblock.LandblockId));
|
||||
|
||||
adapter.OnLandblockUnloaded(landblock.LandblockId);
|
||||
Assert.Empty(meshes.ReferenceCounts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_CommittedIncrementFailure_RetryDoesNotDoubleAcquire()
|
||||
{
|
||||
const ulong id = 0x01000010ul;
|
||||
var meshes = new FaultInjectingMeshAdapter();
|
||||
meshes.FailNext(ReferenceOperation.Increment, id, committed: true);
|
||||
var adapter = new LandblockSpawnAdapter(meshes);
|
||||
LoadedLandblock landblock = MakeLandblock(0x1234FFFFu, (uint)id);
|
||||
|
||||
MeshReferenceMutationException error = Assert.Throws<MeshReferenceMutationException>(
|
||||
() => adapter.OnLandblockLoaded(landblock));
|
||||
|
||||
Assert.True(error.MutationCommitted);
|
||||
Assert.Equal(1, meshes.ReferenceCounts[id]);
|
||||
|
||||
adapter.OnLandblockLoaded(landblock);
|
||||
|
||||
Assert.Equal(1, meshes.ReferenceCounts[id]);
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.Increment, id));
|
||||
Assert.True(adapter.IsLandblockRenderReady(landblock.LandblockId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unload_BeforeCommitFailure_RetryReleasesOnlyStillHeldReferences()
|
||||
{
|
||||
const ulong first = 0x01000010ul;
|
||||
const ulong failed = 0x01000020ul;
|
||||
const ulong last = 0x01000030ul;
|
||||
var meshes = new FaultInjectingMeshAdapter();
|
||||
var adapter = new LandblockSpawnAdapter(meshes);
|
||||
LoadedLandblock landblock = MakeLandblock(0x1234FFFFu, (uint)first, (uint)failed, (uint)last);
|
||||
adapter.OnLandblockLoaded(landblock);
|
||||
meshes.FailNext(ReferenceOperation.Decrement, failed, committed: false);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => adapter.OnLandblockUnloaded(landblock.LandblockId));
|
||||
|
||||
Assert.Single(meshes.ReferenceCounts);
|
||||
Assert.Equal(1, meshes.ReferenceCounts[failed]);
|
||||
Assert.False(adapter.IsLandblockRenderReady(landblock.LandblockId));
|
||||
|
||||
adapter.OnLandblockUnloaded(landblock.LandblockId);
|
||||
|
||||
Assert.Empty(meshes.ReferenceCounts);
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.Decrement, first));
|
||||
Assert.Equal(2, meshes.CallCount(ReferenceOperation.Decrement, failed));
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.Decrement, last));
|
||||
|
||||
adapter.OnLandblockUnloaded(landblock.LandblockId);
|
||||
Assert.Equal(2, meshes.CallCount(ReferenceOperation.Decrement, failed));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unload_CommittedFailure_DropsCompletedRegistrationBeforePropagating()
|
||||
{
|
||||
const ulong id = 0x01000010ul;
|
||||
var meshes = new FaultInjectingMeshAdapter();
|
||||
var adapter = new LandblockSpawnAdapter(meshes);
|
||||
LoadedLandblock landblock = MakeLandblock(0x1234FFFFu, (uint)id);
|
||||
adapter.OnLandblockLoaded(landblock);
|
||||
meshes.FailNext(ReferenceOperation.Decrement, id, committed: true);
|
||||
|
||||
MeshReferenceMutationException error = Assert.Throws<MeshReferenceMutationException>(
|
||||
() => adapter.OnLandblockUnloaded(landblock.LandblockId));
|
||||
|
||||
Assert.True(error.MutationCommitted);
|
||||
Assert.Empty(meshes.ReferenceCounts);
|
||||
Assert.False(adapter.IsLandblockRenderReady(landblock.LandblockId));
|
||||
|
||||
adapter.OnLandblockUnloaded(landblock.LandblockId);
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.Decrement, id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OppositeUnloadAfterPartialLoad_ReleasesOnlyReferencesActuallyAcquired()
|
||||
{
|
||||
const ulong held = 0x01000010ul;
|
||||
const ulong neverAcquired = 0x01000020ul;
|
||||
var meshes = new FaultInjectingMeshAdapter();
|
||||
meshes.FailNext(ReferenceOperation.Increment, neverAcquired, committed: false);
|
||||
var adapter = new LandblockSpawnAdapter(meshes);
|
||||
LoadedLandblock landblock = MakeLandblock(
|
||||
0x1234FFFFu,
|
||||
(uint)held,
|
||||
(uint)neverAcquired);
|
||||
Assert.Throws<InvalidOperationException>(() => adapter.OnLandblockLoaded(landblock));
|
||||
|
||||
adapter.OnLandblockUnloaded(landblock.LandblockId);
|
||||
|
||||
Assert.Empty(meshes.ReferenceCounts);
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.Decrement, held));
|
||||
Assert.Equal(0, meshes.CallCount(ReferenceOperation.Decrement, neverAcquired));
|
||||
Assert.False(adapter.IsLandblockRenderReady(landblock.LandblockId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OppositeLoadAfterPartialUnload_ReusesOverlapAndReconcilesObsoleteResidual()
|
||||
{
|
||||
const ulong released = 0x01000010ul;
|
||||
const ulong residual = 0x01000020ul;
|
||||
const ulong newlyDesired = 0x01000030ul;
|
||||
var meshes = new FaultInjectingMeshAdapter();
|
||||
var adapter = new LandblockSpawnAdapter(meshes);
|
||||
LoadedLandblock original = MakeLandblock(
|
||||
0x1234FFFFu,
|
||||
(uint)released,
|
||||
(uint)residual);
|
||||
adapter.OnLandblockLoaded(original);
|
||||
meshes.FailNext(ReferenceOperation.Decrement, residual, committed: false);
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => adapter.OnLandblockUnloaded(original.LandblockId));
|
||||
|
||||
// The residual id is intentionally absent from the replacement. The
|
||||
// reverse edge must finish its old release while acquiring the new
|
||||
// snapshot, without reacquiring the already-released first id.
|
||||
LoadedLandblock replacement = MakeLandblock(
|
||||
original.LandblockId,
|
||||
(uint)newlyDesired);
|
||||
adapter.OnLandblockLoaded(replacement);
|
||||
|
||||
Assert.Single(meshes.ReferenceCounts);
|
||||
Assert.Equal(1, meshes.ReferenceCounts[newlyDesired]);
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.Increment, released));
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.Increment, residual));
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.Increment, newlyDesired));
|
||||
Assert.Equal(2, meshes.CallCount(ReferenceOperation.Decrement, residual));
|
||||
Assert.True(adapter.IsLandblockRenderReady(replacement.LandblockId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OppositeLoadAfterPartialUnload_RetainsStillHeldOverlapWithoutMutation()
|
||||
{
|
||||
const ulong released = 0x01000010ul;
|
||||
const ulong residualOverlap = 0x01000020ul;
|
||||
var meshes = new FaultInjectingMeshAdapter();
|
||||
var adapter = new LandblockSpawnAdapter(meshes);
|
||||
LoadedLandblock landblock = MakeLandblock(
|
||||
0x1234FFFFu,
|
||||
(uint)released,
|
||||
(uint)residualOverlap);
|
||||
adapter.OnLandblockLoaded(landblock);
|
||||
meshes.FailNext(ReferenceOperation.Decrement, residualOverlap, committed: false);
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => adapter.OnLandblockUnloaded(landblock.LandblockId));
|
||||
|
||||
LoadedLandblock replacement = MakeLandblock(
|
||||
landblock.LandblockId,
|
||||
(uint)residualOverlap);
|
||||
adapter.OnLandblockLoaded(replacement);
|
||||
|
||||
Assert.Single(meshes.ReferenceCounts);
|
||||
Assert.Equal(1, meshes.ReferenceCounts[residualOverlap]);
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.Increment, residualOverlap));
|
||||
Assert.Equal(1, meshes.CallCount(ReferenceOperation.Decrement, residualOverlap));
|
||||
Assert.True(adapter.IsLandblockRenderReady(replacement.LandblockId));
|
||||
}
|
||||
|
||||
private static LoadedLandblock MakeLandblock(uint landblockId, params uint[] gfxObjIds)
|
||||
{
|
||||
WorldEntity[] entities = gfxObjIds.Length == 0
|
||||
? Array.Empty<WorldEntity>()
|
||||
:
|
||||
[
|
||||
new WorldEntity
|
||||
{
|
||||
Id = 1,
|
||||
ServerGuid = 0,
|
||||
SourceGfxObjOrSetupId = gfxObjIds[0],
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = gfxObjIds
|
||||
.Select(id => new MeshRef(id, Matrix4x4.Identity))
|
||||
.ToArray(),
|
||||
},
|
||||
];
|
||||
return new LoadedLandblock(landblockId, new LandBlock(), entities);
|
||||
}
|
||||
|
||||
private enum ReferenceOperation
|
||||
{
|
||||
Increment,
|
||||
PinPrepared,
|
||||
Decrement,
|
||||
}
|
||||
|
||||
private sealed class FaultInjectingMeshAdapter : IWbMeshAdapter
|
||||
{
|
||||
private readonly Dictionary<(ReferenceOperation Operation, ulong Id), Queue<bool>> _failures = new();
|
||||
private readonly List<(ReferenceOperation Operation, ulong Id)> _calls = new();
|
||||
|
||||
public Dictionary<ulong, int> ReferenceCounts { get; } = new();
|
||||
|
||||
public void FailNext(ReferenceOperation operation, ulong id, bool committed)
|
||||
{
|
||||
if (!_failures.TryGetValue((operation, id), out Queue<bool>? failures))
|
||||
{
|
||||
failures = new Queue<bool>();
|
||||
_failures.Add((operation, id), failures);
|
||||
}
|
||||
failures.Enqueue(committed);
|
||||
}
|
||||
|
||||
public int CallCount(ReferenceOperation operation, ulong id)
|
||||
=> _calls.Count(call => call == (operation, id));
|
||||
|
||||
public void IncrementRefCount(ulong id)
|
||||
=> Mutate(ReferenceOperation.Increment, id, delta: 1);
|
||||
|
||||
public void PinPreparedRenderData(ulong id)
|
||||
=> Mutate(ReferenceOperation.PinPrepared, id, delta: 1);
|
||||
|
||||
public void DecrementRefCount(ulong id)
|
||||
=> Mutate(ReferenceOperation.Decrement, id, delta: -1);
|
||||
|
||||
public bool IsRenderDataReady(ulong id) => ReferenceCounts.ContainsKey(id);
|
||||
|
||||
private void Mutate(ReferenceOperation operation, ulong id, int delta)
|
||||
{
|
||||
_calls.Add((operation, id));
|
||||
if (_failures.TryGetValue((operation, id), out Queue<bool>? failures)
|
||||
&& failures.TryDequeue(out bool committed))
|
||||
{
|
||||
if (committed)
|
||||
ApplyDelta(id, delta);
|
||||
if (failures.Count == 0)
|
||||
_failures.Remove((operation, id));
|
||||
|
||||
var inner = new InvalidOperationException("Injected mesh-reference failure.");
|
||||
if (committed)
|
||||
{
|
||||
throw new MeshReferenceMutationException(
|
||||
"Injected committed mesh-reference failure.",
|
||||
mutationCommitted: true,
|
||||
inner);
|
||||
}
|
||||
throw inner;
|
||||
}
|
||||
|
||||
ApplyDelta(id, delta);
|
||||
}
|
||||
|
||||
private void ApplyDelta(ulong id, int delta)
|
||||
{
|
||||
int next = ReferenceCounts.GetValueOrDefault(id) + delta;
|
||||
if (next < 0)
|
||||
throw new InvalidOperationException($"Reference underflow for 0x{id:X}.");
|
||||
if (next == 0)
|
||||
ReferenceCounts.Remove(id);
|
||||
else
|
||||
ReferenceCounts[id] = next;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ReadinessMeshAdapter : IWbMeshAdapter
|
||||
{
|
||||
public HashSet<ulong> ReadyIds { get; } = new();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue