Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
165 lines
9.8 KiB
C
165 lines
9.8 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
|
|
#define AKGL_MAX_HEAP_CHARACTER 256
|
|
|
|
/** @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 *basechar, 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_
|