test: replace composition source freezes

This commit is contained in:
Erik 2026-08-18 15:50:53 +02:00
parent caa5eb8b2b
commit 80c7b44457
8 changed files with 471 additions and 406 deletions

View file

@ -1,6 +1,12 @@
using System.Reflection;
using AcDream.App.Composition;
using AcDream.App.Diagnostics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Wb;
using AcDream.App.Tests.Architecture;
using AcDream.App.Update;
using AcDream.App.World;
namespace AcDream.App.Tests.Composition;
@ -28,48 +34,77 @@ public sealed class FrameRootCompositionTests
[Fact]
public void ProductionPhasePublishesOnlyAfterBothRootsExist()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
MethodInfo compose = typeof(FrameRootCompositionPhase).GetMethod(
"ComposeCore",
BindingFlags.Instance | BindingFlags.NonPublic)
?? throw new MissingMethodException(
typeof(FrameRootCompositionPhase).FullName,
"ComposeCore");
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(compose);
AssertAppearsInOrder(
source,
"new RenderFrameResourceController(",
"new RenderSceneShadowComparisonController(",
"new WorldSceneRenderer(",
"new WorldLifecycleAutomationController(",
"\"world lifecycle automation owner\"",
"\"world lifecycle automation binding\"",
"new SerialRenderFramePostDiagnosticsPhase(",
"new RenderFrameOrchestrator(",
"postDiagnostics,",
"new RetailLiveFrameCoordinator(",
"new UpdateFrameOrchestrator(",
"d.FrameGraphs.PublishOwned(",
"_publication.PublishFrameRoots(result);",
"graphLease.Transfer();",
"bindingsLease.Transfer();");
int resources = CallIndex(calls, typeof(RenderFrameResourceController), ".ctor");
int comparison = CallIndex(
calls,
typeof(RenderSceneShadowComparisonController),
".ctor",
resources + 1);
int scene = CallIndex(calls, typeof(WorldSceneRenderer), ".ctor", comparison + 1);
int automation = CallIndex(
calls,
typeof(WorldLifecycleAutomationController),
".ctor",
scene + 1);
int diagnostics = CallIndex(
calls,
typeof(SerialRenderFramePostDiagnosticsPhase),
".ctor",
automation + 1);
int render = CallIndex(calls, typeof(RenderFrameOrchestrator), ".ctor", diagnostics + 1);
int live = CallIndex(calls, typeof(RetailLiveFrameCoordinator), ".ctor", render + 1);
int update = CallIndex(calls, typeof(UpdateFrameOrchestrator), ".ctor", live + 1);
int graph = CallIndex(calls, typeof(GameFrameGraphSlot), "PublishOwned", update + 1);
int publish = CallIndex(
calls,
typeof(IGameWindowFrameRootPublication),
"PublishFrameRoots",
graph + 1);
int firstTransfer = calls
.Select((call, index) => (call, index))
.First(pair => pair.index > publish
&& pair.call.Target.Name == "Transfer")
.index;
int secondTransfer = calls
.Select((call, index) => (call, index))
.First(pair => pair.index > firstTransfer
&& pair.call.Target.Name == "Transfer")
.index;
Assert.True(secondTransfer > firstTransfer);
}
[Fact]
public void GameWindowRetainsOnlyThePhaseBoundaryAndFrameHandoffs()
{
string source = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
IReadOnlyList<CompiledCall> calls =
CompiledCallGraph.ReadDeclared(typeof(GameWindow));
Assert.Contains("new FrameRootCompositionPhase(", source);
Assert.DoesNotContain("new AcDream.App.Rendering.WorldSceneRenderer(", source);
Assert.DoesNotContain("new AcDream.App.Update.UpdateFrameOrchestrator(", source);
Assert.DoesNotContain("CaptureWorldLifecycleResourceSnapshot", source);
Assert.DoesNotContain("_worldLifecycleAutomation", source);
Assert.DoesNotContain("_frameGraphs.Publish(", source);
Assert.Single(
calls,
call => call.Target.DeclaringType == typeof(FrameRootCompositionPhase)
&& call.Target.IsConstructor);
Assert.DoesNotContain(
calls,
call => call.Target.IsConstructor
&& call.Target.DeclaringType is { } type
&& (type == typeof(WorldSceneRenderer)
|| type == typeof(UpdateFrameOrchestrator)
|| type == typeof(WorldLifecycleAutomationController)));
Assert.DoesNotContain(
calls,
call => call.Target.DeclaringType
== typeof(WorldLifecycleResourceSnapshotSource)
&& call.Target.Name == nameof(
WorldLifecycleResourceSnapshotSource.Capture));
}
[Fact]
@ -80,19 +115,37 @@ public sealed class FrameRootCompositionTests
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.FieldType == typeof(GameWindow));
string snapshots = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Diagnostics",
"WorldLifecycleResourceSnapshotSource.cs"));
Assert.DoesNotContain("GameWindow", snapshots, StringComparison.Ordinal);
Assert.Contains("_liveEntities.PendingTeardownCount", snapshots);
Assert.Contains("GpuMemoryTracker.AllocatedBytes", snapshots);
Assert.Contains("_frameProfiler.LastReport", snapshots);
Assert.Contains("Capture(RenderFrameOutcome outcome)", snapshots);
Assert.Contains("outcome.World.VisibleLandblocks", snapshots);
Assert.Contains("outcome.World.TotalLandblocks", snapshots);
Assert.DoesNotContain(
typeof(WorldLifecycleResourceSnapshotSource).GetFields(
BindingFlags.Instance | BindingFlags.NonPublic),
field => field.FieldType == typeof(GameWindow));
MethodInfo capture = typeof(WorldLifecycleResourceSnapshotSource)
.GetMethod(nameof(WorldLifecycleResourceSnapshotSource.Capture))!;
Assert.Equal(
typeof(RenderFrameOutcome),
Assert.Single(capture.GetParameters()).ParameterType);
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(capture);
Assert.Contains(
calls,
call => call.Target.DeclaringType == typeof(LiveEntityRuntime)
&& call.Target.Name == "get_PendingTeardownCount");
Assert.Contains(
calls,
call => call.Target.DeclaringType == typeof(GpuMemoryTracker)
&& call.Target.Name == "get_AllocatedBytes");
Assert.Contains(
calls,
call => call.Target.DeclaringType == typeof(FrameProfiler)
&& call.Target.Name == "get_LastReport");
Assert.Contains(
calls,
call => call.Target.DeclaringType == typeof(WorldRenderFrameOutcome)
&& call.Target.Name == "get_VisibleLandblocks");
Assert.Contains(
calls,
call => call.Target.DeclaringType == typeof(WorldRenderFrameOutcome)
&& call.Target.Name == "get_TotalLandblocks");
}
private sealed class RetryBinding(
@ -110,27 +163,20 @@ public sealed class FrameRootCompositionTests
}
}
private static void AssertAppearsInOrder(string source, params string[] values)
private static int CallIndex(
IReadOnlyList<CompiledCall> calls,
Type declaringType,
string methodName,
int startIndex = 0)
{
int cursor = 0;
foreach (string value in values)
{
int found = source.IndexOf(value, cursor, StringComparison.Ordinal);
Assert.True(found >= 0, $"Missing expected source fragment: {value}");
cursor = found + value.Length;
}
}
private static string FindRepoRoot()
{
DirectoryInfo? directory = new(AppContext.BaseDirectory);
while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx")))
return directory.FullName;
directory = directory.Parent;
}
throw new DirectoryNotFoundException("Could not find AcDream.slnx.");
int index = CompiledCallGraph.IndexOf(
calls,
declaringType,
methodName,
startIndex);
Assert.True(
index >= startIndex,
$"Missing compiled edge {declaringType.FullName}.{methodName}.");
return index;
}
}