Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws lines across 21 headers read "When the corresponding validation or operation fails" and told a caller nothing beyond the status name. The @param lines were the same shape: every output was "Output destination populated by the function", every instance "Object to initialize, inspect, or modify". Rewritten against the implementations, following the pattern libakstdlib already uses: - @throws names the condition. akgl_sprite_load_json separated AKERR_KEY (absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS (filename too long, or array indexed past its end), and gained AKGL_ERR_SDL and AKGL_ERR_HEAP, which it raises and never declared. - Parameters say whether they are required, what a NULL means, and what is written on a failure path. Where an argument is not checked, the doc says so: akgl_heap_next_actor's dest is a crash on NULL, not an error, and akgl_render_2d_frame_start dereferences self before testing it. - The conventions move up to the file blocks so the per-function docs stay short. json_helpers.h states once that absence is an error here and that json_t * results are borrowed; heap.h explains the pool model and the acquire asymmetry; physics.h carries the thrust/environmental/velocity table. - Struct fields, enum values, macros and exported globals are documented, including the dead ones - sprite_w/sprite_h, movetimer, p_scale and timer_gravity are read by nothing, and say so. Also fixes ten comments in error.h and audio.h that opened with /** rather than /**<, so Doxygen attached them to the following entity and rendered the text as part of the macro's value. Verified against the generated HTML. Comments only - no declaration changed. Doxygen builds clean under WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,20 @@
|
||||
/**
|
||||
* @file controller.h
|
||||
* @brief Declares the public controller API.
|
||||
* @brief Input binding: SDL events in, actor state changes out.
|
||||
*
|
||||
* A control map ties one actor to one keyboard and one gamepad, and holds up to
|
||||
* #AKGL_MAX_CONTROLS bindings. A binding says "when event X arrives from device
|
||||
* Y carrying button or key Z, call this handler". Eight maps means up to eight
|
||||
* locally controlled players, each on its own device.
|
||||
*
|
||||
* The host pumps SDL events into akgl_controller_handle_event(), which scans the
|
||||
* maps in order and stops at the first binding that claims the event -- so a key
|
||||
* bound in two maps only fires once, in the lower-numbered one.
|
||||
*
|
||||
* Alongside that, and independent of it, every key press is pushed into a small
|
||||
* ring buffer that akgl_controller_poll_key() drains. That serves a caller that
|
||||
* wants "is there a key waiting" without owning an event loop, and it sees
|
||||
* keys whether or not a control map also claimed them.
|
||||
*/
|
||||
|
||||
#ifndef _CONTROLLER_H_
|
||||
@@ -10,7 +24,9 @@
|
||||
#include <akerror.h>
|
||||
#include "types.h"
|
||||
|
||||
/** @brief How many control maps exist -- effectively the local player limit. */
|
||||
#define AKGL_MAX_CONTROL_MAPS 8
|
||||
/** @brief Bindings per control map. The default map installed by akgl_controller_default uses 8 of them. */
|
||||
#define AKGL_MAX_CONTROLS 32
|
||||
|
||||
/**
|
||||
@@ -25,106 +41,188 @@
|
||||
|
||||
/** @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);
|
||||
uint32_t event_on; /**< SDL event type that fires `handler_on`, e.g. `SDL_EVENT_KEY_DOWN`. */
|
||||
uint32_t event_off; /**< SDL event type that fires `handler_off`, e.g. `SDL_EVENT_KEY_UP`. */
|
||||
uint8_t button; /**< Gamepad button (`SDL_GamepadButton`) this binding is for. Only consulted for gamepad events. */
|
||||
SDL_Keycode key; /**< Keycode this binding is for. Only consulted for keyboard events. */
|
||||
uint8_t axis; /**< Analogue axis. Declared but not yet consulted by akgl_controller_handle_event. */
|
||||
uint8_t axis_range_min; /**< Low end of the axis range that counts as "on". Not yet consulted. */
|
||||
uint8_t axis_range_max; /**< High end of that range. Not yet consulted. */
|
||||
akerr_ErrorContext AKERR_NOIGNORE *(*handler_on)(akgl_Actor *obj, SDL_Event *event); /**< Called with the map's target on `event_on`. Required if `event_on` can fire. */
|
||||
akerr_ErrorContext AKERR_NOIGNORE *(*handler_off)(akgl_Actor *obj, SDL_Event *event); /**< Called with the map's target on `event_off`. */
|
||||
} 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_Actor *target; /**< The actor these bindings drive. A `NULL` target makes the whole map inert, which is how an unused slot is spelled. */
|
||||
uint16_t nextMap; /**< Number of bindings in use; the index akgl_controller_pushmap writes to next. */
|
||||
akgl_Control controls[AKGL_MAX_CONTROLS]; /**< The bindings, scanned in order. */
|
||||
SDL_KeyboardID kbid; /**< Keyboard this map listens to. A keyboard event from any other id is ignored, which is what keeps two players on two keyboards apart. */
|
||||
SDL_JoystickID jsid; /**< Gamepad this map listens to, matched the same way. */
|
||||
SDL_MouseID mouseid; /**< Mouse this map listens to. Declared but not yet consulted. */
|
||||
SDL_PenID penid; /**< Pen this map listens to. Declared but not yet consulted. */
|
||||
} akgl_ControlMap;
|
||||
|
||||
/** @brief Stores all process-wide actor input maps. */
|
||||
/** @brief Every control map. Zeroed by akgl_game_init; index it by the same id the functions below take. */
|
||||
extern akgl_ControlMap GAME_ControlMaps[AKGL_MAX_CONTROL_MAPS];
|
||||
|
||||
/**
|
||||
* @brief Controller list keyboards.
|
||||
* @brief Log every attached keyboard and its SDL id.
|
||||
*
|
||||
* A diagnostic, not a query: it writes to the SDL log rather than returning
|
||||
* anything. Its use is finding the `kbid` to hand akgl_controller_default when
|
||||
* more than one keyboard is attached.
|
||||
*
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If SDL cannot enumerate keyboards. The message
|
||||
* carries `SDL_GetError()`; note that "no keyboards attached" is
|
||||
* reported by SDL as an empty list, not as a failure.
|
||||
*/
|
||||
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.
|
||||
* @brief Dispatch one SDL event to whichever control map binds it.
|
||||
*
|
||||
* The entry point for the whole subsystem -- call it for every event the host
|
||||
* pumps. A key press is recorded in the poll buffer first, whether or not any
|
||||
* map wants it; then the maps are scanned in index order and, within a map,
|
||||
* bindings in the order they were pushed. The **first** binding whose event type
|
||||
* and device id and button/key all match wins, and the scan stops there.
|
||||
*
|
||||
* An event nothing binds is not an error: it returns success having done
|
||||
* nothing, which is what lets a host pass every event through unconditionally.
|
||||
*
|
||||
* @param appstate Passed through from SDL's callback. Required -- but only as a
|
||||
* non-`NULL` token: nothing here reads it. Pass any non-`NULL`
|
||||
* pointer if your program has no app state.
|
||||
* @param event The event to dispatch. Required.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`.
|
||||
* @throws AKERR_* Whatever the matched binding's handler raises.
|
||||
*
|
||||
* @warning A matched binding's handler pointer is not checked, so a control
|
||||
* pushed with a `NULL` `handler_on` or `handler_off` crashes when its
|
||||
* event arrives.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_event(void *appstate, SDL_Event *event);
|
||||
|
||||
/**
|
||||
* @brief Controller handle button down.
|
||||
* @brief Declared but not defined under this name. Do not call.
|
||||
*
|
||||
* The implementation exists as `gamepad_handle_button_down` in `src/controller.c`
|
||||
* and is `static`-in-spirit -- it is not declared anywhere -- so a caller that
|
||||
* uses this declaration compiles and then fails to link. TODO.md, "Known and
|
||||
* still open" item 10.
|
||||
*
|
||||
* @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.
|
||||
* @param event SDL input event to process.
|
||||
* @return Nothing; it cannot be called.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_button_down(void *appstate, SDL_Event *event);
|
||||
/**
|
||||
* @brief Controller handle button up.
|
||||
* @brief Declared but not defined under this name. Do not call.
|
||||
*
|
||||
* See akgl_controller_handle_button_down. The implementation is
|
||||
* `gamepad_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.
|
||||
* @param event SDL input event to process.
|
||||
* @return Nothing; it cannot be called.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_button_up(void *appstate, SDL_Event *event);
|
||||
/**
|
||||
* @brief Controller handle added.
|
||||
* @brief Declared but not defined under this name. Do not call.
|
||||
*
|
||||
* See akgl_controller_handle_button_down. The implementation is
|
||||
* `gamepad_handle_added`, which opens a newly plugged-in gamepad.
|
||||
*
|
||||
* @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.
|
||||
* @param event SDL input event to process.
|
||||
* @return Nothing; it cannot be called.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_added(void *appstate, SDL_Event *event);
|
||||
/**
|
||||
* @brief Controller handle removed.
|
||||
* @brief Declared but not defined under this name. Do not call.
|
||||
*
|
||||
* See akgl_controller_handle_button_down. The implementation is
|
||||
* `gamepad_handle_removed`, which closes an unplugged gamepad.
|
||||
*
|
||||
* @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.
|
||||
* @param event SDL input event to process.
|
||||
* @return Nothing; it cannot be called.
|
||||
*/
|
||||
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.
|
||||
* @brief Append a binding to a control map.
|
||||
*
|
||||
* The binding is copied, so the caller's `akgl_Control` can be a stack local
|
||||
* reused across pushes -- which is exactly what akgl_controller_default does.
|
||||
* Bindings can only be appended; there is no remove, and no way to reset a map
|
||||
* short of zeroing it in ::GAME_ControlMaps directly.
|
||||
*
|
||||
* @param controlmapid Which map to append to, 0 through
|
||||
* #AKGL_MAX_CONTROL_MAPS - 1.
|
||||
* @param control The binding to copy in. Required.
|
||||
* @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.
|
||||
* @throws AKERR_NULLPOINTER If @p control is `NULL`.
|
||||
* @throws AKERR_OUTOFBOUNDS If @p controlmapid is at or above
|
||||
* #AKGL_MAX_CONTROL_MAPS, or if the map already holds
|
||||
* #AKGL_MAX_CONTROLS bindings.
|
||||
*
|
||||
* @warning A **negative** @p controlmapid is not rejected -- only the upper
|
||||
* bound is checked -- and indexes before the start of
|
||||
* ::GAME_ControlMaps. TODO.md, "Known and still open" item 11.
|
||||
*/
|
||||
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.
|
||||
* @brief Bind an actor to the arrow keys and the D-pad, in one call.
|
||||
*
|
||||
* Points the map at the named actor and pushes eight bindings: the four arrow
|
||||
* keys and the four D-pad directions, each wired to the matching
|
||||
* `akgl_Actor_cmhf_*_on`/`_off` pair. It is the "just give me something that
|
||||
* works" path -- a game wanting different keys builds its own bindings with
|
||||
* akgl_controller_pushmap.
|
||||
*
|
||||
* @param controlmapid Which map to configure, 0 through
|
||||
* #AKGL_MAX_CONTROL_MAPS - 1.
|
||||
* @param actorname Registry name of the actor to drive. Required in practice,
|
||||
* though a `NULL` is reported as "not found" rather than as
|
||||
* a null pointer.
|
||||
* @param kbid SDL keyboard id to listen to. Only events from this
|
||||
* keyboard match; see akgl_controller_list_keyboards for
|
||||
* how to find it.
|
||||
* @param jsid SDL gamepad id to listen to, matched the same way.
|
||||
* @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.
|
||||
* @throws AKERR_OUTOFBOUNDS If @p controlmapid is at or above
|
||||
* #AKGL_MAX_CONTROL_MAPS, or if the map cannot hold eight more bindings.
|
||||
* @throws AKGL_ERR_REGISTRY If @p actorname is not in #AKGL_REGISTRY_ACTOR --
|
||||
* usually because the actor has not been created yet.
|
||||
*
|
||||
* @warning A negative @p controlmapid is not rejected. See
|
||||
* akgl_controller_pushmap.
|
||||
* @note It appends rather than replaces, so calling it twice on the same map
|
||||
* leaves sixteen bindings and the first eight are the ones that fire.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, char *actorname, int kbid, int jsid);
|
||||
|
||||
/**
|
||||
* @brief Controller open gamepads.
|
||||
* @brief Open every gamepad currently attached.
|
||||
*
|
||||
* SDL will not deliver button events from a gamepad nobody has opened, so this
|
||||
* runs once at startup -- akgl_game_init calls it. Devices plugged in later are
|
||||
* SDL_EVENT_GAMEPAD_ADDED events, handled elsewhere.
|
||||
*
|
||||
* No gamepads attached is success, not an error.
|
||||
*
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If SDL reports gamepads present but cannot
|
||||
* enumerate them, or if one of them cannot be opened. The message
|
||||
* carries `SDL_GetError()`.
|
||||
*
|
||||
* @note The enumeration array is only freed on the success path, so a failure
|
||||
* part-way through leaks it.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_open_gamepads(void);
|
||||
|
||||
@@ -146,10 +244,15 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_open_gamepads(void);
|
||||
* 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.
|
||||
* @param keycode Receives the SDL keycode, or 0 when nothing was waiting.
|
||||
* Required -- the return value is the error context.
|
||||
* @param available Receives `true` when a keystroke was taken, `false` when the
|
||||
* buffer was empty. Required. Check this rather than testing
|
||||
* @p keycode against 0.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p keycode or @p available is `NULL`. Both are
|
||||
* required; there is no "I only want to know whether one is waiting"
|
||||
* form.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_key(int *keycode, bool *available);
|
||||
|
||||
@@ -159,7 +262,10 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_key(int *keycode, bool *
|
||||
* 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.
|
||||
* This empties only the polling buffer. Keys already dispatched to control maps
|
||||
* have had their effect and cannot be taken back.
|
||||
*
|
||||
* @return `NULL`. There is no failure path -- it resets two counters.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_flush_keys(void);
|
||||
#endif // _CONTROLLER_H_
|
||||
|
||||
Reference in New Issue
Block a user