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

@ -159,4 +159,97 @@ public sealed class ResourceShutdownTransactionTests
Assert.Equal(1, calls);
Assert.True(transaction.IsComplete);
}
[Fact]
public void ReportableFailureRetriesThenAllowsDependentStagesAndIsStructured()
{
var calls = new List<string>();
var failure = new InvalidOperationException("physical detach failed");
var transaction = new ResourceShutdownTransaction(
new ResourceShutdownStage("physical ingress",
[
new(
"mouse callback",
() =>
{
calls.Add("detach");
throw failure;
},
ResourceShutdownOperationPolicy.ReportAndContinue),
]),
new ResourceShutdownStage("dependent",
[
new("dependent", () => calls.Add("dependent")),
]));
transaction.CompleteOrThrow();
Assert.Equal(["detach", "detach", "dependent"], calls);
Assert.True(transaction.IsComplete);
ResourceShutdownCleanupFailure cleanup = Assert.Single(
transaction.CleanupFailures);
Assert.Equal("physical ingress", cleanup.Stage);
Assert.Equal("mouse callback", cleanup.Operation);
Assert.Same(failure, cleanup.Error);
}
[Fact]
public void TransientReportableFailureConvergesWithoutCleanupFailure()
{
int calls = 0;
var transaction = new ResourceShutdownTransaction(
new ResourceShutdownStage("physical ingress",
[
new(
"keyboard callback",
() =>
{
if (calls++ == 0)
throw new InvalidOperationException("transient");
},
ResourceShutdownOperationPolicy.ReportAndContinue),
]));
transaction.CompleteOrThrow();
Assert.Equal(2, calls);
Assert.Empty(transaction.CleanupFailures);
}
[Fact]
public void SettledReportableFailureNeverReplaysWhileHardBarrierRetries()
{
int softCalls = 0;
int hardCalls = 0;
bool allowHard = false;
var transaction = new ResourceShutdownTransaction(
new ResourceShutdownStage("mixed",
[
new(
"soft",
() =>
{
softCalls++;
throw new InvalidOperationException("soft");
},
ResourceShutdownOperationPolicy.ReportAndContinue),
new("hard", () =>
{
hardCalls++;
if (!allowHard)
throw new InvalidOperationException("hard");
}),
]));
Assert.Throws<AggregateException>(transaction.CompleteOrThrow);
int settledSoftCalls = softCalls;
allowHard = true;
transaction.CompleteOrThrow();
Assert.Equal(settledSoftCalls, softCalls);
Assert.True(hardCalls > 1);
Assert.True(transaction.IsComplete);
Assert.Single(transaction.CleanupFailures);
}
}