diff --git a/CMakeLists.txt b/CMakeLists.txt index 88970b8..3a81fba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -302,6 +302,20 @@ endforeach() add_test(NAME semver_unit COMMAND akgl_test_semver_unit) +# Not a C program, so it is not in AKGL_TEST_SUITES: it reads the built library's +# dynamic symbol table and every public header, and fails when a function with +# external linkage is declared nowhere. That is an ABI question rather than a +# behavioural one, and nineteen symbols had drifted out of the headers before +# anything was checking. +add_test( + NAME api_surface + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/scripts/check_api_surface.sh + $ + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_BINARY_DIR}/include +) +set_tests_properties(api_surface PROPERTIES SKIP_RETURN_CODE 2) + # One translation unit per public header, each including exactly that header and # nothing before it. This is the only shape that proves a header is # self-contained: a second #include in the same file proves nothing about the @@ -325,6 +339,9 @@ int akgl_header_selftest_${header}(void)\n\ list(APPEND AKGL_HEADER_SELFTEST_SOURCES "${generated}") endforeach() target_sources(akgl_test_headers PRIVATE ${AKGL_HEADER_SELFTEST_SOURCES}) +# Hand-written rather than generated: it checks a header's behaviour under a +# caller-supplied override, which is not something the generated shape covers. +target_sources(akgl_test_headers PRIVATE tests/header_pool_override.c) # TIMEOUT is generous because CTest applies the same property to `ctest -T # memcheck`, where every suite runs under valgrind at something like twenty diff --git a/TODO.md b/TODO.md index 7057c04..1fab2e0 100644 --- a/TODO.md +++ b/TODO.md @@ -68,100 +68,110 @@ with no test to notice. ### 2. Header/implementation surface drift -7. **Nineteen non-static functions are defined in `src/` but declared in no - header.** They have external linkage and public-looking names, so they are - part of the ABI whether intended or not, and no consumer can call them: +**Items 7, 8, 9, 11, 12, 13, 14 and 15 are resolved in 0.5.0.** Item 10 is +resolved for every pair it listed. - `akgl_game_load_objectnamemap`, `akgl_game_load_versioncmp`, - `akgl_game_save_actors`, `akgl_game_save_actorname_iterator`, - `akgl_game_save_charactername_iterator`, `akgl_game_save_spritename_iterator`, - `akgl_game_save_spritesheetname_iterator` (`src/game.c`); - `akgl_get_json_properties_double`, `akgl_get_json_properties_float`, - `akgl_get_json_properties_number`, `akgl_tilemap_load_layer_image`, - `akgl_tilemap_load_layer_object_actor`, `akgl_tilemap_load_physics` - (`src/tilemap.c`); `akgl_path_relative_from`, `akgl_path_relative_root` - (`src/util.c`); `gamepad_handle_added`, `gamepad_handle_button_down`, - `gamepad_handle_button_up`, `gamepad_handle_removed` (`src/controller.c`). +7. **Nineteen non-static functions were defined in `src/` but declared in no + header.** Each is now declared or `static`: - Each should be either declared in its header or made `static`. Note that - `tilemap.h` already has a "part of the internal API, exposed here for unit - testing" block — the tilemap entries belong there. + - `akgl_controller_handle_button_down`, `_button_up`, `_added`, `_removed` + were defined as `gamepad_handle_*` while `controller.h` declared the + `akgl_controller_handle_*` names it never defined. The definitions now + carry the declared names, which closes this and **Defects → Known and + still open item 10** in one change, and their documentation moved from the + definitions to the header where the convention puts it. `tests/controller.c` + no longer needs its local re-declarations. + - `akgl_game_save_actors` and `akgl_game_load_versioncmp` are declared in + `game.h` under a new "part of the internal API" block; `tests/game.c` + reaches them through the header now instead of declaring them itself. + - The four save-table iterators and `akgl_game_load_objectnamemap` are + `static` and have dropped the prefix -- `save_actorname_iterator`, + `load_objectnamemap` and so on. They are SDL enumeration callbacks and a + file-local reader; nothing outside `game.c` has any business calling them. + - `akgl_get_json_properties_number`, `_float`, `_double`, + `akgl_tilemap_load_layer_image`, `akgl_tilemap_load_layer_object_actor` and + `akgl_tilemap_load_physics` are declared in `tilemap.h`'s internal-API + block, again with their documentation moved to the header. That is where + the "does not need a renderer" work under **Remaining work** wanted them. + - `akgl_path_relative_root` is `static path_relative_root`. + `akgl_path_relative_from` is **deleted**; see below. -8. **`akgl_game_init_screen` is declared but never defined** - (`include/akgl/game.h:100`). Same failure mode as **Defects → Known and still - open #10**, which covers the four `akgl_controller_handle_*` declarations; - fold this one into that item. + **This is enforced now.** `scripts/check_api_surface.sh` reads the built + library's dynamic symbol table, strips comments out of every public header, + and fails when an exported `akgl_*` symbol is declared nowhere. It runs as + the `api_surface` CTest test. Stripping comments is the point: four of these + symbols were *mentioned* in `controller.h` prose, which is not the same as + being declared there and is exactly how they went unnoticed. -9. **Static helpers use three different naming styles.** `actor_visible` - (`src/actor.c:185`) is bare; `akgl_character_load_json_inner` and - `akgl_character_load_json_state_int_from_strings` (`src/character.c:101,132`) - and `akgl_sprite_load_json_spritesheet` (`src/sprite.c:51`) carry the full - public prefix; `gamepad_handle_*` (`src/controller.c:121+`) uses a third - subsystem word that appears nowhere else. Adopt one rule — the clearest is - that `static` helpers drop the `akgl_` prefix, since it exists to avoid - external collisions. +8. **`akgl_game_init_screen` was declared and never defined.** The declaration + is gone. Screen setup is `akgl_render_2d_init`. -10. **Parameter names disagree between declaration and definition.** Doxygen - documents the header spelling, so the generated docs describe names the - implementation does not use: +9. **Static helpers used three naming styles.** They drop the `akgl_` prefix + now, which is what it is for: `character_load_json_inner`, + `character_load_json_state_int_from_strings`, `sprite_load_json_spritesheet`, + alongside the `actor_visible`, `write_exact` and `write_name_field` that + already did. - | Header | Implementation | - |---|---| - | `character.h:41` `basechar` | `character.c:21` `obj` | - | `character.h:69` `props` | `character.c:75` `registry` | - | ~~`heap.h:121` `ptr`~~ | ~~`heap.c:143` `basechar`~~ — fixed in 0.5.0 | - | `registry.h:97` `value` | `registry.c:163` `src` | - | `json_helpers.h:134` `e` | `json_helpers.c:149` `err` | - | `tilemap.h:134,143` `dest` | `tilemap.c:646,767` `map` | +10. **Parameter names disagreed between declaration and definition.** All six + pairs agree now. `akgl_character_initialize` takes `obj`, + `akgl_character_state_sprites_iterate` takes `props`, `akgl_heap_release_character` + takes `ptr`, `akgl_set_property` takes `value`, and the two `akgl_tilemap_draw*` + functions take `map` -- the header called them `dest` and documented them as + "Output destination populated by the function", which they are not. - The `tilemap.h` pair is the most misleading: the parameter is the map being - *read* and drawn, but it is named `dest` and documented as "Output - destination populated by the function". + `akgl_get_json_with_default` was the interesting one: it took the incoming + context as `err` and named its *own* context `e`, which is the name the + convention reserves for an incoming one. It is `e` and `errctx` now, and + renaming it was what surfaced that the two had been swapped rather than + merely misspelled. -11. **Object-pool size macros are defined twice, and the override hook is - dead.** `heap.h:15-29` wraps `AKGL_MAX_HEAP_ACTOR`, `_SPRITE`, `_SPRITESHEET`, - `_CHARACTER`, and `_STRING` in `#ifndef` guards so a consumer can override - them, but `actor.h:65`, `sprite.h:19-20`, and `character.h:14` define the same - four unconditionally and are included from `heap.h:9-11`. Whichever header - lands first wins and the `#ifndef` never fires, so the override mechanism - cannot work. Define each pool size once — `heap.h` is the natural home. +11. **Object-pool size macros were defined twice and the override hook was + dead.** `AKGL_MAX_HEAP_ACTOR`, `_SPRITE`, `_SPRITESHEET` and `_CHARACTER` are + defined once, in `heap.h`, inside the `#ifndef` guards that were always + supposed to make them overridable. `actor.h`, `sprite.h` and `character.h` + no longer define them. -12. **Headers rely on their includers for types.** `iterator.h` used `uint32_t` - without ``; `json_helpers.h` used `json_t` without ``; - `util.h` used `SDL_FRect` and `bool` without any SDL include. Each compiled - only because of `.c`-file include ordering. + `tests/header_pool_override.c` is the regression test: it defines its own + ceilings, includes `heap.h`, and `#error`s if the guard did not fire or if + `AKGL_MAX_HEAP_SPRITE` stopped deriving from `AKGL_MAX_HEAP_ACTOR`. The + assertion is the compile -- it deliberately disagrees with the built + library's ceilings and never touches the pools, which is fine because + overriding a ceiling means the library and everything linking it have to be + rebuilt together anyway. + + +12. **Headers relied on their includers for types.** `iterator.h` used + `uint32_t` without ``; `json_helpers.h` used `json_t` without + ``; `util.h` used `SDL_FRect` and `bool` without any SDL include. + Each compiled only because of `.c`-file include ordering. **Resolved, and enforced rather than merely fixed.** `AKGL_PUBLIC_HEADERS` in `CMakeLists.txt` is now the single list behind both `install()` and a - generated translation unit per header — each including exactly that header - and nothing before it — linked into the `headers` suite. A header that ships - is a header that is checked, and adding one to that list is all a new header - needs. + generated translation unit per header -- each including exactly that header + and nothing before it -- linked into the `headers` suite. A header that + ships is a header that is checked. - Writing the check found a case this item had missed: `registry.h` uses + Writing that check found a case this item had missed: `registry.h` uses `SDL_PropertiesID` in eight declarations and included no SDL header at all. - That is the argument for generating the check off the install list instead - of hand-listing the headers thought to be at risk. + That is the argument for generating the check off the install list rather + than hand-listing the headers somebody thought were at risk. -13. **Include spelling is split between quoted and angled forms for the same - directory.** **Resolved.** Every in-project include in a public header uses - `#include `. `tests/*.c` still use `#include "testutil.h"`, - correctly: that one is a test-local header rather than an installed one. +13. **Include spelling was split between quoted and angled forms.** + **Resolved.** Every in-project include in a public header uses + `#include `, and `staticstring.h`'s `#include "string.h"` -- + a relative-first lookup that reached the system header by accident -- is + `#include `. `tests/*.c` still use `#include "testutil.h"`, + correctly: that one is test-local rather than installed. -14. **Empty parameter lists.** `akgl_heap_init()`, `akgl_heap_init_actor()`, - `akgl_registry_init*()`, `akgl_game_init()`, `akgl_game_init_screen()`, and - `akgl_game_updateFPS()` declare `()` rather than `(void)`, while - `akgl_controller_list_keyboards(void)`, `akgl_controller_open_gamepads(void)`, - `akgl_game_lowfps(void)`, and `akgl_game_state_lock(void)` use `(void)`. - Before C23 the two are not equivalent — `()` suppresses argument checking. - `akgl_heap_init_actor` is even declared `()` in `heap.h:57` and defined - `(void)` in `heap.c:48`. +14. **Empty parameter lists.** **Resolved.** `akgl_game_init`, + `akgl_game_update_fps`, `akgl_heap_init`, `akgl_heap_init_actor` and the + eight `akgl_registry_init*` functions declare and define `(void)`. Before + C23 `()` means "unspecified arguments" and suppresses argument checking, so + these were the entry points a caller could pass anything to. -15. **`AKERR_NOIGNORE` is applied inconsistently at definition sites.** Headers - use it uniformly (except `akgl_sprite_sheet_coords_for_frame`, `sprite.h:86`, - which omits it). Definitions are split even within one file: `registry.c:55,120,163,172` - repeat it, `registry.c:27,44,63,71,79,96,104,112` do not. Since the attribute - is already on the declaration, drop it from all definitions. +15. **`AKERR_NOIGNORE` was applied inconsistently at definition sites.** + **Resolved.** It is on the declarations, where it does its work, and on no + definition in `src/`. ### 3. Error-handling pattern @@ -359,13 +369,14 @@ with no test to notice. NULL filepath reports `AKERR_NULLPOINTER`. 40. **`akgl_path_relative` and `akgl_path_relative_from` disagree on the output - parameter.** `akgl_path_relative` and `akgl_path_relative_root` take - `akgl_String *dst`; `akgl_path_relative_from` takes `akgl_String **dst` - (`src/util.c:105`). The `**` form matches the rest of the library - (`akgl_get_json_string_value`, `akgl_get_property`, `akgl_heap_next_string`), - which allocate when `*dest` is NULL. Related: **Defects → Known and still - open #4**, which covers the fact that `akgl_path_relative_from` never writes - `*dst` at all. + parameter.** **Moot in 0.5.0**: `akgl_path_relative_from` is deleted (see + **Defects → Known and still open** item 4) and `akgl_path_relative_root` is + `static`, so `akgl_path_relative` is the only member of the family left and + there is nothing left to disagree with. It still takes `akgl_String *dst` + rather than the `**` form the rest of the library uses, which stays as + **item 41**'s question of naming and this one's question of shape -- but + with one function it is a decision to make when something needs it, not a + drift between siblings. 41. **`dst` vs `dest` for output parameters.** `akgl_string_copy` and the `akgl_path_relative*` family use `dst`; everything else uses `dest`. @@ -659,9 +670,13 @@ Each was found by a test written to assert correct behavior. `akgl_physics_init_arcade` and `akgl_render_init2d` silently ignore configuration. `akgl_game_init` does call it; a caller that does not use `akgl_game_init` does not get it. -4. **`akgl_path_relative_from` is a stub that leaks.** `src/util.c:105` claims a - heap string, never writes `*dst`, and never releases it. 256 calls exhaust - the string pool. +4. **`akgl_path_relative_from` is a stub that leaks.** **Deleted in 0.5.0.** + It claimed a heap string, never wrote `*dst`, and never released it, so 256 + calls exhausted the string pool. It was declared in no header, called from + nowhere, and duplicated what `akgl_path_relative` already does. Fixing an + unfinished function nobody can reach is worse than deleting it; this also + closes item 40, which was about the output-parameter shape it disagreed with + the rest of the family on. 5. **`akgl_compare_sdl_surfaces` memcmps without checking geometry.** `src/util.c:208` compares `s1->pitch * s1->h` bytes of `s2` without verifying the surfaces share dimensions, pitch, or format. @@ -682,10 +697,10 @@ Each was found by a test written to assert correct behavior. decide whether to make them symmetric or document the split. 9. **`tests/util.c` defines `test_akgl_collide_point_rectangle_logic` but `main()` never calls it.** -10. **`controller.h` declares functions that do not exist.** It declares - `akgl_controller_handle_button_down`, `_button_up`, `_added`, and `_removed`, - but `src/controller.c` defines them as `gamepad_handle_*`. Anything compiled - against the header alone fails to link. +10. **`controller.h` declares functions that do not exist.** **Fixed in 0.5.0**; + the definitions carry the declared `akgl_controller_handle_*` names now. See + internal-consistency item 7, and `scripts/check_api_surface.sh`, which is + what stops this class of drift coming back. 11. **`akgl_controller_pushmap` and `akgl_controller_default` accept negative map ids.** Both check `controlmapid >= AKGL_MAX_CONTROL_MAPS` but not `controlmapid < 0`, so a negative id indexes before `GAME_ControlMaps`. diff --git a/include/akgl/actor.h b/include/akgl/actor.h index f0289ad..7cbd935 100644 --- a/include/akgl/actor.h +++ b/include/akgl/actor.h @@ -109,8 +109,6 @@ extern char *AKGL_ACTOR_STATE_STRING_NAMES[AKGL_ACTOR_MAX_STATES+1]; /** @brief Children one actor can carry. A child moves with its parent rather than simulating. */ #define AKGL_ACTOR_MAX_CHILDREN 8 -/** @brief Actors in the pool. Override before including heap.h to change it; see heap.h. */ -#define AKGL_MAX_HEAP_ACTOR 64 /** @brief Represents a live actor, including state, motion, hierarchy, and behavior callbacks. */ typedef struct akgl_Actor { diff --git a/include/akgl/character.h b/include/akgl/character.h index adea421..65b6884 100644 --- a/include/akgl/character.h +++ b/include/akgl/character.h @@ -20,7 +20,6 @@ #include #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 { @@ -62,7 +61,7 @@ typedef struct akgl_Character { * #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); +akerr_ErrorContext AKERR_NOIGNORE *akgl_character_initialize(akgl_Character *obj, char *name); /** * @brief Bind a sprite to one exact combination of actor-state bits. * diff --git a/include/akgl/controller.h b/include/akgl/controller.h index 8d16982..7d9b1b0 100644 --- a/include/akgl/controller.h +++ b/include/akgl/controller.h @@ -138,49 +138,77 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_list_keyboards(void); akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_event(void *appstate, SDL_Event *event); /** - * @brief Declared but not defined under this name. Do not call. + * @brief Set the movement and facing bits on the "player" actor for a D-pad or arrow press. * - * The implementation exists as `gamepad_handle_button_down` in `src/controller.c` - * and is `static`-in-spirit -- it is not declared anywhere -- so a caller that - * uses this declaration compiles and then fails to link. TODO.md, "Known and - * still open" item 10. + * A whole-application shortcut rather than a control-map binding: it looks the + * actor up by the literal name `"player"` in #AKGL_REGISTRY_ACTOR instead of + * being told which actor to act on, so it only ever drives one. The + * akgl_actor_cmhf_* handlers are the general form. * - * @param appstate Application state supplied by SDL. - * @param event SDL input event to process. - * @return Nothing; it cannot be called. + * Facing follows movement unless the actor sets `movement_controls_face`, which + * is how an actor that aims independently of the direction it walks opts out. + * + * @param appstate Passed through from SDL. Required as a non-`NULL` token only; + * never read. + * @param event The button or key event. Required. Both the gamepad and + * keyboard unions are read on every call, so the arm that does + * not correspond to the event type is read as garbage -- which + * works only because no real button and keycode pair collides. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`, or if there is + * no actor registered under the name "player". */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_button_down(void *appstate, SDL_Event *event); /** - * @brief Declared but not defined under this name. Do not call. + * @brief Clear the movement bit on the "player" actor for a D-pad or arrow release, and reset its animation. * - * See akgl_controller_handle_button_down. The implementation is - * `gamepad_handle_button_up`. + * The counterpart to akgl_controller_handle_button_down. It clears only the movement + * bit, leaving the facing bits alone so the actor keeps looking the way it was + * walking, and rewinds `curSpriteFrameId` to 0 so the next step starts from the + * first frame of the walk cycle rather than wherever it stopped. * - * @param appstate Application state supplied by SDL. - * @param event SDL input event to process. - * @return Nothing; it cannot be called. + * @param appstate Passed through from SDL. Required as a non-`NULL` token only. + * @param event The button or key release event. Required. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`, or if there is + * no actor registered under the name "player". */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_button_up(void *appstate, SDL_Event *event); /** - * @brief Declared but not defined under this name. Do not call. + * @brief Open a gamepad that has just been plugged in, and log its mapping. * - * See akgl_controller_handle_button_down. The implementation is - * `gamepad_handle_added`, which opens a newly plugged-in gamepad. + * SDL delivers no button events from an unopened gamepad, so a device that + * appears mid-session has to be opened here the way akgl_controller_open_gamepads + * opens the ones present at startup. A gamepad SDL has already opened is logged + * and left alone. * - * @param appstate Application state supplied by SDL. - * @param event SDL input event to process. - * @return Nothing; it cannot be called. + * The mapping is logged because a controller with no entry in the database + * produces no button events at all, and that is otherwise indistinguishable + * from a broken binding. + * + * @param appstate Passed through from SDL. Required as a non-`NULL` token only. + * @param event The SDL_EVENT_GAMEPAD_ADDED event. Required. The joystick id + * is read out of the `gbutton` arm rather than `gdevice`, which + * works because the two share their `which` field. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`. + * + * @note A failed `SDL_OpenGamepad` is logged rather than reported: this returns + * success and the device stays silent. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_added(void *appstate, SDL_Event *event); /** - * @brief Declared but not defined under this name. Do not call. + * @brief Close a gamepad that has been unplugged. * - * See akgl_controller_handle_button_down. The implementation is - * `gamepad_handle_removed`, which closes an unplugged gamepad. + * An unplugged device SDL still has open is a leaked handle, so this closes it. + * A removal for a device that was never opened is logged and otherwise ignored. + * Any control map still holding that `jsid` simply stops matching -- the map is + * not torn down. * - * @param appstate Application state supplied by SDL. - * @param event SDL input event to process. - * @return Nothing; it cannot be called. + * @param appstate Passed through from SDL. Required as a non-`NULL` token only. + * @param event The SDL_EVENT_GAMEPAD_REMOVED event. Required. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_removed(void *appstate, SDL_Event *event); diff --git a/include/akgl/game.h b/include/akgl/game.h index cab29e1..da5c059 100644 --- a/include/akgl/game.h +++ b/include/akgl/game.h @@ -177,17 +177,7 @@ extern SDL_FRect *akgl_camera; * @note It takes the state lock on the way in and releases it on the way out, so * a failure part-way through leaves the mutex held. */ -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_2d_init. - * Tracked in TODO.md under header/implementation surface drift. - * - * @return Nothing; it cannot be called. - */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init_screen(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init(void); /** * @brief Count this frame, and recompute the frame rate once a second has passed. * @@ -198,7 +188,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init_screen(); * the low-FPS threshold and so fires `lowfpsfunc` on every frame until the first * second is up. */ -void akgl_game_update_fps(); +void akgl_game_update_fps(void); /** * @brief Write the game state and the name-to-pointer tables to a save file. * @@ -311,4 +301,51 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void); */ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags); +/* + * These functions are part of the internal API and should not be called by the + * user. They are only exposed here for unit testing. + */ + +/** + * @brief Write all four name-to-address tables, each with its terminator. + * + * Actors, sprites, spritesheets, characters, in that order -- which is the order + * akgl_game_load reads them back in. Each table is the registry enumerated one + * entry at a time, followed by a zeroed name field and a zeroed pointer that the + * reader stops on. + * + * @param fp The open save-game stream. Required. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER If @p fp is `NULL`. + * @throws AKERR_IO On a stream error or a short write while emitting a + * terminator. + * + * @note Only the terminators are written through the error-reporting path. The + * entries themselves go through `SDL_EnumerateProperties`, whose callbacks + * cannot report failure upward and instead terminate the process -- so a + * write error mid-table never reaches this function's return value. + */ +akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp); +/** + * @brief Refuse a save file unless its version matches the running one exactly. + * + * Both strings are parsed as semver and compared with `"="` -- exact equality, + * not compatibility. That is deliberate and strict: a save file is a raw memory + * image of `akgl_Game` and the object tables, so any change to a struct layout + * invalidates it, and semver has no way to say "the layout did not move". + * + * @param versiontype What is being compared -- `"library"` or `"game"`. Used + * only to build the message, but required, since a bare + * "incompatible version" error would not say which. + * @param newversion The version read out of the save file. Required. + * @param curversion The version of the running program or library. Required. + * @return `NULL` when the two match, otherwise an error context owned by the + * caller. + * @throws AKERR_NULLPOINTER If any of the three arguments is `NULL`. + * @throws AKERR_VALUE If either string is not valid semver. The message says + * which side it came from. + * @throws AKERR_API If both parse but are not equal. The message quotes both. + */ +akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion); + #endif //_AKGL_GAME_H_ diff --git a/include/akgl/heap.h b/include/akgl/heap.h index ad055ae..fd92a42 100644 --- a/include/akgl/heap.h +++ b/include/akgl/heap.h @@ -74,7 +74,7 @@ extern akgl_String akgl_heap_strings[AKGL_MAX_HEAP_STRING]; * -- but check it anyway; the signature exists so a layer that needs * real setup has somewhere to report from. */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_init(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_init(void); /** * @brief Zero the actor pool only. * @@ -85,7 +85,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_init(); * * @return `NULL`. No failure path today; see akgl_heap_init. */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_init_actor(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_init_actor(void); /** * @brief Claim a free actor slot. * @param dest Receives a pointer to the free slot. Required, and **not** diff --git a/include/akgl/registry.h b/include/akgl/registry.h index a1c3135..c81ae92 100644 --- a/include/akgl/registry.h +++ b/include/akgl/registry.h @@ -64,7 +64,7 @@ extern SDL_PropertiesID AKGL_REGISTRY_PROPERTIES; * @warning This does not create #AKGL_REGISTRY_PROPERTIES -- see the warning on * this file. */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init(void); /** * @brief Create the music registry. * @return `NULL` on success, otherwise an error context owned by the caller. @@ -72,7 +72,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init(); * @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(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_music(void); /** * @brief Create the font registry. * @return `NULL` on success, otherwise an error context owned by the caller. @@ -80,7 +80,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_music(); * @note Overwrites the existing id without destroying the old set. See * akgl_registry_init_music. */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_font(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_font(void); /** * @brief Create the actor registry, destroying any previous one first. * @@ -94,7 +94,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_font(); * @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(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_actor(void); /** * @brief Create the sprite registry. * @return `NULL` on success, otherwise an error context owned by the caller. @@ -102,7 +102,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_actor(); * @note Overwrites the existing id without destroying the old set. See * akgl_registry_init_music. */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_sprite(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_sprite(void); /** * @brief Create the spritesheet registry. * @return `NULL` on success, otherwise an error context owned by the caller. @@ -110,7 +110,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_sprite(); * @note Overwrites the existing id without destroying the old set. See * akgl_registry_init_music. */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_spritesheet(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_spritesheet(void); /** * @brief Create the character registry. * @return `NULL` on success, otherwise an error context owned by the caller. @@ -118,7 +118,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_spritesheet(); * @note Overwrites the existing id without destroying the old set. See * akgl_registry_init_music. */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_character(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_character(void); /** * @brief Create the configuration-property registry. * @@ -133,7 +133,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_character(); * @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(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_properties(void); /** * @brief Load a JSON configuration file into the property registry. * @@ -172,7 +172,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_load_properties(char *fname); * 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(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_actor_state_strings(void); /** * @brief Set one configuration property. * diff --git a/include/akgl/sprite.h b/include/akgl/sprite.h index 1499364..fa6f3df 100644 --- a/include/akgl/sprite.h +++ b/include/akgl/sprite.h @@ -28,8 +28,6 @@ #define AKGL_SPRITE_MAX_REGISTRY_SIZE 1024 #define AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH 512 -#define AKGL_MAX_HEAP_SPRITE (AKGL_MAX_HEAP_ACTOR * 16) -#define AKGL_MAX_HEAP_SPRITESHEET AKGL_MAX_HEAP_SPRITE /** @brief Stores a loaded spritesheet texture and frame geometry. */ typedef struct { diff --git a/include/akgl/tilemap.h b/include/akgl/tilemap.h index 1e911b9..8f1cb8d 100644 --- a/include/akgl/tilemap.h +++ b/include/akgl/tilemap.h @@ -206,19 +206,18 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load(char *fname, akgl_Tilemap * * * 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 map The map to draw from. Required. * @param viewport The rectangle of the map, in map pixels, that is on screen. * 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. * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_NULLPOINTER If @p dest or @p viewport is `NULL`. + * @throws AKERR_NULLPOINTER If @p map or @p viewport is `NULL`. * @throws AKERR_* Whatever the renderer's `draw_texture` raises. The first * failure abandons the rest of the layer. */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw(akgl_Tilemap *dest, SDL_FRect *viewport, int layeridx); +akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw(akgl_Tilemap *map, SDL_FRect *viewport, int layeridx); /** * @brief Draw a whole tileset to the screen, tile by tile, from its offset table. * @@ -227,11 +226,10 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw(akgl_Tilemap *dest, SDL_FRe * matches the source image the offset table is right -- and if tiles are shifted * or duplicated, it is not. The `charviewer` utility uses it. * - * @param dest The map holding the tileset. Required. Not an output - * parameter despite the name. + * @param map The map holding the tileset. Required. * @param tilesetidx Which tileset to draw, 0 to `numtilesets - 1`. * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_NULLPOINTER If @p dest is `NULL`. + * @throws AKERR_NULLPOINTER If @p map is `NULL`. * @throws AKERR_OUTOFBOUNDS If @p tilesetidx is at or above `numtilesets`. A * negative index is not rejected. * @throws AKERR_* Whatever the renderer's `draw_texture` raises. @@ -240,7 +238,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw(akgl_Tilemap *dest, SDL_FRe * tileset's, so a tileset whose tiles are a different size from the map's * is reconstructed at the wrong pitch. */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw_tileset(akgl_Tilemap *dest, int tilesetidx); +akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw_tileset(akgl_Tilemap *map, int tilesetidx); /* * These functions are part of the internal API and should not be called by the user. @@ -490,4 +488,152 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_release(akgl_Tilemap *dest); */ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_scale_actor(akgl_Tilemap *map, akgl_Actor *actor); +/** + * @brief Read a Tiled custom property declared as `number`. + * + * Tiled writes `float` for a floating-point property, so this matches nothing + * Tiled produces today -- akgl_get_json_properties_float is the one the loader + * uses. Kept because a hand-written or older map may still say `number`. + * + * @param obj The Tiled object whose `properties` array to search. Required. + * @param key The property name. Required. + * @param dest Receives the value as a `float`. Not written on any failure path. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`. + * @throws AKERR_KEY If the property is absent, or @p obj has no properties. + * @throws AKERR_TYPE If the property is not declared `number`, or its `value` is + * not numeric. + * @throws AKGL_ERR_HEAP If the string pool is exhausted. + */ +akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_properties_number(json_t *obj, char *key, float *dest); +/** + * @brief Read a Tiled custom property declared as `float`. + * + * The spelling Tiled actually writes for a floating-point property. This is how + * the `scale` on a perspective marker is read. + * + * @param obj The Tiled object whose `properties` array to search. Required. + * @param key The property name. Required. + * @param dest Receives the value. Not written on any failure path. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`. + * @throws AKERR_KEY If the property is absent, or @p obj has no properties. + * @throws AKERR_TYPE If the property is not declared `float`, or its `value` is + * not numeric. + * @throws AKGL_ERR_HEAP If the string pool is exhausted. + */ +akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_properties_float(json_t *obj, char *key, float *dest); +/** + * @brief Read a Tiled custom property declared as `float`, at full precision. + * + * Same declared type as akgl_get_json_properties_float, but writing a `double`. + * The map's physics constants are `double`, which is the reason both exist. + * + * @param obj The Tiled object whose `properties` array to search. Required. + * @param key The property name. Required. + * @param dest Receives the value. Not written on any failure path. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`. + * @throws AKERR_KEY If the property is absent, or @p obj has no properties. + * akgl_tilemap_load_physics passes this status to + * akgl_get_json_with_default, which turns it into a default of 0.0. + * @throws AKERR_TYPE If the property is not declared `float`, or its `value` is + * not numeric. + * @throws AKGL_ERR_HEAP If the string pool is exhausted. + */ +akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_properties_double(json_t *obj, char *key, double *dest); +/** + * @brief Turn one Tiled object into a live actor, creating it if it is not already registered. + * + * The object's name is the actor's registry key, which is what lets the same + * actor be placed by more than one object: the first placement creates it and + * binds the character named in the object's `character` property, and every + * later one just takes another reference. Either way the object's position, + * visibility, and layer are copied onto the actor and the object keeps a + * pointer to it. + * + * Not declared in tilemap.h -- reachable only through + * akgl_tilemap_load_layer_objects. + * + * @param curobj The tilemap object, with its `name`, `x`, `y`, and + * `visible` already read from JSON. Required, unchecked, + * dereferenced at once. + * @param layerdatavalue The object's JSON, for the custom properties -- + * `character` and `state` -- that are not part of Tiled's + * own object fields. Required. + * @param layerid The layer this object sits on, copied onto the actor. + * @param dirname Directory to resolve paths against. Accepted for + * signature consistency; nothing here uses it. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_KEY If the object's name is empty -- an actor has to be + * addressable -- if the `character` or `state` property is absent, or if + * the named character is not in #AKGL_REGISTRY_CHARACTER. + * @throws AKERR_TYPE If `character` is not declared `string` or `state` not + * declared `int`. + * @throws AKERR_NULLPOINTER If akgl_actor_initialize refuses the name. + * @throws AKGL_ERR_HEAP If the actor or string pool is exhausted. + * + * @note An existing actor's `character` and position are overwritten by each + * later placement, so two objects naming one actor leave it wherever the + * last one put it rather than producing two. + */ +akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layer_object_actor(akgl_TilemapObject *curobj, json_t *layerdatavalue, int layerid, akgl_String *dirname); +/** + * @brief Load one image layer: upload its image as a single texture. + * + * An image layer is one picture rather than a grid, which is what a parallax + * backdrop wants. The layer's `width` and `height` are taken from the loaded + * texture rather than from the JSON, so they are always the true pixel size. + * + * Not declared in tilemap.h -- reachable only through akgl_tilemap_load_layers. + * + * @param dest The map to load into. Required. + * @param root The layer's JSON object. Required. + * @param layerid Which layer slot to fill. Not bounds-checked. + * @param dirname Directory to resolve the layer's `image` path against. + * Required. Joined with a `/` rather than run through + * akgl_path_relative, so unlike a tileset image this path is not + * canonicalized and an absolute one will not work. + * @return `NULL` on success, otherwise an error context owned by the caller. + * @throws AKERR_NULLPOINTER If @p dest, @p root, or @p dirname is `NULL`. + * @throws AKERR_KEY If the layer has no `image`. + * @throws AKERR_TYPE If `image` is not a string. + * @throws AKGL_ERR_SDL If the image cannot be loaded. The message carries + * `SDL_GetError()`. + * @throws AKGL_ERR_HEAP If the string pool is exhausted. + * + * @note The joined path is truncated at + * #AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE without complaint, so an + * over-long path fails as a missing file rather than as a length error. + */ +akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname); +/** + * @brief Give the map its own physics backend, if it asked for one. + * + * A map with a `physics.model` custom property gets its own backend, configured + * from `physics.gravity.x`/`.y`/`.z` and `physics.drag.x`/`.y`/`.z`, and + * `use_own_physics` is set so a caller knows to simulate through it. Each + * constant defaults to 0.0 when absent, so a map can name a model and override + * only what it cares about. + * + * Absence is the normal case at every level: a map with no properties at all, or + * with properties but no `physics.model`, uses the game's global backend and + * this returns success. That is why the AKERR_KEY handlers here are not error + * paths -- they are the "map did not ask" path. + * + * Not declared in tilemap.h -- reachable only through akgl_tilemap_load. + * + * @param dest The map to configure. Required in practice; not checked here, + * since akgl_tilemap_load has already done so. + * @param root The map's root JSON object. Required, likewise. + * @return `NULL` on success -- including when the map declared no physics at + * all -- otherwise an error context owned by the caller. + * @throws AKERR_KEY If `physics.model` names a backend that does not exist. + * @throws AKERR_TYPE If `physics.model` is not declared `string`, or one of the + * six constants is not declared `float`. + * @throws AKERR_NULLPOINTER If akgl_physics_factory refuses its arguments. + * @throws AKGL_ERR_HEAP If the string pool is exhausted. + */ +akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_physics(akgl_Tilemap *dest, json_t *root); + #endif //_AKGL_TILEMAP_H_ diff --git a/scripts/check_api_surface.sh b/scripts/check_api_surface.sh new file mode 100755 index 0000000..8b42993 --- /dev/null +++ b/scripts/check_api_surface.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# +# Every function with external linkage must be declared in a header. +# +# A non-static function that appears in no header is still in the ABI but is +# unreachable by callers, and it collides with any consumer that happens to pick +# the same name. libakgl shipped nineteen of those. The rule is in AGENTS.md +# under "API Surface"; this is what enforces it. +# +# Usage: check_api_surface.sh [ ...] +# +# Exits 0 when every exported akgl_* symbol is declared, 1 otherwise, and 2 when +# it cannot do its job -- a missing library or no nm. + +set -u + +if [ "$#" -lt 2 ]; then + echo "usage: $0 [ ...]" >&2 + exit 2 +fi + +LIBRARY="$1" +shift + +if ! command -v nm >/dev/null 2>&1; then + echo "check_api_surface: nm is not available; skipping" >&2 + exit 2 +fi + +if [ ! -f "${LIBRARY}" ]; then + echo "check_api_surface: no such library: ${LIBRARY}" >&2 + exit 2 +fi + +DECLARATIONS="$(mktemp)" +trap 'rm -f "${DECLARATIONS}"' EXIT + +# Comments are stripped first. Several of these names appear in prose -- the +# whole point of the check is that being *mentioned* in a header is not the same +# as being declared in one, and that distinction is exactly what went wrong with +# the gamepad_handle_* handlers. +for dir in "$@"; do + [ -d "${dir}" ] || continue + find "${dir}" -name '*.h' -print0 | + xargs -0 --no-run-if-empty cat | + sed -e 's://.*::' | + awk 'BEGIN { incomment = 0 } + { + line = $0 + while ( 1 ) { + if ( incomment ) { + idx = index(line, "*/") + if ( idx == 0 ) { line = ""; break } + line = substr(line, idx + 2); incomment = 0 + } else { + idx = index(line, "/*") + if ( idx == 0 ) { break } + printf "%s", substr(line, 1, idx - 1) + line = substr(line, idx + 2); incomment = 1 + } + } + print line + }' >> "${DECLARATIONS}" +done + +UNDECLARED="" +for symbol in $(nm -D --defined-only "${LIBRARY}" | awk '$2 == "T" { print $3 }' | grep '^akgl_' | sort); do + if ! grep -qE "(^|[^A-Za-z0-9_])${symbol}[^A-Za-z0-9_]" "${DECLARATIONS}"; then + UNDECLARED="${UNDECLARED} ${symbol}" + fi +done + +if [ -n "${UNDECLARED}" ]; then + echo "check_api_surface: exported but declared in no header:" >&2 + for symbol in ${UNDECLARED}; do + echo " ${symbol}" >&2 + done + echo "" >&2 + echo "Declare each one in the header for its subsystem, or make it static." >&2 + echo "A function exposed only so tests can reach it goes under the existing" >&2 + echo "\"part of the internal API\" comment block." >&2 + exit 1 +fi + +echo "check_api_surface: every exported akgl_* symbol is declared" +exit 0 diff --git a/src/actor.c b/src/actor.c index 16090e4..09cdd95 100644 --- a/src/actor.c +++ b/src/actor.c @@ -328,7 +328,7 @@ void akgl_registry_iterate_actor(void *userdata, SDL_PropertiesID registry, cons } FINISH_NORETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_on(akgl_Actor *obj, SDL_Event *event) +akerr_ErrorContext *akgl_actor_cmhf_left_on(akgl_Actor *obj, SDL_Event *event) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "actor"); @@ -342,7 +342,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_on(akgl_Actor *obj, SDL_ SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_off(akgl_Actor *obj, SDL_Event *event) +akerr_ErrorContext *akgl_actor_cmhf_left_off(akgl_Actor *obj, SDL_Event *event) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor"); @@ -357,7 +357,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_off(akgl_Actor *obj, SDL SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_on(akgl_Actor *obj, SDL_Event *event) +akerr_ErrorContext *akgl_actor_cmhf_right_on(akgl_Actor *obj, SDL_Event *event) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor"); @@ -371,7 +371,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_on(akgl_Actor *obj, SDL SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_off(akgl_Actor *obj, SDL_Event *event) +akerr_ErrorContext *akgl_actor_cmhf_right_off(akgl_Actor *obj, SDL_Event *event) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor"); @@ -386,7 +386,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_off(akgl_Actor *obj, SD SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_on(akgl_Actor *obj, SDL_Event *event) +akerr_ErrorContext *akgl_actor_cmhf_up_on(akgl_Actor *obj, SDL_Event *event) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor"); @@ -400,7 +400,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_on(akgl_Actor *obj, SDL_Ev SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_off(akgl_Actor *obj, SDL_Event *event) +akerr_ErrorContext *akgl_actor_cmhf_up_off(akgl_Actor *obj, SDL_Event *event) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor"); @@ -415,7 +415,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_off(akgl_Actor *obj, SDL_E SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_on(akgl_Actor *obj, SDL_Event *event) +akerr_ErrorContext *akgl_actor_cmhf_down_on(akgl_Actor *obj, SDL_Event *event) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor"); @@ -429,7 +429,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_on(akgl_Actor *obj, SDL_ SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_off(akgl_Actor *obj, SDL_Event *event) +akerr_ErrorContext *akgl_actor_cmhf_down_off(akgl_Actor *obj, SDL_Event *event) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor"); diff --git a/src/audio.c b/src/audio.c index 2850e8d..9ffdf2f 100644 --- a/src/audio.c +++ b/src/audio.c @@ -283,7 +283,7 @@ static void SDLCALL audio_stream_callback(void *userdata, SDL_AudioStream *strea } } -akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_init(void) +akerr_ErrorContext *akgl_audio_init(void) { SDL_AudioSpec spec; @@ -322,7 +322,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_init(void) SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_shutdown(void) +akerr_ErrorContext *akgl_audio_shutdown(void) { SDL_AudioStream *closing = audiostream; @@ -337,7 +337,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_shutdown(void) SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_tone(int voice, float32_t hz, uint32_t ms) +akerr_ErrorContext *akgl_audio_tone(int voice, float32_t hz, uint32_t ms) { PREPARE_ERROR(errctx); PASS(errctx, check_voice(voice)); @@ -350,7 +350,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_tone(int voice, float32_t hz, uint SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_sweep(int voice, float32_t from_hz, float32_t to_hz, float32_t step_hz, uint32_t ms) +akerr_ErrorContext *akgl_audio_sweep(int voice, float32_t from_hz, float32_t to_hz, float32_t step_hz, uint32_t ms) { PREPARE_ERROR(errctx); PASS(errctx, check_voice(voice)); @@ -368,7 +368,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_sweep(int voice, float32_t from_hz SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_stop(int voice) +akerr_ErrorContext *akgl_audio_stop(int voice) { PREPARE_ERROR(errctx); PASS(errctx, check_voice(voice)); @@ -382,7 +382,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_stop(int voice) SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_waveform(int voice, akgl_AudioWaveform waveform) +akerr_ErrorContext *akgl_audio_waveform(int voice, akgl_AudioWaveform waveform) { PREPARE_ERROR(errctx); PASS(errctx, check_voice(voice)); @@ -401,7 +401,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_waveform(int voice, akgl_AudioWave SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_envelope(int voice, uint32_t attack, uint32_t decay, float32_t sustain, uint32_t release) +akerr_ErrorContext *akgl_audio_envelope(int voice, uint32_t attack, uint32_t decay, float32_t sustain, uint32_t release) { PREPARE_ERROR(errctx); PASS(errctx, check_voice(voice)); @@ -422,7 +422,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_envelope(int voice, uint32_t attac SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_volume(float32_t level) +akerr_ErrorContext *akgl_audio_volume(float32_t level) { PREPARE_ERROR(errctx); FAIL_NONZERO_RETURN( @@ -439,7 +439,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_volume(float32_t level) SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_voice_active(int voice, bool *active) +akerr_ErrorContext *akgl_audio_voice_active(int voice, bool *active) { PREPARE_ERROR(errctx); PASS(errctx, check_voice(voice)); @@ -452,7 +452,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_voice_active(int voice, bool *acti SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_mix(float32_t *dest, int frames) +akerr_ErrorContext *akgl_audio_mix(float32_t *dest, int frames) { akgl_AudioVoice *voice = NULL; float32_t sum = 0.0f; diff --git a/src/character.c b/src/character.c index 6758e50..3657613 100644 --- a/src/character.c +++ b/src/character.c @@ -72,7 +72,7 @@ akerr_ErrorContext *akgl_character_sprite_get(akgl_Character *basechar, int stat // SDL iterator so we can't return error information here, void only // this means we don't have anywhere to send exceptions up to, so if we hit an error, we log and exit(1) here -void akgl_character_state_sprites_iterate(void *userdata, SDL_PropertiesID registry, const char *name) +void akgl_character_state_sprites_iterate(void *userdata, SDL_PropertiesID props, const char *name) { PREPARE_ERROR(errctx); akgl_Sprite *spriteptr; @@ -80,7 +80,7 @@ void akgl_character_state_sprites_iterate(void *userdata, SDL_PropertiesID regis ATTEMPT { FAIL_ZERO_BREAK(errctx, opflags, AKERR_NULLPOINTER, "Character state sprite iterator received null iterator op pointer"); FAIL_ZERO_BREAK(errctx, name, AKERR_NULLPOINTER, "Character state sprite iterator received null sprite name"); - spriteptr = (akgl_Sprite *)SDL_GetPointerProperty(registry, name, NULL); + spriteptr = (akgl_Sprite *)SDL_GetPointerProperty(props, name, NULL); FAIL_ZERO_BREAK(errctx, spriteptr, AKERR_NULLPOINTER, "Character state sprite for %s not found", name); if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_RELEASE) ) { CATCH(errctx, akgl_heap_release_sprite(spriteptr)); @@ -114,7 +114,7 @@ void akgl_character_state_sprites_iterate(void *userdata, SDL_PropertiesID regis * @throws AKERR_OUTOFBOUNDS If the array is indexed past its end. * @throws AKGL_ERR_HEAP If the string pool is exhausted. */ -static akerr_ErrorContext *akgl_character_load_json_state_int_from_strings(json_t *states, int *dest) +static akerr_ErrorContext *character_load_json_state_int_from_strings(json_t *states, int *dest) { int i = 0; long newstate = 0; @@ -161,7 +161,7 @@ static akerr_ErrorContext *akgl_character_load_json_state_int_from_strings(json_ * @throws AKERR_OUTOFBOUNDS If an array is indexed past its end. * @throws AKGL_ERR_HEAP If the string pool is exhausted. */ -static akerr_ErrorContext *akgl_character_load_json_inner(json_t *json, akgl_Character *obj) +static akerr_ErrorContext *character_load_json_inner(json_t *json, akgl_Character *obj) { PREPARE_ERROR(errctx); json_t *mappings = NULL; @@ -189,7 +189,7 @@ static akerr_ErrorContext *akgl_character_load_json_inner(json_t *json, akgl_Cha CATCH(errctx, akgl_get_json_string_value((json_t *)json, "name", &tmpstr2)); CATCH(errctx, akgl_get_json_array_value((json_t *)curmapping, "state", &statearray)); - CATCH(errctx, akgl_character_load_json_state_int_from_strings(statearray, &stateval)); + CATCH(errctx, character_load_json_state_int_from_strings(statearray, &stateval)); CATCH(errctx, akgl_get_json_string_value((json_t *)curmapping, "sprite", &tmpstr)); FAIL_ZERO_BREAK( @@ -236,7 +236,7 @@ akerr_ErrorContext *akgl_character_load_json(char *filename) AKERR_NULLPOINTER, "Error while loading character from %s on line %d: %s", filename, error.line, error.text ); - CATCH(errctx, akgl_character_load_json_inner(json, obj)); + CATCH(errctx, character_load_json_inner(json, obj)); CATCH(errctx, akgl_get_json_integer_value(json, "speedtime", (int *)&obj->speedtime)); obj->speedtime = obj->speedtime * AKGL_TIME_ONEMS_NS; CATCH(errctx, akgl_get_json_number_value(json, "speed_x", &obj->sx)); diff --git a/src/controller.c b/src/controller.c index 5a05ef7..7e4919c 100644 --- a/src/controller.c +++ b/src/controller.c @@ -182,7 +182,7 @@ static bool keybuffer_take(akgl_Keystroke *dest) return true; } -akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_list_keyboards(void) +akerr_ErrorContext *akgl_controller_list_keyboards(void) { int count; SDL_KeyboardID *keyboards = SDL_GetKeyboards(&count); @@ -206,7 +206,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_list_keyboards(void) SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_open_gamepads(void) +akerr_ErrorContext *akgl_controller_open_gamepads(void) { int count = 0; int i = 0; @@ -302,32 +302,7 @@ akerr_ErrorContext *akgl_controller_handle_event(void *appstate, SDL_Event *even SUCCEED_RETURN(errctx); } -/** - * @brief Set the movement and facing bits on the "player" actor for a D-pad or arrow press. - * - * A whole-application shortcut rather than a control-map binding: it looks the - * actor up by the literal name `"player"` in #AKGL_REGISTRY_ACTOR instead of - * being told which actor to act on, so it only ever drives one. The - * akgl_actor_cmhf_* handlers are the general form. - * - * Facing follows movement unless the actor sets `movement_controls_face`, which - * is how an actor that aims independently of the direction it walks opts out. - * - * Declared nowhere -- `controller.h` advertises this as - * `akgl_controller_handle_button_down`, which does not exist. TODO.md, "Known - * and still open" item 10. - * - * @param appstate Passed through from SDL. Required as a non-`NULL` token only; - * never read. - * @param event The button or key event. Required. Both the gamepad and - * keyboard unions are read on every call, so the arm that does - * not correspond to the event type is read as garbage -- which - * works only because no real button and keycode pair collides. - * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`, or if there is - * no actor registered under the name "player". - */ -akerr_ErrorContext *gamepad_handle_button_down(void *appstate, SDL_Event *event) +akerr_ErrorContext *akgl_controller_handle_button_down(void *appstate, SDL_Event *event) { akgl_Actor *player = NULL; @@ -378,23 +353,7 @@ akerr_ErrorContext *gamepad_handle_button_down(void *appstate, SDL_Event *event) SUCCEED_RETURN(errctx); } -/** - * @brief Clear the movement bit on the "player" actor for a D-pad or arrow release, and reset its animation. - * - * The counterpart to gamepad_handle_button_down. It clears only the movement - * bit, leaving the facing bits alone so the actor keeps looking the way it was - * walking, and rewinds `curSpriteFrameId` to 0 so the next step starts from the - * first frame of the walk cycle rather than wherever it stopped. - * - * Declared nowhere; see gamepad_handle_button_down. - * - * @param appstate Passed through from SDL. Required as a non-`NULL` token only. - * @param event The button or key release event. Required. - * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`, or if there is - * no actor registered under the name "player". - */ -akerr_ErrorContext *gamepad_handle_button_up(void *appstate, SDL_Event *event) +akerr_ErrorContext *akgl_controller_handle_button_up(void *appstate, SDL_Event *event) { akgl_Actor *player = NULL; @@ -433,31 +392,7 @@ akerr_ErrorContext *gamepad_handle_button_up(void *appstate, SDL_Event *event) SUCCEED_RETURN(errctx); } -/** - * @brief Open a gamepad that has just been plugged in, and log its mapping. - * - * SDL delivers no button events from an unopened gamepad, so a device that - * appears mid-session has to be opened here the way akgl_controller_open_gamepads - * opens the ones present at startup. A gamepad SDL has already opened is logged - * and left alone. - * - * The mapping is logged because a controller with no entry in the database - * produces no button events at all, and that is otherwise indistinguishable - * from a broken binding. - * - * Declared nowhere; see gamepad_handle_button_down. - * - * @param appstate Passed through from SDL. Required as a non-`NULL` token only. - * @param event The SDL_EVENT_GAMEPAD_ADDED event. Required. The joystick id - * is read out of the `gbutton` arm rather than `gdevice`, which - * works because the two share their `which` field. - * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`. - * - * @note A failed `SDL_OpenGamepad` is logged rather than reported: this returns - * success and the device stays silent. - */ -akerr_ErrorContext *gamepad_handle_added(void *appstate, SDL_Event *event) +akerr_ErrorContext *akgl_controller_handle_added(void *appstate, SDL_Event *event) { SDL_JoystickID which; SDL_Gamepad *gamepad = NULL; @@ -487,22 +422,7 @@ akerr_ErrorContext *gamepad_handle_added(void *appstate, SDL_Event *event) SUCCEED_RETURN(errctx); } -/** - * @brief Close a gamepad that has been unplugged. - * - * An unplugged device SDL still has open is a leaked handle, so this closes it. - * A removal for a device that was never opened is logged and otherwise ignored. - * Any control map still holding that `jsid` simply stops matching -- the map is - * not torn down. - * - * Declared nowhere; see gamepad_handle_button_down. - * - * @param appstate Passed through from SDL. Required as a non-`NULL` token only. - * @param event The SDL_EVENT_GAMEPAD_REMOVED event. Required. - * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`. - */ -akerr_ErrorContext *gamepad_handle_removed(void *appstate, SDL_Event *event) +akerr_ErrorContext *akgl_controller_handle_removed(void *appstate, SDL_Event *event) { SDL_JoystickID which; SDL_Gamepad *gamepad = NULL; @@ -521,7 +441,7 @@ akerr_ErrorContext *gamepad_handle_removed(void *appstate, SDL_Event *event) SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akgl_Control *control) +akerr_ErrorContext *akgl_controller_pushmap(int controlmapid, akgl_Control *control) { int newmapid = 0; PREPARE_ERROR(errctx); @@ -538,7 +458,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akg SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, char *actorname, int kbid, int jsid) +akerr_ErrorContext *akgl_controller_default(int controlmapid, char *actorname, int kbid, int jsid) { akgl_ControlMap *controlmap; akgl_Control control; @@ -630,7 +550,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha } FINISH(errctx, true); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_key(int *keycode, bool *available) +akerr_ErrorContext *akgl_controller_poll_key(int *keycode, bool *available) { akgl_Keystroke keystroke; @@ -655,7 +575,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_key(int *keycode, bool * SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_keystroke(akgl_Keystroke *dest, bool *available) +akerr_ErrorContext *akgl_controller_poll_keystroke(akgl_Keystroke *dest, bool *available) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL keystroke destination"); @@ -666,7 +586,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_keystroke(akgl_Keystroke SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_flush_keys(void) +akerr_ErrorContext *akgl_controller_flush_keys(void) { PREPARE_ERROR(errctx); keybuffer_head = 0; diff --git a/src/draw.c b/src/draw.c index 5b76ace..e218dfd 100644 --- a/src/draw.c +++ b/src/draw.c @@ -236,7 +236,7 @@ static akerr_ErrorContext *flood_region(SDL_Surface *surface, int x, int y, uint SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_point(akgl_RenderBackend *self, float32_t x, float32_t y, SDL_Color color) +akerr_ErrorContext *akgl_draw_point(akgl_RenderBackend *self, float32_t x, float32_t y, SDL_Color color) { SDL_Color previous; @@ -258,7 +258,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_point(akgl_RenderBackend *self, flo SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_line(akgl_RenderBackend *self, float32_t x1, float32_t y1, float32_t x2, float32_t y2, SDL_Color color) +akerr_ErrorContext *akgl_draw_line(akgl_RenderBackend *self, float32_t x1, float32_t y1, float32_t x2, float32_t y2, SDL_Color color) { SDL_Color previous; @@ -280,7 +280,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_line(akgl_RenderBackend *self, floa SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_rect(akgl_RenderBackend *self, SDL_FRect *rect, SDL_Color color) +akerr_ErrorContext *akgl_draw_rect(akgl_RenderBackend *self, SDL_FRect *rect, SDL_Color color) { SDL_Color previous; @@ -303,7 +303,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_rect(akgl_RenderBackend *self, SDL_ SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_filled_rect(akgl_RenderBackend *self, SDL_FRect *rect, SDL_Color color) +akerr_ErrorContext *akgl_draw_filled_rect(akgl_RenderBackend *self, SDL_FRect *rect, SDL_Color color) { SDL_Color previous; @@ -326,7 +326,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_filled_rect(akgl_RenderBackend *sel SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_circle(akgl_RenderBackend *self, float32_t x, float32_t y, float32_t radius, SDL_Color color) +akerr_ErrorContext *akgl_draw_circle(akgl_RenderBackend *self, float32_t x, float32_t y, float32_t radius, SDL_Color color) { SDL_Color previous; SDL_FPoint octants[8]; @@ -393,7 +393,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_circle(akgl_RenderBackend *self, fl SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_flood_fill(akgl_RenderBackend *self, int x, int y, SDL_Color color) +akerr_ErrorContext *akgl_draw_flood_fill(akgl_RenderBackend *self, int x, int y, SDL_Color color) { SDL_Surface *target = NULL; SDL_Surface *rgba = NULL; @@ -481,7 +481,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_flood_fill(akgl_RenderBackend *self SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_copy_region(akgl_RenderBackend *self, SDL_Rect *src, SDL_Surface **dest) +akerr_ErrorContext *akgl_draw_copy_region(akgl_RenderBackend *self, SDL_Rect *src, SDL_Surface **dest) { SDL_Surface *saved = NULL; int width = 0; @@ -551,7 +551,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_copy_region(akgl_RenderBackend *sel SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_paste_region(akgl_RenderBackend *self, SDL_Surface *src, float32_t x, float32_t y) +akerr_ErrorContext *akgl_draw_paste_region(akgl_RenderBackend *self, SDL_Surface *src, float32_t x, float32_t y) { SDL_Texture *patch = NULL; SDL_FRect dest; diff --git a/src/game.c b/src/game.c index 217d6f1..a58d8ee 100644 --- a/src/game.c +++ b/src/game.c @@ -129,7 +129,7 @@ void akgl_game_lowfps(void) return; } -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init() +akerr_ErrorContext *akgl_game_init(void) { int screenwidth = 0; int screenheight = 0; @@ -213,7 +213,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init() SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_lock(void) +akerr_ErrorContext *akgl_game_state_lock(void) { PREPARE_ERROR(e); // Milliseconds, which is what SDL_Delay takes. This used to count against @@ -240,14 +240,14 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_lock(void) SDL_GetError()); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void) +akerr_ErrorContext *akgl_game_state_unlock(void) { PREPARE_ERROR(e); SDL_UnlockMutex(akgl_game.statelock); SUCCEED_RETURN(e); } -void akgl_game_update_fps() +void akgl_game_update_fps(void) { SDL_Time curTime; curTime = SDL_GetTicksNS(); @@ -287,7 +287,7 @@ void akgl_game_update_fps() * **exits the process**. A failed write during a save terminates the * game rather than returning an error. */ -void akgl_game_save_actorname_iterator(void *userdata, SDL_PropertiesID props, const char *name) +static void save_actorname_iterator(void *userdata, SDL_PropertiesID props, const char *name) { FILE *fp = (FILE *)userdata; akgl_Actor *actor = NULL; @@ -305,7 +305,7 @@ void akgl_game_save_actorname_iterator(void *userdata, SDL_PropertiesID props, c /** * @brief Write one sprite's name and save-time address to the name table. * - * As akgl_game_save_actorname_iterator, but the name field is + * As save_actorname_iterator, but the name field is * #AKGL_SPRITE_MAX_NAME_LENGTH wide. * * @param userdata The open output stream, as a `FILE *`. Required. @@ -313,9 +313,9 @@ void akgl_game_save_actorname_iterator(void *userdata, SDL_PropertiesID props, c * @param name The sprite's registry key, written at fixed width. * * @warning Terminates the process on an unhandled error. See - * akgl_game_save_actorname_iterator. + * save_actorname_iterator. */ -void akgl_game_save_spritename_iterator(void *userdata, SDL_PropertiesID props, const char *name) +static void save_spritename_iterator(void *userdata, SDL_PropertiesID props, const char *name) { FILE *fp = (FILE *)userdata; akgl_Sprite *sprite = NULL; @@ -333,7 +333,7 @@ void akgl_game_save_spritename_iterator(void *userdata, SDL_PropertiesID props, /** * @brief Write one spritesheet's name and save-time address to the name table. * - * As akgl_game_save_actorname_iterator, but the name field is + * As save_actorname_iterator, but the name field is * #AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH wide -- the widest of the four, since * a spritesheet is keyed by its resolved path. * @@ -342,12 +342,12 @@ void akgl_game_save_spritename_iterator(void *userdata, SDL_PropertiesID props, * @param name The spritesheet's registry key, written at fixed width. * * @warning Terminates the process on an unhandled error. See - * akgl_game_save_actorname_iterator. + * save_actorname_iterator. * @note This is the table the loader disagrees with: it reads every table at * #AKGL_ACTOR_MAX_NAME_LENGTH, so a save containing any spritesheet cannot * be read back. TODO.md, "Known and still open" item 7. */ -void akgl_game_save_spritesheetname_iterator(void *userdata, SDL_PropertiesID props, const char *name) +static void save_spritesheetname_iterator(void *userdata, SDL_PropertiesID props, const char *name) { FILE *fp = (FILE *)userdata; akgl_SpriteSheet *spritesheet = NULL; @@ -365,7 +365,7 @@ void akgl_game_save_spritesheetname_iterator(void *userdata, SDL_PropertiesID pr /** * @brief Write one character's name and save-time address to the name table. * - * As akgl_game_save_actorname_iterator, but the name field is + * As save_actorname_iterator, but the name field is * #AKGL_CHARACTER_MAX_NAME_LENGTH wide. * * @param userdata The open output stream, as a `FILE *`. Required. @@ -373,9 +373,9 @@ void akgl_game_save_spritesheetname_iterator(void *userdata, SDL_PropertiesID pr * @param name The character's registry key, written at fixed width. * * @warning Terminates the process on an unhandled error. See - * akgl_game_save_actorname_iterator. + * save_actorname_iterator. */ -void akgl_game_save_charactername_iterator(void *userdata, SDL_PropertiesID props, const char *name) +static void save_charactername_iterator(void *userdata, SDL_PropertiesID props, const char *name) { FILE *fp = (FILE *)userdata; PREPARE_ERROR(e); @@ -390,30 +390,11 @@ void akgl_game_save_charactername_iterator(void *userdata, SDL_PropertiesID prop } FINISH_NORETURN(e); } -/** - * @brief Write all four name-to-address tables, each with its terminator. - * - * Actors, sprites, spritesheets, characters, in that order -- which is the order - * akgl_game_load reads them back in. Each table is the registry enumerated one - * entry at a time, followed by a zeroed name field and a zeroed pointer that the - * reader stops on. - * - * @param fp The open save-game stream. Required. - * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_NULLPOINTER If @p fp is `NULL`. - * @throws AKERR_IO On a stream error or a short write while emitting a - * terminator. - * - * @note Only the terminators are written through the error-reporting path. The - * entries themselves go through `SDL_EnumerateProperties`, whose callbacks - * cannot report failure upward and instead terminate the process -- so a - * write error mid-table never reaches this function's return value. - */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp) +akerr_ErrorContext *akgl_game_save_actors(FILE *fp) { PREPARE_ERROR(e); // Each name table ends with a zeroed name field and a zeroed pointer, which - // is what akgl_game_load_objectnamemap() looks for to stop reading. The + // is what load_objectnamemap() looks for to stop reading. The // terminator has to come from a buffer at least as long as the longest name // field: writing N bytes from the address of a single char would emit N-1 // bytes of whatever happened to follow it on the stack, which both leaks @@ -428,28 +409,28 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp) // write the actor name pointer table SDL_EnumerateProperties( AKGL_REGISTRY_ACTOR, - &akgl_game_save_actorname_iterator, + &save_actorname_iterator, (void *)fp); CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp)); CATCH(e, write_exact((void *)&nullbuf, 1, sizeof(akgl_Actor *), fp)); // write the sprite name pointer table SDL_EnumerateProperties( AKGL_REGISTRY_SPRITE, - &akgl_game_save_spritename_iterator, + &save_spritename_iterator, (void *)fp); CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp)); CATCH(e, write_exact((void *)&nullbuf, 1, sizeof(akgl_Sprite *), fp)); // write the spritesheet name pointer table SDL_EnumerateProperties( AKGL_REGISTRY_SPRITESHEET, - &akgl_game_save_spritesheetname_iterator, + &save_spritesheetname_iterator, (void *)fp); CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp)); CATCH(e, write_exact((void *)&nullbuf, 1, sizeof(akgl_SpriteSheet *), fp)); // write the character name pointer table SDL_EnumerateProperties( AKGL_REGISTRY_CHARACTER, - &akgl_game_save_charactername_iterator, + &save_charactername_iterator, (void *)fp); CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_CHARACTER_MAX_NAME_LENGTH, fp)); CATCH(e, write_exact((void *)&nullbuf, 1, sizeof(akgl_Character *), fp)); @@ -459,7 +440,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp) SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath) +akerr_ErrorContext *akgl_game_save(char *fpath) { FILE *fp = NULL; PREPARE_ERROR(e); @@ -517,7 +498,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath) * rather than being reported: the entry is written with whatever * `SDL_GetPointerProperty` returned. */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_objectnamemap(FILE *fp, SDL_PropertiesID map, int namelength, int ptrlength, SDL_PropertiesID registry) +static akerr_ErrorContext *load_objectnamemap(FILE *fp, SDL_PropertiesID map, int namelength, int ptrlength, SDL_PropertiesID registry) { void *ptr = NULL; char ptrstring[32]; @@ -561,27 +542,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_objectnamemap(FILE *fp, SDL_Pr SUCCEED_RETURN(e); } -/** - * @brief Refuse a save file unless its version matches the running one exactly. - * - * Both strings are parsed as semver and compared with `"="` -- exact equality, - * not compatibility. That is deliberate and strict: a save file is a raw memory - * image of `akgl_Game` and the object tables, so any change to a struct layout - * invalidates it, and semver has no way to say "the layout did not move". - * - * @param versiontype What is being compared -- `"library"` or `"game"`. Used - * only to build the message, but required, since a bare - * "incompatible version" error would not say which. - * @param newversion The version read out of the save file. Required. - * @param curversion The version of the running program or library. Required. - * @return `NULL` when the two match, otherwise an error context owned by the - * caller. - * @throws AKERR_NULLPOINTER If any of the three arguments is `NULL`. - * @throws AKERR_VALUE If either string is not valid semver. The message says - * which side it came from. - * @throws AKERR_API If both parse but are not equal. The message quotes both. - */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion) +akerr_ErrorContext *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion) { semver_t current_version = {}; semver_t compare_version = {}; @@ -622,7 +583,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath) +akerr_ErrorContext *akgl_game_load(char *fpath) { akgl_Game savegame; SDL_PropertiesID actormap; @@ -653,16 +614,16 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath) memcpy((void *)&akgl_game, (void *)&savegame, sizeof(akgl_Game)); // Load actor name map actormap = SDL_CreateProperties(); - CATCH(e, akgl_game_load_objectnamemap(fp, actormap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Actor *), AKGL_REGISTRY_ACTOR)); + CATCH(e, load_objectnamemap(fp, actormap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Actor *), AKGL_REGISTRY_ACTOR)); // Load sprite name map spritemap = SDL_CreateProperties(); - CATCH(e, akgl_game_load_objectnamemap(fp, spritemap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Sprite *), AKGL_REGISTRY_SPRITE)); + CATCH(e, load_objectnamemap(fp, spritemap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Sprite *), AKGL_REGISTRY_SPRITE)); // Load spritesheet name map spritesheetmap = SDL_CreateProperties(); - CATCH(e, akgl_game_load_objectnamemap(fp, spritesheetmap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_SpriteSheet *), AKGL_REGISTRY_SPRITESHEET)); + CATCH(e, load_objectnamemap(fp, spritesheetmap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_SpriteSheet *), AKGL_REGISTRY_SPRITESHEET)); // Load character name map charactermap = SDL_CreateProperties(); - CATCH(e, akgl_game_load_objectnamemap(fp, charactermap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Character *), AKGL_REGISTRY_CHARACTER)); + CATCH(e, load_objectnamemap(fp, charactermap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Character *), AKGL_REGISTRY_CHARACTER)); // Now that we have all of our pointer maps built, we can load the actual binary objects and reset their pointers } CLEANUP { if ( fp != NULL ) { @@ -674,7 +635,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath) SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags) +akerr_ErrorContext *akgl_game_update(akgl_Iterator *opflags) { PREPARE_ERROR(e); akgl_Iterator defflags = { diff --git a/src/heap.c b/src/heap.c index dd049cf..7b6a8b5 100644 --- a/src/heap.c +++ b/src/heap.c @@ -20,7 +20,7 @@ akgl_SpriteSheet akgl_heap_spritesheets[AKGL_MAX_HEAP_SPRITESHEET]; akgl_Character akgl_heap_characters[AKGL_MAX_HEAP_CHARACTER]; akgl_String akgl_heap_strings[AKGL_MAX_HEAP_STRING]; -akerr_ErrorContext *akgl_heap_init() +akerr_ErrorContext *akgl_heap_init(void) { PREPARE_ERROR(errctx); int i = 0; diff --git a/src/json_helpers.c b/src/json_helpers.c index af46851..75f6f89 100644 --- a/src/json_helpers.c +++ b/src/json_helpers.c @@ -57,7 +57,7 @@ akerr_ErrorContext *akgl_get_json_number_value(json_t *obj, char *key, float *de SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_double_value(json_t *obj, char *key, double *dest) +akerr_ErrorContext *akgl_get_json_double_value(json_t *obj, char *key, double *dest) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference"); @@ -146,25 +146,28 @@ akerr_ErrorContext *akgl_get_json_array_index_string(json_t *array, int index, a SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_with_default(akerr_ErrorContext *err, void *defval, void *dest, uint32_t defsize) +akerr_ErrorContext *akgl_get_json_with_default(akerr_ErrorContext *e, void *defval, void *dest, uint32_t defsize) { - PREPARE_ERROR(e); - if ( err == NULL ) { - SUCCEED_RETURN(e); + // Two contexts, and the names now say which is which: `e` is the incoming + // one being inspected, `errctx` is this function's own. They were `err` and + // `e`, which put the incoming context under the name the convention + // reserves for exactly that and the local one under a name that reads like + // it. + PREPARE_ERROR(errctx); + if ( e == NULL ) { + SUCCEED_RETURN(errctx); } - int docopy = 0; - FAIL_ZERO_RETURN(e, err, AKERR_NULLPOINTER, "err"); - FAIL_ZERO_RETURN(e, defval, AKERR_NULLPOINTER, "defval"); - FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dest"); + FAIL_ZERO_RETURN(errctx, defval, AKERR_NULLPOINTER, "defval"); + FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "dest"); ATTEMPT { } CLEANUP { - } PROCESS(err) { - } HANDLE_GROUP(err, AKERR_KEY) { - } HANDLE_GROUP(err, AKERR_INDEX) { + } PROCESS(e) { + } HANDLE_GROUP(e, AKERR_KEY) { + } HANDLE_GROUP(e, AKERR_INDEX) { memcpy(dest, defval, defsize); - } FINISH(err, true); + } FINISH(e, true); - SUCCEED_RETURN(e); + SUCCEED_RETURN(errctx); } diff --git a/src/physics.c b/src/physics.c index 655f638..79b472d 100644 --- a/src/physics.c +++ b/src/physics.c @@ -12,28 +12,28 @@ #include #include -akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_null_gravity(akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt) +akerr_ErrorContext *akgl_physics_null_gravity(akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_null_collide(akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2) +akerr_ErrorContext *akgl_physics_null_collide(akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_null_move(struct akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt) +akerr_ErrorContext *akgl_physics_null_move(struct akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_null(akgl_PhysicsBackend *self) +akerr_ErrorContext *akgl_physics_init_null(akgl_PhysicsBackend *self) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); @@ -47,7 +47,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_null(akgl_PhysicsBackend *s } -akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_gravity(akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt) +akerr_ErrorContext *akgl_physics_arcade_gravity(akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); @@ -69,7 +69,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_gravity(akgl_PhysicsBacke SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_collide(akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2) +akerr_ErrorContext *akgl_physics_arcade_collide(akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); @@ -77,7 +77,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_collide(akgl_PhysicsBacke SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_move(struct akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt) +akerr_ErrorContext *akgl_physics_arcade_move(struct akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); @@ -88,7 +88,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_move(struct akgl_PhysicsB SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_arcade(akgl_PhysicsBackend *self) +akerr_ErrorContext *akgl_physics_init_arcade(akgl_PhysicsBackend *self) { akgl_String *tmp; PREPARE_ERROR(e); @@ -121,7 +121,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_arcade(akgl_PhysicsBackend SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *self, akgl_Iterator *opflags) +akerr_ErrorContext *akgl_physics_simulate(akgl_PhysicsBackend *self, akgl_Iterator *opflags) { PREPARE_ERROR(e); akgl_Iterator defflags = { @@ -227,7 +227,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *se SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_factory(akgl_PhysicsBackend *self, akgl_String *type) +akerr_ErrorContext *akgl_physics_factory(akgl_PhysicsBackend *self, akgl_String *type) { uint32_t hashval; PREPARE_ERROR(e); diff --git a/src/registry.c b/src/registry.c index 10bbefc..77c0e52 100644 --- a/src/registry.c +++ b/src/registry.c @@ -25,7 +25,7 @@ SDL_PropertiesID AKGL_REGISTRY_MUSIC = 0; SDL_PropertiesID AKGL_REGISTRY_FONT = 0; SDL_PropertiesID AKGL_REGISTRY_PROPERTIES = 0; -akerr_ErrorContext *akgl_registry_init() +akerr_ErrorContext *akgl_registry_init(void) { PREPARE_ERROR(errctx); ATTEMPT { @@ -42,7 +42,7 @@ akerr_ErrorContext *akgl_registry_init() SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_registry_init_actor() +akerr_ErrorContext *akgl_registry_init_actor(void) { PREPARE_ERROR(errctx); if ( AKGL_REGISTRY_ACTOR != 0 ) { @@ -53,7 +53,7 @@ akerr_ErrorContext *akgl_registry_init_actor() SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_font() +akerr_ErrorContext *akgl_registry_init_font(void) { PREPARE_ERROR(errctx); AKGL_REGISTRY_FONT = SDL_CreateProperties(); @@ -61,7 +61,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_font() SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_registry_init_music() +akerr_ErrorContext *akgl_registry_init_music(void) { PREPARE_ERROR(errctx); AKGL_REGISTRY_MUSIC = SDL_CreateProperties(); @@ -69,7 +69,7 @@ akerr_ErrorContext *akgl_registry_init_music() SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_registry_init_properties() +akerr_ErrorContext *akgl_registry_init_properties(void) { PREPARE_ERROR(errctx); AKGL_REGISTRY_PROPERTIES = SDL_CreateProperties(); @@ -77,7 +77,7 @@ akerr_ErrorContext *akgl_registry_init_properties() SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_registry_init_actor_state_strings() +akerr_ErrorContext *akgl_registry_init_actor_state_strings(void) { int i = 0; int flag = 0; @@ -94,7 +94,7 @@ akerr_ErrorContext *akgl_registry_init_actor_state_strings() SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_registry_init_sprite() +akerr_ErrorContext *akgl_registry_init_sprite(void) { PREPARE_ERROR(errctx); AKGL_REGISTRY_SPRITE = SDL_CreateProperties(); @@ -102,7 +102,7 @@ akerr_ErrorContext *akgl_registry_init_sprite() SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_registry_init_spritesheet() +akerr_ErrorContext *akgl_registry_init_spritesheet(void) { PREPARE_ERROR(errctx); AKGL_REGISTRY_SPRITESHEET = SDL_CreateProperties(); @@ -110,7 +110,7 @@ akerr_ErrorContext *akgl_registry_init_spritesheet() SUCCEED_RETURN(errctx); } -akerr_ErrorContext *akgl_registry_init_character() +akerr_ErrorContext *akgl_registry_init_character(void) { PREPARE_ERROR(errctx); AKGL_REGISTRY_CHARACTER = SDL_CreateProperties(); @@ -118,7 +118,7 @@ akerr_ErrorContext *akgl_registry_init_character() SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_load_properties(char *fname) +akerr_ErrorContext *akgl_registry_load_properties(char *fname) { json_t *json = NULL; json_t *props = NULL; @@ -175,16 +175,16 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_load_properties(char *fname) SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_set_property(char *name, char *src) +akerr_ErrorContext *akgl_set_property(char *name, char *value) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, name, AKERR_NULLPOINTER, "NULL char *"); - FAIL_ZERO_RETURN(e, src, AKERR_NULLPOINTER, "NULL char *"); - SDL_SetStringProperty(AKGL_REGISTRY_PROPERTIES, name, src); + FAIL_ZERO_RETURN(e, value, AKERR_NULLPOINTER, "NULL char *"); + SDL_SetStringProperty(AKGL_REGISTRY_PROPERTIES, name, value); SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_get_property(char *name, akgl_String **dest, char *def) +akerr_ErrorContext *akgl_get_property(char *name, akgl_String **dest, char *def) { const char *value = NULL; size_t valuelen = 0; diff --git a/src/renderer.c b/src/renderer.c index 99b6270..b3ec671 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -14,7 +14,7 @@ #include #include -akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_init(akgl_RenderBackend *self) +akerr_ErrorContext *akgl_render_2d_init(akgl_RenderBackend *self) { akgl_String *width = NULL; akgl_String *height = NULL; @@ -47,7 +47,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_init(akgl_RenderBackend *self) SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_bind(akgl_RenderBackend *self) +akerr_ErrorContext *akgl_render_2d_bind(akgl_RenderBackend *self) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); @@ -65,13 +65,13 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_bind(akgl_RenderBackend *self) } -akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_shutdown(akgl_RenderBackend *self) +akerr_ErrorContext *akgl_render_2d_shutdown(akgl_RenderBackend *self) { PREPARE_ERROR(e); SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_start(akgl_RenderBackend *self) +akerr_ErrorContext *akgl_render_2d_frame_start(akgl_RenderBackend *self) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend"); @@ -80,7 +80,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_start(akgl_RenderBackend SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_end(akgl_RenderBackend *self) +akerr_ErrorContext *akgl_render_2d_frame_end(akgl_RenderBackend *self) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend"); @@ -88,7 +88,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_end(akgl_RenderBackend * SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_texture(akgl_RenderBackend *self, SDL_Texture *texture, SDL_FRect *src, SDL_FRect *dest, double angle, SDL_FPoint *center, SDL_FlipMode flip) +akerr_ErrorContext *akgl_render_2d_draw_texture(akgl_RenderBackend *self, SDL_Texture *texture, SDL_FRect *src, SDL_FRect *dest, double angle, SDL_FPoint *center, SDL_FlipMode flip) { PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); @@ -113,13 +113,13 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_texture(akgl_RenderBacken SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_mesh(akgl_RenderBackend *self) +akerr_ErrorContext *akgl_render_2d_draw_mesh(akgl_RenderBackend *self) { PREPARE_ERROR(e); FAIL_RETURN(e, AKERR_API, "Not implemented"); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend *self, akgl_Iterator *opflags) +akerr_ErrorContext *akgl_render_2d_draw_world(akgl_RenderBackend *self, akgl_Iterator *opflags) { PREPARE_ERROR(e); akgl_Iterator defflags; diff --git a/src/sprite.c b/src/sprite.c index a0224f1..4d540a7 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -67,7 +67,7 @@ akerr_ErrorContext *akgl_spritesheet_coords_for_frame(akgl_Sprite *self, SDL_FRe * @throws AKGL_ERR_SDL If the image cannot be decoded. * @throws AKGL_ERR_HEAP If the spritesheet or string pool is exhausted. */ -static akerr_ErrorContext *akgl_sprite_load_json_spritesheet(json_t *json, akgl_SpriteSheet **sheet, char *relative_path) +static akerr_ErrorContext *sprite_load_json_spritesheet(json_t *json, akgl_SpriteSheet **sheet, char *relative_path) { PREPARE_ERROR(errctx); json_t *spritesheet_json = NULL; @@ -145,7 +145,7 @@ akerr_ErrorContext *akgl_sprite_load_json(char *filename) "Error while loading sprite from %s on line %d: %s", filename, error.line, error.text ); - CATCH(errctx, akgl_sprite_load_json_spritesheet((json_t *)json, &sheet, dirname(filename_copy->data))); + CATCH(errctx, sprite_load_json_spritesheet((json_t *)json, &sheet, dirname(filename_copy->data))); CATCH(errctx, akgl_get_json_string_value((json_t *)json, "name", &spritename)); CATCH(errctx, akgl_sprite_initialize( diff --git a/src/text.c b/src/text.c index f710a39..2e269a6 100644 --- a/src/text.c +++ b/src/text.c @@ -10,7 +10,7 @@ #include #include -akerr_ErrorContext AKERR_NOIGNORE *akgl_text_loadfont(char *name, char *filepath, int size) +akerr_ErrorContext *akgl_text_loadfont(char *name, char *filepath, int size) { TTF_Font *font = NULL; @@ -37,7 +37,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_loadfont(char *name, char *filepath SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_text_unloadfont(char *name) +akerr_ErrorContext *akgl_text_unloadfont(char *name) { TTF_Font *font = NULL; @@ -54,7 +54,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_unloadfont(char *name) SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_text_rendertextat(TTF_Font *font, char *text, SDL_Color color, int wraplength, int x, int y) +akerr_ErrorContext *akgl_text_rendertextat(TTF_Font *font, char *text, SDL_Color color, int wraplength, int x, int y) { SDL_Surface *textsurf = NULL; SDL_Texture *texture = NULL; @@ -96,7 +96,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_rendertextat(TTF_Font *font, char * SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure(TTF_Font *font, char *text, int *w, int *h) +akerr_ErrorContext *akgl_text_measure(TTF_Font *font, char *text, int *w, int *h) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, font, AKERR_NULLPOINTER, "NULL font"); @@ -114,7 +114,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure(TTF_Font *font, char *text, SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure_wrapped(TTF_Font *font, char *text, int wraplength, int *w, int *h) +akerr_ErrorContext *akgl_text_measure_wrapped(TTF_Font *font, char *text, int wraplength, int *w, int *h) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, font, AKERR_NULLPOINTER, "NULL font"); diff --git a/src/tilemap.c b/src/tilemap.c index a2a3e37..2a69b2f 100644 --- a/src/tilemap.c +++ b/src/tilemap.c @@ -87,23 +87,6 @@ akerr_ErrorContext *akgl_get_json_properties_integer(json_t *obj, char *key, int SUCCEED_RETURN(errctx); } -/** - * @brief Read a Tiled custom property declared as `number`. - * - * Tiled writes `float` for a floating-point property, so this matches nothing - * Tiled produces today -- akgl_get_json_properties_float is the one the loader - * uses. Kept because a hand-written or older map may still say `number`. - * - * @param obj The Tiled object whose `properties` array to search. Required. - * @param key The property name. Required. - * @param dest Receives the value as a `float`. Not written on any failure path. - * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`. - * @throws AKERR_KEY If the property is absent, or @p obj has no properties. - * @throws AKERR_TYPE If the property is not declared `number`, or its `value` is - * not numeric. - * @throws AKGL_ERR_HEAP If the string pool is exhausted. - */ akerr_ErrorContext *akgl_get_json_properties_number(json_t *obj, char *key, float *dest) { PREPARE_ERROR(errctx); @@ -114,22 +97,6 @@ akerr_ErrorContext *akgl_get_json_properties_number(json_t *obj, char *key, floa SUCCEED_RETURN(errctx); } -/** - * @brief Read a Tiled custom property declared as `float`. - * - * The spelling Tiled actually writes for a floating-point property. This is how - * the `scale` on a perspective marker is read. - * - * @param obj The Tiled object whose `properties` array to search. Required. - * @param key The property name. Required. - * @param dest Receives the value. Not written on any failure path. - * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`. - * @throws AKERR_KEY If the property is absent, or @p obj has no properties. - * @throws AKERR_TYPE If the property is not declared `float`, or its `value` is - * not numeric. - * @throws AKGL_ERR_HEAP If the string pool is exhausted. - */ akerr_ErrorContext *akgl_get_json_properties_float(json_t *obj, char *key, float *dest) { PREPARE_ERROR(errctx); @@ -140,24 +107,6 @@ akerr_ErrorContext *akgl_get_json_properties_float(json_t *obj, char *key, float SUCCEED_RETURN(errctx); } -/** - * @brief Read a Tiled custom property declared as `float`, at full precision. - * - * Same declared type as akgl_get_json_properties_float, but writing a `double`. - * The map's physics constants are `double`, which is the reason both exist. - * - * @param obj The Tiled object whose `properties` array to search. Required. - * @param key The property name. Required. - * @param dest Receives the value. Not written on any failure path. - * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`. - * @throws AKERR_KEY If the property is absent, or @p obj has no properties. - * akgl_tilemap_load_physics passes this status to - * akgl_get_json_with_default, which turns it into a default of 0.0. - * @throws AKERR_TYPE If the property is not declared `float`, or its `value` is - * not numeric. - * @throws AKGL_ERR_HEAP If the string pool is exhausted. - */ akerr_ErrorContext *akgl_get_json_properties_double(json_t *obj, char *key, double *dest) { PREPARE_ERROR(errctx); @@ -288,41 +237,6 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root, SUCCEED_RETURN(errctx); } -/** - * @brief Turn one Tiled object into a live actor, creating it if it is not already registered. - * - * The object's name is the actor's registry key, which is what lets the same - * actor be placed by more than one object: the first placement creates it and - * binds the character named in the object's `character` property, and every - * later one just takes another reference. Either way the object's position, - * visibility, and layer are copied onto the actor and the object keeps a - * pointer to it. - * - * Not declared in tilemap.h -- reachable only through - * akgl_tilemap_load_layer_objects. - * - * @param curobj The tilemap object, with its `name`, `x`, `y`, and - * `visible` already read from JSON. Required, unchecked, - * dereferenced at once. - * @param layerdatavalue The object's JSON, for the custom properties -- - * `character` and `state` -- that are not part of Tiled's - * own object fields. Required. - * @param layerid The layer this object sits on, copied onto the actor. - * @param dirname Directory to resolve paths against. Accepted for - * signature consistency; nothing here uses it. - * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_KEY If the object's name is empty -- an actor has to be - * addressable -- if the `character` or `state` property is absent, or if - * the named character is not in #AKGL_REGISTRY_CHARACTER. - * @throws AKERR_TYPE If `character` is not declared `string` or `state` not - * declared `int`. - * @throws AKERR_NULLPOINTER If akgl_actor_initialize refuses the name. - * @throws AKGL_ERR_HEAP If the actor or string pool is exhausted. - * - * @note An existing actor's `character` and position are overwritten by each - * later placement, so two objects naming one actor leave it wherever the - * last one put it rather than producing two. - */ akerr_ErrorContext *akgl_tilemap_load_layer_object_actor(akgl_TilemapObject *curobj, json_t *layerdatavalue, int layerid, akgl_String *dirname) { PREPARE_ERROR(errctx); @@ -444,34 +358,6 @@ akerr_ErrorContext *akgl_tilemap_load_layer_tile(akgl_Tilemap *dest, json_t *roo SUCCEED_RETURN(errctx); } -/** - * @brief Load one image layer: upload its image as a single texture. - * - * An image layer is one picture rather than a grid, which is what a parallax - * backdrop wants. The layer's `width` and `height` are taken from the loaded - * texture rather than from the JSON, so they are always the true pixel size. - * - * Not declared in tilemap.h -- reachable only through akgl_tilemap_load_layers. - * - * @param dest The map to load into. Required. - * @param root The layer's JSON object. Required. - * @param layerid Which layer slot to fill. Not bounds-checked. - * @param dirname Directory to resolve the layer's `image` path against. - * Required. Joined with a `/` rather than run through - * akgl_path_relative, so unlike a tileset image this path is not - * canonicalized and an absolute one will not work. - * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_NULLPOINTER If @p dest, @p root, or @p dirname is `NULL`. - * @throws AKERR_KEY If the layer has no `image`. - * @throws AKERR_TYPE If `image` is not a string. - * @throws AKGL_ERR_SDL If the image cannot be loaded. The message carries - * `SDL_GetError()`. - * @throws AKGL_ERR_HEAP If the string pool is exhausted. - * - * @note The joined path is truncated at - * #AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE without complaint, so an - * over-long path fails as a missing file rather than as a length error. - */ akerr_ErrorContext *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname) { PREPARE_ERROR(errctx); @@ -558,33 +444,6 @@ akerr_ErrorContext *akgl_tilemap_load_layers(akgl_Tilemap *dest, json_t *root, a SUCCEED_RETURN(errctx); } -/** - * @brief Give the map its own physics backend, if it asked for one. - * - * A map with a `physics.model` custom property gets its own backend, configured - * from `physics.gravity.x`/`.y`/`.z` and `physics.drag.x`/`.y`/`.z`, and - * `use_own_physics` is set so a caller knows to simulate through it. Each - * constant defaults to 0.0 when absent, so a map can name a model and override - * only what it cares about. - * - * Absence is the normal case at every level: a map with no properties at all, or - * with properties but no `physics.model`, uses the game's global backend and - * this returns success. That is why the AKERR_KEY handlers here are not error - * paths -- they are the "map did not ask" path. - * - * Not declared in tilemap.h -- reachable only through akgl_tilemap_load. - * - * @param dest The map to configure. Required in practice; not checked here, - * since akgl_tilemap_load has already done so. - * @param root The map's root JSON object. Required, likewise. - * @return `NULL` on success -- including when the map declared no physics at - * all -- otherwise an error context owned by the caller. - * @throws AKERR_KEY If `physics.model` names a backend that does not exist. - * @throws AKERR_TYPE If `physics.model` is not declared `string`, or one of the - * six constants is not declared `float`. - * @throws AKERR_NULLPOINTER If akgl_physics_factory refuses its arguments. - * @throws AKGL_ERR_HEAP If the string pool is exhausted. - */ akerr_ErrorContext *akgl_tilemap_load_physics(akgl_Tilemap *dest, json_t *root) { PREPARE_ERROR(e); @@ -913,7 +772,7 @@ akerr_ErrorContext *akgl_tilemap_draw_tileset(akgl_Tilemap *map, int tilesetidx) SUCCEED_RETURN(errctx); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_scale_actor(akgl_Tilemap *map, akgl_Actor *actor) +akerr_ErrorContext *akgl_tilemap_scale_actor(akgl_Tilemap *map, akgl_Actor *actor) { PREPARE_ERROR(e); @@ -930,7 +789,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_scale_actor(akgl_Tilemap *map, a SUCCEED_RETURN(e); } -akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_release(akgl_Tilemap *dest) +akerr_ErrorContext *akgl_tilemap_release(akgl_Tilemap *dest) { // Release all tileset textures // Release all image layer textures diff --git a/src/util.c b/src/util.c index 90da0b4..ad06506 100644 --- a/src/util.c +++ b/src/util.c @@ -27,7 +27,8 @@ * the join, so symlinks and `..` are folded out and the result is absolute. It * is the fallback, reached only once resolving @p path on its own has failed. * - * Not declared in util.h -- it is reachable only through akgl_path_relative. + * `static`: it is reachable only through akgl_path_relative, which is the only + * caller and the only sensible one. * * @param root Directory to resolve against, normally `dirname` of the file that * named @p path. Required. A trailing `/` is harmless; the join adds @@ -50,7 +51,7 @@ * scratch strings are never released. This is exactly the hazard AGENTS.md * warns about; it wants a `FAIL_BREAK`. */ -akerr_ErrorContext *akgl_path_relative_root(char *root, char *path, akgl_String *dst) +static akerr_ErrorContext *path_relative_root(char *root, char *path, akgl_String *dst) { PREPARE_ERROR(e); akgl_String *pathbuf; @@ -128,47 +129,11 @@ akerr_ErrorContext *akgl_path_relative(char *root, char *path, akgl_String *dst) } FINISH(e, true); if ( relative_to_root == true ) { - PASS(e, akgl_path_relative_root(root, path, dst)); + PASS(e, path_relative_root(root, path, dst)); } SUCCEED_RETURN(e); } -/** - * @brief Intended to resolve @p path against the directory holding @p from. Unfinished. - * - * The shape is there -- resolve @p from, take its `dirname` -- but the body - * stops before it does anything with @p path, and `*dst` is never written. Not - * declared in util.h, and nothing in the tree calls it. - * - * @param path The path to resolve. Required, and currently ignored past the - * `NULL` check. - * @param from A file whose directory @p path should be taken as relative to. - * Required. Must exist -- it is resolved with `realpath(3)`. - * @param dst Intended to receive the resolved path. Never written. Note the - * double indirection, which disagrees with the rest of the - * `akgl_path_relative*` family; see TODO.md item 40. - * @return `NULL` on success, otherwise an error context owned by the caller. - * @throws AKERR_NULLPOINTER If @p path or @p from is `NULL`. - * @throws ENOENT If @p from does not exist, along with any other `errno` - * `realpath(3)` raises. - * @throws AKGL_ERR_HEAP If the string pool is exhausted. - * - * @warning Known defect: the scratch string it claims is never released, so 256 - * calls exhaust the string pool. TODO.md, "Known and still open" item 4. - */ -akerr_ErrorContext *akgl_path_relative_from(char *path, char *from, akgl_String **dst) -{ - akgl_String *dirnamestr; - PREPARE_ERROR(e); - FAIL_ZERO_RETURN(e, path, AKERR_NULLPOINTER, "path"); - FAIL_ZERO_RETURN(e, from, AKERR_NULLPOINTER, "from"); - PASS(e, akgl_heap_next_string(&dirnamestr)); - PASS(e, aksl_realpath(from, (char *)&dirnamestr->data, sizeof(dirnamestr->data))); - dirname((char *)&dirnamestr->data); - - SUCCEED_RETURN(e); -} - akerr_ErrorContext *akgl_rectangle_points(akgl_RectanglePoints *dest, SDL_FRect *rect) { PREPARE_ERROR(errctx); diff --git a/tests/controller.c b/tests/controller.c index 2f947b6..047ad4f 100644 --- a/tests/controller.c +++ b/tests/controller.c @@ -24,16 +24,6 @@ #include "testutil.h" -/* - * akgl/controller.h declares these as akgl_controller_handle_*, but src has - * always defined them as gamepad_handle_*. Declared here under the names that - * actually link, so the tests can reach them without renaming shipped symbols. - */ -akerr_ErrorContext *gamepad_handle_button_down(void *appstate, SDL_Event *event); -akerr_ErrorContext *gamepad_handle_button_up(void *appstate, SDL_Event *event); -akerr_ErrorContext *gamepad_handle_added(void *appstate, SDL_Event *event); -akerr_ErrorContext *gamepad_handle_removed(void *appstate, SDL_Event *event); - /** @brief Keyboard id the tests bind their control maps to. */ #define TEST_KBID 11 /** @brief Gamepad id the tests bind their control maps to. */ @@ -424,7 +414,7 @@ akerr_ErrorContext *test_controller_gamepad_button_handlers(void) player->state = 0; player->movement_controls_face = false; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN); - TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), + TEST_EXPECT_OK(e, akgl_controller_handle_button_down(&appstate_placeholder, &event), "dpad down press handler"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_DOWN), "the dpad down handler did not set MOVING_DOWN (state %d)", player->state); @@ -432,7 +422,7 @@ akerr_ErrorContext *test_controller_gamepad_button_handlers(void) TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_FACE_DOWN), "the dpad down handler did not set FACE_DOWN (state %d)", player->state); - TEST_EXPECT_OK(e, gamepad_handle_button_up(&appstate_placeholder, &event), + TEST_EXPECT_OK(e, akgl_controller_handle_button_up(&appstate_placeholder, &event), "dpad down release handler"); TEST_ASSERT(e, AKGL_BITMASK_HASNOT(player->state, AKGL_ACTOR_STATE_MOVING_DOWN), "the dpad down release handler did not clear MOVING_DOWN (state %d)", player->state); @@ -441,55 +431,55 @@ akerr_ErrorContext *test_controller_gamepad_button_handlers(void) player->state = 0; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_UP); - TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), "dpad up press handler"); + TEST_EXPECT_OK(e, akgl_controller_handle_button_down(&appstate_placeholder, &event), "dpad up press handler"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_UP), "the dpad up handler did not set MOVING_UP (state %d)", player->state); - TEST_EXPECT_OK(e, gamepad_handle_button_up(&appstate_placeholder, &event), "dpad up release handler"); + TEST_EXPECT_OK(e, akgl_controller_handle_button_up(&appstate_placeholder, &event), "dpad up release handler"); player->state = 0; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_LEFT); - TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), "dpad left press handler"); + TEST_EXPECT_OK(e, akgl_controller_handle_button_down(&appstate_placeholder, &event), "dpad left press handler"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_LEFT), "the dpad left handler did not set MOVING_LEFT (state %d)", player->state); - TEST_EXPECT_OK(e, gamepad_handle_button_up(&appstate_placeholder, &event), "dpad left release handler"); + TEST_EXPECT_OK(e, akgl_controller_handle_button_up(&appstate_placeholder, &event), "dpad left release handler"); player->state = 0; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_RIGHT); - TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), "dpad right press handler"); + TEST_EXPECT_OK(e, akgl_controller_handle_button_down(&appstate_placeholder, &event), "dpad right press handler"); TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_RIGHT), "the dpad right handler did not set MOVING_RIGHT (state %d)", player->state); - TEST_EXPECT_OK(e, gamepad_handle_button_up(&appstate_placeholder, &event), "dpad right release handler"); + TEST_EXPECT_OK(e, akgl_controller_handle_button_up(&appstate_placeholder, &event), "dpad right release handler"); // With automatic facing on, the handler leaves facing to the actor's own // face function. player->state = 0; player->movement_controls_face = true; make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN); - TEST_EXPECT_OK(e, gamepad_handle_button_down(&appstate_placeholder, &event), + TEST_EXPECT_OK(e, akgl_controller_handle_button_down(&appstate_placeholder, &event), "dpad down press with automatic facing on"); TEST_ASSERT(e, AKGL_BITMASK_HASNOT(player->state, AKGL_ACTOR_STATE_FACE_DOWN), "the handler set facing even though the actor faces automatically (state %d)", player->state); - TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_button_down(NULL, &event), + TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_handle_button_down(NULL, &event), "dpad press handler with a NULL appstate"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, - gamepad_handle_button_down(&appstate_placeholder, NULL), + akgl_controller_handle_button_down(&appstate_placeholder, NULL), "dpad press handler with a NULL event"); - TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_button_up(NULL, &event), + TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_handle_button_up(NULL, &event), "dpad release handler with a NULL appstate"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, - gamepad_handle_button_up(&appstate_placeholder, NULL), + akgl_controller_handle_button_up(&appstate_placeholder, NULL), "dpad release handler with a NULL event"); // With no actor named "player" registered there is nothing to drive. CATCH(e, akgl_registry_init_actor()); make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, - gamepad_handle_button_down(&appstate_placeholder, &event), + akgl_controller_handle_button_down(&appstate_placeholder, &event), "dpad press handler with no player actor registered"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, - gamepad_handle_button_up(&appstate_placeholder, &event), + akgl_controller_handle_button_up(&appstate_placeholder, &event), "dpad release handler with no player actor registered"); } CLEANUP { } PROCESS(e) { @@ -506,22 +496,22 @@ akerr_ErrorContext *test_controller_device_events(void) // The dummy drivers present no gamepads, so the add and remove handlers // take their "unknown device" paths. Neither may crash. make_button_event(&event, SDL_EVENT_GAMEPAD_ADDED, 999, 0); - TEST_EXPECT_OK(e, gamepad_handle_added(&appstate_placeholder, &event), + TEST_EXPECT_OK(e, akgl_controller_handle_added(&appstate_placeholder, &event), "handling an add for a device that cannot be opened"); make_button_event(&event, SDL_EVENT_GAMEPAD_REMOVED, 999, 0); - TEST_EXPECT_OK(e, gamepad_handle_removed(&appstate_placeholder, &event), + TEST_EXPECT_OK(e, akgl_controller_handle_removed(&appstate_placeholder, &event), "handling a remove for a device that was never open"); - TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_added(NULL, &event), + TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_handle_added(NULL, &event), "add handler with a NULL appstate"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, - gamepad_handle_added(&appstate_placeholder, NULL), + akgl_controller_handle_added(&appstate_placeholder, NULL), "add handler with a NULL event"); - TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, gamepad_handle_removed(NULL, &event), + TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_handle_removed(NULL, &event), "remove handler with a NULL appstate"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, - gamepad_handle_removed(&appstate_placeholder, NULL), + akgl_controller_handle_removed(&appstate_placeholder, NULL), "remove handler with a NULL event"); } CLEANUP { } PROCESS(e) { diff --git a/tests/game.c b/tests/game.c index 75dbae1..4832e1b 100644 --- a/tests/game.c +++ b/tests/game.c @@ -24,14 +24,6 @@ #include "testutil.h" -/* - * Exported by src/game.c but not declared in akgl/game.h, because they are - * savegame internals rather than part of the supported surface. Declared here - * so the tests can reach them without widening the public API. - */ -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp); -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion); - /** @brief Scratch savegame path, created and removed by the tests that use it. */ static char savepath[] = "akgl_test_savegame.bin"; /** @brief Scratch path for deliberately malformed savegames. */ diff --git a/tests/header_pool_override.c b/tests/header_pool_override.c new file mode 100644 index 0000000..517eeab --- /dev/null +++ b/tests/header_pool_override.c @@ -0,0 +1,42 @@ +/** + * @file header_pool_override.c + * @brief Checks that the compile-time pool-size overrides in heap.h actually fire. + * + * `heap.h` wraps every `AKGL_MAX_HEAP_*` ceiling in an `#ifndef` so a game that + * needs more actors can define its own before including it. That mechanism was + * dead until 0.5.0: `actor.h`, `sprite.h` and `character.h` defined the same + * four unconditionally, and `heap.h` includes all three *above* its own + * `#ifndef` block, so whichever landed first won and the guard never fired. The + * override was documented, and did nothing. + * + * The assertion is the compile. Nothing here links against the pools -- it + * cannot, because the library was built with the default ceilings and this + * translation unit deliberately disagrees with it. That disagreement is the + * documented consequence of overriding a ceiling, not something this file is + * working around: the arrays are sized at compile time, so the library and + * everything linking it have to agree. + */ + +#define AKGL_MAX_HEAP_ACTOR 128 +#define AKGL_MAX_HEAP_STRING 512 + +#include + +#if AKGL_MAX_HEAP_ACTOR != 128 +#error "heap.h overrode a caller's AKGL_MAX_HEAP_ACTOR; the #ifndef guard is dead again" +#endif + +#if AKGL_MAX_HEAP_STRING != 512 +#error "heap.h overrode a caller's AKGL_MAX_HEAP_STRING; the #ifndef guard is dead again" +#endif + +/* The derived ceilings have to follow the override rather than a stale literal. */ +#if AKGL_MAX_HEAP_SPRITE != (128 * 16) +#error "AKGL_MAX_HEAP_SPRITE no longer derives from AKGL_MAX_HEAP_ACTOR" +#endif + +int akgl_header_selftest_pool_override(void); +int akgl_header_selftest_pool_override(void) +{ + return 0; +}