refactor(render): compose render frame orchestrator
Move the accepted draw transaction and failure recovery behind typed frame-phase owners so GameWindow only supplies immutable frame input. Preserve retail draw order, make ImGui/bootstrap shutdown ownership explicit, and restore exact text-render GL state on failures.\n\nCo-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
28e1cf8029
commit
9d7df1bfc5
25 changed files with 1675 additions and 279 deletions
|
|
@ -148,21 +148,77 @@ public sealed class RenderFrameOrchestratorTests
|
|||
phases.ObservedInputs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderAndRecoveryFailure_AreAggregatedBeforeGpuClose()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
var renderFailure = new InvalidOperationException("render");
|
||||
var recoveryFailure = new InvalidOperationException("recovery");
|
||||
var phases = new RecordingPhases(calls)
|
||||
{
|
||||
FailurePoint = "world",
|
||||
Failure = renderFailure,
|
||||
RecoveryFailure = recoveryFailure,
|
||||
};
|
||||
|
||||
AggregateException actual = Assert.Throws<AggregateException>(
|
||||
() => Create(phases).Render(Input));
|
||||
|
||||
Assert.Equal(2, actual.InnerExceptions.Count);
|
||||
Assert.Same(renderFailure, actual.InnerExceptions[0]);
|
||||
Assert.Same(recoveryFailure, actual.InnerExceptions[1]);
|
||||
Assert.Equal(
|
||||
["gpu-begin", "resources", "world", "recovery-abort", "gpu-end"],
|
||||
calls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RenderRecoveryAndCloseFailures_AllRemainObservableInCausalOrder()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
var renderFailure = new InvalidOperationException("render");
|
||||
var recoveryFailure = new InvalidOperationException("recovery");
|
||||
var closeFailure = new InvalidOperationException("close");
|
||||
var phases = new RecordingPhases(calls)
|
||||
{
|
||||
FailurePoint = "presentation",
|
||||
Failure = renderFailure,
|
||||
RecoveryFailure = recoveryFailure,
|
||||
CloseFailure = closeFailure,
|
||||
};
|
||||
|
||||
AggregateException actual = Assert.Throws<AggregateException>(
|
||||
() => Create(phases).Render(Input));
|
||||
|
||||
Assert.Equal(3, actual.InnerExceptions.Count);
|
||||
Assert.Same(renderFailure, actual.InnerExceptions[0]);
|
||||
Assert.Same(recoveryFailure, actual.InnerExceptions[1]);
|
||||
Assert.Same(closeFailure, actual.InnerExceptions[2]);
|
||||
Assert.Equal(
|
||||
[
|
||||
"gpu-begin", "resources", "world", "presentation",
|
||||
"recovery-abort", "gpu-end",
|
||||
],
|
||||
calls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_RejectsEveryMissingRequiredOwner()
|
||||
{
|
||||
var phases = new RecordingPhases([]);
|
||||
|
||||
Assert.Throws<ArgumentNullException>(() => new RenderFrameOrchestrator(
|
||||
null!, phases, phases, phases, phases));
|
||||
null!, phases, phases, phases, phases, phases));
|
||||
Assert.Throws<ArgumentNullException>(() => new RenderFrameOrchestrator(
|
||||
phases, null!, phases, phases, phases));
|
||||
phases, null!, phases, phases, phases, phases));
|
||||
Assert.Throws<ArgumentNullException>(() => new RenderFrameOrchestrator(
|
||||
phases, phases, null!, phases, phases));
|
||||
phases, phases, null!, phases, phases, phases));
|
||||
Assert.Throws<ArgumentNullException>(() => new RenderFrameOrchestrator(
|
||||
phases, phases, phases, null!, phases));
|
||||
phases, phases, phases, null!, phases, phases));
|
||||
Assert.Throws<ArgumentNullException>(() => new RenderFrameOrchestrator(
|
||||
phases, phases, phases, phases, null!));
|
||||
phases, phases, phases, phases, null!, phases));
|
||||
Assert.Throws<ArgumentNullException>(() => new RenderFrameOrchestrator(
|
||||
phases, phases, phases, phases, phases, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -175,6 +231,7 @@ public sealed class RenderFrameOrchestratorTests
|
|||
typeof(IWorldSceneFramePhase),
|
||||
typeof(IPrivatePresentationFramePhase),
|
||||
typeof(IRenderFrameDiagnosticsPhase),
|
||||
typeof(IRenderFrameFailureRecovery),
|
||||
];
|
||||
FieldInfo[] fields = typeof(RenderFrameOrchestrator).GetFields(
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
|
@ -239,14 +296,17 @@ public sealed class RenderFrameOrchestratorTests
|
|||
|
||||
private static string[] ExpectedFailureCalls(string failurePoint) => failurePoint switch
|
||||
{
|
||||
"resources" => ["gpu-begin", "resources", "gpu-end"],
|
||||
"world" => ["gpu-begin", "resources", "world", "gpu-end"],
|
||||
"resources" => ["gpu-begin", "resources", "recovery-abort", "gpu-end"],
|
||||
"world" => ["gpu-begin", "resources", "world", "recovery-abort", "gpu-end"],
|
||||
"presentation" =>
|
||||
["gpu-begin", "resources", "world", "presentation", "gpu-end"],
|
||||
[
|
||||
"gpu-begin", "resources", "world", "presentation",
|
||||
"recovery-abort", "gpu-end",
|
||||
],
|
||||
"diagnostics" =>
|
||||
[
|
||||
"gpu-begin", "resources", "world", "presentation", "diagnostics",
|
||||
"gpu-end",
|
||||
"recovery-abort", "gpu-end",
|
||||
],
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(failurePoint)),
|
||||
};
|
||||
|
|
@ -266,14 +326,15 @@ public sealed class RenderFrameOrchestratorTests
|
|||
}
|
||||
|
||||
private static RenderFrameOrchestrator Create(RecordingPhases phases) =>
|
||||
new(phases, phases, phases, phases, phases);
|
||||
new(phases, phases, phases, phases, phases, phases);
|
||||
|
||||
private sealed class RecordingPhases :
|
||||
IRenderFrameLifetime,
|
||||
IRenderFrameResourcePhase,
|
||||
IWorldSceneFramePhase,
|
||||
IPrivatePresentationFramePhase,
|
||||
IRenderFrameDiagnosticsPhase
|
||||
IRenderFrameDiagnosticsPhase,
|
||||
IRenderFrameFailureRecovery
|
||||
{
|
||||
private readonly List<string> _calls;
|
||||
|
||||
|
|
@ -285,6 +346,7 @@ public sealed class RenderFrameOrchestratorTests
|
|||
public string? FailurePoint { get; init; }
|
||||
public Exception? Failure { get; init; }
|
||||
public Exception? CloseFailure { get; init; }
|
||||
public Exception? RecoveryFailure { get; init; }
|
||||
public WorldRenderFrameOutcome World { get; init; } = new(7, 19, true);
|
||||
public PrivatePresentationFrameOutcome Presentation { get; init; } = new(false, false);
|
||||
public List<(string Phase, RenderFrameInput Input)> ObservedInputs { get; } = [];
|
||||
|
|
@ -300,6 +362,13 @@ public sealed class RenderFrameOrchestratorTests
|
|||
throw CloseFailure;
|
||||
}
|
||||
|
||||
public void AbortFrame()
|
||||
{
|
||||
_calls.Add("recovery-abort");
|
||||
if (RecoveryFailure is not null)
|
||||
throw RecoveryFailure;
|
||||
}
|
||||
|
||||
public void Prepare(RenderFrameInput input)
|
||||
{
|
||||
ObservedInputs.Add(("resources", input));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue