Let a caller take keystrokes without owning the event loop

akbasic's GET and GETKEY ask "is there a keystroke waiting, yes or no" and must
not require the interpreter to pump SDL events itself. akgl_controller_poll_key
drains one key per call from a fixed 32-entry ring that
akgl_controller_handle_event fills, so the host keeps pumping and the embedded
interpreter reads at its own pace. akgl_controller_flush_keys discards a
backlog.

The recording happens before the control-map scan, not after: that scan returns
as soon as a binding claims the event, so recording afterwards would have
dropped exactly the keys a game also acts on. A full buffer refuses the newest
keystroke rather than overwriting the oldest, so a caller reading a line gets
what was typed first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 06:57:50 -04:00
parent 17e6e04c79
commit 1ddc64010a
4 changed files with 287 additions and 0 deletions

13
TODO.md
View File

@@ -708,6 +708,19 @@ a consumer is the wanted outcome. Each entry says what the BASIC verb needs, wha
synthetic `SDL_EVENT_KEY_DOWN` events through the existing handler and drain them, plus the
empty-buffer and overflow cases.
**Resolved.** `akgl_controller_poll_key(int *keycode, bool *available)` and
`akgl_controller_flush_keys(void)` are in `include/akgl/controller.h`, over a
fixed `AKGL_CONTROLLER_KEY_BUFFER` (32) ring in `src/controller.c`.
`akgl_controller_handle_event` records every `SDL_EVENT_KEY_DOWN` *before* it
scans the control maps, so a key bound to an actor still reaches a polling
caller — the scan returns as soon as a binding claims the event, and doing it
afterwards would have lost exactly the keys a game also acts on. An empty
buffer is success with `available` false, not an error. A full buffer drops
the newest key rather than overwriting the oldest, matching the Commodore
keyboard buffer and keeping what was typed first. `tests/controller.c` covers
drain order, the release-is-not-a-keystroke case, a key shared with a control
map, flush, both NULL arguments, and overflow plus reuse afterwards.
## Carried over
1. **Make character-to-sprite state bindings release their references symmetrically.**