Files
libakgl/include/akgl/controller.h
Andrew Kesterson 1ddc64010a 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>
2026-07-31 06:57:50 -04:00

166 lines
6.7 KiB
C

/**
* @file controller.h
* @brief Declares the public controller API.
*/
#ifndef _CONTROLLER_H_
#define _CONTROLLER_H_
#include <SDL3/SDL.h>
#include <akerror.h>
#include "types.h"
#define AKGL_MAX_CONTROL_MAPS 8
#define AKGL_MAX_CONTROLS 32
/**
* @brief Keystrokes akgl_controller_handle_event() will hold for a poller.
*
* The Commodore keyboard buffer this serves held ten. Thirty-two is enough that
* a program which polls once per frame never loses a key to a fast typist, and
* small enough that the buffer stays a fixed-size object in the library's data
* segment.
*/
#define AKGL_CONTROLLER_KEY_BUFFER 32
/** @brief Maps one SDL input to pressed and released callbacks. */
typedef struct {
uint32_t event_on;
uint32_t event_off;
uint8_t button;
SDL_Keycode key;
uint8_t axis;
uint8_t axis_range_min;
uint8_t axis_range_max;
akerr_ErrorContext AKERR_NOIGNORE *(*handler_on)(akgl_Actor *obj, SDL_Event *event);
akerr_ErrorContext AKERR_NOIGNORE *(*handler_off)(akgl_Actor *obj, SDL_Event *event);
} akgl_Control;
/** @brief Groups input bindings for one actor and its input devices. */
typedef struct {
akgl_Actor *target;
uint16_t nextMap;
akgl_Control controls[AKGL_MAX_CONTROLS];
SDL_KeyboardID kbid;
SDL_JoystickID jsid;
SDL_MouseID mouseid;
SDL_PenID penid;
} akgl_ControlMap;
/** @brief Stores all process-wide actor input maps. */
extern akgl_ControlMap GAME_ControlMaps[AKGL_MAX_CONTROL_MAPS];
/**
* @brief Controller list keyboards.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_list_keyboards(void);
/**
* @brief Controller handle event.
* @param appstate Application state supplied by SDL.
* @param event SDL input event to process.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_event(void *appstate, SDL_Event *event);
/**
* @brief Controller handle button down.
* @param appstate Application state supplied by SDL.
* @param event SDL input event to process.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_* Propagates an error reported by a delegated operation.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_button_down(void *appstate, SDL_Event *event);
/**
* @brief Controller handle button up.
* @param appstate Application state supplied by SDL.
* @param event SDL input event to process.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_* Propagates an error reported by a delegated operation.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_button_up(void *appstate, SDL_Event *event);
/**
* @brief Controller handle added.
* @param appstate Application state supplied by SDL.
* @param event SDL input event to process.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_* Propagates an error reported by a delegated operation.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_added(void *appstate, SDL_Event *event);
/**
* @brief Controller handle removed.
* @param appstate Application state supplied by SDL.
* @param event SDL input event to process.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_* Propagates an error reported by a delegated operation.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_removed(void *appstate, SDL_Event *event);
/**
* @brief Controller pushmap.
* @param controlmapid Index of the control map to update.
* @param control Control binding to append to the selected map.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
* @throws AKERR_OUTOFBOUNDS When the corresponding validation or operation fails.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akgl_Control *control);
/**
* @brief Controller default.
* @param controlmapid Index of the control map to update.
* @param actorname Registry name of the controlled actor.
* @param kbid SDL keyboard identifier assigned to the map.
* @param jsid SDL joystick or gamepad identifier assigned to the map.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_OUTOFBOUNDS When the corresponding validation or operation fails.
* @throws AKGL_ERR_REGISTRY When the corresponding validation or operation fails.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, char *actorname, int kbid, int jsid);
/**
* @brief Controller open gamepads.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_open_gamepads(void);
/**
* @brief Take the oldest waiting keystroke, if there is one.
*
* The rest of this header is built around the host pumping SDL events into
* akgl_controller_handle_event(), which suits a game loop and does not suit an
* embedded interpreter asking "is there a key waiting, yes or no" without
* owning the event loop itself. Every key press that reaches
* akgl_controller_handle_event() is recorded in a fixed ring buffer first,
* whether or not a control map claims it, and this drains that buffer one
* keystroke per call.
*
* When no key is waiting the call still succeeds: @p available is set to
* `false` and @p keycode to 0. The caller polls, it does not block.
*
* A full buffer drops the *newest* keystroke rather than the oldest, so what
* was typed first is what is read first. This runs on whichever thread pumps
* events; it is not synchronized.
*
* @param keycode Output destination populated with the SDL keycode, or 0.
* @param available Output destination set to `true` when a keystroke was taken.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_key(int *keycode, bool *available);
/**
* @brief Discard every keystroke waiting in the buffer.
*
* For a caller that has been ignoring input and does not want a backlog acted
* on the moment it starts polling again.
*
* @return `NULL` on success, otherwise an error context owned by the caller.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_flush_keys(void);
#endif // _CONTROLLER_H_