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>
411 lines
16 KiB
C#
411 lines
16 KiB
C#
using System.Numerics;
|
|
using AcDream.App.Rendering.Wb;
|
|
using AcDream.Core.World;
|
|
using DatReaderWriter.DBObjs;
|
|
|
|
namespace AcDream.App.Tests.Rendering.Wb;
|
|
|
|
public sealed class LandblockSpawnAdapterReadinessTests
|
|
{
|
|
[Fact]
|
|
public void RenderReady_WaitsForStaticAndEnvCellMeshes()
|
|
{
|
|
var meshes = new ReadinessMeshAdapter();
|
|
var adapter = new LandblockSpawnAdapter(meshes);
|
|
var entity = new WorldEntity
|
|
{
|
|
Id = 1,
|
|
ServerGuid = 0,
|
|
SourceGfxObjOrSetupId = 0x01000010u,
|
|
Position = Vector3.Zero,
|
|
Rotation = Quaternion.Identity,
|
|
MeshRefs = [new MeshRef(0x01000010u, Matrix4x4.Identity)],
|
|
};
|
|
var landblock = new LoadedLandblock(
|
|
0x1234FFFFu,
|
|
new LandBlock(),
|
|
new[] { entity });
|
|
const ulong envCellGeometryId = 0x2_0000_1234ul;
|
|
|
|
adapter.OnLandblockLoaded(landblock, new[] { envCellGeometryId });
|
|
|
|
Assert.False(adapter.IsLandblockRenderReady(landblock.LandblockId));
|
|
meshes.ReadyIds.Add(0x01000010ul);
|
|
Assert.False(adapter.IsLandblockRenderReady(landblock.LandblockId));
|
|
meshes.ReadyIds.Add(envCellGeometryId);
|
|
Assert.True(adapter.IsLandblockRenderReady(landblock.LandblockId));
|
|
}
|
|
|
|
[Fact]
|
|
public void RenderReady_EmptyPublishedLandblockIsReadyUntilUnload()
|
|
{
|
|
var meshes = new ReadinessMeshAdapter();
|
|
var adapter = new LandblockSpawnAdapter(meshes);
|
|
var landblock = new LoadedLandblock(
|
|
0x1234FFFFu,
|
|
new LandBlock(),
|
|
Array.Empty<WorldEntity>());
|
|
|
|
adapter.OnLandblockLoaded(landblock);
|
|
|
|
Assert.True(adapter.IsLandblockRenderReady(landblock.LandblockId));
|
|
adapter.OnLandblockUnloaded(landblock.LandblockId);
|
|
Assert.False(adapter.IsLandblockRenderReady(landblock.LandblockId));
|
|
}
|
|
|
|
[Fact]
|
|
public void SharedEnvCellGeometry_IsPinnedPerLandblockAndReleasedSymmetrically()
|
|
{
|
|
const ulong sharedGeometryId = 0x2_0000_1234ul;
|
|
var meshes = new ReadinessMeshAdapter();
|
|
var adapter = new LandblockSpawnAdapter(meshes);
|
|
var first = new LoadedLandblock(
|
|
0x1234FFFFu,
|
|
new LandBlock(),
|
|
Array.Empty<WorldEntity>());
|
|
var second = new LoadedLandblock(
|
|
0x1235FFFFu,
|
|
new LandBlock(),
|
|
Array.Empty<WorldEntity>());
|
|
|
|
adapter.OnLandblockLoaded(first, new[] { sharedGeometryId, sharedGeometryId });
|
|
adapter.OnLandblockLoaded(second, new[] { sharedGeometryId });
|
|
|
|
Assert.Equal(2, meshes.ReferenceCounts[sharedGeometryId]);
|
|
Assert.Equal(2, meshes.PreparedPinCalls.Count);
|
|
|
|
adapter.OnLandblockUnloaded(first.LandblockId);
|
|
Assert.Equal(1, meshes.ReferenceCounts[sharedGeometryId]);
|
|
|
|
adapter.OnLandblockUnloaded(second.LandblockId);
|
|
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();
|
|
public Dictionary<ulong, int> ReferenceCounts { get; } = new();
|
|
public List<ulong> PreparedPinCalls { get; } = new();
|
|
public void IncrementRefCount(ulong id) =>
|
|
ReferenceCounts[id] = ReferenceCounts.GetValueOrDefault(id) + 1;
|
|
public void PinPreparedRenderData(ulong id)
|
|
{
|
|
PreparedPinCalls.Add(id);
|
|
IncrementRefCount(id);
|
|
}
|
|
public void DecrementRefCount(ulong id)
|
|
{
|
|
int count = ReferenceCounts[id] - 1;
|
|
if (count == 0) ReferenceCounts.Remove(id);
|
|
else ReferenceCounts[id] = count;
|
|
}
|
|
public bool IsRenderDataReady(ulong id) => ReadyIds.Contains(id);
|
|
}
|
|
}
|