Summary
The current helios.input module (InputManager, InputAdapter) and its GLFW implementation (helios.ext.glfw.input) should be dissolved. Input polling and key/gamepad state management should be integrated into the engine's command-driven platform layer (helios.engine.platform), following the same pattern established for window and event management (see #279).
Motivation
1. Hard dependency on helios.window.Window
Both InputManager and InputAdapter depend directly on the abstract helios.window.Window class:
InputManager::observe(const Window&) stores a pointer to a Window and calls Window::pollEvents() in its poll() method.
InputAdapter::isKeyPressed(Key, const Window&) and InputAdapter::isKeyReleased(Key, const Window&) require a Window reference for every key query.
GLFWInputAdapter downcasts the Window& to GLFWWindow via dynamic_cast to access the native handle.
Since helios.window.Window is scheduled for removal (#279), the input layer cannot remain in its current form.
2. Imperative polling model
InputManager::poll() calls observedWin_->pollEvents() directly on the observed window. This conflates two concerns: GLFW event polling (which is a platform concern) and gamepad state updates (which is an input concern). In the new architecture, event polling is handled by PollEventsSystem / PollEventsCommand / GLFWPlatformManager — the input layer should not duplicate this.
3. OOP inheritance hierarchy
InputAdapter is an abstract base class with virtual methods (isKeyPressed, isKeyReleased, updateGamepadState, gamepadState, isConnected). GLFWInputAdapter inherits from it and performs GLFW-specific translations. This polymorphic design introduces vtable overhead and dynamic_cast downcasting that should be replaced by a data-oriented approach consistent with the ECS architecture.
4. Ownership model conflicts with ResourceRegistry
InputManager owns an InputAdapter via std::unique_ptr and is itself owned by the Application. With Application being dissolved (#279), ownership must be transferred. The natural home is ResourceRegistry or a dedicated input manager registered with the GameWorld.
5. InputSnapshot is already decoupled — but created externally
InputSnapshot is a well-designed, immutable value type that is passed into GameLoop::update(). However, its construction currently happens in user code (main.cpp), which must manually call inputManager.gamepadState() and wrap it in a snapshot. This boilerplate should be handled by the engine.
Requirements
Key State as ECS Component Data
- Key press/release state should be queryable without a
Window reference. The platform manager (which owns the native window handle) should populate key state data that systems can read through the UpdateContext or through a dedicated input resource.
- The
InputAdapter / Window coupling for isKeyPressed / isKeyReleased must be removed. The GLFW implementation should query key state directly from the GLFWWindowHandleComponent on the window entity.
Gamepad Polling as Platform-Integrated System
- Gamepad state updates (
updateGamepadState) should be performed by a system registered in the game loop, not by InputManager::poll().
InputSnapshot construction should be internalized into the engine's frame lifecycle, removing the manual polling + snapshot creation from main.cpp.
Removal of Window::pollEvents() Delegation
InputManager::poll() currently calls observedWin_->pollEvents(). With the new architecture, event polling is a platform command (PollEventsCommand). The input layer must not call pollEvents() independently.
Backend-Specific Adapter Without Inheritance
- The GLFW-specific input translation (key lookup, gamepad lookup, deadzone application) should be encapsulated in a platform-specific input component or manager within
helios.engine.platform.glfw, without requiring a virtual InputAdapter base class.
GLFWKeyLookup and GLFWGamepadLookup remain as utility types but should not require dynamic_cast<GLFWWindow*> to function.
Gamepad Configuration
- Per-gamepad settings (
GamepadSettings, DeadzoneStrategy) should be stored as resources or components rather than embedded in an adapter class hierarchy.
- Registration of gamepads via bitmask (
registerGamepads()) should be expressible through ECS configuration rather than imperative calls on an InputManager.
InputSnapshot Lifecycle
InputSnapshot should continue to serve as the immutable per-frame input record passed through UpdateContext.
- Its construction should be automated: the platform layer polls input, the engine captures the snapshot, and systems consume it via
UpdateContext::inputSnapshot().
- The external
main.cpp boilerplate (inputManager.poll() → inputManager.gamepadState() → InputSnapshot(...)) should be replaced by a single engine-internal step.
Module Structure
Target layout within helios.engine.platform:
helios.engine.platform
├── common
│ ├── commands (..., InputPollCommand)
│ ├── components (..., KeyStateComponent?, GamepadStateComponent?)
│ └── systems (..., GamepadPollSystem, InputSnapshotSystem)
└── glfw
├── GLFWPlatformManager (extended with input command handling)
├── input/
│ ├── GLFWKeyLookup (moved from helios.ext.glfw.input)
│ ├── GLFWGamepadLookup (moved from helios.ext.glfw.input)
│ └── GLFWInputHandler (replaces GLFWInputAdapter, no inheritance)
└── ...
Alternatively, gamepad and key state could be managed as resources in ResourceRegistry, with a thin system that populates them each frame.
Modules Affected
helios.input.InputManager — to be removed
helios.input.InputAdapter — to be removed (abstract base class)
helios.ext.glfw.input.GLFWInputAdapter — to be removed (replaced by platform-integrated handler)
helios.ext.glfw.input.GLFWKeyLookup — to be relocated into helios.engine.platform.glfw
helios.ext.glfw.input.GLFWGamepadLookup — to be relocated into helios.engine.platform.glfw
helios.input.InputSnapshot — retained, construction internalized
helios.input.gamepad.* — GamepadState, GamepadSettings, DeadzoneStrategy, RadialDeadzoneStrategy retained as value types, ownership model updated
helios.input.types.* — Key, Gamepad retained as enums
Acceptance Criteria
Related
Summary
The current
helios.inputmodule (InputManager,InputAdapter) and its GLFW implementation (helios.ext.glfw.input) should be dissolved. Input polling and key/gamepad state management should be integrated into the engine's command-driven platform layer (helios.engine.platform), following the same pattern established for window and event management (see #279).Motivation
1. Hard dependency on
helios.window.WindowBoth
InputManagerandInputAdapterdepend directly on the abstracthelios.window.Windowclass:InputManager::observe(const Window&)stores a pointer to aWindowand callsWindow::pollEvents()in itspoll()method.InputAdapter::isKeyPressed(Key, const Window&)andInputAdapter::isKeyReleased(Key, const Window&)require aWindowreference for every key query.GLFWInputAdapterdowncasts theWindow&toGLFWWindowviadynamic_castto access the native handle.Since
helios.window.Windowis scheduled for removal (#279), the input layer cannot remain in its current form.2. Imperative polling model
InputManager::poll()callsobservedWin_->pollEvents()directly on the observed window. This conflates two concerns: GLFW event polling (which is a platform concern) and gamepad state updates (which is an input concern). In the new architecture, event polling is handled byPollEventsSystem/PollEventsCommand/GLFWPlatformManager— the input layer should not duplicate this.3. OOP inheritance hierarchy
InputAdapteris an abstract base class with virtual methods (isKeyPressed,isKeyReleased,updateGamepadState,gamepadState,isConnected).GLFWInputAdapterinherits from it and performs GLFW-specific translations. This polymorphic design introduces vtable overhead anddynamic_castdowncasting that should be replaced by a data-oriented approach consistent with the ECS architecture.4. Ownership model conflicts with
ResourceRegistryInputManagerowns anInputAdapterviastd::unique_ptrand is itself owned by theApplication. WithApplicationbeing dissolved (#279), ownership must be transferred. The natural home isResourceRegistryor a dedicated input manager registered with theGameWorld.5.
InputSnapshotis already decoupled — but created externallyInputSnapshotis a well-designed, immutable value type that is passed intoGameLoop::update(). However, its construction currently happens in user code (main.cpp), which must manually callinputManager.gamepadState()and wrap it in a snapshot. This boilerplate should be handled by the engine.Requirements
Key State as ECS Component Data
Windowreference. The platform manager (which owns the native window handle) should populate key state data that systems can read through theUpdateContextor through a dedicated input resource.InputAdapter/Windowcoupling forisKeyPressed/isKeyReleasedmust be removed. The GLFW implementation should query key state directly from theGLFWWindowHandleComponenton the window entity.Gamepad Polling as Platform-Integrated System
updateGamepadState) should be performed by a system registered in the game loop, not byInputManager::poll().InputSnapshotconstruction should be internalized into the engine's frame lifecycle, removing the manual polling + snapshot creation frommain.cpp.Removal of
Window::pollEvents()DelegationInputManager::poll()currently callsobservedWin_->pollEvents(). With the new architecture, event polling is a platform command (PollEventsCommand). The input layer must not callpollEvents()independently.Backend-Specific Adapter Without Inheritance
helios.engine.platform.glfw, without requiring a virtualInputAdapterbase class.GLFWKeyLookupandGLFWGamepadLookupremain as utility types but should not requiredynamic_cast<GLFWWindow*>to function.Gamepad Configuration
GamepadSettings,DeadzoneStrategy) should be stored as resources or components rather than embedded in an adapter class hierarchy.registerGamepads()) should be expressible through ECS configuration rather than imperative calls on anInputManager.InputSnapshotLifecycleInputSnapshotshould continue to serve as the immutable per-frame input record passed throughUpdateContext.UpdateContext::inputSnapshot().main.cppboilerplate (inputManager.poll()→inputManager.gamepadState()→InputSnapshot(...)) should be replaced by a single engine-internal step.Module Structure
Target layout within
helios.engine.platform:Alternatively, gamepad and key state could be managed as resources in
ResourceRegistry, with a thin system that populates them each frame.Modules Affected
helios.input.InputManager— to be removedhelios.input.InputAdapter— to be removed (abstract base class)helios.ext.glfw.input.GLFWInputAdapter— to be removed (replaced by platform-integrated handler)helios.ext.glfw.input.GLFWKeyLookup— to be relocated intohelios.engine.platform.glfwhelios.ext.glfw.input.GLFWGamepadLookup— to be relocated intohelios.engine.platform.glfwhelios.input.InputSnapshot— retained, construction internalizedhelios.input.gamepad.*—GamepadState,GamepadSettings,DeadzoneStrategy,RadialDeadzoneStrategyretained as value types, ownership model updatedhelios.input.types.*—Key,Gamepadretained as enumsAcceptance Criteria
InputManagerandInputAdapterare no longer required by examples or the enginehelios.window.WindowInputManager::poll()InputSnapshotis constructed internally by the engine frame lifecycledynamic_cast<GLFWWindow*>in input code pathsGamepadSettingsandDeadzoneStrategyare configurable without anInputAdapterinstanceInputManager/InputAdapterabstractionhelios.engine.platform.glfwRelated