Make scripted lifecycle checkpoints acknowledged post-diagnostics render barriers, capture the exact frame outcome beside canonical resource ownership, and harden the nine-stop route with ordered same-location cache and lifetime gates without weakening process residency thresholds. Co-authored-by: Codex <codex@openai.com>
179 lines
5.2 KiB
C#
179 lines
5.2 KiB
C#
using AcDream.App.Rendering;
|
|
|
|
namespace AcDream.App.Tests.Rendering;
|
|
|
|
public sealed class RenderFrameRecoveryIntegrationTests
|
|
{
|
|
private static readonly RenderFrameInput Input = new(1d / 60d, 1280, 720);
|
|
|
|
[Theory]
|
|
[InlineData("devtools-begin")]
|
|
[InlineData("weather")]
|
|
[InlineData("world")]
|
|
[InlineData("portal")]
|
|
[InlineData("paperdoll")]
|
|
[InlineData("gameplay-ui")]
|
|
[InlineData("devtools-render")]
|
|
public void DownstreamFailure_AbortsDevToolsBeforeGpuClose_ThenNextFrameSucceeds(
|
|
string failurePoint)
|
|
{
|
|
var failure = new FailureSwitch { Point = failurePoint };
|
|
var gpu = new GpuLifetime(failure);
|
|
var devTools = new DevTools(failure);
|
|
var preparation = new RenderFramePreparationController(
|
|
new Resources(),
|
|
devTools,
|
|
new Weather(failure));
|
|
var presentation = new PrivatePresentationRenderer(
|
|
new Portal(failure),
|
|
new FoundationSource(),
|
|
new Paperdoll(failure),
|
|
new GameplayUi(failure),
|
|
devTools,
|
|
new Screenshots());
|
|
var orchestrator = new RenderFrameOrchestrator(
|
|
gpu,
|
|
preparation,
|
|
new World(failure),
|
|
presentation,
|
|
new Diagnostics(),
|
|
NullRenderFramePostDiagnosticsPhase.Instance,
|
|
devTools);
|
|
|
|
Assert.Throws<InvalidOperationException>(() => orchestrator.Render(Input));
|
|
|
|
Assert.False(devTools.IsOpen);
|
|
Assert.Equal(1, devTools.AbortCount);
|
|
Assert.Equal(1, gpu.EndCount);
|
|
Assert.True(devTools.AbortSequence < gpu.LastEndSequence);
|
|
|
|
failure.Point = null;
|
|
RenderFrameOutcome outcome = orchestrator.Render(Input);
|
|
|
|
Assert.False(devTools.IsOpen);
|
|
Assert.Equal(2, devTools.BeginCount);
|
|
Assert.Equal(2, gpu.EndCount);
|
|
Assert.True(outcome.World.NormalWorldDrawn);
|
|
}
|
|
|
|
private sealed class FailureSwitch
|
|
{
|
|
private int _sequence;
|
|
|
|
public string? Point { get; set; }
|
|
|
|
public int NextSequence() => ++_sequence;
|
|
|
|
public void ThrowIf(string point)
|
|
{
|
|
if (Point == point)
|
|
throw new InvalidOperationException(point);
|
|
}
|
|
}
|
|
|
|
private sealed class GpuLifetime(FailureSwitch failure) : IRenderFrameLifetime
|
|
{
|
|
public int EndCount { get; private set; }
|
|
public int LastEndSequence { get; private set; }
|
|
|
|
public void BeginFrame()
|
|
{
|
|
}
|
|
|
|
public void EndFrame()
|
|
{
|
|
EndCount++;
|
|
LastEndSequence = failure.NextSequence();
|
|
}
|
|
}
|
|
|
|
private sealed class DevTools(FailureSwitch failure) : IDevToolsFrameLifecycle
|
|
{
|
|
public bool IsOpen { get; private set; }
|
|
public int BeginCount { get; private set; }
|
|
public int AbortCount { get; private set; }
|
|
public int AbortSequence { get; private set; }
|
|
|
|
public void BeginFrame(float deltaSeconds)
|
|
{
|
|
Assert.False(IsOpen);
|
|
IsOpen = true;
|
|
BeginCount++;
|
|
failure.ThrowIf("devtools-begin");
|
|
}
|
|
|
|
public void Render(double deltaSeconds, int viewportWidth, int viewportHeight)
|
|
{
|
|
Assert.True(IsOpen);
|
|
failure.ThrowIf("devtools-render");
|
|
IsOpen = false;
|
|
}
|
|
|
|
public void AbortFrame()
|
|
{
|
|
if (!IsOpen)
|
|
return;
|
|
|
|
AbortCount++;
|
|
AbortSequence = failure.NextSequence();
|
|
IsOpen = false;
|
|
}
|
|
}
|
|
|
|
private sealed class Resources : IRenderFrameResourcePhase
|
|
{
|
|
public void Prepare(RenderFrameInput input)
|
|
{
|
|
}
|
|
}
|
|
|
|
private sealed class Weather(FailureSwitch failure) : IRenderWeatherFramePhase
|
|
{
|
|
public void Tick(double deltaSeconds) => failure.ThrowIf("weather");
|
|
}
|
|
|
|
private sealed class World(FailureSwitch failure) : IWorldSceneFramePhase
|
|
{
|
|
public WorldRenderFrameOutcome Render(RenderFrameInput input)
|
|
{
|
|
failure.ThrowIf("world");
|
|
return new WorldRenderFrameOutcome(1, 1, NormalWorldDrawn: true);
|
|
}
|
|
}
|
|
|
|
private sealed class Portal(FailureSwitch failure) : IPrivatePortalViewport
|
|
{
|
|
public void Draw(int width, int height) => failure.ThrowIf("portal");
|
|
}
|
|
|
|
private sealed class Paperdoll(FailureSwitch failure) : IPrivatePaperdollFrame
|
|
{
|
|
public void Render() => failure.ThrowIf("paperdoll");
|
|
}
|
|
|
|
private sealed class GameplayUi(FailureSwitch failure) : IRetainedGameplayUiFrame
|
|
{
|
|
public void Render(double deltaSeconds, int width, int height) =>
|
|
failure.ThrowIf("gameplay-ui");
|
|
}
|
|
|
|
private sealed class FoundationSource : IRenderFrameFoundationSource
|
|
{
|
|
public RenderFrameFoundation Foundation { get; } = new(
|
|
PortalViewportVisible: false,
|
|
default,
|
|
default);
|
|
}
|
|
|
|
private sealed class Screenshots : IPrivateFrameScreenshot
|
|
{
|
|
public bool CapturePending(int width, int height) => false;
|
|
}
|
|
|
|
private sealed class Diagnostics : IRenderFrameDiagnosticsPhase
|
|
{
|
|
public void Publish(RenderFrameInput input, RenderFrameOutcome outcome)
|
|
{
|
|
}
|
|
}
|
|
}
|