refactor(app): compose atomic frame roots

Move the complete update/render construction graph into a typed Phase-8 owner, explicitly carry the content dependencies it consumes, and publish both roots through one exact lease. Extract lifecycle resource sampling and frame-owned late bindings so partial startup and shutdown withdraw the same generation without window callbacks.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 19:02:08 +02:00
parent 826f9ea9b5
commit 3628aeb520
21 changed files with 1052 additions and 374 deletions

View file

@ -46,6 +46,7 @@ public sealed class GameWindowRenderLeafCompositionTests
"Composition",
"SessionPlayerComposition.cs"));
string source = GameWindowSource();
string framePhase = FrameRootSource();
AssertAppearsInOrder(
phase,
@ -57,10 +58,13 @@ public sealed class GameWindowRenderLeafCompositionTests
AssertAppearsInOrder(
source,
"new SessionPlayerCompositionPhase(",
"new AcDream.App.Rendering.LocalPlayerTeleportRenderStateSource(",
"new AcDream.App.Rendering.RenderFrameResourceController(",
"new AcDream.App.Rendering.PrivatePresentationRenderer(",
"new AcDream.App.Rendering.RenderFrameOrchestrator(");
"new FrameRootCompositionPhase(");
AssertAppearsInOrder(
framePhase,
"new LocalPlayerTeleportRenderStateSource(",
"new RenderFrameResourceController(",
"new PrivatePresentationRenderer(",
"new RenderFrameOrchestrator(");
}
[Fact]
@ -111,10 +115,11 @@ public sealed class GameWindowRenderLeafCompositionTests
"Composition",
"SettingsDevToolsComposition.cs"));
Assert.Contains("new DevToolsFramePresenter(", settingsComposition);
Assert.Contains("new AcDream.App.Rendering.RenderFrameResourceController(", source);
Assert.Contains("new AcDream.App.Rendering.RenderWeatherFrameController(", source);
Assert.Contains("new AcDream.App.Rendering.PrivatePresentationRenderer(", source);
Assert.Contains("new AcDream.App.Rendering.RenderFrameOrchestrator(", source);
string framePhase = FrameRootSource();
Assert.Contains("new RenderFrameResourceController(", framePhase);
Assert.Contains("new RenderWeatherFrameController(", framePhase);
Assert.Contains("new PrivatePresentationRenderer(", framePhase);
Assert.Contains("new RenderFrameOrchestrator(", framePhase);
Assert.Contains("new DisplayFramePacingController(", source);
string pointerSource = File.ReadAllText(Path.Combine(
FindRepoRoot(),
@ -142,7 +147,8 @@ public sealed class GameWindowRenderLeafCompositionTests
AssertAppearsInOrder(
source,
"new ResourceShutdownStage(\"frame borrowers\"",
"_frameGraphs.Withdraw();",
"publication.Dispose();",
"bindings.Dispose();",
"new ResourceShutdownStage(\"session dependents\"");
AssertAppearsInOrder(
source,
@ -150,7 +156,7 @@ public sealed class GameWindowRenderLeafCompositionTests
"new(\"frame profiler\", _frameProfiler.Dispose)");
AssertAppearsInOrder(
source,
"_frameGraphs.Withdraw();",
"publication.Dispose();",
"owner.DisposeFrontend()",
"new ResourceShutdownStage(\"input\"",
"_input?.Dispose();",
@ -186,7 +192,7 @@ public sealed class GameWindowRenderLeafCompositionTests
"private void OnRender(double deltaSeconds)",
StringComparison.Ordinal);
int end = source.IndexOf(
"private AcDream.App.Diagnostics.WorldLifecycleResourceSnapshot",
"private void OnFramebufferResize(",
start,
StringComparison.Ordinal);
Assert.True(start >= 0 && end > start);
@ -216,12 +222,12 @@ public sealed class GameWindowRenderLeafCompositionTests
[Fact]
public void TerrainAndFrameDiagnostics_AreComposedAsOneFocusedOwner()
{
string source = GameWindowSource();
string source = FrameRootSource();
Assert.Contains(
"new AcDream.App.Rendering.TerrainDrawDiagnosticsController(",
"new TerrainDrawDiagnosticsController(",
source);
Assert.Contains("new AcDream.App.Rendering.WorldScenePassExecutor(", source);
Assert.Contains("new WorldScenePassExecutor(", source);
Assert.DoesNotContain("_terrainDrawDiagnostics!.Begin();", source);
Assert.DoesNotContain("_terrainDrawDiagnostics.Complete();", source);
}
@ -240,6 +246,13 @@ public sealed class GameWindowRenderLeafCompositionTests
"Composition",
"LivePresentationComposition.cs"));
private static string FrameRootSource() => File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
private static string Source(string fileName) => File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",

