using AcDream.App.Input; using AcDream.App.Rendering; using AcDream.UI.Abstractions.Input; using Silk.NET.Input; using Silk.NET.Maths; using Silk.NET.OpenGL; namespace AcDream.App.Composition; internal interface IGameWindowHostInputCameraPublication { void PublishGpuFrameFlights(GpuFrameFlightController value); void PublishKeyboardSource(SilkKeyboardSource value); void PublishMouseSource(SilkMouseSource value); void PublishMouseLookCursor(IMouseLookCursor value); void PublishInputDispatcher(InputDispatcher value); void PublishCameraController(CameraController value); void PublishCameraPointerInput(CameraPointerInputController value); } internal sealed record HostInputCameraResult( GpuFrameFlightController GpuFrameFlights, WorldRenderDiagnostics WorldRenderDiagnostics, SilkKeyboardSource? KeyboardSource, SilkMouseSource? MouseSource, IMouseLookCursor? MouseLookCursor, InputDispatcher? InputDispatcher, CameraController CameraController, CameraPointerInputController? CameraPointerInput); internal sealed record HostInputCameraDependencies( FramebufferResizeController FramebufferResize, Vector2D InitialFramebufferSize, HostQuiescenceGate HostQuiescence, IInputCaptureSource InputCapture, KeyBindings KeyBindings, DispatcherMovementInputSource MovementInput, DispatcherCameraInputSource CameraInput, LocalPlayerModeState LocalPlayerMode, ChaseCameraInputState ChaseCameraInput, PointerPositionState PointerPosition, IRenderFrameDiagnosticLog RenderDiagnosticLog); internal interface IHostInputCameraCompositionFactory { IFramebufferViewportTarget CreateViewportTarget(GL gl); GpuFrameFlightController CreateGpuFrameFlights(GL gl); WorldRenderDiagnostics CreateWorldRenderDiagnostics( GL gl, IRenderFrameDiagnosticLog log); SilkKeyboardSource CreateKeyboardSource( IKeyboard keyboard, HostQuiescenceGate quiescence); SilkMouseSource CreateMouseSource( IMouse mouse, IInputCaptureSource capture, IKeyboardSource? keyboard, HostQuiescenceGate quiescence); IMouseLookCursor CreateMouseLookCursor(IMouse mouse); InputDispatcher CreateInputDispatcher( IKeyboardSource keyboard, IMouseSource mouse, KeyBindings bindings); CameraController CreateCameraController(); IFramebufferCameraTarget CreateCameraTarget(CameraController camera); CameraPointerInputController CreateCameraPointerInput( IReadOnlyList mice, HostQuiescenceGate quiescence, IInputCaptureSource capture, LocalPlayerModeState playerMode, CameraController camera, ChaseCameraInputState chase, IMouseSource mouse, PointerPositionState pointer); } internal sealed class RetailHostInputCameraCompositionFactory : IHostInputCameraCompositionFactory { public IFramebufferViewportTarget CreateViewportTarget(GL gl) => new SilkFramebufferViewportTarget(gl); public GpuFrameFlightController CreateGpuFrameFlights(GL gl) => new(gl); public WorldRenderDiagnostics CreateWorldRenderDiagnostics( GL gl, IRenderFrameDiagnosticLog log) => new(new SilkRenderGlStateReader(gl), log); public SilkKeyboardSource CreateKeyboardSource( IKeyboard keyboard, HostQuiescenceGate quiescence) => SilkKeyboardSource.CreateDetached(keyboard, quiescence); public SilkMouseSource CreateMouseSource( IMouse mouse, IInputCaptureSource capture, IKeyboardSource? keyboard, HostQuiescenceGate quiescence) => SilkMouseSource.CreateDetached(mouse, capture, keyboard, quiescence); public IMouseLookCursor CreateMouseLookCursor(IMouse mouse) => new SilkMouseLookCursor(mouse); public InputDispatcher CreateInputDispatcher( IKeyboardSource keyboard, IMouseSource mouse, KeyBindings bindings) => InputDispatcher.CreateDetached(keyboard, mouse, bindings); public CameraController CreateCameraController() => new(new OrbitCamera(), new FlyCamera()); public IFramebufferCameraTarget CreateCameraTarget(CameraController camera) => new CameraFramebufferTarget(camera); public CameraPointerInputController CreateCameraPointerInput( IReadOnlyList mice, HostQuiescenceGate quiescence, IInputCaptureSource capture, LocalPlayerModeState playerMode, CameraController camera, ChaseCameraInputState chase, IMouseSource mouse, PointerPositionState pointer) => CameraPointerInputController.Create( mice, quiescence, capture, playerMode, camera, chase, mouse, pointer, new EnvironmentInputMonotonicClock()); } internal enum HostInputCameraCompositionPoint { ViewportBound, GpuFrameFlightsPublished, KeyboardPublished, KeyboardAttached, MousePublished, MouseAttached, MouseLookCursorPublished, DispatcherPublished, DispatcherAttached, MovementInputBound, CameraInputBound, CameraPublished, CameraTargetBound, InitialFramebufferApplied, CameraPointerPublished, CameraPointerAttached, } /// /// Production Phase 1. Callback-bearing owners publish to the lifetime shell /// before attachment, so a side-effecting event accessor cannot leave an /// unreachable subscription after partial startup failure. /// internal sealed class HostInputCameraCompositionPhase : IHostInputCameraCompositionPhase< GameWindowPlatformResult, HostInputCameraResult> { private readonly HostInputCameraDependencies _dependencies; private readonly IGameWindowHostInputCameraPublication _publication; private readonly IHostInputCameraCompositionFactory _factory; private readonly Action? _faultInjection; public HostInputCameraCompositionPhase( HostInputCameraDependencies dependencies, IGameWindowHostInputCameraPublication publication, IHostInputCameraCompositionFactory? factory = null, Action? faultInjection = null) { _dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies)); _publication = publication ?? throw new ArgumentNullException(nameof(publication)); _factory = factory ?? new RetailHostInputCameraCompositionFactory(); _faultInjection = faultInjection; } public HostInputCameraResult Compose( GameWindowPlatformResult platform) { ArgumentNullException.ThrowIfNull(platform); var scope = new CompositionAcquisitionScope(); try { HostInputCameraResult result = ComposeCore(platform, scope); scope.Complete(); return result; } catch (Exception failure) { scope.RollbackAndThrow(failure); throw new System.Diagnostics.UnreachableException(); } } private HostInputCameraResult ComposeCore( GameWindowPlatformResult platform, CompositionAcquisitionScope scope) { GL gl = platform.Graphics; IInputContext input = platform.Input; _dependencies.FramebufferResize.BindViewport( _factory.CreateViewportTarget(gl)); Fault(HostInputCameraCompositionPoint.ViewportBound); GpuFrameFlightController gpuFrames = scope.Acquire( "GPU frame flights", () => _factory.CreateGpuFrameFlights(gl), static value => value.Dispose()).Publish( _publication.PublishGpuFrameFlights); Fault(HostInputCameraCompositionPoint.GpuFrameFlightsPublished); WorldRenderDiagnostics diagnostics = _factory.CreateWorldRenderDiagnostics( gl, _dependencies.RenderDiagnosticLog); IKeyboard? firstKeyboard = input.Keyboards.FirstOrDefault(); IMouse? firstMouse = input.Mice.FirstOrDefault(); SilkKeyboardSource? keyboard = null; SilkMouseSource? mouse = null; IMouseLookCursor? cursor = null; InputDispatcher? dispatcher = null; CameraPointerInputController? pointer = null; if (firstKeyboard is not null) { keyboard = scope.Acquire( "keyboard source", () => _factory.CreateKeyboardSource( firstKeyboard, _dependencies.HostQuiescence), static value => value.Dispose()).Publish( _publication.PublishKeyboardSource); Fault(HostInputCameraCompositionPoint.KeyboardPublished); keyboard.Attach(); Fault(HostInputCameraCompositionPoint.KeyboardAttached); } if (firstMouse is not null) { mouse = scope.Acquire( "mouse source", () => _factory.CreateMouseSource( firstMouse, _dependencies.InputCapture, keyboard, _dependencies.HostQuiescence), static value => value.Dispose()).Publish( _publication.PublishMouseSource); Fault(HostInputCameraCompositionPoint.MousePublished); mouse.Attach(); Fault(HostInputCameraCompositionPoint.MouseAttached); cursor = _factory.CreateMouseLookCursor(firstMouse); _publication.PublishMouseLookCursor(cursor); Fault(HostInputCameraCompositionPoint.MouseLookCursorPublished); } if (keyboard is not null && mouse is not null) { dispatcher = scope.Acquire( "input dispatcher", () => _factory.CreateInputDispatcher( keyboard, mouse, _dependencies.KeyBindings), static value => value.Dispose()).Publish( _publication.PublishInputDispatcher); Fault(HostInputCameraCompositionPoint.DispatcherPublished); dispatcher.Attach(); Fault(HostInputCameraCompositionPoint.DispatcherAttached); _dependencies.MovementInput.Bind(dispatcher); Fault(HostInputCameraCompositionPoint.MovementInputBound); _dependencies.CameraInput.Bind(dispatcher); Fault(HostInputCameraCompositionPoint.CameraInputBound); } CameraController camera = _factory.CreateCameraController(); _publication.PublishCameraController(camera); Fault(HostInputCameraCompositionPoint.CameraPublished); _dependencies.FramebufferResize.BindCamera( _factory.CreateCameraTarget(camera)); Fault(HostInputCameraCompositionPoint.CameraTargetBound); _dependencies.FramebufferResize.Resize( _dependencies.InitialFramebufferSize); Fault(HostInputCameraCompositionPoint.InitialFramebufferApplied); if (mouse is not null && firstMouse is not null) { pointer = scope.Acquire( "camera pointer input", () => _factory.CreateCameraPointerInput( input.Mice, _dependencies.HostQuiescence, _dependencies.InputCapture, _dependencies.LocalPlayerMode, camera, _dependencies.ChaseCameraInput, mouse, _dependencies.PointerPosition), static value => value.Dispose()).Publish( _publication.PublishCameraPointerInput); Fault(HostInputCameraCompositionPoint.CameraPointerPublished); pointer.AttachRaw(); Fault(HostInputCameraCompositionPoint.CameraPointerAttached); } return new HostInputCameraResult( gpuFrames, diagnostics, keyboard, mouse, cursor, dispatcher, camera, pointer); } private void Fault(HostInputCameraCompositionPoint point) => _faultInjection?.Invoke(point); }