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:
parent
5a55d08106
commit
31e6e192b3
21 changed files with 1297 additions and 572 deletions
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue