refactor(render): extract frame presentation diagnostics

This commit is contained in:
Erik 2026-07-22 05:02:31 +02:00
parent 7e4cfb37c3
commit 733126a272
20 changed files with 3767 additions and 1021 deletions

View file

@ -0,0 +1,387 @@
using System.Numerics;
using AcDream.App.Input;
using AcDream.App.Net;
using AcDream.UI.Abstractions;
using AcDream.UI.Abstractions.Panels.Chat;
using AcDream.UI.Abstractions.Panels.Debug;
using AcDream.UI.Abstractions.Panels.Settings;
using AcDream.UI.Abstractions.Panels.Vitals;
using AcDream.UI.ImGui;
using ImGuiNET;
namespace AcDream.App.Rendering;
internal enum DevToolsPanelLayoutCondition
{
FirstUseEver,
Always,
}
internal enum DevToolsPanelKind
{
Vitals,
Chat,
Debug,
Settings,
}
internal interface IDevToolsFrameBackend
{
void BeginFrame(float deltaSeconds);
bool BeginMainMenuBar();
void EndMainMenuBar();
bool BeginMenu(string label);
void EndMenu();
bool MenuItem(string label, string? shortcut = null, bool selected = false);
void Separator();
void RenderPanels(PanelContext context);
void RenderDrawData();
void SetWindowLayout(
string title,
Vector2 position,
Vector2 size,
DevToolsPanelLayoutCondition condition);
}
internal interface IDevToolsCameraMenuOperations
{
bool IsFlyMode { get; }
bool CollideCamera { get; set; }
void ToggleFlyOrChase();
}
internal interface IDevToolsCommandBusSource
{
ICommandBus Current { get; }
}
internal interface IDevToolsPanelSet
{
bool Contains(DevToolsPanelKind kind);
string? GetTitle(DevToolsPanelKind kind);
bool IsVisible(DevToolsPanelKind kind);
void Toggle(DevToolsPanelKind kind);
void FocusChatInput();
void AddDebugToast(string message);
}
/// <summary>Concrete ImGui backend for the developer presentation owner.</summary>
internal sealed class ImGuiDevToolsFrameBackend : IDevToolsFrameBackend
{
private readonly ImGuiBootstrapper _bootstrap;
private readonly ImGuiPanelHost _panels;
public ImGuiDevToolsFrameBackend(
ImGuiBootstrapper bootstrap,
ImGuiPanelHost panels)
{
_bootstrap = bootstrap ?? throw new ArgumentNullException(nameof(bootstrap));
_panels = panels ?? throw new ArgumentNullException(nameof(panels));
}
public void BeginFrame(float deltaSeconds) => _bootstrap.BeginFrame(deltaSeconds);
public bool BeginMainMenuBar() => ImGuiNET.ImGui.BeginMainMenuBar();
public void EndMainMenuBar() => ImGuiNET.ImGui.EndMainMenuBar();
public bool BeginMenu(string label) => ImGuiNET.ImGui.BeginMenu(label);
public void EndMenu() => ImGuiNET.ImGui.EndMenu();
public bool MenuItem(string label, string? shortcut = null, bool selected = false) =>
ImGuiNET.ImGui.MenuItem(label, shortcut ?? string.Empty, selected);
public void Separator() => ImGuiNET.ImGui.Separator();
public void RenderPanels(PanelContext context) => _panels.RenderAll(context);
public void RenderDrawData() => _bootstrap.Render();
public void SetWindowLayout(
string title,
Vector2 position,
Vector2 size,
DevToolsPanelLayoutCondition condition)
{
ImGuiCond imguiCondition = condition switch
{
DevToolsPanelLayoutCondition.FirstUseEver => ImGuiCond.FirstUseEver,
DevToolsPanelLayoutCondition.Always => ImGuiCond.Always,
_ => throw new ArgumentOutOfRangeException(nameof(condition)),
};
ImGuiNET.ImGui.SetWindowPos(title, position, imguiCondition);
ImGuiNET.ImGui.SetWindowSize(title, size, imguiCondition);
}
}
/// <summary>Mutable late-composition seam for player-mode menu operations.</summary>
internal sealed class DevToolsCameraMenuOperations : IDevToolsCameraMenuOperations
{
private readonly CameraController _camera;
private PlayerModeController? _playerMode;
public DevToolsCameraMenuOperations(CameraController camera)
{
_camera = camera ?? throw new ArgumentNullException(nameof(camera));
}
public bool IsFlyMode => _camera.IsFlyMode;
public bool CollideCamera
{
get => AcDream.Core.Rendering.CameraDiagnostics.CollideCamera;
set => AcDream.Core.Rendering.CameraDiagnostics.CollideCamera = value;
}
public void Bind(PlayerModeController playerMode)
{
_playerMode = playerMode ?? throw new ArgumentNullException(nameof(playerMode));
}
public void ToggleFlyOrChase() => _playerMode?.ToggleFlyOrChase();
}
/// <summary>Resolves the reconnect-safe command bus at draw time.</summary>
internal sealed class DevToolsCommandBusSource : IDevToolsCommandBusSource
{
private LiveSessionController? _session;
public ICommandBus Current =>
_session?.Commands ?? NullCommandBus.Instance;
public void Bind(LiveSessionController session)
{
_session = session ?? throw new ArgumentNullException(nameof(session));
}
}
/// <summary>Typed panel operations used by menu and input presentation.</summary>
internal sealed class DevToolsPanelSet : IDevToolsPanelSet
{
private readonly VitalsPanel _vitals;
private readonly ChatPanel _chat;
private readonly DebugPanel _debug;
private readonly DebugVM _debugViewModel;
private readonly SettingsPanel? _settings;
public DevToolsPanelSet(
VitalsPanel vitals,
ChatPanel chat,
DebugPanel debug,
DebugVM debugViewModel,
SettingsPanel? settings)
{
_vitals = vitals ?? throw new ArgumentNullException(nameof(vitals));
_chat = chat ?? throw new ArgumentNullException(nameof(chat));
_debug = debug ?? throw new ArgumentNullException(nameof(debug));
_debugViewModel = debugViewModel
?? throw new ArgumentNullException(nameof(debugViewModel));
_settings = settings;
}
public bool Contains(DevToolsPanelKind kind) =>
kind != DevToolsPanelKind.Settings || _settings is not null;
public string? GetTitle(DevToolsPanelKind kind) => GetPanel(kind)?.Title;
public bool IsVisible(DevToolsPanelKind kind) => GetPanel(kind)?.IsVisible == true;
public void Toggle(DevToolsPanelKind kind)
{
if (GetPanel(kind) is { } panel)
panel.IsVisible = !panel.IsVisible;
}
public void FocusChatInput() => _chat.FocusInput();
public void AddDebugToast(string message) => _debugViewModel.AddToast(message);
private IPanel? GetPanel(DevToolsPanelKind kind) => kind switch
{
DevToolsPanelKind.Vitals => _vitals,
DevToolsPanelKind.Chat => _chat,
DevToolsPanelKind.Debug => _debug,
DevToolsPanelKind.Settings => _settings,
_ => throw new ArgumentOutOfRangeException(nameof(kind)),
};
}
/// <summary>
/// Owns the optional ImGui developer frame, menu policy, panel actions, and
/// reusable default layout. Retained gameplay UI remains a separate earlier
/// presentation phase.
/// </summary>
internal sealed class DevToolsFramePresenter
{
private readonly IDevToolsFrameBackend _backend;
private readonly IDevToolsCameraMenuOperations _camera;
private readonly IDevToolsCommandBusSource _commands;
private readonly IDevToolsPanelSet _panels;
private readonly AcDream.App.Diagnostics.FrameProfiler _profiler;
public DevToolsFramePresenter(
IDevToolsFrameBackend backend,
IDevToolsCameraMenuOperations camera,
IDevToolsCommandBusSource commands,
AcDream.App.Diagnostics.FrameProfiler profiler,
IDevToolsPanelSet panels)
{
_backend = backend ?? throw new ArgumentNullException(nameof(backend));
_camera = camera ?? throw new ArgumentNullException(nameof(camera));
_commands = commands ?? throw new ArgumentNullException(nameof(commands));
_profiler = profiler ?? throw new ArgumentNullException(nameof(profiler));
_panels = panels ?? throw new ArgumentNullException(nameof(panels));
}
public void BeginFrame(float deltaSeconds) => _backend.BeginFrame(deltaSeconds);
public void Render(double deltaSeconds, int viewportWidth, int viewportHeight)
{
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();
}
public void ToggleDebugPanel()
{
_panels.Toggle(DevToolsPanelKind.Debug);
_panels.AddDebugToast(
$"Debug panel {(_panels.IsVisible(DevToolsPanelKind.Debug) ? "ON" : "OFF")}");
}
public void FocusChatInput() => _panels.FocusChatInput();
public void ToggleSettingsPanel()
{
_panels.Toggle(DevToolsPanelKind.Settings);
}
public void ResetLayout(
int viewportWidth,
int viewportHeight,
DevToolsPanelLayoutCondition condition)
{
float width = Math.Max(viewportWidth, 480);
float height = Math.Max(viewportHeight, 320);
SetLayout(
DevToolsPanelKind.Vitals,
new Vector2(10f, 30f),
new Vector2(220f, 110f),
condition);
SetLayout(
DevToolsPanelKind.Chat,
new Vector2(10f, height - 320f),
new Vector2(450f, 300f),
condition);
SetLayout(
DevToolsPanelKind.Debug,
new Vector2(width - 380f, 30f),
new Vector2(370f, 520f),
condition);
if (_panels.Contains(DevToolsPanelKind.Settings))
{
SetLayout(
DevToolsPanelKind.Settings,
new Vector2((width - 700f) * 0.5f, (height - 500f) * 0.5f),
new Vector2(700f, 500f),
condition);
}
}
private void DrawMenu(int viewportWidth, int viewportHeight)
{
if (!_backend.BeginMainMenuBar())
return;
try
{
if (_backend.BeginMenu("View"))
{
try
{
if (_panels.Contains(DevToolsPanelKind.Settings)
&& _backend.MenuItem("Settings", "F11"))
{
_panels.Toggle(DevToolsPanelKind.Settings);
}
if (_backend.MenuItem("Vitals"))
_panels.Toggle(DevToolsPanelKind.Vitals);
if (_backend.MenuItem("Chat"))
_panels.Toggle(DevToolsPanelKind.Chat);
if (_backend.MenuItem("Debug", "Ctrl+F1"))
_panels.Toggle(DevToolsPanelKind.Debug);
_backend.Separator();
if (_backend.MenuItem("Reset window layout"))
ResetLayout(
viewportWidth,
viewportHeight,
DevToolsPanelLayoutCondition.Always);
}
finally
{
_backend.EndMenu();
}
}
if (_backend.BeginMenu("Camera"))
{
try
{
string flyLabel = _camera.IsFlyMode
? "Exit Free-Fly Mode"
: "Enter Free-Fly Mode";
if (_backend.MenuItem(flyLabel, "Ctrl+Shift+F"))
_camera.ToggleFlyOrChase();
bool collide = _camera.CollideCamera;
if (_backend.MenuItem(
"Collide Camera (spring arm)",
selected: collide))
{
_camera.CollideCamera = !collide;
}
}
finally
{
_backend.EndMenu();
}
}
}
finally
{
_backend.EndMainMenuBar();
}
}
private void SetLayout(
DevToolsPanelKind panel,
Vector2 position,
Vector2 size,
DevToolsPanelLayoutCondition condition)
{
string? title = _panels.GetTitle(panel);
if (!string.IsNullOrEmpty(title))
_backend.SetWindowLayout(title, position, size, condition);
}
}