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:
Erik 2026-07-22 08:03:49 +02:00
parent 28e1cf8029
commit 9d7df1bfc5
25 changed files with 1675 additions and 279 deletions

View file

@ -25,10 +25,19 @@ internal enum DevToolsPanelKind
Settings,
}
internal interface IDevToolsFrameLifecycle : IRenderFrameFailureRecovery
{
void BeginFrame(float deltaSeconds);
void Render(double deltaSeconds, int viewportWidth, int viewportHeight);
}
internal interface IDevToolsFrameBackend
{
void BeginFrame(float deltaSeconds);
void AbortFrame();
bool BeginMainMenuBar();
void EndMainMenuBar();
@ -82,10 +91,11 @@ internal interface IDevToolsPanelSet
}
/// <summary>Concrete ImGui backend for the developer presentation owner.</summary>
internal sealed class ImGuiDevToolsFrameBackend : IDevToolsFrameBackend
internal sealed class ImGuiDevToolsFrameBackend : IDevToolsFrameBackend, IDisposable
{
private readonly ImGuiBootstrapper _bootstrap;
private readonly ImGuiPanelHost _panels;
private bool _disposed;
public ImGuiDevToolsFrameBackend(
ImGuiBootstrapper bootstrap,
@ -97,6 +107,8 @@ internal sealed class ImGuiDevToolsFrameBackend : IDevToolsFrameBackend
public void BeginFrame(float deltaSeconds) => _bootstrap.BeginFrame(deltaSeconds);
public void AbortFrame() => _bootstrap.AbortFrame();
public bool BeginMainMenuBar() => ImGuiNET.ImGui.BeginMainMenuBar();
public void EndMainMenuBar() => ImGuiNET.ImGui.EndMainMenuBar();
@ -130,6 +142,15 @@ internal sealed class ImGuiDevToolsFrameBackend : IDevToolsFrameBackend
ImGuiNET.ImGui.SetWindowSize(title, size, imguiCondition);
}
public void Dispose()
{
if (_disposed)
return;
_bootstrap.Dispose();
_disposed = true;
}
}
/// <summary>Mutable late-composition seam for player-mode menu operations.</summary>
@ -229,13 +250,14 @@ internal sealed class DevToolsPanelSet : IDevToolsPanelSet
/// reusable default layout. Retained gameplay UI remains a separate earlier
/// presentation phase.
/// </summary>
internal sealed class DevToolsFramePresenter
internal sealed class DevToolsFramePresenter : IDevToolsFrameLifecycle
{
private readonly IDevToolsFrameBackend _backend;
private readonly IDevToolsCameraMenuOperations _camera;
private readonly IDevToolsCommandBusSource _commands;
private readonly IDevToolsPanelSet _panels;
private readonly AcDream.App.Diagnostics.FrameProfiler _profiler;
private bool _frameOpen;
public DevToolsFramePresenter(
IDevToolsFrameBackend backend,
@ -251,15 +273,47 @@ internal sealed class DevToolsFramePresenter
_panels = panels ?? throw new ArgumentNullException(nameof(panels));
}
public void BeginFrame(float deltaSeconds) => _backend.BeginFrame(deltaSeconds);
public void BeginFrame(float deltaSeconds)
{
if (_frameOpen)
{
throw new InvalidOperationException(
"The previous developer-tools frame must render or abort before a new frame begins.");
}
_frameOpen = true;
_backend.BeginFrame(deltaSeconds);
}
public void Render(double deltaSeconds, int viewportWidth, int viewportHeight)
{
if (!_frameOpen)
{
throw new InvalidOperationException(
"BeginFrame must open the developer-tools frame before Render.");
}
var context = new PanelContext((float)deltaSeconds, _commands.Current);
DrawMenu(viewportWidth, viewportHeight);
_backend.RenderPanels(context);
using var stage = _profiler.BeginStage(AcDream.App.Diagnostics.FrameStage.ImGui);
_backend.RenderDrawData();
_frameOpen = false;
}
public void AbortFrame()
{
if (!_frameOpen)
return;
try
{
_backend.AbortFrame();
}
finally
{
_frameOpen = false;
}
}
public void ToggleDebugPanel()