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

@ -11,6 +11,361 @@ namespace AcDream.App.Tests.Streaming;
public sealed class GpuWorldStateVisibilityTests
{
[Fact]
public void PendingSpawnFlood_DoesNotPublishOrRebuildLoadedView()
{
const uint landblock = 0x0101FFFFu;
const int count = 10_000;
var state = new GpuWorldState();
int edges = 0;
state.LiveProjectionVisibilityChanged += (_, _) => edges++;
long commitsBefore = state.VisibilityCommitCount;
using (state.BeginMutationBatch())
{
for (int i = 0; i < count; i++)
{
state.PlaceLiveEntityProjection(
landblock,
Entity((uint)(i + 1), 0x70000000u + (uint)i));
}
Assert.Empty(state.Entities);
Assert.Equal(count, state.PendingLiveEntityCount);
Assert.Equal(commitsBefore, state.VisibilityCommitCount);
Assert.Equal(0, edges);
}
Assert.Empty(state.Entities);
Assert.Equal(count, state.PendingLiveEntityCount);
Assert.Equal(commitsBefore + 1, state.VisibilityCommitCount);
Assert.Equal(0, edges);
}
[Fact]
public void LoadedSpawnFlood_AppendsInPlaceAndPublishesOneBatch()
{
const uint landblock = 0x0101FFFFu;
const int count = 10_000;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
landblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
Assert.True(state.TryGetLandblock(landblock, out LoadedLandblock? loaded));
IReadOnlyList<WorldEntity> residentBucket = loaded!.Entities;
int edges = 0;
state.LiveProjectionVisibilityChanged += (_, visible) =>
{
Assert.True(visible);
edges++;
};
long commitsBefore = state.VisibilityCommitCount;
using (state.BeginMutationBatch())
{
for (int i = 0; i < count; i++)
{
state.PlaceLiveEntityProjection(
landblock,
Entity((uint)(i + 1), 0x71000000u + (uint)i));
}
Assert.Same(residentBucket, loaded.Entities);
Assert.Equal(count, residentBucket.Count);
Assert.Equal(count, state.Entities.Count);
Assert.Equal(0, edges);
Assert.Equal(commitsBefore, state.VisibilityCommitCount);
}
Assert.Equal(count, edges);
Assert.Equal(commitsBefore + 1, state.VisibilityCommitCount);
Assert.Equal(count, state.Entities.Count);
}
[Fact]
public void DenseSameBucketRebucketAndDelete_PreserveEveryProjectionIndex()
{
const uint sourceLandblock = 0x0101FFFFu;
const uint targetLandblock = 0x0202FFFFu;
const int count = 10_000;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
sourceLandblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
state.AddLandblock(new LoadedLandblock(
targetLandblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
var entities = new WorldEntity[count];
using (state.BeginMutationBatch())
{
for (int i = 0; i < count; i++)
{
entities[i] = Entity((uint)(i + 1), 0x72000000u + (uint)i);
state.PlaceLiveEntityProjection(sourceLandblock, entities[i]);
}
}
using (state.BeginMutationBatch())
{
for (int i = 0; i < count; i++)
state.RebucketLiveEntity(entities[i], targetLandblock);
}
Assert.True(state.TryGetLandblock(sourceLandblock, out LoadedLandblock? source));
Assert.True(state.TryGetLandblock(targetLandblock, out LoadedLandblock? target));
Assert.Empty(source!.Entities);
Assert.Equal(count, target!.Entities.Count);
Assert.Equal(count, state.Entities.Count);
using (state.BeginMutationBatch())
{
for (int i = 0; i < count; i++)
state.RemoveLiveEntityProjection(entities[i]);
}
Assert.Empty(target.Entities);
Assert.Empty(state.Entities);
}
[Fact]
public void DenseDemotion_CompactsLiveEntriesAndKeepsTheirIndexesValid()
{
const uint sourceLandblock = 0x0101FFFFu;
const uint targetLandblock = 0x0202FFFFu;
const int liveCount = 2_000;
var state = new GpuWorldState();
var mixed = new List<WorldEntity>(liveCount * 2);
var live = new List<WorldEntity>(liveCount);
for (int i = 0; i < liveCount; i++)
{
mixed.Add(Entity(0xC1000000u + (uint)i, 0u));
WorldEntity projection = Entity((uint)(i + 1), 0x73000000u + (uint)i);
mixed.Add(projection);
live.Add(projection);
}
state.AddLandblock(new LoadedLandblock(
sourceLandblock,
new LandBlock(),
mixed));
state.AddLandblock(new LoadedLandblock(
targetLandblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
state.RemoveEntitiesFromLandblock(sourceLandblock);
Assert.True(state.TryGetLandblock(sourceLandblock, out LoadedLandblock? source));
Assert.Equal(liveCount, source!.Entities.Count);
Assert.All(source.Entities, entity => Assert.NotEqual(0u, entity.ServerGuid));
using (state.BeginMutationBatch())
{
foreach (WorldEntity entity in live)
state.RebucketLiveEntity(entity, targetLandblock);
}
Assert.Empty(source.Entities);
Assert.True(state.TryGetLandblock(targetLandblock, out LoadedLandblock? target));
Assert.Equal(liveCount, target!.Entities.Count);
}
[Fact]
public void PendingDemotion_CompactsMixedStaticAndLiveEntriesAndKeepsLiveIndexesValid()
{
const uint pendingLandblock = 0x0101FFFFu;
const uint targetLandblock = 0x0202FFFFu;
var state = new GpuWorldState();
WorldEntity first = Entity(1u, 0x73500001u);
WorldEntity second = Entity(2u, 0x73500002u);
WorldEntity[] mixed =
[
Entity(0xC1000001u, 0u),
first,
Entity(0xC1000002u, 0u),
second,
];
Assert.False(state.AddEntitiesToExistingLandblock(pendingLandblock, mixed));
Assert.Equal(4, state.PendingLiveEntityCount);
state.RemoveEntitiesFromLandblock(pendingLandblock);
Assert.Equal(2, state.PendingLiveEntityCount);
state.AddLandblock(new LoadedLandblock(
pendingLandblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
state.AddLandblock(new LoadedLandblock(
targetLandblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
Assert.True(state.TryGetLandblock(pendingLandblock, out LoadedLandblock? pending));
Assert.Equal([first, second], pending!.Entities);
state.RebucketLiveEntity(first, targetLandblock);
state.RebucketLiveEntity(second, targetLandblock);
Assert.Empty(pending.Entities);
Assert.True(state.TryGetLandblock(targetLandblock, out LoadedLandblock? target));
Assert.Equal(2, target!.Entities.Count);
Assert.Contains(first, target.Entities);
Assert.Contains(second, target.Entities);
}
[Fact]
public void UnloadToPendingThenReload_PreservesProjectionIdentityAndVisibilityEdges()
{
const uint landblock = 0x0101FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
landblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
WorldEntity entity = Entity(1u, 0x73600001u);
state.PlaceLiveEntityProjection(landblock, entity);
var edges = new List<(uint Guid, bool Visible)>();
state.LiveProjectionVisibilityChanged += (guid, visible) => edges.Add((guid, visible));
state.RemoveLandblock(landblock);
Assert.Empty(state.Entities);
Assert.Equal(1, state.PendingLiveEntityCount);
Assert.False(state.IsLiveEntityVisible(entity.ServerGuid));
state.AddLandblock(new LoadedLandblock(
landblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
Assert.Same(entity, Assert.Single(state.Entities));
Assert.True(state.TryGetLandblock(landblock, out LoadedLandblock? reloaded));
Assert.Same(entity, Assert.Single(reloaded!.Entities));
Assert.True(state.IsLiveEntityVisible(entity.ServerGuid));
Assert.Equal(
[(entity.ServerGuid, false), (entity.ServerGuid, true)],
edges);
state.RemoveLiveEntityProjection(entity);
Assert.Empty(state.Entities);
Assert.Empty(reloaded.Entities);
}
[Fact]
public void BatchedVisibilityObserver_SeesCommittedFlatAndResidentViews()
{
const uint landblock = 0x0101FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
landblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
WorldEntity entity = Entity(1u, 0x73700001u);
int callbacks = 0;
state.LiveProjectionVisibilityChanged += (guid, visible) =>
{
callbacks++;
Assert.Equal(entity.ServerGuid, guid);
Assert.True(visible);
Assert.True(state.IsLiveEntityVisible(guid));
Assert.Same(entity, Assert.Single(state.Entities));
Assert.True(state.TryGetLandblock(landblock, out LoadedLandblock? loaded));
Assert.Same(entity, Assert.Single(loaded!.Entities));
};
using (state.BeginMutationBatch())
{
state.PlaceLiveEntityProjection(landblock, entity);
Assert.Equal(0, callbacks);
}
Assert.Equal(1, callbacks);
}
[Fact]
public void LoadedToLoadedRebucket_EmitsNoVisibilityPulse()
{
const uint sourceLandblock = 0x0101FFFFu;
const uint targetLandblock = 0x0202FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
sourceLandblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
state.AddLandblock(new LoadedLandblock(
targetLandblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
WorldEntity entity = Entity(1u, 0x73800001u);
state.PlaceLiveEntityProjection(sourceLandblock, entity);
var edges = new List<(uint Guid, bool Visible)>();
state.LiveProjectionVisibilityChanged += (guid, visible) => edges.Add((guid, visible));
state.RebucketLiveEntity(entity, targetLandblock);
Assert.Empty(edges);
Assert.True(state.IsLiveEntityVisible(entity.ServerGuid));
Assert.True(state.TryGetLandblock(sourceLandblock, out LoadedLandblock? source));
Assert.Empty(source!.Entities);
Assert.True(state.TryGetLandblock(targetLandblock, out LoadedLandblock? target));
Assert.Same(entity, Assert.Single(target!.Entities));
Assert.Same(entity, Assert.Single(state.Entities));
}
[Fact]
public void SameGuidOverlap_ExactRemovalPromotesSecondaryWithoutVisibilityPulse()
{
const uint landblock = 0x0101FFFFu;
const uint guid = 0x73900001u;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
landblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
WorldEntity first = Entity(1u, guid);
WorldEntity second = Entity(2u, guid);
state.PlaceLiveEntityProjection(landblock, first);
state.PlaceLiveEntityProjection(landblock, second);
var edges = new List<(uint Guid, bool Visible)>();
state.LiveProjectionVisibilityChanged += (edgeGuid, visible) =>
edges.Add((edgeGuid, visible));
state.RemoveLiveEntityProjection(first);
Assert.Empty(edges);
Assert.True(state.IsLiveEntityVisible(guid));
Assert.Same(second, Assert.Single(state.Entities));
state.RemoveLiveEntityProjection(guid);
Assert.Equal([(guid, false)], edges);
Assert.False(state.IsLiveEntityVisible(guid));
Assert.Empty(state.Entities);
}
[Fact]
public void DuplicateDirectPlacement_IsRejectedBeforeAnyBucketMutation()
{
const uint landblock = 0x0101FFFFu;
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
landblock,
new LandBlock(),
Array.Empty<WorldEntity>()));
WorldEntity entity = Entity(1u, 0x74000001u);
state.PlaceLiveEntityProjection(landblock, entity);
Assert.Throws<InvalidOperationException>(() =>
state.PlaceLiveEntityProjection(landblock, entity));
Assert.Same(entity, Assert.Single(state.Entities));
Assert.True(state.TryGetLandblock(landblock, out LoadedLandblock? loaded));
Assert.Same(entity, Assert.Single(loaded!.Entities));
}
[Fact]
public void ThrowingObserver_DoesNotStrandLaterSubscribersOrQueuedOwnerEdges()
{
@ -80,6 +435,42 @@ public sealed class GpuWorldStateVisibilityTests
Assert.Equal(0, state.PendingVisibilityTransitionCount);
}
[Fact]
public void CopyLiveEntitiesNearLandblock_UsesBoundedNeighborhoodAndSkipsStatics()
{
var centerLive = Entity(1, 0x70000001u);
var neighborLive = Entity(2, 0x70000002u);
var farLive = Entity(3, 0x70000003u);
WorldEntity[] staticEntities = Enumerable.Range(0, 20_000)
.Select(index => Entity((uint)(index + 10), 0u))
.ToArray();
var state = new GpuWorldState();
state.AddLandblock(new LoadedLandblock(
0x1010FFFFu,
new LandBlock(),
staticEntities.Append(centerLive).ToArray()));
state.AddLandblock(new LoadedLandblock(
0x1110FFFFu,
new LandBlock(),
new[] { neighborLive }));
state.AddLandblock(new LoadedLandblock(
0x1310FFFFu,
new LandBlock(),
new[] { farLive }));
var destination = new List<KeyValuePair<uint, WorldEntity>>
{
new(0xDEADBEEFu, farLive),
};
state.CopyLiveEntitiesNearLandblock(0x10100001u, 1, destination);
Assert.Equal(2, destination.Count);
Assert.Contains(destination, pair => pair.Key == centerLive.ServerGuid);
Assert.Contains(destination, pair => pair.Key == neighborLive.ServerGuid);
Assert.DoesNotContain(destination, pair => pair.Key == farLive.ServerGuid);
Assert.DoesNotContain(destination, pair => pair.Key == 0u);
}
private static WorldEntity Entity(uint id, uint guid) => new()
{
Id = id,