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( () => 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(), new[] { envCellGeometryId }); state.AddLandblock(new LoadedLandblock(id, new LandBlock(), Array.Empty())); 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()), tier: LandblockStreamTier.Far); Assert.False(controller.IsRenderNeighborhoodResident(id, 0)); state.AddEntitiesToExistingLandblock( id, Array.Empty(), 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()), 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(); int ensureCalls = 0; var shell = new EnvCellShellPlacement( 0x12360001u, geometryId, 0x0D000001u, 2, ImmutableArray.Create(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(), new[] { shell }); var build = new LandblockBuild( new LoadedLandblock(id, new LandBlock(), Array.Empty()), envCells); outbox.Enqueue(new LandblockStreamResult.Loaded( id, LandblockStreamTier.Near, build, new AcDream.Core.Terrain.LandblockMeshData( Array.Empty(), Array.Empty()))); var controller = new StreamingController( enqueueLoad: (_, _) => { }, enqueueUnload: _ => { }, drainCompletions: max => { var batch = new List(); 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(); int ensureCalls = 0; var shell = new EnvCellShellPlacement( 0x12360001u, geometryId, 0x0D000001u, 2, ImmutableArray.Create(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(), new[] { shell }); var nearBuild = new LandblockBuild( new LoadedLandblock(id, new LandBlock(), Array.Empty()), envCells); var mesh = new AcDream.Core.Terrain.LandblockMeshData( Array.Empty(), Array.Empty()); outbox.Enqueue(new LandblockStreamResult.Promoted(id, nearBuild, mesh)); var controller = new StreamingController( enqueueLoad: (_, _) => { }, enqueueUnload: _ => { }, drainCompletions: max => { var batch = new List(); 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())); 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(); int ensureCalls = 0; var appliedBuilds = new List(); var demotedWhileStaticPresent = new List(); var shell = new EnvCellShellPlacement( 0x12360001u, geometryId, 0x0D000001u, 2, ImmutableArray.Create(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(), 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(), Array.Empty()); outbox.Enqueue(new LandblockStreamResult.Promoted(id, nearBuild, mesh)); var controller = new StreamingController( enqueueLoad: (_, _) => { }, enqueueUnload: _ => { }, drainCompletions: max => { var batch = new List(); 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.Contains(staticEntity, state.Entities); demotedWhileStaticPresent.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())); 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 }, demotedWhileStaticPresent); 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(), (_, _) => { }, 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())); } private sealed class ReadinessMeshAdapter : IWbMeshAdapter { public HashSet ReadyIds { get; } = new(); public Dictionary 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); } }