Skip to content
This repository was archived by the owner on Jul 29, 2026. It is now read-only.
This repository was archived by the owner on Jul 29, 2026. It is now read-only.

refactor: dissolve helios.input outer layer into engine platform #280

Description

@ThorstenSuckow

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

  • InputManager and InputAdapter are no longer required by examples or the engine
  • Key state queries do not depend on helios.window.Window
  • Gamepad polling is performed by an engine system, not by InputManager::poll()
  • InputSnapshot is constructed internally by the engine frame lifecycle
  • No dynamic_cast<GLFWWindow*> in input code paths
  • GamepadSettings and DeadzoneStrategy are configurable without an InputAdapter instance
  • All existing examples build and run without the old InputManager / InputAdapter abstraction
  • GLFW-specific input code is confined to helios.engine.platform.glfw

Related

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions