Event-based Input Manager: rebindable actions with defaults (currently poll-based); frame loop should drive the device layer #83

Closed
opened 2026-09-02 04:52:42 +02:00 by orkun · 1 comment
Owner

Input is loop/poll-based (Input.poll() each frame + Input.key_down(...)). Action maps exist (Input.bind) but are still polled, and there is no first-class Input Manager offering: (a) event-based dispatch (on-press/on-release handlers), (b) rebindable actions with sensible defaults shipped by the game, (c) device-agnostic actions (keyboard/mouse/gamepad mapped to one action).

Separately, a papercut/bug: the generated frame loop calls rt_poll() (feeds only Input.key()) but never input_poll(), so Input.key_down/mouse/pad read empty unless the game manually calls Input.poll() at the top of its Input phase. The loop should commit the device layer automatically when a game uses it.

Proposal: an Input Manager (declare actions + default bindings, rebind at runtime, receive input as events), and auto-driving of the device layer.

Input is loop/poll-based (`Input.poll()` each frame + `Input.key_down(...)`). Action maps exist (`Input.bind`) but are still polled, and there is no first-class Input Manager offering: (a) event-based dispatch (on-press/on-release handlers), (b) rebindable actions with sensible defaults shipped by the game, (c) device-agnostic actions (keyboard/mouse/gamepad mapped to one action). Separately, a papercut/bug: the generated frame loop calls `rt_poll()` (feeds only `Input.key()`) but never `input_poll()`, so `Input.key_down`/`mouse`/`pad` read empty unless the game manually calls `Input.poll()` at the top of its Input phase. The loop should commit the device layer automatically when a game uses it. Proposal: an Input Manager (declare actions + default bindings, rebind at runtime, receive input as events), and auto-driving of the device layer.
Author
Owner

Shipped in bccd26f (real implementation, tested — full suite 107/0, golden renders byte-identical, C-free bootstrap fixpoint intact).

The bug/papercut — fixed. The generated frame loop called rt_poll() (which feeds only Input.key) but never input_poll(), so Input.active / key_down / mouse / pad read empty unless the game manually called Input.poll() at the top of its Input phase. The loop now auto-commits the device layer: when a game uses any Input action-map / device method it calls input_poll() each frame (reads the live key, records/replays, rebuilds the held-key + mouse + gamepad state) and stores the returned key so Input.key still works. A game that uses no Input runtime keeps the plain rt_poll path, byte-identical. Verified by examples/library/input_auto.ludic — a handler game that reads Input.active in Update and never calls Input.poll, driven by stdin keys.

Input Manager API (the proposal's three asks, in a deterministic, dispatch-free form):

  • rebindable actions with defaults — Input.action(name, key) ships a default binding, kept if the action is already bound so a player's Input.rebind (or a loaded key-map) is never clobbered.
  • device-agnostic actions — Input.bind_pad(name, button) maps a gamepad button to the same action; one action fires from keyboard or pad.
  • event-based dispatch — Input.active (held), Input.just_pressed (on-press edge), Input.just_released (on-release edge) read the whole multi-key device layer. These are the deterministic equivalent of on-press/on-release handlers: a handler polls the edge and reacts, so a replay fires the exact same edges. True runtime callback dispatch is intentionally not added — it would break the no-dispatch determinism guarantee the engine (and lockstep netcode) rests on; edge-polling is the idiomatic replacement.

Verified by examples/library/input_manager.ludic. Both wired as feat_cases; 5 docs pages added.

Shipped in bccd26f (real implementation, tested — full suite 107/0, golden renders byte-identical, C-free bootstrap fixpoint intact). **The bug/papercut — fixed.** The generated frame loop called `rt_poll()` (which feeds only `Input.key`) but never `input_poll()`, so `Input.active` / `key_down` / mouse / pad read empty unless the game manually called `Input.poll()` at the top of its Input phase. The loop now **auto-commits the device layer**: when a game uses any Input action-map / device method it calls `input_poll()` each frame (reads the live key, records/replays, rebuilds the held-key + mouse + gamepad state) and stores the returned key so `Input.key` still works. A game that uses no Input runtime keeps the plain `rt_poll` path, **byte-identical**. Verified by `examples/library/input_auto.ludic` — a handler game that reads `Input.active` in Update and never calls `Input.poll`, driven by stdin keys. **Input Manager API** (the proposal's three asks, in a deterministic, dispatch-free form): - **rebindable actions with defaults** — `Input.action(name, key)` ships a default binding, kept if the action is already bound so a player's `Input.rebind` (or a loaded key-map) is never clobbered. - **device-agnostic actions** — `Input.bind_pad(name, button)` maps a gamepad button to the same action; one action fires from keyboard *or* pad. - **event-based dispatch** — `Input.active` (held), `Input.just_pressed` (on-press edge), `Input.just_released` (on-release edge) read the whole multi-key device layer. These are the deterministic equivalent of on-press/on-release handlers: a handler polls the edge and reacts, so a replay fires the exact same edges. True runtime callback dispatch is intentionally *not* added — it would break the no-dispatch determinism guarantee the engine (and lockstep netcode) rests on; edge-polling is the idiomatic replacement. Verified by `examples/library/input_manager.ludic`. Both wired as feat_cases; 5 docs pages added.
orkun closed this issue 2026-09-02 06:04:27 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: workshopsoft/ludic#83
No description provided.