Namespace every exported symbol, and bump to 0.5.0

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>
This commit is contained in:
2026-07-31 23:32:21 -04:00
parent 93e8e4afa4
commit 9924d74dcc
60 changed files with 1199 additions and 860 deletions

View File

@@ -33,8 +33,8 @@
#define _AKGL_ACTOR_H_
#include <stdint.h>
#include "types.h"
#include "character.h"
#include <akgl/types.h>
#include <akgl/character.h>
// ---- LOW WORD STATUSES ----
@@ -225,7 +225,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_set_character(akgl_Actor *obj, cha
* @return `NULL` on success -- including every skipped case above -- otherwise
* an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
* @throws AKERR_* Whatever akgl_sprite_sheet_coords_for_frame or the renderer's
* @throws AKERR_* Whatever akgl_spritesheet_coords_for_frame or the renderer's
* `draw_texture` raises.
*
* @note The destination height is computed from the sprite's *width*, so a
@@ -368,7 +368,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_add_child(akgl_Actor *obj, akgl_Ac
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_on(akgl_Actor *obj, SDL_Event *event);
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_on(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Stop the actor moving left, zeroing everything on the x axis.
* @param obj The actor to stop. Required. `basechar` is not needed here.
@@ -378,7 +378,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_on(akgl_Actor *obj, SDL_
* @note It clears the x axis whichever way the actor was going, so releasing
* left while holding right also stops the rightward movement.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_off(akgl_Actor *obj, SDL_Event *event);
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_off(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Start the actor moving right, and turn it to face right.
* @param obj The actor to move. Required, along with its `basechar`.
@@ -386,7 +386,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_off(akgl_Actor *obj, SDL
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_on(akgl_Actor *obj, SDL_Event *event);
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_on(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Stop the actor moving right, zeroing everything on the x axis.
* @param obj The actor to stop. Required.
@@ -394,7 +394,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_on(akgl_Actor *obj, SDL
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_off(akgl_Actor *obj, SDL_Event *event);
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_off(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Start the actor moving up the screen, and turn it to face up.
* @param obj The actor to move. Required, along with its `basechar`, whose y
@@ -403,7 +403,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_off(akgl_Actor *obj, SD
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_on(akgl_Actor *obj, SDL_Event *event);
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_on(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Stop the actor moving up, zeroing everything on the y axis.
* @param obj The actor to stop. Required.
@@ -413,7 +413,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_on(akgl_Actor *obj, SDL_Ev
* @note This also zeroes `ey`, so an actor under gravity has its accumulated
* fall cancelled by releasing a movement key.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_off(akgl_Actor *obj, SDL_Event *event);
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_off(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Start the actor moving down the screen, and turn it to face down.
* @param obj The actor to move. Required, along with its `basechar`.
@@ -421,16 +421,16 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_off(akgl_Actor *obj, SDL_E
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_down_on(akgl_Actor *obj, SDL_Event *event);
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_on(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Stop the actor moving down, zeroing everything on the y axis.
* @param obj The actor to stop. Required.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
* @note Zeroes `ey` as well; see akgl_Actor_cmhf_up_off.
* @note Zeroes `ey` as well; see akgl_actor_cmhf_up_off.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_down_off(akgl_Actor *obj, SDL_Event *event);
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_off(akgl_Actor *obj, SDL_Event *event);
/**
* @brief `SDL_EnumerateProperties` callback that applies an akgl_Iterator to one registered actor.

View File

@@ -3,8 +3,8 @@
* @brief Loads the game's startup assets into the global mixer and track table.
*/
#ifndef _ASSETS_H_
#define _ASSETS_H_
#ifndef _AKGL_ASSETS_H_
#define _AKGL_ASSETS_H_
#include <akerror.h>
@@ -41,4 +41,4 @@
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_load_start_bgm(char *fname);
#endif //_ASSETS_H_
#endif //_AKGL_ASSETS_H_

View File

@@ -16,18 +16,18 @@
#define _AKGL_CHARACTER_H_
#include <SDL3/SDL_properties.h>
#include "types.h"
#include "sprite.h"
#include <akgl/types.h>
#include <akgl/sprite.h>
#define AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH 128
#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_SPRITE_MAX_CHARACTER_NAME_LENGTH]; /**< Registry key. Truncated, not rejected, if the source name is longer. */
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_ONESEC_MS, which despite its name is nanoseconds-per-millisecond. TODO.md item 6. */
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. */
@@ -52,7 +52,7 @@ typedef struct akgl_Character {
* 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_SPRITE_MAX_CHARACTER_NAME_LENGTH. A name already in the
* #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.

View File

@@ -24,12 +24,12 @@
* akgl_controller_poll_keystroke().
*/
#ifndef _CONTROLLER_H_
#define _CONTROLLER_H_
#ifndef _AKGL_CONTROLLER_H_
#define _AKGL_CONTROLLER_H_
#include <SDL3/SDL.h>
#include <akerror.h>
#include "types.h"
#include <akgl/types.h>
// The binding handlers below take an akgl_Actor *, so this header cannot be
// included before akgl/actor.h without one. akgl_Actor is a typedef of a named
// struct, and repeating a typedef is not C99, so it is included rather than
@@ -94,7 +94,7 @@ typedef struct {
} akgl_ControlMap;
/** @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];
extern akgl_ControlMap akgl_controlmaps[AKGL_MAX_CONTROL_MAPS];
/**
* @brief Log every attached keyboard and its SDL id.
@@ -190,7 +190,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_removed(void *appstate
* 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.
* short of zeroing it in ::akgl_controlmaps directly.
*
* @param controlmapid Which map to append to, 0 through
* #AKGL_MAX_CONTROL_MAPS - 1.
@@ -203,7 +203,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_removed(void *appstate
*
* @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.
* ::akgl_controlmaps. TODO.md, "Known and still open" item 11.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akgl_Control *control);
@@ -212,7 +212,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akg
*
* 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
* `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.
*
@@ -334,4 +334,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_keystroke(akgl_Keystroke
* @return `NULL`. There is no failure path -- it resets two counters.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_flush_keys(void);
#endif // _CONTROLLER_H_
#endif // _AKGL_CONTROLLER_H_

View File

@@ -16,8 +16,8 @@
* what the next SDL_RenderClear() paints.
*/
#ifndef _DRAW_H_
#define _DRAW_H_
#ifndef _AKGL_DRAW_H_
#define _AKGL_DRAW_H_
#include <SDL3/SDL.h>
@@ -42,7 +42,7 @@
* A diagnostic backdrop, not a general primitive -- `charviewer` uses it so a
* sprite's transparent pixels are visible rather than blending into black. It
* is the one function in this file that does not follow the file's conventions:
* it draws through the *global* `renderer` rather than a backend the caller
* it draws through the *global* `akgl_renderer` rather than a backend the caller
* passes in, it leaves the renderer's draw colour changed, and it reports
* nothing.
*
@@ -233,4 +233,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_copy_region(akgl_RenderBackend *sel
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_paste_region(akgl_RenderBackend *self, SDL_Surface *src, float32_t x, float32_t y);
#endif //_DRAW_H_
#endif //_AKGL_DRAW_H_

View File

@@ -3,8 +3,8 @@
* @brief Declares the public error API.
*/
#ifndef _ERROR_H_
#define _ERROR_H_
#ifndef _AKGL_ERROR_H_
#define _AKGL_ERROR_H_
#include <akerror.h>
@@ -75,4 +75,4 @@
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_error_init(void);
#endif // _ERROR_H_
#endif // _AKGL_ERROR_H_

View File

@@ -6,24 +6,24 @@
* registries and the audio and font engines; `akgl_game_update` is one frame --
* update every actor, step the physics, draw the world.
*
* There is exactly one of everything. `renderer`, `physics`, `camera`, and
* `gamemap` are globals pointing at the `_akgl_*` storage below them, so a
* There is exactly one of everything. `akgl_renderer`, `akgl_physics`, `akgl_camera`, and
* `akgl_gamemap` are globals pointing at the `akgl_default_*` storage below them, so a
* program can swap in its own instance by reassigning the pointer without the
* rest of the library knowing. That is the whole extent of the indirection:
* there is no notion of two worlds at once.
*
* The startup order that actually works:
*
* 1. fill in `game.name`, `game.version`, and `game.uri` -- akgl_game_init
* 1. fill in `akgl_game.name`, `akgl_game.version`, and `akgl_game.uri` -- akgl_game_init
* refuses to run without them;
* 2. akgl_game_init();
* 3. akgl_registry_load_properties() or akgl_set_property(), to configure
* screen size, physics constants and so on;
* 4. akgl_render_init2d(renderer) and akgl_physics_factory(physics, ...), both
* 4. akgl_render_2d_init(renderer) and akgl_physics_factory(physics, ...), both
* of which read that configuration;
* 5. load assets, then loop on akgl_game_update().
*
* @warning None of this is thread-safe beyond the `game.state` mutex, and that
* @warning None of this is thread-safe beyond the `akgl_game.state` mutex, and that
* mutex protects the state flags, not the pools or the registries.
*/
@@ -32,10 +32,10 @@
#include <stdint.h>
#include <SDL3_mixer/SDL_mixer.h>
#include "types.h"
#include "tilemap.h"
#include "renderer.h"
#include "physics.h"
#include <akgl/types.h>
#include <akgl/tilemap.h>
#include <akgl/renderer.h>
#include <akgl/physics.h>
// AKGL_VERSION used to be defined here by hand, which is how akgl.pc came to
// ship an empty Version field: nothing tied the two together.
#include <akgl/version.h>
@@ -48,15 +48,29 @@
/** @brief Nanoseconds in one second. The unit `SDL_GetTicksNS` reports in. */
#define AKGL_TIME_ONESEC_NS 1000000000
/**
* @brief Misnamed: this is nanoseconds per **millisecond**, not milliseconds per second.
* @brief Nanoseconds in one millisecond. The scale factor from JSON durations to internal ones.
*
* One second in milliseconds is 1000. 1000000 is one millisecond in
* nanoseconds, which is how sprite and character load actually use it -- as a
* milliseconds-to-nanoseconds scale factor -- and is not how
* akgl_game_state_lock uses it. TODO.md item 6 proposes renaming it
* `AKGL_TIME_ONEMS_NS`.
* Sprite and character definitions state frame durations in milliseconds
* because that is what a human writing one wants to type; everything that
* compares them against `SDL_GetTicksNS` needs nanoseconds.
*
* This was called `AKGL_TIME_ONESEC_MS` until 0.5.0, which said "one second in
* milliseconds" and held 1000000 -- a name and a value that described two
* different quantities. Both callers that meant the scale factor were right;
* the one that read it as a one-second budget was wrong by a factor of a
* thousand. See akgl_game_state_lock.
*/
#define AKGL_TIME_ONESEC_MS 1000000
#define AKGL_TIME_ONEMS_NS 1000000
/**
* @brief How long akgl_game_state_lock keeps trying for the state mutex, in milliseconds.
*
* A deliberate ceiling rather than a blocking wait: a deadlock here reports an
* error the caller can act on instead of hanging the process.
*/
#define AKGL_GAME_STATE_LOCK_BUDGET_MS 1000
/** @brief How long akgl_game_state_lock sleeps between attempts, in milliseconds. */
#define AKGL_GAME_STATE_LOCK_RETRY_MS 100
/* ==================== GAME STATE VARIABLES =================== */
@@ -82,39 +96,39 @@ typedef struct {
SDL_Mutex *statelock; /**< Guards `state`. Created by akgl_game_init. */
int16_t fps; /**< Frames drawn during the last completed second. Recomputed once per second, not per frame. */
SDL_Time gameStartTime; /**< `SDL_GetTicksNS()` at akgl_game_init. */
SDL_Time lastIterTime; /**< Timestamp of the most recent akgl_game_updateFPS call. */
SDL_Time lastIterTime; /**< Timestamp of the most recent akgl_game_update_fps call. */
SDL_Time lastFPSTime; /**< When `fps` was last recomputed. */
int16_t framesSinceUpdate; /**< Frames counted so far in the current second. */
void (*lowfpsfunc)(void); /**< Called every frame while `fps` is under 30. Defaults to akgl_game_lowfps; replace it to do something more useful than log. */
} akgl_Game;
/** @brief The SDL window, created by akgl_render_init2d. `NULL` until then. */
extern SDL_Window *window;
/** @brief The SDL window, created by akgl_render_2d_init. `NULL` until then. */
extern SDL_Window *akgl_window;
/** @brief The background music, loaded by akgl_load_start_bgm. `NULL` until then. */
extern MIX_Audio *bgm;
extern MIX_Audio *akgl_bgm;
/** @brief The mixer device, created by akgl_game_init. Everything audio goes through it. */
extern MIX_Mixer *akgl_mixer;
/** @brief Playback tracks by slot. #AKGL_GAME_AUDIO_TRACK_BGM is the music track; the rest are the application's to assign. */
extern MIX_Track *akgl_tracks[AKGL_GAME_AUDIO_MAX_TRACKS];
/** @brief Storage behind the default `camera`. Point `camera` elsewhere rather than reaching for this. */
extern SDL_FRect _akgl_camera;
/** @brief Storage behind the default `akgl_camera`. Point `akgl_camera` elsewhere rather than reaching for this. */
extern SDL_FRect akgl_default_camera;
/** @brief The one game object: metadata, timing, and FPS accounting. */
extern akgl_Game game;
/** @brief Storage behind the default `renderer`. */
extern akgl_RenderBackend _akgl_renderer;
/** @brief Storage behind the default `physics`. */
extern akgl_PhysicsBackend _akgl_physics;
/** @brief Storage behind the default `gamemap`. */
extern akgl_Tilemap _akgl_gamemap;
extern akgl_Game akgl_game;
/** @brief Storage behind the default `akgl_renderer`. */
extern akgl_RenderBackend akgl_default_renderer;
/** @brief Storage behind the default `akgl_physics`. */
extern akgl_PhysicsBackend akgl_default_physics;
/** @brief Storage behind the default `akgl_gamemap`. */
extern akgl_Tilemap akgl_default_gamemap;
/** @brief Currently active tilemap. */
extern akgl_Tilemap *gamemap;
extern akgl_Tilemap *akgl_gamemap;
/** @brief Currently active renderer. */
extern akgl_RenderBackend *renderer;
extern akgl_RenderBackend *akgl_renderer;
/** @brief Currently active physics backend. */
extern akgl_PhysicsBackend *physics;
extern akgl_PhysicsBackend *akgl_physics;
/** @brief Currently active camera. */
extern SDL_FRect *camera;
extern SDL_FRect *akgl_camera;
/**
* @brief True when every bit of `y` is set in `x`. Not "any of them" -- all of them.
@@ -141,14 +155,14 @@ extern SDL_FRect *camera;
* pools, create the registries, hand SDL the app metadata, clear the control
* maps, `SDL_Init` video/gamepad/audio, load the bundled controller database,
* open any attached gamepads, start SDL_mixer and SDL_ttf, and finally point
* `renderer`, `physics`, `camera`, and `gamemap` at their default storage.
* `akgl_renderer`, `akgl_physics`, `akgl_camera`, and `akgl_gamemap` at their default storage.
*
* What it does *not* do: create the window, choose a physics backend, or load
* any configuration. Those read properties, so they come after the caller has
* set them. See the sequence at the top of this file.
*
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If `game.name`, `game.version`, or `game.uri` is
* @throws AKERR_NULLPOINTER If `akgl_game.name`, `akgl_game.version`, or `akgl_game.uri` is
* empty. All three are required and there are no defaults -- the
* window title, SDL's app metadata, and the savegame compatibility
* check are all built from them.
@@ -168,7 +182,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init();
* @brief Declared but never defined. Do not call.
*
* There is no definition anywhere in the library, so a translation unit that
* calls this compiles and then fails to link. Screen setup is akgl_render_init2d.
* calls this compiles and then fails to link. Screen setup is akgl_render_2d_init.
* Tracked in TODO.md under header/implementation surface drift.
*
* @return Nothing; it cannot be called.
@@ -178,13 +192,13 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init_screen();
* @brief Count this frame, and recompute the frame rate once a second has passed.
*
* Called at the top of akgl_game_update, so a caller running its own loop needs
* to call it itself. `game.fps` is only refreshed when a full second has
* to call it itself. `akgl_game.fps` is only refreshed when a full second has
* elapsed, so it is a completed-second average rather than an instantaneous
* figure -- and it reads 0 for the first second of the process, which is under
* the low-FPS threshold and so fires `lowfpsfunc` on every frame until the first
* second is up.
*/
void akgl_game_updateFPS();
void akgl_game_update_fps();
/**
* @brief Write the game state and the name-to-pointer tables to a save file.
*
@@ -236,9 +250,9 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath);
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath);
/**
* @brief The default `game.lowfpsfunc`: log the current frame rate.
* @brief The default `akgl_game.lowfpsfunc`: log the current frame rate.
*
* Called from akgl_game_updateFPS on every frame where `game.fps` is under 30.
* Called from akgl_game_update_fps on every frame where `akgl_game.fps` is under 30.
* It is a placeholder -- the point of the hook is that a game can replace it
* with something that actually sheds work.
*/
@@ -251,15 +265,16 @@ void akgl_game_lowfps(void);
*
* @return `NULL` once the lock is held, otherwise an error context owned by the
* caller.
* @throws AKGL_ERR_SDL If the budget runs out with the lock still held
* elsewhere. The message carries `SDL_GetError()`, which after a failed
* `SDL_TryLockMutex` is usually stale or empty -- the status is the
* signal, not the text.
* @throws AKGL_ERR_SDL If #AKGL_GAME_STATE_LOCK_BUDGET_MS elapses with the lock
* still held elsewhere. `SDL_GetError()` is appended for whatever it is
* worth, but after a failed `SDL_TryLockMutex` it is usually stale or
* empty -- contention is reported by the return value, not by an error
* string. The status is the signal, not the text.
*
* @note The budget is meant to be one second but is not: the loop counts against
* #AKGL_TIME_ONESEC_MS, which is nanoseconds-per-millisecond (1000000)
* rather than milliseconds-per-second, so it retries 10,000 times at 100 ms
* and gives up after roughly 16 minutes. TODO.md item 6.
* @note Before 0.5.0 the loop counted against a constant named
* `AKGL_TIME_ONESEC_MS` that held 1000000, so it retried 10,000 times at
* 100 ms and blocked for roughly sixteen minutes rather than the one
* second documented here.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_lock(void);
/**
@@ -276,8 +291,8 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void);
*
* Takes the state lock, counts the frame, then walks layers 0 through
* #AKGL_TILEMAP_MAX_LAYERS calling each live actor's `updatefunc`, optionally
* rescaling it to the tilemap first. Then it steps `physics` and draws through
* `renderer`, and releases the lock.
* rescaling it to the tilemap first. Then it steps `akgl_physics` and draws through
* `akgl_renderer`, and releases the lock.
*
* @param opflags Iterator flags. Optional -- `NULL` selects a default set that
* sweeps one layer at a time, in which case `layerid` is advanced

View File

@@ -29,10 +29,10 @@
#ifndef _AKGL_HEAP_H_
#define _AKGL_HEAP_H_
#include "sprite.h"
#include "actor.h"
#include "character.h"
#include "staticstring.h"
#include <akgl/sprite.h>
#include <akgl/actor.h>
#include <akgl/character.h>
#include <akgl/staticstring.h>
#include <akerror.h>
#ifndef AKGL_MAX_HEAP_ACTOR
@@ -52,15 +52,15 @@
#endif
/** @brief The actor pool. Public so the render and physics sweeps can walk it directly instead of going through the registry. */
extern akgl_Actor HEAP_ACTOR[AKGL_MAX_HEAP_ACTOR];
extern akgl_Actor akgl_heap_actors[AKGL_MAX_HEAP_ACTOR];
/** @brief The sprite pool. 16 per actor, on the assumption of one sprite per state combination. */
extern akgl_Sprite HEAP_SPRITE[AKGL_MAX_HEAP_SPRITE];
extern akgl_Sprite akgl_heap_sprites[AKGL_MAX_HEAP_SPRITE];
/** @brief The spritesheet pool. Sized like the sprite pool, though sharing means far fewer are used in practice. */
extern akgl_SpriteSheet HEAP_SPRITESHEET[AKGL_MAX_HEAP_SPRITESHEET];
extern akgl_SpriteSheet akgl_heap_spritesheets[AKGL_MAX_HEAP_SPRITESHEET];
/** @brief The character pool. */
extern akgl_Character HEAP_CHARACTER[AKGL_MAX_HEAP_CHARACTER];
extern akgl_Character akgl_heap_characters[AKGL_MAX_HEAP_CHARACTER];
/** @brief The string pool. Every entry is PATH_MAX bytes, so this is the largest of the five by a wide margin. */
extern akgl_String HEAP_STRING[AKGL_MAX_HEAP_STRING];
extern akgl_String akgl_heap_strings[AKGL_MAX_HEAP_STRING];
/**
* @brief Zero every pool, marking every slot free.
@@ -195,11 +195,12 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_release_spritesheet(akgl_SpriteShee
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p ptr is `NULL`.
*
* @note It does not walk the state-to-sprite map, so the references the
* character took in akgl_character_sprite_add are never given back, and
* the SDL property set holding the map is leaked. Release the sprites
* first with akgl_character_state_sprites_iterate and
* #AKGL_ITERATOR_OP_RELEASE if you need them back.
* @note At zero it also walks the state-to-sprite map with
* akgl_character_state_sprites_iterate and #AKGL_ITERATOR_OP_RELEASE,
* giving back every reference akgl_character_sprite_add took, and then
* destroys the `SDL_PropertiesID` holding the map. Before 0.5.0 it did
* neither, so loading and releasing characters level by level exhausted
* the sprite pool and leaked a property set per character.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_release_character(akgl_Character *ptr);
/**

View File

@@ -20,6 +20,8 @@
#ifndef _AKGL_ITERATOR_H_
#define _AKGL_ITERATOR_H_
#include <stdint.h>
/** @brief Selects operations and an optional layer for actor traversal. */
typedef struct {
uint32_t flags; /**< Bitwise OR of the `AKGL_ITERATOR_OP_*` values below. */

View File

@@ -26,11 +26,12 @@
* - **The document itself is never modified.**
*/
#ifndef _JSON_HELPERS_H_
#define _JSON_HELPERS_H_
#ifndef _AKGL_JSON_HELPERS_H_
#define _AKGL_JSON_HELPERS_H_
#include <akerror.h>
#include "staticstring.h"
#include <jansson.h>
#include <akgl/staticstring.h>
/**
* @brief Read a nested object out of a JSON object.
@@ -233,4 +234,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_array_index_string(json_t *arra
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_with_default(akerr_ErrorContext *e, void *defval, void *dest, uint32_t defsize);
#endif // _JSON_HELPERS_H_
#endif // _AKGL_JSON_HELPERS_H_

View File

@@ -25,8 +25,8 @@
* falling one.
*/
#ifndef _PHYSICS_H_
#define _PHYSICS_H_
#ifndef _AKGL_PHYSICS_H_
#define _AKGL_PHYSICS_H_
#include <SDL3/SDL.h>
#include <akerror.h>
@@ -235,4 +235,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_factory(akgl_PhysicsBackend *sel
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *self, akgl_Iterator *opflags);
#endif // _PHYSICS_H_
#endif // _AKGL_PHYSICS_H_

View File

@@ -21,14 +21,15 @@
* 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
* 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 _REGISTRY_H_
#define _REGISTRY_H_
#ifndef _AKGL_REGISTRY_H_
#define _AKGL_REGISTRY_H_
#include <SDL3/SDL.h>
#include <akgl/error.h>
#include <akgl/staticstring.h>
@@ -124,7 +125,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_character();
* 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
* 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.
@@ -138,7 +139,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_properties();
*
* 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`.
* 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.
@@ -215,4 +216,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_set_property(char *name, char *value);
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_property(char *name, akgl_String **dest, char *def);
#endif //_REGISTRY_H_
#endif //_AKGL_REGISTRY_H_

View File

@@ -2,27 +2,27 @@
* @file renderer.h
* @brief The pluggable rendering backend: a record of function pointers plus an initializer.
*
* There is one backend shipped, the 2D SDL one, and `akgl_render_init2d` is its
* There is one backend shipped, the 2D SDL one, and `akgl_render_2d_init` is its
* initializer -- it creates the window and the `SDL_Renderer` and then fills in
* the six `akgl_render_2d_*` entry points. A different renderer is a different
* initializer populating the same struct, not a branch inside these functions.
*
* Those are two separable jobs, and a host that already owns an `SDL_Renderer`
* -- an embedded interpreter, which must not create the window -- wants only the
* second. akgl_render_bind2d() is that half on its own; akgl_render_init2d()
* second. akgl_render_2d_bind() is that half on its own; akgl_render_2d_init()
* makes the window and then calls it.
*
* Callers do not normally name the `akgl_render_2d_*` functions directly; they
* go through the pointers on the backend (`renderer->frame_start(renderer)`),
* which is what makes the swap possible. The global `renderer` in game.h is the
* which is what makes the swap possible. The global `akgl_renderer` in game.h is the
* instance the rest of the library draws through.
*
* A frame is `frame_start` (clear), any number of `draw_*` calls, then
* `frame_end` (present).
*/
#ifndef _RENDERER_H_
#define _RENDERER_H_
#ifndef _AKGL_RENDERER_H_
#define _AKGL_RENDERER_H_
#include <SDL3/SDL.h>
@@ -61,7 +61,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_shutdown(akgl_RenderBackend *s
* it is checked -- a `NULL` @p self is a crash, not an error.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If `self->sdl_renderer` is `NULL`, which means the
* backend was never run through akgl_render_init2d.
* backend was never run through akgl_render_2d_init.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_start(akgl_RenderBackend *self);
/**
@@ -70,7 +70,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_start(akgl_RenderBackend
* it is checked -- a `NULL` @p self is a crash, not an error.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If `self->sdl_renderer` is `NULL`, which means the
* backend was never run through akgl_render_init2d.
* backend was never run through akgl_render_2d_init.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_end(akgl_RenderBackend *self);
/**
@@ -116,12 +116,12 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_mesh(akgl_RenderBackend *
* @brief Draw the whole scene: every tilemap layer, and the actors standing on it.
*
* Walks layers from 0 to #AKGL_TILEMAP_MAX_LAYERS. For each, it draws that layer
* of the global `gamemap` through `camera` (if the map has that many layers),
* of the global `akgl_gamemap` through `akgl_camera` (if the map has that many layers),
* then sweeps the actor pool and calls `renderfunc` on every live actor whose
* `layer` matches. Sweeping in layer order is what puts actors in front of the
* scenery they stand on and behind the scenery they walk under.
*
* It reads the globals `gamemap`, `camera`, and `HEAP_ACTOR` directly rather
* It reads the globals `akgl_gamemap`, `akgl_camera`, and `akgl_heap_actors` directly rather
* than taking them as arguments, so there is exactly one world to draw.
*
* @param self The backend to draw through. Required.
@@ -137,7 +137,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_mesh(akgl_RenderBackend *
* @throws AKERR_* Whatever akgl_tilemap_draw or an actor's own `renderfunc`
* raises; the first failure aborts the frame and propagates unchanged.
*
* @warning Neither the global `gamemap` nor a live actor's `renderfunc` is
* @warning Neither the global `akgl_gamemap` nor a live actor's `renderfunc` is
* checked before use, so drawing before akgl_tilemap_load, or with a
* hand-built actor that was never run through akgl_actor_initialize,
* is a crash rather than an error context.
@@ -147,7 +147,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend
/**
* @brief Install the 2D backend's methods on a backend the caller owns.
*
* The vtable half of akgl_render_init2d(), with no window, no renderer, and no
* The vtable half of akgl_render_2d_init(), with no window, no renderer, and no
* property registry involved: it points @p self's six function pointers at the
* `akgl_render_2d_*` entry points and returns. `sdl_renderer` is not touched,
* so a caller who has already put its own `SDL_Renderer` there keeps it, and a
@@ -156,7 +156,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend
*
* This is the entry point for a host that owns its own window -- it can drive
* akgl_actor_render(), akgl_tilemap_draw() and the rest through a backend
* libakgl never created. akgl_render_init2d() is the same thing with a window
* libakgl never created. akgl_render_2d_init() is the same thing with a window
* in front of it.
*
* @param self The backend to bind. Required. Only the method pointers are
@@ -164,15 +164,15 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p self is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_bind2d(akgl_RenderBackend *self);
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_bind(akgl_RenderBackend *self);
/**
* @brief Create the window and SDL renderer, and bind the 2D backend's methods.
*
* Reads `game.screenwidth` and `game.screenheight` from the property registry
* Reads `akgl_game.screenwidth` and `akgl_game.screenheight` from the property registry
* (both defaulting to the string "0", which asks SDL for a zero-sized window),
* creates the window and renderer with `game.uri` as the title, points `camera`
* at the full screen rectangle, and then calls akgl_render_bind2d() to install
* creates the window and renderer with `akgl_game.uri` as the title, points `akgl_camera`
* at the full screen rectangle, and then calls akgl_render_2d_bind() to install
* the six `akgl_render_2d_*` function pointers on @p self.
*
* Because the dimensions come from the registry, akgl_registry_init_properties
@@ -184,7 +184,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_bind2d(akgl_RenderBackend *self);
* pointers are overwritten; anything it held before is not released.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p self is `NULL`.
* @throws AKERR_VALUE If `game.screenwidth` or `game.screenheight` is set to
* @throws AKERR_VALUE If `akgl_game.screenwidth` or `akgl_game.screenheight` is set to
* something that is not a base-10 integer.
* @throws ERANGE If either dimension does not fit in an `int`.
* @throws AKGL_ERR_SDL If the window and renderer cannot be created. The message
@@ -195,6 +195,6 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_bind2d(akgl_RenderBackend *self);
* @note The two pooled strings holding the dimensions are only released on the
* success path, so each failed initialization leaks two string slots.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_init2d(akgl_RenderBackend *self);
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_init(akgl_RenderBackend *self);
#endif // _RENDERER_H_
#endif // _AKGL_RENDERER_H_

View File

@@ -12,7 +12,7 @@
* both publish themselves in a registry under their name, which is how
* akgl_character_sprite_add and the tilemap loader find them. Because loading a
* sheet uploads a texture, the renderer has to exist first -- so
* akgl_render_init2d, or akgl_game_init, before any of this.
* akgl_render_2d_init, or akgl_game_init, before any of this.
*/
#ifndef _AKGL_SPRITE_H_
@@ -47,7 +47,7 @@ typedef struct {
uint32_t frames; /**< How many entries of frameids are in use. */
uint32_t width; /**< Frame width in pixels; also the horizontal stride used to find a frame on the sheet. */
uint32_t height; /**< Frame height in pixels; the vertical stride once frames wrap to the next row. */
uint32_t speed; /**< Nanoseconds one frame is held. Read from JSON in milliseconds and scaled by #AKGL_TIME_ONESEC_MS, which despite its name is nanoseconds-per-millisecond. TODO.md item 6. */
uint32_t speed; /**< Nanoseconds one frame is held. Read from JSON in milliseconds and scaled by #AKGL_TIME_ONEMS_NS. */
bool loop; /**< Restart from frame 0 when the last frame is reached, instead of holding it. */
bool loopReverse; /**< Play back down to frame 0 instead of jumping to it -- a ping-pong loop. */
char name[AKGL_SPRITE_MAX_NAME_LENGTH]; /**< Registry key, from the JSON `name` field. */
@@ -82,7 +82,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_sprite_initialize(akgl_Sprite *spr, char
/**
* @brief Load an image file as a texture and publish it as a spritesheet.
*
* Uploads @p filename through SDL_image using the global `renderer`, so the
* Uploads @p filename through SDL_image using the global `akgl_renderer`, so the
* renderer must already be initialized. The sheet is registered in
* #AKGL_REGISTRY_SPRITESHEET under @p filename, which is the key
* akgl_sprite_load_json looks it up by to avoid loading the same image twice.
@@ -166,6 +166,6 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_sprite_load_json(char *filename);
* `NULL`. Note that `sheet->texture` is dereferenced without a check,
* so a registered-but-unloaded sheet is a crash rather than an error.
*/
akerr_ErrorContext *akgl_sprite_sheet_coords_for_frame(akgl_Sprite *self, SDL_FRect *srccoords, uint8_t frameid);
akerr_ErrorContext *akgl_spritesheet_coords_for_frame(akgl_Sprite *self, SDL_FRect *srccoords, uint8_t frameid);
#endif //_AKGL_SPRITE_H_

View File

@@ -8,10 +8,10 @@
* these functions truncate rather than grow, and truncation is silent.
*/
#ifndef _STRING_H_
#define _STRING_H_
#ifndef _AKGL_STATICSTRING_H_
#define _AKGL_STATICSTRING_H_
#include "string.h"
#include <string.h>
#include <akerror.h>
#include <limits.h>
@@ -68,4 +68,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_string_initialize(akgl_String *obj, char
* this path is unreachable rather than merely rare.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_string_copy(akgl_String *src, akgl_String *dst, int count);
#endif //_STRING_H_
#endif //_AKGL_STATICSTRING_H_

View File

@@ -13,8 +13,8 @@
* static text redrawn every frame.
*/
#ifndef _TEXT_H_
#define _TEXT_H_
#ifndef _AKGL_TEXT_H_
#define _AKGL_TEXT_H_
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
@@ -73,7 +73,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_unloadfont(char *name);
* @brief Rasterize a string and blit it at a screen position, in one call.
*
* Renders blended (anti-aliased, alpha-blended) through SDL_ttf, uploads the
* result to a texture, draws it through the global `renderer`, and destroys both
* result to a texture, draws it through the global `akgl_renderer`, and destroys both
* the texture and the surface before returning. The text is drawn at its natural
* size -- @p x and @p y are the top-left corner, not a centre.
*
@@ -91,9 +91,9 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_unloadfont(char *name);
* @param y Top edge of the text, in screen pixels.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p font or @p text is `NULL`; if the global
* `renderer`, its `sdl_renderer`, or its `draw_texture` is `NULL` --
* `akgl_renderer`, its `sdl_renderer`, or its `draw_texture` is `NULL` --
* that last one is the state a backend is in between being allocated
* and being run through akgl_render_bind2d(); if SDL_ttf cannot
* and being run through akgl_render_2d_bind(); if SDL_ttf cannot
* rasterize the string; or if the surface cannot be uploaded as a
* texture. The last two carry `SDL_GetError()` and are a reused status
* rather than a pointer problem.
@@ -155,4 +155,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure(TTF_Font *font, char *text,
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure_wrapped(TTF_Font *font, char *text, int wraplength, int *w, int *h);
#endif // _TEXT_H_
#endif // _AKGL_TEXT_H_

View File

@@ -27,12 +27,12 @@
*
* Note the size of akgl_Tilemap: a layer's tile grid alone is 512x512 ints, and
* a tileset's offset table is 65536 pairs. It is a megabytes-large object and
* belongs in static storage, which is where `_akgl_gamemap` puts it. Do not put
* belongs in static storage, which is where `akgl_default_gamemap` puts it. Do not put
* one on the stack.
*/
#ifndef _TILEMAP_H_
#define _TILEMAP_H_
#ifndef _AKGL_TILEMAP_H_
#define _AKGL_TILEMAP_H_
#include <limits.h>
#include <akgl/actor.h>
@@ -151,7 +151,7 @@ typedef struct {
akgl_TilemapLayer layers[AKGL_TILEMAP_MAX_LAYERS]; /**< The layers, in draw order: index 0 is furthest back. */
// Different levels may have different physics.
bool use_own_physics; /**< Set when the map declared a `physics.model` property. A caller that honours it simulates through `physics` instead of the global backend. */
bool use_own_physics; /**< Set when the map declared a `physics.model` property. A caller that honours it simulates through `akgl_physics` instead of the global backend. */
akgl_PhysicsBackend physics; /**< This map's own backend, valid only when `use_own_physics` is set. */
} akgl_Tilemap;
@@ -204,12 +204,12 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load(char *fname, akgl_Tilemap *
* tile grid. An image layer ignores the viewport entirely and is drawn once at
* the origin.
*
* Drawing goes through the global `renderer`, not through a backend passed in.
* Drawing goes through the global `akgl_renderer`, not through a backend passed in.
*
* @param dest The map to draw from. Required. Not an output parameter
* despite the name.
* @param viewport The rectangle of the map, in map pixels, that is on screen.
* Required. Usually the global `camera`.
* Required. Usually the global `akgl_camera`.
* @param layeridx Which layer to draw, 0 to `numlayers - 1`. **Not**
* bounds-checked: an index past the end reads a neighbouring
* layer, or past the array.
@@ -490,4 +490,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_release(akgl_Tilemap *dest);
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_scale_actor(akgl_Tilemap *map, akgl_Actor *actor);
#endif //_TILEMAP_H_
#endif //_AKGL_TILEMAP_H_

View File

@@ -12,18 +12,20 @@
* separating-axis test.
*/
#ifndef _UTIL_H_
#define _UTIL_H_
#ifndef _AKGL_UTIL_H_
#define _AKGL_UTIL_H_
#include <SDL3/SDL.h>
#include <akerror.h>
#include <stdbool.h>
#include <akgl/staticstring.h>
/** @brief An integer point. Carries a `z` the collision routines do not use. */
typedef struct point {
typedef struct akgl_Point {
int x; /**< Horizontal position, in whatever space the caller is working in. */
int y; /**< Vertical position. */
int z; /**< Depth. Never written by akgl_rectangle_points and never read by the collision tests. */
} point;
} akgl_Point;
/**
* @brief The four corners of an axis-aligned rectangle, precomputed.
@@ -32,12 +34,12 @@ typedef struct point {
* spans, so it wants the corners as points. akgl_rectangle_points derives one of
* these from an `SDL_FRect`.
*/
typedef struct RectanglePoints {
point topleft; /**< (x, y). */
point topright; /**< (x + w, y). */
point bottomleft; /**< (x, y + h). */
point bottomright; /**< (x + w, y + h). */
} RectanglePoints;
typedef struct akgl_RectanglePoints {
akgl_Point topleft; /**< (x, y). */
akgl_Point topright; /**< (x + w, y). */
akgl_Point bottomleft; /**< (x, y + h). */
akgl_Point bottomright; /**< (x + w, y + h). */
} akgl_RectanglePoints;
/**
* @brief Do not use. Three open parentheses, two closes -- any expansion is a
@@ -62,7 +64,7 @@ typedef struct RectanglePoints {
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p dest or @p rect is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_rectangle_points(RectanglePoints *dest, SDL_FRect *rect);
akerr_ErrorContext AKERR_NOIGNORE *akgl_rectangle_points(akgl_RectanglePoints *dest, SDL_FRect *rect);
/**
* @brief Test whether a point falls inside a rectangle, edges included.
*
@@ -78,7 +80,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_rectangle_points(RectanglePoints *dest,
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p p, @p r, or @p collide is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_collide_point_rectangle(point *p, RectanglePoints *r, bool *collide);
akerr_ErrorContext AKERR_NOIGNORE *akgl_collide_point_rectangle(akgl_Point *p, akgl_RectanglePoints *r, bool *collide);
/**
* @brief Test whether two rectangles overlap, edges included.
*
@@ -188,4 +190,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_compare_sdl_surfaces(SDL_Surface *s1, SD
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_and_compare(SDL_Texture *t1, SDL_Texture *t2, int x, int y, int w, int h, char *writeout);
#endif // _UTIL_H_
#endif // _AKGL_UTIL_H_