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>
437 lines
17 KiB
C#
437 lines
17 KiB
C#
using AcDream.App.Streaming;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.Rendering.Wb;
|
|
using AcDream.Core.World;
|
|
using DatReaderWriter.DBObjs;
|
|
using System.Collections.Immutable;
|
|
|
|
namespace AcDream.App.Tests.Streaming;
|
|
|
|
public sealed class StreamingControllerReadinessTests
|
|
{
|
|
[Fact]
|
|
public void RenderNeighborhoodResident_RequiresEveryPublishedLandblockInRing()
|
|
{
|
|
var state = new GpuWorldState();
|
|
StreamingController controller = CreateController(state);
|
|
|
|
for (int dx = -1; dx <= 1; dx++)
|
|
for (int dy = -1; dy <= 1; dy++)
|
|
{
|
|
if (dx == 1 && dy == 1)
|
|
continue;
|
|
AddPublished(state, 0x12 + dx, 0x36 + dy);
|
|
}
|
|
|
|
Assert.False(controller.IsRenderNeighborhoodResident(0x12360022u, 1));
|
|
|
|
AddPublished(state, 0x13, 0x37);
|
|
|
|
Assert.True(controller.IsRenderNeighborhoodResident(0x12360022u, 1));
|
|
}
|
|
|
|
[Fact]
|
|
public void RenderNeighborhoodResident_RadiusZeroAcceptsEnvCellId()
|
|
{
|
|
var state = new GpuWorldState();
|
|
StreamingController controller = CreateController(state);
|
|
AddPublished(state, 0x8C, 0x04);
|
|
|
|
Assert.True(controller.IsRenderNeighborhoodResident(0x8C0401ADu, 0));
|
|
Assert.False(controller.IsRenderNeighborhoodResident(0x8D0401ADu, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void RenderNeighborhoodResident_SkipsOffMapNeighborsLikePhysicsGate()
|
|
{
|
|
var state = new GpuWorldState();
|
|
StreamingController controller = CreateController(state);
|
|
AddPublished(state, 0, 0);
|
|
AddPublished(state, 0, 1);
|
|
AddPublished(state, 1, 0);
|
|
AddPublished(state, 1, 1);
|
|
|
|
Assert.True(controller.IsRenderNeighborhoodResident(0x00000001u, 1));
|
|
}
|
|
|
|
[Fact]
|
|
public void RenderNeighborhoodResident_RejectsNegativeRadius()
|
|
{
|
|
StreamingController controller = CreateController(new GpuWorldState());
|
|
|
|
Assert.Throws<ArgumentOutOfRangeException>(
|
|
() => controller.IsRenderNeighborhoodResident(0x1236FFFFu, -1));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0xFF0401ADu)]
|
|
[InlineData(0x04FF01ADu)]
|
|
[InlineData(0x12360000u)]
|
|
[InlineData(0x12360041u)]
|
|
[InlineData(0x1236FFFEu)]
|
|
public void RenderNeighborhoodResident_RejectsInvalidMapEdgeDestination(uint cellId)
|
|
{
|
|
StreamingController controller = CreateController(new GpuWorldState());
|
|
|
|
Assert.False(controller.IsRenderNeighborhoodResident(cellId, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void RenderNeighborhoodResident_WaitsForActualMeshUpload()
|
|
{
|
|
var meshes = new ReadinessMeshAdapter();
|
|
var state = new GpuWorldState(new LandblockSpawnAdapter(meshes));
|
|
StreamingController controller = CreateController(state);
|
|
uint id = 0x1236FFFFu;
|
|
const ulong envCellGeometryId = 0x2_0000_1234ul;
|
|
var entity = new WorldEntity
|
|
{
|
|
Id = 1,
|
|
ServerGuid = 0,
|
|
SourceGfxObjOrSetupId = 0x01000010u,
|
|
Position = System.Numerics.Vector3.Zero,
|
|
Rotation = System.Numerics.Quaternion.Identity,
|
|
MeshRefs = new[] { new MeshRef(0x01000010u, System.Numerics.Matrix4x4.Identity) },
|
|
};
|
|
state.AddLandblock(
|
|
new LoadedLandblock(id, new LandBlock(), new[] { entity }),
|
|
new[] { envCellGeometryId });
|
|
|
|
Assert.False(controller.IsRenderNeighborhoodResident(id, 0));
|
|
meshes.ReadyIds.Add(0x01000010ul);
|
|
Assert.False(controller.IsRenderNeighborhoodResident(id, 0));
|
|
meshes.ReadyIds.Add(envCellGeometryId);
|
|
Assert.True(controller.IsRenderNeighborhoodResident(id, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void RenderNeighborhoodResident_PreservesShellGateWhenPromotionPrecedesBaseLoad()
|
|
{
|
|
var meshes = new ReadinessMeshAdapter();
|
|
var state = new GpuWorldState(new LandblockSpawnAdapter(meshes));
|
|
StreamingController controller = CreateController(state);
|
|
uint id = 0x1236FFFFu;
|
|
const ulong envCellGeometryId = 0x2_0000_1234ul;
|
|
|
|
state.AddEntitiesToExistingLandblock(
|
|
id,
|
|
Array.Empty<WorldEntity>(),
|
|
new[] { envCellGeometryId });
|
|
state.AddLandblock(new LoadedLandblock(id, new LandBlock(), Array.Empty<WorldEntity>()));
|
|
|
|
Assert.True(state.IsNearTier(id));
|
|
Assert.False(controller.IsRenderNeighborhoodResident(id, 0));
|
|
meshes.ReadyIds.Add(envCellGeometryId);
|
|
Assert.True(controller.IsRenderNeighborhoodResident(id, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void RenderNeighborhoodResident_FarTerrainDoesNotSatisfyPortalGate()
|
|
{
|
|
var meshes = new ReadinessMeshAdapter();
|
|
var state = new GpuWorldState(new LandblockSpawnAdapter(meshes));
|
|
StreamingController controller = CreateController(state);
|
|
uint id = 0x1236FFFFu;
|
|
const ulong envCellGeometryId = 0x2_0000_1234ul;
|
|
state.AddLandblock(
|
|
new LoadedLandblock(id, new LandBlock(), Array.Empty<WorldEntity>()),
|
|
tier: LandblockStreamTier.Far);
|
|
|
|
Assert.False(controller.IsRenderNeighborhoodResident(id, 0));
|
|
|
|
state.AddEntitiesToExistingLandblock(
|
|
id,
|
|
Array.Empty<WorldEntity>(),
|
|
new[] { envCellGeometryId });
|
|
Assert.False(controller.IsRenderNeighborhoodResident(id, 0));
|
|
meshes.ReadyIds.Add(envCellGeometryId);
|
|
Assert.True(controller.IsRenderNeighborhoodResident(id, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void StaleFarPublication_DoesNotReplaceNearEntitiesOrReadiness()
|
|
{
|
|
var meshes = new ReadinessMeshAdapter();
|
|
var state = new GpuWorldState(new LandblockSpawnAdapter(meshes));
|
|
StreamingController controller = CreateController(state);
|
|
uint id = 0x1236FFFFu;
|
|
const ulong envCellGeometryId = 0x2_0000_1234ul;
|
|
var entity = new WorldEntity
|
|
{
|
|
Id = 1,
|
|
ServerGuid = 0,
|
|
SourceGfxObjOrSetupId = 0x01000010u,
|
|
Position = System.Numerics.Vector3.Zero,
|
|
Rotation = System.Numerics.Quaternion.Identity,
|
|
MeshRefs = [new MeshRef(0x01000010u, System.Numerics.Matrix4x4.Identity)],
|
|
};
|
|
meshes.ReadyIds.UnionWith([0x01000010ul, envCellGeometryId]);
|
|
state.AddLandblock(
|
|
new LoadedLandblock(id, new LandBlock(), new[] { entity }),
|
|
new[] { envCellGeometryId },
|
|
LandblockStreamTier.Near);
|
|
Assert.True(controller.IsRenderNeighborhoodResident(id, 0));
|
|
|
|
state.AddLandblock(
|
|
new LoadedLandblock(id, new LandBlock(), Array.Empty<WorldEntity>()),
|
|
tier: LandblockStreamTier.Far);
|
|
|
|
Assert.True(state.IsNearTier(id));
|
|
Assert.Contains(entity, state.Entities);
|
|
Assert.True(controller.IsRenderNeighborhoodResident(id, 0));
|
|
}
|
|
|
|
[Fact]
|
|
public void EnvCellPublication_RearmsSpecializedPreparationAfterPin()
|
|
{
|
|
uint id = 0x1236FFFFu;
|
|
const ulong geometryId = 0x2_0000_1234ul;
|
|
var meshes = new ReadinessMeshAdapter();
|
|
var state = new GpuWorldState(new LandblockSpawnAdapter(meshes));
|
|
var outbox = new Queue<LandblockStreamResult>();
|
|
int ensureCalls = 0;
|
|
var shell = new EnvCellShellPlacement(
|
|
0x12360001u,
|
|
geometryId,
|
|
0x0D000001u,
|
|
2,
|
|
ImmutableArray.Create<ushort>(3, 4),
|
|
System.Numerics.Vector3.Zero,
|
|
System.Numerics.Quaternion.Identity,
|
|
System.Numerics.Matrix4x4.Identity,
|
|
new WbBoundingBox(System.Numerics.Vector3.Zero, System.Numerics.Vector3.One),
|
|
new WbBoundingBox(System.Numerics.Vector3.Zero, System.Numerics.Vector3.One));
|
|
var envCells = new EnvCellLandblockBuild(
|
|
id,
|
|
Array.Empty<LoadedCell>(),
|
|
new[] { shell });
|
|
var build = new LandblockBuild(
|
|
new LoadedLandblock(id, new LandBlock(), Array.Empty<WorldEntity>()),
|
|
envCells);
|
|
outbox.Enqueue(new LandblockStreamResult.Loaded(
|
|
id,
|
|
LandblockStreamTier.Near,
|
|
build,
|
|
new AcDream.Core.Terrain.LandblockMeshData(
|
|
Array.Empty<AcDream.Core.Terrain.TerrainVertex>(),
|
|
Array.Empty<uint>())));
|
|
var controller = new StreamingController(
|
|
enqueueLoad: (_, _) => { },
|
|
enqueueUnload: _ => { },
|
|
drainCompletions: max =>
|
|
{
|
|
var batch = new List<LandblockStreamResult>();
|
|
while (batch.Count < max && outbox.Count > 0) batch.Add(outbox.Dequeue());
|
|
return batch;
|
|
},
|
|
applyTerrain: (_, _) => { },
|
|
state: state,
|
|
nearRadius: 0,
|
|
farRadius: 0,
|
|
ensureEnvCellMeshes: completed =>
|
|
{
|
|
// The specialized request is replayed only after the synthetic
|
|
// id is pinned, closing existing-at-schedule -> evicted-before-
|
|
// publication without falling through generic GfxObj decode.
|
|
Assert.Equal(1, meshes.ReferenceCounts[geometryId]);
|
|
Assert.Equal(shell, Assert.Single(completed.Shells));
|
|
ensureCalls++;
|
|
});
|
|
|
|
controller.Tick(0x12, 0x36);
|
|
|
|
Assert.Equal(1, ensureCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public void PromotionWithoutBase_PublishesSelfContainedNearAndPinsBeforeReplay()
|
|
{
|
|
uint id = 0x1236FFFFu;
|
|
const ulong geometryId = 0x2_0000_5678ul;
|
|
var meshes = new ReadinessMeshAdapter();
|
|
var state = new GpuWorldState(new LandblockSpawnAdapter(meshes));
|
|
var outbox = new Queue<LandblockStreamResult>();
|
|
int ensureCalls = 0;
|
|
var shell = new EnvCellShellPlacement(
|
|
0x12360001u,
|
|
geometryId,
|
|
0x0D000001u,
|
|
2,
|
|
ImmutableArray.Create<ushort>(3, 4),
|
|
System.Numerics.Vector3.Zero,
|
|
System.Numerics.Quaternion.Identity,
|
|
System.Numerics.Matrix4x4.Identity,
|
|
new WbBoundingBox(System.Numerics.Vector3.Zero, System.Numerics.Vector3.One),
|
|
new WbBoundingBox(System.Numerics.Vector3.Zero, System.Numerics.Vector3.One));
|
|
var envCells = new EnvCellLandblockBuild(
|
|
id,
|
|
Array.Empty<LoadedCell>(),
|
|
new[] { shell });
|
|
var nearBuild = new LandblockBuild(
|
|
new LoadedLandblock(id, new LandBlock(), Array.Empty<WorldEntity>()),
|
|
envCells);
|
|
var mesh = new AcDream.Core.Terrain.LandblockMeshData(
|
|
Array.Empty<AcDream.Core.Terrain.TerrainVertex>(),
|
|
Array.Empty<uint>());
|
|
outbox.Enqueue(new LandblockStreamResult.Promoted(id, nearBuild, mesh));
|
|
var controller = new StreamingController(
|
|
enqueueLoad: (_, _) => { },
|
|
enqueueUnload: _ => { },
|
|
drainCompletions: max =>
|
|
{
|
|
var batch = new List<LandblockStreamResult>();
|
|
while (batch.Count < max && outbox.Count > 0) batch.Add(outbox.Dequeue());
|
|
return batch;
|
|
},
|
|
applyTerrain: (_, _) => { },
|
|
state: state,
|
|
nearRadius: 0,
|
|
farRadius: 0,
|
|
ensureEnvCellMeshes: completed =>
|
|
{
|
|
Assert.Equal(1, meshes.ReferenceCounts[geometryId]);
|
|
Assert.Equal(shell, Assert.Single(completed.Shells));
|
|
ensureCalls++;
|
|
});
|
|
|
|
controller.Tick(0x12, 0x36);
|
|
|
|
Assert.Equal(1, ensureCalls);
|
|
Assert.Equal(1, meshes.ReferenceCounts[geometryId]);
|
|
Assert.True(state.IsNearTier(id));
|
|
|
|
// A Far job that had already started may still finish after the
|
|
// self-contained promotion. It must not replace or replay the Near tier.
|
|
var farBase = new LandblockBuild(
|
|
new LoadedLandblock(id, new LandBlock(), Array.Empty<WorldEntity>()));
|
|
outbox.Enqueue(new LandblockStreamResult.Loaded(
|
|
id,
|
|
LandblockStreamTier.Far,
|
|
farBase,
|
|
mesh));
|
|
|
|
controller.Tick(0x12, 0x36);
|
|
|
|
Assert.Equal(1, ensureCalls);
|
|
Assert.Equal(1, meshes.ReferenceCounts[geometryId]);
|
|
Assert.True(state.IsNearTier(id));
|
|
}
|
|
|
|
[Fact]
|
|
public void PromotionBeforeBase_DemotedBeforeBase_DropsPendingNearPresentation()
|
|
{
|
|
uint id = 0x1236FFFFu;
|
|
const ulong geometryId = 0x2_0000_9ABCul;
|
|
var meshes = new ReadinessMeshAdapter();
|
|
var state = new GpuWorldState(new LandblockSpawnAdapter(meshes));
|
|
var outbox = new Queue<LandblockStreamResult>();
|
|
int ensureCalls = 0;
|
|
var appliedBuilds = new List<LandblockBuild>();
|
|
var demotedAfterStaticDetach = new List<uint>();
|
|
var shell = new EnvCellShellPlacement(
|
|
0x12360001u,
|
|
geometryId,
|
|
0x0D000001u,
|
|
2,
|
|
ImmutableArray.Create<ushort>(3, 4),
|
|
System.Numerics.Vector3.Zero,
|
|
System.Numerics.Quaternion.Identity,
|
|
System.Numerics.Matrix4x4.Identity,
|
|
new WbBoundingBox(System.Numerics.Vector3.Zero, System.Numerics.Vector3.One),
|
|
new WbBoundingBox(System.Numerics.Vector3.Zero, System.Numerics.Vector3.One));
|
|
var envCells = new EnvCellLandblockBuild(
|
|
id,
|
|
Array.Empty<LoadedCell>(),
|
|
new[] { shell });
|
|
var staticEntity = new WorldEntity
|
|
{
|
|
Id = 1,
|
|
ServerGuid = 0,
|
|
SourceGfxObjOrSetupId = 0x01000010u,
|
|
Position = System.Numerics.Vector3.Zero,
|
|
Rotation = System.Numerics.Quaternion.Identity,
|
|
MeshRefs = [new MeshRef(0x01000010u, System.Numerics.Matrix4x4.Identity)],
|
|
};
|
|
var nearBuild = new LandblockBuild(
|
|
new LoadedLandblock(id, new LandBlock(), new[] { staticEntity }),
|
|
envCells);
|
|
var mesh = new AcDream.Core.Terrain.LandblockMeshData(
|
|
Array.Empty<AcDream.Core.Terrain.TerrainVertex>(),
|
|
Array.Empty<uint>());
|
|
outbox.Enqueue(new LandblockStreamResult.Promoted(id, nearBuild, mesh));
|
|
var controller = new StreamingController(
|
|
enqueueLoad: (_, _) => { },
|
|
enqueueUnload: _ => { },
|
|
drainCompletions: max =>
|
|
{
|
|
var batch = new List<LandblockStreamResult>();
|
|
while (batch.Count < max && outbox.Count > 0) batch.Add(outbox.Dequeue());
|
|
return batch;
|
|
},
|
|
applyTerrain: (build, _) => appliedBuilds.Add(build),
|
|
state: state,
|
|
nearRadius: 0,
|
|
farRadius: 3,
|
|
demoteNearLayer: demotedId =>
|
|
{
|
|
Assert.DoesNotContain(staticEntity, state.Entities);
|
|
demotedAfterStaticDetach.Add(demotedId);
|
|
},
|
|
ensureEnvCellMeshes: _ => ensureCalls++);
|
|
|
|
controller.Tick(0x12, 0x36); // self-contained promotion publishes Near.
|
|
Assert.Single(appliedBuilds);
|
|
Assert.NotNull(appliedBuilds[0].EnvCells);
|
|
controller.Tick(0x12, 0x39); // normal Near->Far demotion retires presentation.
|
|
|
|
var farBase = new LandblockBuild(
|
|
new LoadedLandblock(id, new LandBlock(), Array.Empty<WorldEntity>()));
|
|
outbox.Enqueue(new LandblockStreamResult.Loaded(
|
|
id,
|
|
LandblockStreamTier.Far,
|
|
farBase,
|
|
mesh));
|
|
controller.Tick(0x12, 0x39);
|
|
|
|
Assert.Equal(1, ensureCalls);
|
|
Assert.DoesNotContain(geometryId, meshes.ReferenceCounts);
|
|
Assert.DoesNotContain(staticEntity, state.Entities);
|
|
Assert.Equal(new[] { id }, demotedAfterStaticDetach);
|
|
Assert.True(state.IsLoaded(id));
|
|
Assert.False(state.IsNearTier(id));
|
|
Assert.Equal(2, appliedBuilds.Count);
|
|
Assert.Null(appliedBuilds[1].EnvCells);
|
|
Assert.Empty(appliedBuilds[1].Landblock.Entities);
|
|
}
|
|
|
|
private static StreamingController CreateController(GpuWorldState state)
|
|
=> new(
|
|
(_, _) => { },
|
|
_ => { },
|
|
_ => Array.Empty<LandblockStreamResult>(),
|
|
(_, _) => { },
|
|
state,
|
|
nearRadius: 1,
|
|
farRadius: 2);
|
|
|
|
private static void AddPublished(GpuWorldState state, int x, int y)
|
|
{
|
|
uint id = ((uint)x << 24) | ((uint)y << 16) | 0xFFFFu;
|
|
state.AddLandblock(new LoadedLandblock(id, new LandBlock(), Array.Empty<WorldEntity>()));
|
|
}
|
|
|
|
private sealed class ReadinessMeshAdapter : IWbMeshAdapter
|
|
{
|
|
public HashSet<ulong> ReadyIds { get; } = new();
|
|
public Dictionary<ulong, int> ReferenceCounts { get; } = new();
|
|
public void IncrementRefCount(ulong id) =>
|
|
ReferenceCounts[id] = ReferenceCounts.GetValueOrDefault(id) + 1;
|
|
public void DecrementRefCount(ulong id)
|
|
{
|
|
int next = ReferenceCounts.GetValueOrDefault(id) - 1;
|
|
if (next <= 0) ReferenceCounts.Remove(id);
|
|
else ReferenceCounts[id] = next;
|
|
}
|
|
public bool IsRenderDataReady(ulong id) => ReadyIds.Contains(id);
|
|
}
|
|
}
|