/** * @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_init2d * and akgl_physics_init_arcade quietly ignore their configuration. * TODO.md, "Known and still open" item 3. */ #ifndef _REGISTRY_H_ #define _REGISTRY_H_ #include #include /** @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_init2d 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 -- `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_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 The copy is a fixed #AKGL_MAX_STRING_LENGTH bytes rather than the length * of the value, so a short property value is read past its end. It works * because the destination is a full-sized pool string; it is still an * overread of the source. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_property(char *name, akgl_String **dest, char *def); #endif //_REGISTRY_H_