View file

@ -65,10 +65,21 @@ public sealed class GameWindowSlice8BoundaryTests
"new LivePresentationCompositionPhase(",
"this).Compose(",
"new SessionPlayerCompositionPhase(",
"var renderFrameOrchestrator =",
"var updateFrameOrchestrator = new AcDream.App.Update.UpdateFrameOrchestrator(",
"_frameGraphs.Publish(updateFrameOrchestrator, renderFrameOrchestrator);",
"sessionPlayer.SessionHost.Start(_options)");
"FrameRootResult frameRoots = new FrameRootCompositionPhase(",
"frameRoots.SessionHost.Start(_options)");
string framePhase = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
AssertAppearsInOrder(
framePhase,
"new RenderFrameOrchestrator(",
"new UpdateFrameOrchestrator(",
"d.FrameGraphs.PublishOwned(",
"_publication.PublishFrameRoots(result);");
string sessionPhase = File.ReadAllText(Path.Combine(
FindRepoRoot(),
@ -86,7 +97,7 @@ public sealed class GameWindowSlice8BoundaryTests
"_publication.PublishSessionPlayer(result);");
Assert.Equal(1, CountOccurrences(
body,
"sessionPlayer.SessionHost.Start(_options)"));
"frameRoots.SessionHost.Start(_options)"));
string phaseOne = Slice(
File.ReadAllText(Path.Combine(
FindRepoRoot(),
@ -107,7 +118,7 @@ public sealed class GameWindowSlice8BoundaryTests
phaseOne,
StringComparison.Ordinal);
int start = body.IndexOf(
"sessionPlayer.SessionHost.Start(_options)",
"frameRoots.SessionHost.Start(_options)",
StringComparison.Ordinal);
string postStart = body[start..];
Assert.Contains("switch (liveStart.Status)", postStart, StringComparison.Ordinal);
@ -331,7 +342,8 @@ public sealed class GameWindowSlice8BoundaryTests
"this).Compose(platform, hostInputCamera, contentEffectsAudio);",
"new WorldRenderCompositionPhase(",
"new SessionPlayerCompositionPhase(",
"sessionPlayer.SessionHost.Start(_options)");
"new FrameRootCompositionPhase(",
"frameRoots.SessionHost.Start(_options)");
string sessionPhase = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
@ -385,7 +397,7 @@ public sealed class GameWindowSlice8BoundaryTests
string render = Slice(
source,
"private void OnRender(double deltaSeconds)",
"private AcDream.App.Diagnostics.WorldLifecycleResourceSnapshot");
"private void OnFramebufferResize(");
string focus = Slice(
source,
"private void OnFocusChanged(bool focused)",
@ -524,6 +536,12 @@ public sealed class GameWindowSlice8BoundaryTests
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
string framePhase = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
AssertAppearsInOrder(
livePhase,
@ -536,7 +554,12 @@ public sealed class GameWindowSlice8BoundaryTests
"new WorldRenderCompositionPhase(",
"new LivePresentationCompositionPhase(",
"new SessionPlayerCompositionPhase(",
"_frameGraphs.Publish(updateFrameOrchestrator, renderFrameOrchestrator);");
"new FrameRootCompositionPhase(");
AssertAppearsInOrder(
framePhase,
"new RenderFrameOrchestrator(",
"new UpdateFrameOrchestrator(",
"d.FrameGraphs.PublishOwned(");
AssertAppearsInOrder(
sessionPhase,
"d.PortalTunnelFallback.Transfer(",
@ -550,7 +573,8 @@ public sealed class GameWindowSlice8BoundaryTests
"TerrainModernRenderer terrain = AcquireAndPublish(");
AssertAppearsInOrder(
shutdown,
"_frameGraphs.Withdraw();",
"publication.Dispose();",
"bindings.Dispose();",
"_retailUiLease.Dispose();",
"_localPlayerTeleport?.Dispose();",
"_portalTunnelFallback.ReleaseFallback();",

View file

@ -225,11 +225,11 @@ public sealed class RetailPViewPassExecutorTests
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
"Composition",
"FrameRootComposition.cs"));
Assert.Contains("new AcDream.App.Rendering.RetailPViewPassExecutor(", source);
Assert.Contains("new AcDream.App.Rendering.WorldScenePViewRenderer(", source);
Assert.Contains("new RetailPViewPassExecutor(", source);
Assert.Contains("new WorldScenePViewRenderer(", source);
Assert.DoesNotContain("new AcDream.App.Rendering.RetailPViewFrameInput", source);
Assert.DoesNotContain("RetailPViewDrawContext", source);
Assert.DoesNotContain("DrawLandscapeSlice =", source);

