acdream/src/AcDream.App/Rendering/FramebufferResizeController.cs
Erik 8b8afeefa3 refactor(input): own pointer and callback lifetime
Move camera pointer, framebuffer resize, and retained/devtools input edges behind focused reversible owners. Preserve input priority while making shutdown deactivate callbacks before live-session retirement and retry physical detach without stranding transport teardown.
2026-07-22 11:59:33 +02:00

118 lines
3.7 KiB
C#

using AcDream.App.Input;
using Silk.NET.Maths;
using Silk.NET.OpenGL;
namespace AcDream.App.Rendering;
internal interface IFramebufferViewportTarget
{
void ResizeViewport(int width, int height);
}
internal sealed class SilkFramebufferViewportTarget(GL gl)
: IFramebufferViewportTarget
{
private readonly GL _gl = gl ?? throw new ArgumentNullException(nameof(gl));
public void ResizeViewport(int width, int height) =>
_gl.Viewport(0, 0, (uint)width, (uint)height);
}
internal interface IFramebufferCameraTarget
{
void SetAspect(float aspect);
}
internal sealed class CameraFramebufferTarget(CameraController camera)
: IFramebufferCameraTarget
{
private readonly CameraController _camera = camera
?? throw new ArgumentNullException(nameof(camera));
public void SetAspect(float aspect) => _camera.SetAspect(aspect);
}
internal interface IFramebufferDevToolsTarget
{
void ResetLayout(int width, int height);
}
internal sealed class DevToolsFramebufferTarget(DevToolsFramePresenter presenter)
: IFramebufferDevToolsTarget
{
private readonly DevToolsFramePresenter _presenter = presenter
?? throw new ArgumentNullException(nameof(presenter));
public void ResetLayout(int width, int height) =>
_presenter.ResetLayout(
width,
height,
DevToolsPanelLayoutCondition.Always);
}
/// <summary>
/// The one framebuffer-size target. Logical gameplay/UI dimensions continue
/// to come from Window.Size; this owner only applies physical framebuffer
/// viewport/aspect changes in the frozen acdream host order.
/// </summary>
internal sealed class FramebufferResizeController
{
private readonly ViewportAspectState _viewportAspect;
private IFramebufferViewportTarget? _viewport;
private IFramebufferCameraTarget? _camera;
private IFramebufferDevToolsTarget? _devTools;
public FramebufferResizeController(ViewportAspectState viewportAspect) =>
_viewportAspect = viewportAspect
?? throw new ArgumentNullException(nameof(viewportAspect));
public void BindViewport(IFramebufferViewportTarget viewport)
{
ArgumentNullException.ThrowIfNull(viewport);
BindOnce(ref _viewport, viewport, "viewport");
}
public void BindCamera(IFramebufferCameraTarget camera)
{
ArgumentNullException.ThrowIfNull(camera);
BindOnce(ref _camera, camera, "camera");
}
public void BindDevTools(IFramebufferDevToolsTarget devTools)
{
ArgumentNullException.ThrowIfNull(devTools);
BindOnce(ref _devTools, devTools, "developer tools");
}
public void UnbindDevTools(IFramebufferDevToolsTarget devTools)
{
ArgumentNullException.ThrowIfNull(devTools);
if (ReferenceEquals(_devTools, devTools))
_devTools = null;
}
public void Resize(Vector2D<int> newSize) => Resize(newSize.X, newSize.Y);
public void Resize(int width, int height)
{
if (width <= 0 || height <= 0)
return;
// Frozen order: GL viewport, shared aspect publication, camera, then
// optional ImGui layout reset. Late binding never replays an earlier
// resize transition.
_viewport?.ResizeViewport(width, height);
_viewportAspect.Update(width, height);
_camera?.SetAspect(width / (float)height);
_devTools?.ResetLayout(width, height);
}
private static void BindOnce<T>(ref T? slot, T value, string name)
where T : class
{
if (slot is not null && !ReferenceEquals(slot, value))
throw new InvalidOperationException(
$"The framebuffer {name} target is already bound.");
slot = value;
}
}