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; public sealed class FrameRootCompositionTests { [Fact] public void RuntimeBindingsReleaseInReverseAndRetryOnlyFailedEdges() { var calls = new List(); var bindings = new FrameRootRuntimeBindings(); bindings.Adopt("first", new RetryBinding("first", calls, 0)); bindings.Adopt("second", new RetryBinding("second", calls, 1)); Assert.Throws(bindings.Dispose); Assert.Equal(["second", "first"], calls); bindings.Dispose(); bindings.Dispose(); Assert.Equal(["second", "first", "second"], calls); Assert.Throws(() => bindings.Adopt("late", new RetryBinding("late", calls, 0))); } [Fact] public void ProductionPhasePublishesOnlyAfterBothRootsExist() { MethodInfo compose = typeof(FrameRootCompositionPhase).GetMethod( "ComposeCore", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException( typeof(FrameRootCompositionPhase).FullName, "ComposeCore"); IReadOnlyList calls = CompiledCallGraph.Read(compose); 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() { IReadOnlyList calls = CompiledCallGraph.ReadDeclared(typeof(GameWindow)); 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] public void FramePhaseAndSnapshotSourceRetainNoWindowOwner() { Assert.DoesNotContain( typeof(FrameRootCompositionPhase).GetFields( BindingFlags.Instance | BindingFlags.NonPublic), field => field.FieldType == typeof(GameWindow)); 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 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( string name, List calls, int failures) : IDisposable { private int _failures = failures; public void Dispose() { calls.Add(name); if (_failures-- > 0) throw new InvalidOperationException("retry"); } } private static int CallIndex( IReadOnlyList calls, Type declaringType, string methodName, int startIndex = 0) { int index = CompiledCallGraph.IndexOf( calls, declaringType, methodName, startIndex); Assert.True( index >= startIndex, $"Missing compiled edge {declaringType.FullName}.{methodName}."); return index; } }