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.
This commit is contained in:
parent
d09e246d3a
commit
8b8afeefa3
42 changed files with 4029 additions and 461 deletions
|
|
@ -0,0 +1,76 @@
|
|||
using AcDream.App.Input;
|
||||
using AcDream.App.Rendering;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class FramebufferResizeControllerTests
|
||||
{
|
||||
[Fact]
|
||||
public void ResizePreservesViewportAspectCameraDevToolsOrder()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
var aspect = new ViewportAspectState();
|
||||
var owner = new FramebufferResizeController(aspect);
|
||||
owner.BindViewport(new Viewport(calls));
|
||||
owner.BindCamera(new Camera(calls));
|
||||
owner.BindDevTools(new DevTools(calls));
|
||||
|
||||
owner.Resize(1600, 900);
|
||||
|
||||
Assert.Equal(["viewport:1600x900", "camera:1.778", "devtools:1600x900"], calls);
|
||||
Assert.Equal(1600f / 900f, aspect.Aspect, 5);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 720)]
|
||||
[InlineData(1280, 0)]
|
||||
[InlineData(-1, 720)]
|
||||
[InlineData(1280, -1)]
|
||||
public void NonPositiveFramebufferIsRejectedWithoutMutatingTargets(int width, int height)
|
||||
{
|
||||
var calls = new List<string>();
|
||||
var aspect = new ViewportAspectState();
|
||||
var owner = new FramebufferResizeController(aspect);
|
||||
owner.BindViewport(new Viewport(calls));
|
||||
owner.BindCamera(new Camera(calls));
|
||||
owner.BindDevTools(new DevTools(calls));
|
||||
|
||||
owner.Resize(width, height);
|
||||
|
||||
Assert.Empty(calls);
|
||||
Assert.Equal(16f / 9f, aspect.Aspect);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LateBindingDoesNotReplayEarlierResize()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
var owner = new FramebufferResizeController(new ViewportAspectState());
|
||||
owner.Resize(1024, 768);
|
||||
|
||||
owner.BindViewport(new Viewport(calls));
|
||||
owner.BindCamera(new Camera(calls));
|
||||
owner.BindDevTools(new DevTools(calls));
|
||||
|
||||
Assert.Empty(calls);
|
||||
owner.Resize(800, 600);
|
||||
Assert.Equal(["viewport:800x600", "camera:1.333", "devtools:800x600"], calls);
|
||||
}
|
||||
|
||||
private sealed class Viewport(List<string> calls) : IFramebufferViewportTarget
|
||||
{
|
||||
public void ResizeViewport(int width, int height) =>
|
||||
calls.Add($"viewport:{width}x{height}");
|
||||
}
|
||||
|
||||
private sealed class Camera(List<string> calls) : IFramebufferCameraTarget
|
||||
{
|
||||
public void SetAspect(float aspect) => calls.Add($"camera:{aspect:F3}");
|
||||
}
|
||||
|
||||
private sealed class DevTools(List<string> calls) : IFramebufferDevToolsTarget
|
||||
{
|
||||
public void ResetLayout(int width, int height) =>
|
||||
calls.Add($"devtools:{width}x{height}");
|
||||
}
|
||||
}
|
||||
|
|
@ -99,7 +99,13 @@ public sealed class GameWindowRenderLeafCompositionTests
|
|||
Assert.Contains("new AcDream.App.Rendering.PrivatePresentationRenderer(", source);
|
||||
Assert.Contains("new AcDream.App.Rendering.RenderFrameOrchestrator(", source);
|
||||
Assert.Contains("new DisplayFramePacingController(", source);
|
||||
Assert.Contains("_inputCapture.WantCaptureMouse", source);
|
||||
string pointerSource = File.ReadAllText(Path.Combine(
|
||||
FindRepoRoot(),
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"Input",
|
||||
"CameraPointerInputController.cs"));
|
||||
Assert.Contains("_capture.WantCaptureMouse", pointerSource);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -54,14 +54,18 @@ public sealed class GameWindowSlice8BoundaryTests
|
|||
body,
|
||||
"_gl = GL.GetApi(_window!);",
|
||||
"_input = _window!.CreateInput();",
|
||||
"_kbSource = new AcDream.App.Input.SilkKeyboardSource(firstKb);",
|
||||
"_mouseSource = new AcDream.App.Input.SilkMouseSource(",
|
||||
"_inputDispatcher = new AcDream.UI.Abstractions.Input.InputDispatcher(",
|
||||
"_kbSource = AcDream.App.Input.SilkKeyboardSource.CreateDetached(",
|
||||
"_kbSource.Attach();",
|
||||
"_mouseSource = AcDream.App.Input.SilkMouseSource.CreateDetached(",
|
||||
"_mouseSource.Attach();",
|
||||
"_inputDispatcher = AcDream.UI.Abstractions.Input.InputDispatcher.CreateDetached(",
|
||||
"_inputDispatcher.Attach();",
|
||||
"_movementInput.Bind(_inputDispatcher);",
|
||||
"_cameraInput.Bind(_inputDispatcher);",
|
||||
"_inputDispatcher.Fired += OnInputAction;",
|
||||
"mouse.MouseMove +=",
|
||||
"_cameraController = new CameraController(orbit, fly);",
|
||||
"_cameraPointerInput = AcDream.App.Input.CameraPointerInputController.Create(",
|
||||
"_cameraPointerInput.AttachRaw();",
|
||||
"_dats = RuntimeDatCollectionFactory.OpenReadOnly(_datDir);",
|
||||
"LoadAndApplyPersistedSettings();",
|
||||
"_uiHost = new AcDream.App.UI.UiHost(",
|
||||
|
|
@ -74,6 +78,16 @@ public sealed class GameWindowSlice8BoundaryTests
|
|||
"_liveSessionHost = CreateLiveSessionHost();",
|
||||
"_liveSessionHost.Start(_options)");
|
||||
Assert.Equal(1, CountOccurrences(body, "_liveSessionHost.Start(_options)"));
|
||||
Assert.Contains("if (firstKb is not null)", body, StringComparison.Ordinal);
|
||||
Assert.Contains("if (firstMouse is not null)", body, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"if (_kbSource is not null && _mouseSource is not null)",
|
||||
body,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"if (firstKb is not null && firstMouse is not null)",
|
||||
body,
|
||||
StringComparison.Ordinal);
|
||||
int start = body.IndexOf("_liveSessionHost.Start(_options)", StringComparison.Ordinal);
|
||||
string postStart = body[start..];
|
||||
Assert.Contains("switch (liveStart.Status)", postStart, StringComparison.Ordinal);
|
||||
|
|
@ -144,20 +158,28 @@ public sealed class GameWindowSlice8BoundaryTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void FramebufferResize_PreservesViewportCameraAndDevtoolsOrder()
|
||||
public void FramebufferResize_IsOneTypedOwnerHandoff()
|
||||
{
|
||||
string load = MethodBody(
|
||||
"private void OnLoad()",
|
||||
"private AcDream.App.Net.LiveSessionHost");
|
||||
string body = MethodBody(
|
||||
"private void OnFramebufferResize(",
|
||||
"private void ApplyDisplayWindowState(");
|
||||
|
||||
Assert.Contains("=> _framebufferResize.Resize(newSize);", body, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("Viewport(", body, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("SetAspect(", body, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("ResetLayout(", body, StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
body,
|
||||
"if (newSize.X <= 0 || newSize.Y <= 0) return;",
|
||||
"_gl?.Viewport(",
|
||||
"_viewportAspect.Update(",
|
||||
"_cameraController?.SetAspect(",
|
||||
"_devToolsFramePresenter?.ResetLayout(");
|
||||
Assert.DoesNotContain("_uiHost", body, StringComparison.Ordinal);
|
||||
load,
|
||||
"_framebufferResize.BindViewport(",
|
||||
"_framebufferResize.BindCamera(",
|
||||
"_framebufferResize.Resize(_window!.FramebufferSize);");
|
||||
Assert.DoesNotContain(
|
||||
"_viewportAspect.Update(_window",
|
||||
load,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -167,7 +189,7 @@ public sealed class GameWindowSlice8BoundaryTests
|
|||
string update = Slice(
|
||||
source,
|
||||
"private void OnUpdate(double dt)",
|
||||
"private void OnCameraModeChanged(");
|
||||
"private void OnRender(double deltaSeconds)");
|
||||
string render = Slice(
|
||||
source,
|
||||
"private void OnRender(double deltaSeconds)",
|
||||
|
|
@ -189,7 +211,10 @@ public sealed class GameWindowSlice8BoundaryTests
|
|||
"_renderFrameOrchestrator!.Render(",
|
||||
"new AcDream.App.Rendering.RenderFrameInput(");
|
||||
Assert.DoesNotContain("FramebufferSize", render, StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(focus, "if (!focused)", "_gameplayInputFrame?.EndMouseLook();");
|
||||
Assert.Contains(
|
||||
"=> _cameraPointerInput?.HandleFocusChanged(focused);",
|
||||
focus,
|
||||
StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
close,
|
||||
"_hostQuiescence.StopAccepting();",
|
||||
|
|
@ -212,7 +237,9 @@ public sealed class GameWindowSlice8BoundaryTests
|
|||
|
||||
string[] stages =
|
||||
[
|
||||
"new ResourceShutdownStage(\"input callback deactivation\"",
|
||||
"new ResourceShutdownStage(\"session lifetime\"",
|
||||
"new ResourceShutdownStage(\"input callback detach\"",
|
||||
"new ResourceShutdownStage(\"frame borrowers\"",
|
||||
"new ResourceShutdownStage(\"session dependents\"",
|
||||
"new ResourceShutdownStage(\"live entities\"",
|
||||
|
|
@ -236,6 +263,17 @@ public sealed class GameWindowSlice8BoundaryTests
|
|||
"binding.Dispose();",
|
||||
"if (!binding.IsDisposalComplete)",
|
||||
"_windowCallbacks = null;");
|
||||
AssertAppearsInOrder(
|
||||
manifest,
|
||||
"new(\"camera pointer\", () => _cameraPointerInput?.Deactivate())",
|
||||
"new ResourceShutdownStage(\"session lifetime\"",
|
||||
"controller.Dispose();",
|
||||
"new ResourceShutdownStage(\"input callback detach\"",
|
||||
"pointer.Dispose();",
|
||||
"new ResourceShutdownStage(\"session dependents\"",
|
||||
"pointer.ReleaseMouseLookAfterSessionRetirement();",
|
||||
"pointer.UnbindGameplayFrame(gameplayFrame);",
|
||||
"_cameraPointerInput = null;");
|
||||
AssertAppearsInOrder(
|
||||
dispose,
|
||||
"CompleteShutdown();",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue