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>
220 lines
11 KiB
C
220 lines
11 KiB
C
/**
|
|
* @file registry.h
|
|
* @brief The name-to-object lookup tables, and the string configuration store.
|
|
*
|
|
* Nothing in this library is passed around by pointer where a name will do. An
|
|
* actor names the character it instantiates, a character names the sprites it
|
|
* draws, a sprite names the sheet it cuts frames from -- all resolved at load
|
|
* time through these eight registries. That is what lets the whole asset graph
|
|
* be described in JSON files that reference each other by name.
|
|
*
|
|
* They are SDL property sets, not akgl types, so a caller can enumerate one with
|
|
* `SDL_EnumerateProperties` -- which is exactly what akgl_registry_iterate_actor
|
|
* and akgl_character_state_sprites_iterate are for.
|
|
*
|
|
* Every id starts at 0, which SDL treats as "no such property set": reads return
|
|
* the default and writes are silently dropped. So an uninitialized registry does
|
|
* not fail loudly, it fails quietly, and akgl_registry_init has to run first.
|
|
*
|
|
* @warning akgl_registry_init does *not* initialize
|
|
* #AKGL_REGISTRY_PROPERTIES -- akgl_registry_init_properties is a
|
|
* separate call, made by akgl_game_init but by nothing else. A program
|
|
* that builds its own startup path and skips it gets a silently
|
|
* no-op akgl_set_property and an akgl_get_property that always hands
|
|
* back the caller's default, which in turn means akgl_render_2d_init
|
|
* and akgl_physics_init_arcade quietly ignore their configuration.
|
|
* TODO.md, "Known and still open" item 3.
|
|
*/
|
|
|
|
#ifndef _AKGL_REGISTRY_H_
|
|
#define _AKGL_REGISTRY_H_
|
|
|
|
#include <SDL3/SDL.h>
|
|
#include <akgl/error.h>
|
|
#include <akgl/staticstring.h>
|
|
|
|
/** @brief Actor name -> `akgl_Actor *`. Written by akgl_actor_initialize, cleared when an actor's last reference goes. */
|
|
extern SDL_PropertiesID AKGL_REGISTRY_ACTOR;
|
|
/** @brief Sprite name -> `akgl_Sprite *`. The `name` field from a sprite JSON. */
|
|
extern SDL_PropertiesID AKGL_REGISTRY_SPRITE;
|
|
/** @brief Resolved image path -> `akgl_SpriteSheet *`. Keyed by path so two sprites sharing an image share the texture. */
|
|
extern SDL_PropertiesID AKGL_REGISTRY_SPRITESHEET;
|
|
/** @brief Character name -> `akgl_Character *`. What akgl_actor_set_character resolves against. */
|
|
extern SDL_PropertiesID AKGL_REGISTRY_CHARACTER;
|
|
/** @brief Actor-state name -> its bit value, as a number. Lets character JSON say "AKGL_ACTOR_STATE_FACE_LEFT" instead of 2. */
|
|
extern SDL_PropertiesID AKGL_REGISTRY_ACTOR_STATE_STRINGS;
|
|
/** @brief Font name -> `TTF_Font *`. Names are chosen by the caller of akgl_text_loadfont, not derived from the file. */
|
|
extern SDL_PropertiesID AKGL_REGISTRY_FONT;
|
|
/** @brief Music name -> audio handle. Created but not yet populated by anything in the library. */
|
|
extern SDL_PropertiesID AKGL_REGISTRY_MUSIC;
|
|
/** @brief Configuration key -> string value. Read through akgl_get_property; everything is a string, including numbers. */
|
|
extern SDL_PropertiesID AKGL_REGISTRY_PROPERTIES;
|
|
|
|
/**
|
|
* @brief Create the seven asset registries, in dependency order.
|
|
*
|
|
* Spritesheet, sprite, character, actor, actor-state-strings, font, music. Call
|
|
* it once at startup, before loading anything.
|
|
*
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If SDL cannot allocate any one of the property sets;
|
|
* the message names which registry failed. The registries created before
|
|
* the failure are left in place rather than torn down.
|
|
*
|
|
* @warning This does not create #AKGL_REGISTRY_PROPERTIES -- see the warning on
|
|
* this file.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init();
|
|
/**
|
|
* @brief Create the music registry.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If SDL cannot allocate the property set.
|
|
* @note Overwrites the existing id without destroying the old set, so calling it
|
|
* twice leaks the first one along with everything registered in it.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_music();
|
|
/**
|
|
* @brief Create the font registry.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If SDL cannot allocate the property set.
|
|
* @note Overwrites the existing id without destroying the old set. See
|
|
* akgl_registry_init_music.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_font();
|
|
/**
|
|
* @brief Create the actor registry, destroying any previous one first.
|
|
*
|
|
* The one initializer here that cleans up after itself, which is what makes it
|
|
* safe to call between levels: it destroys the old property set before creating
|
|
* the replacement, so the actors from the previous map are unregistered in one
|
|
* step. Note that it destroys the *registry*, not the actors -- releasing those
|
|
* is akgl_heap_release_actor's job.
|
|
*
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If SDL cannot allocate the property set. The old one
|
|
* has already been destroyed at that point, so the registry is left at 0.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_actor();
|
|
/**
|
|
* @brief Create the sprite registry.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If SDL cannot allocate the property set.
|
|
* @note Overwrites the existing id without destroying the old set. See
|
|
* akgl_registry_init_music.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_sprite();
|
|
/**
|
|
* @brief Create the spritesheet registry.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If SDL cannot allocate the property set.
|
|
* @note Overwrites the existing id without destroying the old set. See
|
|
* akgl_registry_init_music.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_spritesheet();
|
|
/**
|
|
* @brief Create the character registry.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If SDL cannot allocate the property set.
|
|
* @note Overwrites the existing id without destroying the old set. See
|
|
* akgl_registry_init_music.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_character();
|
|
/**
|
|
* @brief Create the configuration-property registry.
|
|
*
|
|
* Deliberately separate from akgl_registry_init, because configuration has to be
|
|
* in place before the subsystems that read it start up. akgl_game_init creates
|
|
* it; filling it in -- with akgl_registry_load_properties or akgl_set_property --
|
|
* is the caller's job, and has to happen before akgl_render_2d_init or
|
|
* akgl_physics_init_arcade run.
|
|
*
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If SDL cannot allocate the property set.
|
|
* @note Overwrites the existing id without destroying the old set, so every
|
|
* property set before a second call is lost.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_properties();
|
|
/**
|
|
* @brief Load a JSON configuration file into the property registry.
|
|
*
|
|
* Expects a document with a top-level `properties` object whose members are all
|
|
* strings; each becomes one entry. Numbers are configured as strings here and
|
|
* parsed by whoever reads them -- `akgl_game.screenwidth` is `"800"`, not `800`.
|
|
*
|
|
* @param fname Path to the JSON document. Required. Used verbatim.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p fname is `NULL`, or if the file cannot be
|
|
* opened or does not parse. The message carries jansson's line number
|
|
* and text.
|
|
* @throws AKERR_KEY If the document has no top-level `properties` member.
|
|
* @throws AKERR_TYPE If `properties` is not an object, or if one of its members
|
|
* is not a string.
|
|
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
|
*
|
|
* @note Writes into #AKGL_REGISTRY_PROPERTIES, so akgl_registry_init_properties
|
|
* has to have run -- otherwise every entry is silently dropped and this
|
|
* still returns success.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_load_properties(char *fname);
|
|
/**
|
|
* @brief Create the actor-state-name registry and fill it from the name table.
|
|
*
|
|
* Walks `AKGL_ACTOR_STATE_STRING_NAMES` and maps entry `i` to the value `1 << i`,
|
|
* which is what lets a character JSON write `"AKGL_ACTOR_STATE_FACE_LEFT"` and
|
|
* have akgl_character_load_json turn it into a bit.
|
|
*
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If SDL cannot allocate the property set. The
|
|
* individual name registrations are not checked.
|
|
*
|
|
* @note The name table it reads disagrees with actor.h in two places: bits 11
|
|
* and 12 are named `UNDEFINED_11`/`UNDEFINED_12` rather than `MOVING_IN`
|
|
* and `MOVING_OUT`, so those two states cannot be named from JSON at all.
|
|
* TODO.md items 24-26.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_actor_state_strings();
|
|
/**
|
|
* @brief Set one configuration property.
|
|
*
|
|
* @param name Property key. Required.
|
|
* @param value Property value. Required. SDL copies it, so the caller's buffer
|
|
* can go away afterwards.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p name or @p value is `NULL`.
|
|
*
|
|
* @note The write itself is unchecked. If #AKGL_REGISTRY_PROPERTIES is still 0 --
|
|
* akgl_registry_init_properties never having run -- this discards the
|
|
* value and returns success.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_set_property(char *name, char *value);
|
|
/**
|
|
* @brief Read one configuration property into a pooled string, with a fallback.
|
|
*
|
|
* Absence is not an error: an unset property yields @p def, which is how every
|
|
* caller in the library gets a working default without checking first.
|
|
*
|
|
* @param name Property key. Required.
|
|
* @param dest Receives the value. Required. If `*dest` is `NULL` a string is
|
|
* claimed from the pool for you, so initialize it to `NULL` on the
|
|
* first call; either way the caller releases it with
|
|
* akgl_heap_release_string. If `*dest` is non-`NULL` it is written
|
|
* in place.
|
|
* @param def Value to use when @p name is not set. Effectively required --
|
|
* a `NULL` here on an unset property is an AKERR_NULLPOINTER rather
|
|
* than an empty result.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p name or @p dest is `NULL`, or if the property
|
|
* is unset and @p def is `NULL`.
|
|
* @throws AKERR_OUTOFBOUNDS If the stored value is #AKGL_MAX_STRING_LENGTH bytes
|
|
* or longer, which will not fit an akgl_String with its terminator.
|
|
* @throws AKERR_VALUE If the value and the destination overlap in memory.
|
|
* @throws AKGL_ERR_HEAP If `*dest` was `NULL` and the string pool is exhausted.
|
|
*
|
|
* @note Only the value and its terminator are copied. The rest of @p dest keeps
|
|
* whatever the previous holder left there, so read the result as a C
|
|
* string rather than as #AKGL_MAX_STRING_LENGTH bytes.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_property(char *name, akgl_String **dest, char *def);
|
|
|
|
#endif //_AKGL_REGISTRY_H_
|