refactor(app): extract focused window lifetime

Move the exact retryable shutdown manifest, typed root snapshot, terminal reporting, and native-window-last release out of GameWindow. Keep session and GPU convergence as hard barriers while reporting persistent physical callback cleanup without stranding dependent owners.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 19:43:52 +02:00
parent 5a55d08106
commit 31e6e192b3
21 changed files with 1297 additions and 572 deletions

View file

@ -233,9 +233,7 @@ public sealed class GameWindowSlice8BoundaryTests
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
string shutdown = MethodBody(
"private ResourceShutdownTransaction CreateShutdownTransaction()",
"private void OnFocusChanged(bool focused)");
string shutdown = GameWindowLifetimeSource();
Assert.DoesNotContain("private void OnInputAction(", source, StringComparison.Ordinal);
Assert.DoesNotContain("private void SetInputCombatScope(", source, StringComparison.Ordinal);
@ -256,15 +254,17 @@ public sealed class GameWindowSlice8BoundaryTests
"retainedGameplayLease.Resource.Attach();"));
AssertAppearsInOrder(
shutdown,
"_liveCombatModeCommands.Deactivate",
"_runtimeDiagnosticCommands.Deactivate",
"_retainedUiGameplayBinding?.Deactivate()",
"_gameplayInputActions?.Deactivate()",
"new ResourceShutdownStage(\"session lifetime\"",
"binding.Dispose();",
"_retainedUiGameplayBinding = null;",
"actions.Dispose();",
"_gameplayInputActions = null;");
"Hard(\"combat command slot\", ingress.CombatCommands.Deactivate)",
"Hard(\"diagnostic command slot\", ingress.DiagnosticCommands.Deactivate)",
"Hard(\"retained gameplay\", () => ingress.RetainedGameplay?.Deactivate())",
"Hard(\"gameplay actions\", () => ingress.GameplayActions?.Deactivate())",
"Hard(\"live session\", () => DisposeLiveSession(ingress.LiveSession))",
"new ResourceShutdownStage(\"physical ingress cleanup\"",
"Soft(\"retained gameplay\", () => DisposeRetainedGameplay(ingress.RetainedGameplay))",
"Soft(\"gameplay actions\", () => DisposeGameplayActions(ingress.GameplayActions))",
"Soft(\"camera pointer\", () => DisposeCameraPointer(ingress.CameraPointer))",
"new ResourceShutdownStage(\"session dependents\"",
"Hard(\"mouse capture\", () => live.CameraPointer?.ReleaseMouseLookAfterSessionRetirement())");
}
[Fact]
@ -310,9 +310,7 @@ public sealed class GameWindowSlice8BoundaryTests
string load = MethodBody(
"private void OnLoad()",
"private void OnUpdate(double dt)");
string shutdown = MethodBody(
"private ResourceShutdownTransaction CreateShutdownTransaction()",
"private void OnFocusChanged(bool focused)");
string shutdown = GameWindowLifetimeSource();
Assert.Contains(
"private readonly RuntimeSettingsController _runtimeSettings;",
@ -390,13 +388,13 @@ public sealed class GameWindowSlice8BoundaryTests
"_factory.CreateTerrain(");
AssertAppearsInOrder(
shutdown,
"_runtimeSettings.UnbindViewModel()",
"Soft(\"settings view model\", () => ingress.Settings.UnbindViewModel())",
"new ResourceShutdownStage(\"frame borrowers\"",
"bindings.Dispose();",
"_retailUiLease.Dispose()",
"_streamer?.Dispose()",
"_wbDrawDispatcher?.Dispose()",
"_terrain?.Dispose()");
"Hard(\"frame-root bindings\", () => frame.FrameBindings?.Dispose())",
"Hard(\"retail UI\", () => DisposeRetailUi(live.RetailUi))",
"Hard(\"streamer\", () => live.Streamer?.Dispose())",
"Hard(\"mesh draw dispatcher\", () => render.DrawDispatcher?.Dispose())",
"Hard(\"terrain\", () => render.Terrain?.Dispose())");
}
[Fact]
@ -418,7 +416,7 @@ public sealed class GameWindowSlice8BoundaryTests
string close = Slice(
source,
"private void OnClosing()",
"private ResourceShutdownTransaction CreateShutdownTransaction()");
"private void OnFocusChanged(bool focused)");
Assert.Equal(1, CountOccurrences(update, "_frameGraphs.Tick("));
Assert.Equal(1, CountOccurrences(render, "_frameGraphs.Render("));
@ -432,30 +430,42 @@ public sealed class GameWindowSlice8BoundaryTests
"=> _cameraPointerInput?.HandleFocusChanged(focused);",
focus,
StringComparison.Ordinal);
Assert.Contains(
"private void OnClosing() => CompleteShutdown(releaseNativeWindow: false);",
close,
StringComparison.Ordinal);
AssertAppearsInOrder(
close,
"_hostQuiescence.StopAccepting();",
"CompleteShutdown();",
"_shutdown.CompleteOrThrow();");
"private void CompleteShutdown(bool releaseNativeWindow)",
"if (!_lifetime.HasShutdownRoots)",
"_lifetime.PublishShutdownRoots(CaptureShutdownRoots());",
"? _lifetime.CompleteAndReleaseNativeWindow()",
": _lifetime.TryComplete();");
Assert.DoesNotContain("new ResourceShutdownStage(", close, StringComparison.Ordinal);
Assert.DoesNotContain("CompleteOrThrow", close, StringComparison.Ordinal);
}
[Fact]
public void Shutdown_PreservesDependencyStagesAndNativeWindowLast()
{
string source = GameWindowSource();
string lifetime = GameWindowLifetimeSource();
string manifest = Slice(
source,
"private ResourceShutdownTransaction CreateShutdownTransaction()",
"private void OnFocusChanged(bool focused)");
lifetime,
"internal static class GameWindowShutdownManifest",
"private static ResourceShutdownOperation Hard");
string nativeRelease = Slice(
lifetime,
"public GameWindowLifetimeReport CompleteAndReleaseNativeWindow()",
"private void EnsureTransaction()");
int disposeStart = source.IndexOf("public void Dispose()", StringComparison.Ordinal);
Assert.True(disposeStart >= 0);
string dispose = source[disposeStart..];
string[] stages =
[
"new ResourceShutdownStage(\"input callback deactivation\"",
"new ResourceShutdownStage(\"session lifetime\"",
"new ResourceShutdownStage(\"input callback detach\"",
"new ResourceShutdownStage(\"host and session barriers\"",
"new ResourceShutdownStage(\"physical ingress cleanup\"",
"new ResourceShutdownStage(\"frame borrowers\"",
"new ResourceShutdownStage(\"session dependents\"",
"new ResourceShutdownStage(\"live entities\"",
@ -470,7 +480,7 @@ public sealed class GameWindowSlice8BoundaryTests
"new ResourceShutdownStage(\"failed render construction cleanup\"",
"new ResourceShutdownStage(\"frame flight owner\"",
"new ResourceShutdownStage(\"content mappings\"",
"new ResourceShutdownStage(\"input\"",
"new ResourceShutdownStage(\"input context\"",
"new ResourceShutdownStage(\"OpenGL context\"",
];
AssertAppearsInOrder(manifest, stages);
@ -479,32 +489,38 @@ public sealed class GameWindowSlice8BoundaryTests
Assert.Equal(1, CountOccurrences(manifest, stage));
AssertAppearsInOrder(
manifest,
"binding.Dispose();",
"if (!binding.IsDisposalComplete)",
"_windowCallbacks = null;");
AssertAppearsInOrder(
manifest,
"new(\"combat command slot\", _liveCombatModeCommands.Deactivate)",
"new(\"diagnostic command slot\", _runtimeDiagnosticCommands.Deactivate)",
"new(\"retained gameplay\", () => _retainedUiGameplayBinding?.Deactivate())",
"new(\"gameplay actions\", () => _gameplayInputActions?.Deactivate())",
"new(\"camera pointer\", () => _cameraPointerInput?.Deactivate())",
"new ResourceShutdownStage(\"session lifetime\"",
"controller.Dispose();",
"new ResourceShutdownStage(\"input callback detach\"",
"binding.Dispose();",
"actions.Dispose();",
"pointer.Dispose();",
"Hard(\"combat command slot\", ingress.CombatCommands.Deactivate)",
"Hard(\"diagnostic command slot\", ingress.DiagnosticCommands.Deactivate)",
"Hard(\"retained gameplay\", () => ingress.RetainedGameplay?.Deactivate())",
"Hard(\"gameplay actions\", () => ingress.GameplayActions?.Deactivate())",
"Hard(\"camera pointer\", () => ingress.CameraPointer?.Deactivate())",
"Hard(\"live session\", () => DisposeLiveSession(ingress.LiveSession))",
"new ResourceShutdownStage(\"physical ingress cleanup\"",
"Soft(\"retained gameplay\", () => DisposeRetainedGameplay(ingress.RetainedGameplay))",
"Soft(\"gameplay actions\", () => DisposeGameplayActions(ingress.GameplayActions))",
"Soft(\"camera pointer\", () => DisposeCameraPointer(ingress.CameraPointer))",
"Soft(\"native window callbacks\", () => DisposeWindowCallbacks(ingress.WindowCallbacks))",
"new ResourceShutdownStage(\"session dependents\"",
"pointer.ReleaseMouseLookAfterSessionRetirement();",
"_cameraPointerInput = null;");
"Hard(\"mouse capture\", () => live.CameraPointer?.ReleaseMouseLookAfterSessionRetirement())");
Assert.Contains(
"new(name, action, ResourceShutdownOperationPolicy.ReportAndContinue)",
lifetime,
StringComparison.Ordinal);
Assert.Contains(
"UiHost? RetainedUiHost,",
lifetime,
StringComparison.Ordinal);
Assert.Contains("_uiHost,", source, StringComparison.Ordinal);
AssertAppearsInOrder(
nativeRelease,
"TryComplete();",
"ReleaseNativeWindow();");
AssertAppearsInOrder(
dispose,
"CompleteShutdown();",
"_window?.Dispose();",
"CompleteShutdown(releaseNativeWindow: true);",
"_window = null;");
Assert.Equal(1, CountOccurrences(dispose, "CompleteShutdown();"));
Assert.Equal(1, CountOccurrences(dispose, "_window?.Dispose();"));
Assert.Equal(1, CountOccurrences(dispose, "CompleteShutdown(releaseNativeWindow: true);"));
Assert.DoesNotContain("_window?.Dispose();", dispose, StringComparison.Ordinal);
Assert.Equal(1, CountOccurrences(dispose, "_window = null;"));
int nativeReleased = dispose.IndexOf("_window = null;", StringComparison.Ordinal)
+ "_window = null;".Length;
@ -526,10 +542,7 @@ public sealed class GameWindowSlice8BoundaryTests
string load = MethodBody(
"private void OnLoad()",
"private void OnUpdate(double dt)");
string shutdown = Slice(
source,
"private ResourceShutdownTransaction CreateShutdownTransaction()",
"private void OnFocusChanged(bool focused)");
string shutdown = GameWindowLifetimeSource();
string terrainAtlas = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
@ -586,17 +599,17 @@ public sealed class GameWindowSlice8BoundaryTests
"TerrainModernRenderer terrain = AcquireAndPublish(");
AssertAppearsInOrder(
shutdown,
"publication.Dispose();",
"bindings.Dispose();",
"_retailUiLease.Dispose();",
"_localPlayerTeleport?.Dispose();",
"_portalTunnelFallback.ReleaseFallback();",
"_skyRenderer?.Dispose()",
"_terrain?.Dispose();",
"_renderResourceLifetime.ReleaseSkyShader",
"_renderResourceLifetime.ReleaseTerrainAtlas",
"_glConstructionCleanup.Dispose",
"_gl?.Dispose();");
"frame.FrameGraphPublication?.Dispose()",
"frame.FrameBindings?.Dispose()",
"DisposeRetailUi(live.RetailUi)",
"render.LocalTeleport?.Dispose();",
"render.PortalTunnelFallback.ReleaseFallback();",
"render.Sky?.Dispose()",
"render.Terrain?.Dispose()",
"render.DedicatedResources.ReleaseSkyShader",
"render.DedicatedResources.ReleaseTerrainAtlas",
"render.ConstructionCleanup.Dispose",
"platform.Gl?.Dispose()");
Assert.DoesNotContain(
"RetailUiRuntime.Mount(",
@ -614,7 +627,12 @@ public sealed class GameWindowSlice8BoundaryTests
source,
"_window.Run();",
"_glConstructionCleanup.RetainFrom(failure);",
"_glConstructionCleanup.Dispose");
"private GameWindowShutdownRoots CaptureShutdownRoots()",
"_glConstructionCleanup)");
Assert.Contains(
"Hard(\"GL construction ledger\", render.ConstructionCleanup.Dispose)",
shutdown,
StringComparison.Ordinal);
}
private static string MethodBody(string start, string end) =>
@ -661,6 +679,13 @@ public sealed class GameWindowSlice8BoundaryTests
"Rendering",
"GameWindow.cs"));
private static string GameWindowLifetimeSource() => File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Rendering",
"GameWindowLifetime.cs"));
private static string LivePresentationSource() => File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",