Closes internal-consistency items 7 through 15. Nineteen non-static functions were in the ABI with no declaration anywhere, so no consumer could call them and any consumer could collide with them. The four gamepad_handle_* functions are the ones that mattered: controller.h declared akgl_controller_handle_button_down and three siblings that did not exist, so anything compiled against the header alone failed to link. The definitions carry the declared names now, which also closes Defects -> Known and still open item 10, and their documentation moved to the header. The rest are either declared under a "part of the internal API" block -- akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to declare for itself, plus six tilemap loader helpers the untested-loader work wants to reach -- or static, which is what the four save iterators and load_objectnamemap should always have been. akgl_path_relative_from is deleted: declared nowhere, called from nowhere, never wrote its output, and leaked a pooled string on every call, so it closes Known and still open item 4 and item 40 by ceasing to exist. scripts/check_api_surface.sh keeps it closed. It reads the built library's dynamic symbol table and every public header with comments stripped, and fails on an exported akgl_* symbol that is declared nowhere. Stripping comments is the whole point -- four of these were mentioned in controller.h prose, which is how they went unnoticed. The pool-size ceilings are defined once, in heap.h, so the #ifndef override hook fires for the first time; actor.h, sprite.h and character.h were defining the same four unconditionally from headers heap.h includes above its own guard. tests/header_pool_override.c fails the compile if that regresses. Also here: (void) rather than () on the twelve no-argument entry points, AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix dropped, and the six parameter-name mismatches. akgl_get_json_with_default had its two contexts swapped rather than merely misspelled -- the incoming one was `err` and its own was `e`, which is the name reserved for an incoming one. 24/24 pass, reindent --check clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
164 lines
9.7 KiB
C
164 lines
9.7 KiB
C
/**
|
|
* @file character.h
|
|
* @brief The reusable half of an actor: movement constants and a state-to-sprite map.
|
|
*
|
|
* A character is the template; an akgl_Actor is the instance. Everything that is
|
|
* the same for every goblin on the map -- top speed, acceleration, frame dwell
|
|
* time, and which sprite to draw for which combination of actor-state bits --
|
|
* lives here and is shared by pointer, so a hundred actors cost one character.
|
|
*
|
|
* Characters are pool objects (akgl_heap_next_character) and are published in
|
|
* the #AKGL_REGISTRY_CHARACTER property registry under their name, which is how
|
|
* akgl_actor_set_character finds them. akgl_registry_init must have run first.
|
|
*/
|
|
|
|
#ifndef _AKGL_CHARACTER_H_
|
|
#define _AKGL_CHARACTER_H_
|
|
|
|
#include <SDL3/SDL_properties.h>
|
|
#include <akgl/types.h>
|
|
#include <akgl/sprite.h>
|
|
|
|
#define AKGL_CHARACTER_MAX_NAME_LENGTH 128
|
|
|
|
/** @brief Defines reusable movement parameters and actor-state sprite bindings. */
|
|
typedef struct akgl_Character {
|
|
uint8_t refcount; /**< Pool bookkeeping; 0 means the slot is free. */
|
|
char name[AKGL_CHARACTER_MAX_NAME_LENGTH]; /**< Registry key. Truncated, not rejected, if the source name is longer. */
|
|
SDL_PropertiesID state_sprites; /**< State bitmask (decimal, as a string) -> akgl_Sprite *. */
|
|
uint64_t speedtime; /**< Nanoseconds one sprite frame is held before advancing. Read from JSON in milliseconds and scaled by #AKGL_TIME_ONEMS_NS. */
|
|
float32_t ax; /**< Acceleration along x, world units per second squared. Copied into an actor by akgl_actor_set_character. */
|
|
float32_t ay; /**< Acceleration along y. */
|
|
float32_t az; /**< Acceleration along z. Not read from JSON; stays 0 unless set by hand. */
|
|
float32_t sx; /**< Maximum speed along x, world units per second. */
|
|
float32_t sy; /**< Maximum speed along y. */
|
|
float32_t sz; /**< Maximum speed along z. Not read from JSON; stays 0 unless set by hand. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*sprite_add)(struct akgl_Character *, akgl_Sprite *, int); /**< Bound to akgl_character_sprite_add by akgl_character_initialize. */
|
|
akerr_ErrorContext AKERR_NOIGNORE *(*sprite_get)(struct akgl_Character *, int, akgl_Sprite **); /**< Bound to akgl_character_sprite_get by akgl_character_initialize. */
|
|
} akgl_Character;
|
|
|
|
|
|
/**
|
|
* @brief Zero a pooled character, name it, and publish it in the character registry.
|
|
*
|
|
* Wipes the struct, copies @p name into it, creates the empty state-to-sprite
|
|
* property set, binds the `sprite_add`/`sprite_get` function pointers, inserts
|
|
* the character into #AKGL_REGISTRY_CHARACTER under @p name, and takes the first
|
|
* reference. Everything numeric (speeds, accelerations, `speedtime`) is left at
|
|
* zero for the caller -- or akgl_character_load_json -- to fill in.
|
|
*
|
|
* @param basechar Pooled character to initialize, normally straight from
|
|
* akgl_heap_next_character. Required. Any previous contents are
|
|
* discarded without releasing the sprites they referenced.
|
|
* @param name Registry key, NUL-terminated. Required. Truncated at
|
|
* #AKGL_CHARACTER_MAX_NAME_LENGTH. A name already in the
|
|
* registry is silently replaced, and the character it displaced
|
|
* becomes unreachable rather than being released.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p basechar or @p name is `NULL`, or if SDL
|
|
* cannot create the state-to-sprite property set (out of memory).
|
|
* @throws AKERR_KEY If the character cannot be written into
|
|
* #AKGL_REGISTRY_CHARACTER -- in practice, because akgl_registry_init
|
|
* has not run and the registry id is still 0.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_character_initialize(akgl_Character *obj, char *name);
|
|
/**
|
|
* @brief Bind a sprite to one exact combination of actor-state bits.
|
|
*
|
|
* The map key is the decimal spelling of @p state, so lookups match the *whole*
|
|
* value: a sprite added for `FACE_LEFT|MOVING_LEFT` is not found by a lookup for
|
|
* `FACE_LEFT` alone. Callers are expected to register every combination they
|
|
* intend to draw. Adding a sprite takes a reference on it.
|
|
*
|
|
* @param basechar Character to add the mapping to. Required. Must already have
|
|
* been through akgl_character_initialize.
|
|
* @param ref Sprite to draw for @p state. Required. Its `refcount` is
|
|
* incremented, so the character keeps it alive.
|
|
* @param state The exact actor-state bitmask (`AKGL_ACTOR_STATE_*`) this
|
|
* sprite is for. 0 is accepted and is a usable key.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p basechar or @p ref is `NULL`.
|
|
*
|
|
* @note Re-adding a different sprite for a @p state that is already mapped
|
|
* replaces the entry without releasing the sprite it displaced, so the
|
|
* displaced sprite's reference is never given back.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_character_sprite_add(akgl_Character *basechar, akgl_Sprite *ref, int state);
|
|
/**
|
|
* @brief Look up the sprite bound to one exact combination of actor-state bits.
|
|
*
|
|
* The counterpart to akgl_character_sprite_add, and matched the same way: an
|
|
* exact match on the whole @p state value, with no fallback to a subset of the
|
|
* bits and no default sprite. Unlike most lookups in this codebase, *not finding
|
|
* one is an error* rather than a successful "nothing here" -- an actor with no
|
|
* sprite for its current state cannot be drawn.
|
|
*
|
|
* @param basechar Character to search. Required.
|
|
* @param state The exact actor-state bitmask to look up.
|
|
* @param dest Receives the mapped sprite. Required -- the return value is
|
|
* spoken for by the error context. Set to `NULL` when there is
|
|
* no mapping, alongside the AKERR_KEY error.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p basechar or @p dest is `NULL`.
|
|
* @throws AKERR_KEY If no sprite is bound to exactly that @p state. The message
|
|
* carries the state both as a decimal and as a bit pattern.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_character_sprite_get(akgl_Character *basechar, int state, akgl_Sprite **dest);
|
|
|
|
// This is an SDL iterator so we can't return our error state from it.
|
|
/**
|
|
* @brief `SDL_EnumerateProperties` callback that applies an akgl_Iterator to one state-to-sprite entry.
|
|
*
|
|
* Currently implements exactly one operation: with #AKGL_ITERATOR_OP_RELEASE
|
|
* set, it hands the mapped sprite back to its heap layer. Every other flag is
|
|
* ignored here. Used to tear a character's sprite map down entry by entry.
|
|
*
|
|
* @param userdata The akgl_Iterator carrying the operation flags, passed through
|
|
* by `SDL_EnumerateProperties`. Required despite the `void *` --
|
|
* a `NULL` here is an error, not "no operations".
|
|
* @param props The character's `state_sprites` set, supplied by SDL.
|
|
* @param name The property key: one state bitmask in decimal. Required.
|
|
*
|
|
* @warning This is an SDL callback, so it returns `void` and has nowhere to
|
|
* propagate an error to. It ends in `FINISH_NORETURN`, which logs the
|
|
* stack trace and then calls libakerror's unhandled-error handler --
|
|
* whose default implementation **exits the process** with the error
|
|
* status. A missing sprite or a `NULL` @p userdata therefore terminates
|
|
* the game rather than skipping an entry. Install your own
|
|
* `akerr_handler_unhandled_error` if that is not what you want.
|
|
*/
|
|
void akgl_character_state_sprites_iterate(void *userdata, SDL_PropertiesID props, const char *name);
|
|
|
|
/**
|
|
* @brief Build a character from a JSON definition file and register it.
|
|
*
|
|
* Claims a character from the pool, then reads: `name` (the registry key),
|
|
* `sprite_mappings` (an array of `{ "sprite": <name>, "state": [<state names>] }`
|
|
* objects), `speedtime` in seconds, `speed_x`, `speed_y`, `acceleration_x`, and
|
|
* `acceleration_y`. Each mapping's `state` array is OR-ed together into one
|
|
* bitmask by looking every name up in #AKGL_REGISTRY_ACTOR_STATE_STRINGS, so
|
|
* `["FACE_LEFT", "MOVING_LEFT"]` becomes a single key.
|
|
*
|
|
* Every referenced sprite must already be in #AKGL_REGISTRY_SPRITE: this loads
|
|
* characters, not sprites, so akgl_sprite_load_json runs first. On any failure
|
|
* the pooled character is released again.
|
|
*
|
|
* @param filename Path to the JSON document. Required. Used verbatim -- it is
|
|
* not resolved against `SDL_GetBasePath()`.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p filename is `NULL`; if the file cannot be
|
|
* opened or does not parse (the message carries jansson's line number
|
|
* and text); or if a mapping names a sprite that is not in the sprite
|
|
* registry.
|
|
* @throws AKERR_KEY If a `state` array contains a name that is not a known
|
|
* actor state, if a required top-level key is absent, or if the
|
|
* character cannot be added to the registry.
|
|
* @throws AKERR_TYPE If a key is present but holds the wrong JSON type -- for
|
|
* example `speedtime` as a string. The message names the key.
|
|
* @throws AKERR_OUTOFBOUNDS If a `state` array is indexed past its end.
|
|
* @throws AKGL_ERR_HEAP If the character pool or the string pool is exhausted.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_character_load_json(char *filename);
|
|
|
|
#endif // _AKGL_CHARACTER_H_
|