View file

@ -152,6 +152,28 @@ public sealed class RuntimeResourceSlotTests
Assert.Equal(["update:2"], calls);
}
[Fact]
public void OwnedFramePublicationWithdrawsOnlyItsExactPair()
{
var calls = new List<string>();
var slot = new GameFrameGraphSlot();
IDisposable stale = slot.PublishOwned(
new RecordingUpdateRoot(calls),
new RecordingRenderRoot(calls));
stale.Dispose();
IDisposable current = slot.PublishOwned(
new RecordingUpdateRoot(calls),
new RecordingRenderRoot(calls));
stale.Dispose();
Assert.True(slot.Tick(new UpdateFrameInput(3)));
current.Dispose();
current.Dispose();
Assert.False(slot.Tick(new UpdateFrameInput(4)));
Assert.Equal(["update:3"], calls);
}
[Fact]
public void OwnedSlotRejectsReentrantAcquireWithoutLosingOuterResource()
{

View file

@ -328,6 +328,12 @@ public sealed class WorldRenderFrameBuilderTests
public void World_scene_uses_the_typed_builder_and_orchestrator_owns_its_local_composition()
{
string gameWindow = GameWindowSource();
string frameRoot = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"FrameRootComposition.cs"));
string worldScene = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
@ -357,11 +363,14 @@ public sealed class WorldRenderFrameBuilderTests
"_passes.DrawFlatTerrain(",
"NormalWorldDrawn: true");
AssertAppearsInOrder(
gameWindow,
"var skyPesFrame = new AcDream.App.Rendering.SkyPesFrameController(",
frameRoot,
"var skyPesFrame = new SkyPesFrameController(",
"var worldRenderFrameBuilder =",
"new AcDream.App.Rendering.RenderFrameOrchestrator(",
"_frameGraphs.Withdraw();",
"new RenderFrameOrchestrator(",
"d.FrameGraphs.PublishOwned(");
AssertAppearsInOrder(
gameWindow,
"publication.Dispose();",
"new(\"equipped children\"",
"new(\"effect network state\"",
"new(\"audio\"",

View file

@ -279,11 +279,11 @@ public sealed class WorldSceneRendererTests
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindow.cs"));
"Composition",
"FrameRootComposition.cs"));
Assert.Contains("new AcDream.App.Rendering.WorldSceneRenderer(", source);
Assert.Contains("new AcDream.App.Rendering.RenderFrameOrchestrator(", source);
Assert.Contains("new WorldSceneRenderer(", source);
Assert.Contains("new RenderFrameOrchestrator(", source);
Assert.DoesNotContain("_worldSceneRenderer", source);
Assert.DoesNotContain("_retailPViewRenderer.DrawInside(", source);
Assert.DoesNotContain("_retailAlphaQueue.BeginFrame();", source);