using System.Numerics; using System.Reflection; using AcDream.App.Rendering; using AcDream.App.Rendering.Wb; using AcDream.App.Streaming; using AcDream.Core.Terrain; using AcDream.Core.World; using DatReaderWriter.DBObjs; namespace AcDream.App.Tests.Streaming; public sealed class LandblockRenderPublisherTests { private const uint LandblockId = 0xA9B4FFFFu; private static readonly LandblockBuildOrigin CapturedOrigin = new(0xA8, 0xB3); [Fact] public void BeginPublication_PublishesTerrainVisibilityAndAabbFromCapturedOrigin() { var calls = new List(); var visibility = new CellVisibility(); var state = new GpuWorldState(); LoadedCell cell = VisibilityCell(0xA9B40100u); LandblockBuild build = Build( new EnvCellLandblockBuild( LandblockId, [cell], Array.Empty())); LandblockMeshData mesh = MeshAtHeights(7f, 19f); var publisher = Publisher( calls, visibility, state, out _, out _, out _); LandblockRenderPublication receipt = publisher.BeginPublication(build, mesh); Assert.Equal(new Vector3(192f, 192f, 0f), receipt.Origin); Assert.Equal(["terrain:A9B4FFFF:192:192"], calls); Assert.True(visibility.TryGetCell(cell.CellId, out LoadedCell? committed)); Assert.Same(cell, committed); Assert.Single(receipt.VisibilityCells); state.AddLandblock(build.Landblock); var bounds = Assert.Single(state.LandblockBounds); Assert.Equal(new Vector3(192f, 192f, -3f), bounds.AabbMin); Assert.Equal(new Vector3(384f, 384f, 69f), bounds.AabbMax); } [Fact] public void BeginPublication_VisibilityOnlyCellDoesNotRequireDrawableShell() { var visibility = new CellVisibility(); LoadedCell cell = VisibilityCell(0xA9B40100u); LandblockBuild build = Build(new EnvCellLandblockBuild( LandblockId, [cell], Array.Empty())); var publisher = Publisher( new List(), visibility, new GpuWorldState(), out _, out _, out _); LandblockRenderPublication receipt = publisher.BeginPublication( build, EmptyMesh()); publisher.CompletePublication(receipt); Assert.True(visibility.TryGetCell(cell.CellId, out _)); Assert.Single(publisher.BuildingRegistries); Assert.Empty(Assert.Single(publisher.BuildingRegistries).All()); } [Fact] public void BeginPublication_MismatchedEnvCellTransactionHasNoSideEffects() { var calls = new List(); var visibility = new CellVisibility(); var state = new GpuWorldState(); LoadedCell cell = VisibilityCell(0xAAB40100u); LandblockBuild build = Build(new EnvCellLandblockBuild( 0xAAB4FFFFu, [cell], Array.Empty())); var publisher = Publisher( calls, visibility, state, out _, out _, out _); Assert.Throws(() => publisher.BeginPublication(build, EmptyMesh())); Assert.Empty(calls); Assert.False(visibility.TryGetCell(cell.CellId, out _)); Assert.Null(state.DetachLandblock(LandblockId)); Assert.Equal(0, publisher.Diagnostics.BeginCount); } [Fact] public void CompletePublication_CommitsBuildingThenEnvCellsAndIsIdempotent() { var calls = new List(); LandblockBuild build = Build(EmptyEnvCells()); int envCommits = 0; LandblockRenderPublisher? publisher = null; publisher = new LandblockRenderPublisher( publishTerrain: (_, _, _) => calls.Add("terrain"), removeTerrain: _ => { }, cellVisibility: new CellVisibility(), worldState: new GpuWorldState(), commitEnvCells: _ => { Assert.Single(publisher!.BuildingRegistries); calls.Add("env-cells"); envCommits++; }); LandblockRenderPublication receipt = publisher.BeginPublication(build, EmptyMesh()); publisher.CompletePublication(receipt); publisher.CompletePublication(receipt); Assert.Equal(["terrain", "env-cells"], calls); Assert.Equal(1, envCommits); Assert.Single(publisher.BuildingRegistries); Assert.Equal(1, publisher.Diagnostics.CompleteCount); } [Fact] public void CompletePublication_FailedEnvCommitRetriesWithoutDuplicatingRegistry() { int attempts = 0; LandblockBuild build = Build(EmptyEnvCells()); var publisher = new LandblockRenderPublisher( publishTerrain: (_, _, _) => { }, removeTerrain: _ => { }, cellVisibility: new CellVisibility(), worldState: new GpuWorldState(), commitEnvCells: _ => { attempts++; if (attempts == 1) throw new InvalidOperationException("injected EnvCell failure"); }); LandblockRenderPublication receipt = publisher.BeginPublication(build, EmptyMesh()); Assert.Throws(() => publisher.CompletePublication(receipt)); publisher.CompletePublication(receipt); Assert.Equal(2, attempts); Assert.Single(publisher.BuildingRegistries); Assert.Equal(1, publisher.Diagnostics.CompleteCount); } [Fact] public void CompletePublication_ForeignReceiptIsRejectedWithoutConsumingIt() { var first = Publisher( new List(), new CellVisibility(), new GpuWorldState(), out int[] firstCommits, out _, out _); var second = Publisher( new List(), new CellVisibility(), new GpuWorldState(), out int[] secondCommits, out _, out _); LandblockRenderPublication receipt = first.BeginPublication( Build(EmptyEnvCells()), EmptyMesh()); Assert.Throws(() => second.CompletePublication(receipt)); Assert.Empty(second.BuildingRegistries); Assert.Equal(0, secondCommits[0]); Assert.Equal(0, second.Diagnostics.CompleteCount); first.CompletePublication(receipt); Assert.Single(first.BuildingRegistries); Assert.Equal(1, firstCommits[0]); Assert.Equal(1, first.Diagnostics.CompleteCount); } [Fact] public void PrepareAndRetirementOperationsStayBalancedAtTheirOwnerBoundary() { var calls = new List(); var visibility = new CellVisibility(); var publisher = Publisher( calls, visibility, new GpuWorldState(), out _, out int[] preparations, out int[] envRemovals); LoadedCell cell = VisibilityCell(0xA9B40100u); EnvCellLandblockBuild envCells = new( LandblockId, [cell], Array.Empty()); LandblockRenderPublication receipt = publisher.BeginPublication( Build(envCells), EmptyMesh()); publisher.CompletePublication(receipt); publisher.PrepareAfterRenderPins(envCells); publisher.RemoveTerrain(LandblockId); publisher.RemoveCellVisibility(LandblockId); publisher.RemoveBuildingRegistry(LandblockId); publisher.RemoveEnvironmentCells(LandblockId); Assert.Equal(1, preparations[0]); Assert.Equal(1, envRemovals[0]); Assert.Equal( [ "terrain:A9B4FFFF:192:192", "prepare", "remove-terrain:A9B4FFFF", "remove-env:A9B4FFFF", ], calls); Assert.False(visibility.TryGetCell(cell.CellId, out _)); Assert.Empty(publisher.BuildingRegistries); LandblockRenderPublisherDiagnostics diagnostics = publisher.Diagnostics; Assert.Equal(1, diagnostics.EnvCellPreparationCount); Assert.Equal(1, diagnostics.TerrainRemovalCount); Assert.Equal(1, diagnostics.CellVisibilityRemovalCount); Assert.Equal(1, diagnostics.BuildingRegistryRemovalCount); Assert.Equal(1, diagnostics.EnvCellRemovalCount); } [Fact] public void PublisherSurface_HasNoDatResidencyPolicyOrLiveOriginDependency() { Type type = typeof(LandblockRenderPublisher); Type[] dependencies = type .GetFields(BindingFlags.Instance | BindingFlags.NonPublic) .Select(field => field.FieldType) .Concat(type.GetConstructors().SelectMany(ctor => ctor.GetParameters().Select(parameter => parameter.ParameterType))) .ToArray(); Assert.DoesNotContain(dependencies, dependency => dependency.FullName?.Contains("DatReader", StringComparison.Ordinal) == true); Assert.DoesNotContain(dependencies, dependency => dependency.FullName?.Contains("StreamingRegion", StringComparison.Ordinal) == true); Assert.DoesNotContain(dependencies, dependency => dependency.FullName?.Contains("LiveWorldOrigin", StringComparison.Ordinal) == true); } [Fact] public void GameWindow_HasNoDirectRenderPublicationBodies() { string source = File.ReadAllText(Path.Combine( FindRepoRoot(), "src", "AcDream.App", "Rendering", "GameWindow.cs")); Assert.DoesNotContain("_terrain.AddLandblockWithMesh", source, StringComparison.Ordinal); Assert.DoesNotContain("_cellVisibility.CommitLandblock", source, StringComparison.Ordinal); Assert.DoesNotContain("_buildingRegistries", source, StringComparison.Ordinal); Assert.DoesNotContain("_envCellRenderer?.CommitLandblock", source, StringComparison.Ordinal); Assert.DoesNotContain("EnsureEnvCellMeshesAfterPin", source, StringComparison.Ordinal); } private static LandblockRenderPublisher Publisher( List calls, CellVisibility visibility, GpuWorldState state, out int[] envCommits, out int[] preparations, out int[] envRemovals) { envCommits = [0]; preparations = [0]; envRemovals = [0]; int[] commitCounter = envCommits; int[] preparationCounter = preparations; int[] removalCounter = envRemovals; return new LandblockRenderPublisher( publishTerrain: (id, _, origin) => calls.Add( $"terrain:{id:X8}:{origin.X:0}:{origin.Y:0}"), removeTerrain: id => calls.Add($"remove-terrain:{id:X8}"), cellVisibility: visibility, worldState: state, commitEnvCells: _ => commitCounter[0]++, prepareEnvCells: _ => { preparationCounter[0]++; calls.Add("prepare"); }, removeEnvCells: id => { removalCounter[0]++; calls.Add($"remove-env:{id:X8}"); }); } private static LandblockBuild Build(EnvCellLandblockBuild envCells) { var physics = new PhysicsDatBundle( new LandBlockInfo(), new Dictionary(), new Dictionary(), new Dictionary(), new Dictionary()); return new LandblockBuild( new LoadedLandblock( LandblockId, new LandBlock(), Array.Empty(), physics), envCells, CapturedOrigin); } private static EnvCellLandblockBuild EmptyEnvCells() => new( LandblockId, Array.Empty(), Array.Empty()); private static LoadedCell VisibilityCell(uint cellId) => new() { CellId = cellId, WorldTransform = Matrix4x4.Identity, InverseWorldTransform = Matrix4x4.Identity, }; private static LandblockMeshData MeshAtHeights(float first, float second) => new( [ new TerrainVertex(new Vector3(0f, 0f, first), Vector3.UnitZ, 0, 0, 0, 0), new TerrainVertex(new Vector3(1f, 1f, second), Vector3.UnitZ, 0, 0, 0, 0), ], Array.Empty()); private static LandblockMeshData EmptyMesh() => new(Array.Empty(), Array.Empty()); private static string FindRepoRoot() { string? directory = AppContext.BaseDirectory; while (directory is not null) { if (File.Exists(Path.Combine(directory, "AcDream.slnx"))) return directory; directory = Directory.GetParent(directory)?.FullName; } throw new DirectoryNotFoundException("Could not locate repository root."); } }