Give every exported function a declaration, and check that it stays that way
Closes internal-consistency items 7 through 15. Nineteen non-static functions were in the ABI with no declaration anywhere, so no consumer could call them and any consumer could collide with them. The four gamepad_handle_* functions are the ones that mattered: controller.h declared akgl_controller_handle_button_down and three siblings that did not exist, so anything compiled against the header alone failed to link. The definitions carry the declared names now, which also closes Defects -> Known and still open item 10, and their documentation moved to the header. The rest are either declared under a "part of the internal API" block -- akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to declare for itself, plus six tilemap loader helpers the untested-loader work wants to reach -- or static, which is what the four save iterators and load_objectnamemap should always have been. akgl_path_relative_from is deleted: declared nowhere, called from nowhere, never wrote its output, and leaked a pooled string on every call, so it closes Known and still open item 4 and item 40 by ceasing to exist. scripts/check_api_surface.sh keeps it closed. It reads the built library's dynamic symbol table and every public header with comments stripped, and fails on an exported akgl_* symbol that is declared nowhere. Stripping comments is the whole point -- four of these were mentioned in controller.h prose, which is how they went unnoticed. The pool-size ceilings are defined once, in heap.h, so the #ifndef override hook fires for the first time; actor.h, sprite.h and character.h were defining the same four unconditionally from headers heap.h includes above its own guard. tests/header_pool_override.c fails the compile if that regresses. Also here: (void) rather than () on the twelve no-argument entry points, AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix dropped, and the six parameter-name mismatches. akgl_get_json_with_default had its two contexts swapped rather than merely misspelled -- the incoming one was `err` and its own was `e`, which is the name reserved for an incoming one. 24/24 pass, reindent --check clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -302,6 +302,20 @@ endforeach()
|
|||||||
|
|
||||||
add_test(NAME semver_unit COMMAND akgl_test_semver_unit)
|
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
|
||||||
|
$<TARGET_FILE:akgl>
|
||||||
|
${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
|
# 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
|
# 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
|
# 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}")
|
list(APPEND AKGL_HEADER_SELFTEST_SOURCES "${generated}")
|
||||||
endforeach()
|
endforeach()
|
||||||
target_sources(akgl_test_headers PRIVATE ${AKGL_HEADER_SELFTEST_SOURCES})
|
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
|
# TIMEOUT is generous because CTest applies the same property to `ctest -T
|
||||||
# memcheck`, where every suite runs under valgrind at something like twenty
|
# memcheck`, where every suite runs under valgrind at something like twenty
|
||||||
|
|||||||
197
TODO.md
197
TODO.md
@@ -68,100 +68,110 @@ with no test to notice.
|
|||||||
|
|
||||||
### 2. Header/implementation surface drift
|
### 2. Header/implementation surface drift
|
||||||
|
|
||||||
7. **Nineteen non-static functions are defined in `src/` but declared in no
|
**Items 7, 8, 9, 11, 12, 13, 14 and 15 are resolved in 0.5.0.** Item 10 is
|
||||||
header.** They have external linkage and public-looking names, so they are
|
resolved for every pair it listed.
|
||||||
part of the ABI whether intended or not, and no consumer can call them:
|
|
||||||
|
|
||||||
`akgl_game_load_objectnamemap`, `akgl_game_load_versioncmp`,
|
7. **Nineteen non-static functions were defined in `src/` but declared in no
|
||||||
`akgl_game_save_actors`, `akgl_game_save_actorname_iterator`,
|
header.** Each is now declared or `static`:
|
||||||
`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`).
|
|
||||||
|
|
||||||
Each should be either declared in its header or made `static`. Note that
|
- `akgl_controller_handle_button_down`, `_button_up`, `_added`, `_removed`
|
||||||
`tilemap.h` already has a "part of the internal API, exposed here for unit
|
were defined as `gamepad_handle_*` while `controller.h` declared the
|
||||||
testing" block — the tilemap entries belong there.
|
`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**
|
**This is enforced now.** `scripts/check_api_surface.sh` reads the built
|
||||||
(`include/akgl/game.h:100`). Same failure mode as **Defects → Known and still
|
library's dynamic symbol table, strips comments out of every public header,
|
||||||
open #10**, which covers the four `akgl_controller_handle_*` declarations;
|
and fails when an exported `akgl_*` symbol is declared nowhere. It runs as
|
||||||
fold this one into that item.
|
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`
|
8. **`akgl_game_init_screen` was declared and never defined.** The declaration
|
||||||
(`src/actor.c:185`) is bare; `akgl_character_load_json_inner` and
|
is gone. Screen setup is `akgl_render_2d_init`.
|
||||||
`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.
|
|
||||||
|
|
||||||
10. **Parameter names disagree between declaration and definition.** Doxygen
|
9. **Static helpers used three naming styles.** They drop the `akgl_` prefix
|
||||||
documents the header spelling, so the generated docs describe names the
|
now, which is what it is for: `character_load_json_inner`,
|
||||||
implementation does not use:
|
`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 |
|
10. **Parameter names disagreed between declaration and definition.** All six
|
||||||
|---|---|
|
pairs agree now. `akgl_character_initialize` takes `obj`,
|
||||||
| `character.h:41` `basechar` | `character.c:21` `obj` |
|
`akgl_character_state_sprites_iterate` takes `props`, `akgl_heap_release_character`
|
||||||
| `character.h:69` `props` | `character.c:75` `registry` |
|
takes `ptr`, `akgl_set_property` takes `value`, and the two `akgl_tilemap_draw*`
|
||||||
| ~~`heap.h:121` `ptr`~~ | ~~`heap.c:143` `basechar`~~ — fixed in 0.5.0 |
|
functions take `map` -- the header called them `dest` and documented them as
|
||||||
| `registry.h:97` `value` | `registry.c:163` `src` |
|
"Output destination populated by the function", which they are not.
|
||||||
| `json_helpers.h:134` `e` | `json_helpers.c:149` `err` |
|
|
||||||
| `tilemap.h:134,143` `dest` | `tilemap.c:646,767` `map` |
|
|
||||||
|
|
||||||
The `tilemap.h` pair is the most misleading: the parameter is the map being
|
`akgl_get_json_with_default` was the interesting one: it took the incoming
|
||||||
*read* and drawn, but it is named `dest` and documented as "Output
|
context as `err` and named its *own* context `e`, which is the name the
|
||||||
destination populated by the function".
|
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
|
11. **Object-pool size macros were defined twice and the override hook was
|
||||||
dead.** `heap.h:15-29` wraps `AKGL_MAX_HEAP_ACTOR`, `_SPRITE`, `_SPRITESHEET`,
|
dead.** `AKGL_MAX_HEAP_ACTOR`, `_SPRITE`, `_SPRITESHEET` and `_CHARACTER` are
|
||||||
`_CHARACTER`, and `_STRING` in `#ifndef` guards so a consumer can override
|
defined once, in `heap.h`, inside the `#ifndef` guards that were always
|
||||||
them, but `actor.h:65`, `sprite.h:19-20`, and `character.h:14` define the same
|
supposed to make them overridable. `actor.h`, `sprite.h` and `character.h`
|
||||||
four unconditionally and are included from `heap.h:9-11`. Whichever header
|
no longer define them.
|
||||||
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.
|
|
||||||
|
|
||||||
12. **Headers rely on their includers for types.** `iterator.h` used `uint32_t`
|
`tests/header_pool_override.c` is the regression test: it defines its own
|
||||||
without `<stdint.h>`; `json_helpers.h` used `json_t` without `<jansson.h>`;
|
ceilings, includes `heap.h`, and `#error`s if the guard did not fire or if
|
||||||
`util.h` used `SDL_FRect` and `bool` without any SDL include. Each compiled
|
`AKGL_MAX_HEAP_SPRITE` stopped deriving from `AKGL_MAX_HEAP_ACTOR`. The
|
||||||
only because of `.c`-file include ordering.
|
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 `<stdint.h>`; `json_helpers.h` used `json_t` without
|
||||||
|
`<jansson.h>`; `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`
|
**Resolved, and enforced rather than merely fixed.** `AKGL_PUBLIC_HEADERS`
|
||||||
in `CMakeLists.txt` is now the single list behind both `install()` and a
|
in `CMakeLists.txt` is now the single list behind both `install()` and a
|
||||||
generated translation unit per header — each including exactly that header
|
generated translation unit per header -- each including exactly that header
|
||||||
and nothing before it — linked into the `headers` suite. A header that ships
|
and nothing before it -- linked into the `headers` suite. A header that
|
||||||
is a header that is checked, and adding one to that list is all a new header
|
ships is a header that is checked.
|
||||||
needs.
|
|
||||||
|
|
||||||
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.
|
`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
|
That is the argument for generating the check off the install list rather
|
||||||
of hand-listing the headers thought to be at risk.
|
than hand-listing the headers somebody thought were at risk.
|
||||||
|
|
||||||
13. **Include spelling is split between quoted and angled forms for the same
|
13. **Include spelling was split between quoted and angled forms.**
|
||||||
directory.** **Resolved.** Every in-project include in a public header uses
|
**Resolved.** Every in-project include in a public header uses
|
||||||
`#include <akgl/sibling.h>`. `tests/*.c` still use `#include "testutil.h"`,
|
`#include <akgl/sibling.h>`, and `staticstring.h`'s `#include "string.h"` --
|
||||||
correctly: that one is a test-local header rather than an installed one.
|
a relative-first lookup that reached the system header by accident -- is
|
||||||
|
`#include <string.h>`. `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()`,
|
14. **Empty parameter lists.** **Resolved.** `akgl_game_init`,
|
||||||
`akgl_registry_init*()`, `akgl_game_init()`, `akgl_game_init_screen()`, and
|
`akgl_game_update_fps`, `akgl_heap_init`, `akgl_heap_init_actor` and the
|
||||||
`akgl_game_updateFPS()` declare `()` rather than `(void)`, while
|
eight `akgl_registry_init*` functions declare and define `(void)`. Before
|
||||||
`akgl_controller_list_keyboards(void)`, `akgl_controller_open_gamepads(void)`,
|
C23 `()` means "unspecified arguments" and suppresses argument checking, so
|
||||||
`akgl_game_lowfps(void)`, and `akgl_game_state_lock(void)` use `(void)`.
|
these were the entry points a caller could pass anything to.
|
||||||
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`.
|
|
||||||
|
|
||||||
15. **`AKERR_NOIGNORE` is applied inconsistently at definition sites.** Headers
|
15. **`AKERR_NOIGNORE` was applied inconsistently at definition sites.**
|
||||||
use it uniformly (except `akgl_sprite_sheet_coords_for_frame`, `sprite.h:86`,
|
**Resolved.** It is on the declarations, where it does its work, and on no
|
||||||
which omits it). Definitions are split even within one file: `registry.c:55,120,163,172`
|
definition in `src/`.
|
||||||
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.
|
|
||||||
|
|
||||||
### 3. Error-handling pattern
|
### 3. Error-handling pattern
|
||||||
|
|
||||||
@@ -359,13 +369,14 @@ with no test to notice.
|
|||||||
NULL filepath reports `AKERR_NULLPOINTER`.
|
NULL filepath reports `AKERR_NULLPOINTER`.
|
||||||
|
|
||||||
40. **`akgl_path_relative` and `akgl_path_relative_from` disagree on the output
|
40. **`akgl_path_relative` and `akgl_path_relative_from` disagree on the output
|
||||||
parameter.** `akgl_path_relative` and `akgl_path_relative_root` take
|
parameter.** **Moot in 0.5.0**: `akgl_path_relative_from` is deleted (see
|
||||||
`akgl_String *dst`; `akgl_path_relative_from` takes `akgl_String **dst`
|
**Defects → Known and still open** item 4) and `akgl_path_relative_root` is
|
||||||
(`src/util.c:105`). The `**` form matches the rest of the library
|
`static`, so `akgl_path_relative` is the only member of the family left and
|
||||||
(`akgl_get_json_string_value`, `akgl_get_property`, `akgl_heap_next_string`),
|
there is nothing left to disagree with. It still takes `akgl_String *dst`
|
||||||
which allocate when `*dest` is NULL. Related: **Defects → Known and still
|
rather than the `**` form the rest of the library uses, which stays as
|
||||||
open #4**, which covers the fact that `akgl_path_relative_from` never writes
|
**item 41**'s question of naming and this one's question of shape -- but
|
||||||
`*dst` at all.
|
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
|
41. **`dst` vs `dest` for output parameters.** `akgl_string_copy` and the
|
||||||
`akgl_path_relative*` family use `dst`; everything else uses `dest`.
|
`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
|
`akgl_physics_init_arcade` and `akgl_render_init2d` silently ignore
|
||||||
configuration. `akgl_game_init` does call it; a caller that does not use
|
configuration. `akgl_game_init` does call it; a caller that does not use
|
||||||
`akgl_game_init` does not get it.
|
`akgl_game_init` does not get it.
|
||||||
4. **`akgl_path_relative_from` is a stub that leaks.** `src/util.c:105` claims a
|
4. **`akgl_path_relative_from` is a stub that leaks.** **Deleted in 0.5.0.**
|
||||||
heap string, never writes `*dst`, and never releases it. 256 calls exhaust
|
It claimed a heap string, never wrote `*dst`, and never released it, so 256
|
||||||
the string pool.
|
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.**
|
5. **`akgl_compare_sdl_surfaces` memcmps without checking geometry.**
|
||||||
`src/util.c:208` compares `s1->pitch * s1->h` bytes of `s2` without verifying
|
`src/util.c:208` compares `s1->pitch * s1->h` bytes of `s2` without verifying
|
||||||
the surfaces share dimensions, pitch, or format.
|
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.
|
decide whether to make them symmetric or document the split.
|
||||||
9. **`tests/util.c` defines `test_akgl_collide_point_rectangle_logic` but
|
9. **`tests/util.c` defines `test_akgl_collide_point_rectangle_logic` but
|
||||||
`main()` never calls it.**
|
`main()` never calls it.**
|
||||||
10. **`controller.h` declares functions that do not exist.** It declares
|
10. **`controller.h` declares functions that do not exist.** **Fixed in 0.5.0**;
|
||||||
`akgl_controller_handle_button_down`, `_button_up`, `_added`, and `_removed`,
|
the definitions carry the declared `akgl_controller_handle_*` names now. See
|
||||||
but `src/controller.c` defines them as `gamepad_handle_*`. Anything compiled
|
internal-consistency item 7, and `scripts/check_api_surface.sh`, which is
|
||||||
against the header alone fails to link.
|
what stops this class of drift coming back.
|
||||||
11. **`akgl_controller_pushmap` and `akgl_controller_default` accept negative map
|
11. **`akgl_controller_pushmap` and `akgl_controller_default` accept negative map
|
||||||
ids.** Both check `controlmapid >= AKGL_MAX_CONTROL_MAPS` but not
|
ids.** Both check `controlmapid >= AKGL_MAX_CONTROL_MAPS` but not
|
||||||
`controlmapid < 0`, so a negative id indexes before `GAME_ControlMaps`.
|
`controlmapid < 0`, so a negative id indexes before `GAME_ControlMaps`.
|
||||||
|
|||||||
@@ -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. */
|
/** @brief Children one actor can carry. A child moves with its parent rather than simulating. */
|
||||||
#define AKGL_ACTOR_MAX_CHILDREN 8
|
#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. */
|
/** @brief Represents a live actor, including state, motion, hierarchy, and behavior callbacks. */
|
||||||
typedef struct akgl_Actor {
|
typedef struct akgl_Actor {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include <akgl/sprite.h>
|
#include <akgl/sprite.h>
|
||||||
|
|
||||||
#define AKGL_CHARACTER_MAX_NAME_LENGTH 128
|
#define AKGL_CHARACTER_MAX_NAME_LENGTH 128
|
||||||
#define AKGL_MAX_HEAP_CHARACTER 256
|
|
||||||
|
|
||||||
/** @brief Defines reusable movement parameters and actor-state sprite bindings. */
|
/** @brief Defines reusable movement parameters and actor-state sprite bindings. */
|
||||||
typedef struct akgl_Character {
|
typedef struct akgl_Character {
|
||||||
@@ -62,7 +61,7 @@ typedef struct akgl_Character {
|
|||||||
* #AKGL_REGISTRY_CHARACTER -- in practice, because akgl_registry_init
|
* #AKGL_REGISTRY_CHARACTER -- in practice, because akgl_registry_init
|
||||||
* has not run and the registry id is still 0.
|
* 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.
|
* @brief Bind a sprite to one exact combination of actor-state bits.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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);
|
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`
|
* A whole-application shortcut rather than a control-map binding: it looks the
|
||||||
* and is `static`-in-spirit -- it is not declared anywhere -- so a caller that
|
* actor up by the literal name `"player"` in #AKGL_REGISTRY_ACTOR instead of
|
||||||
* uses this declaration compiles and then fails to link. TODO.md, "Known and
|
* being told which actor to act on, so it only ever drives one. The
|
||||||
* still open" item 10.
|
* akgl_actor_cmhf_* handlers are the general form.
|
||||||
*
|
*
|
||||||
* @param appstate Application state supplied by SDL.
|
* Facing follows movement unless the actor sets `movement_controls_face`, which
|
||||||
* @param event SDL input event to process.
|
* is how an actor that aims independently of the direction it walks opts out.
|
||||||
* @return Nothing; it cannot be called.
|
*
|
||||||
|
* @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);
|
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
|
* The counterpart to akgl_controller_handle_button_down. It clears only the movement
|
||||||
* `gamepad_handle_button_up`.
|
* 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 appstate Passed through from SDL. Required as a non-`NULL` token only.
|
||||||
* @param event SDL input event to process.
|
* @param event The button or key release event. Required.
|
||||||
* @return Nothing; it cannot be called.
|
* @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);
|
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
|
* SDL delivers no button events from an unopened gamepad, so a device that
|
||||||
* `gamepad_handle_added`, which opens a newly plugged-in gamepad.
|
* 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.
|
* The mapping is logged because a controller with no entry in the database
|
||||||
* @param event SDL input event to process.
|
* produces no button events at all, and that is otherwise indistinguishable
|
||||||
* @return Nothing; it cannot be called.
|
* 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);
|
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
|
* An unplugged device SDL still has open is a leaked handle, so this closes it.
|
||||||
* `gamepad_handle_removed`, which closes an unplugged gamepad.
|
* 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 appstate Passed through from SDL. Required as a non-`NULL` token only.
|
||||||
* @param event SDL input event to process.
|
* @param event The SDL_EVENT_GAMEPAD_REMOVED event. Required.
|
||||||
* @return Nothing; it cannot be called.
|
* @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);
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_removed(void *appstate, SDL_Event *event);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
* @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.
|
* a failure part-way through leaves the mutex held.
|
||||||
*/
|
*/
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init();
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init(void);
|
||||||
/**
|
|
||||||
* @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();
|
|
||||||
/**
|
/**
|
||||||
* @brief Count this frame, and recompute the frame rate once a second has passed.
|
* @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
|
* the low-FPS threshold and so fires `lowfpsfunc` on every frame until the first
|
||||||
* second is up.
|
* 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.
|
* @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);
|
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_
|
#endif //_AKGL_GAME_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
|
* -- but check it anyway; the signature exists so a layer that needs
|
||||||
* real setup has somewhere to report from.
|
* 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.
|
* @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.
|
* @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.
|
* @brief Claim a free actor slot.
|
||||||
* @param dest Receives a pointer to the free slot. Required, and **not**
|
* @param dest Receives a pointer to the free slot. Required, and **not**
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ extern SDL_PropertiesID AKGL_REGISTRY_PROPERTIES;
|
|||||||
* @warning This does not create #AKGL_REGISTRY_PROPERTIES -- see the warning on
|
* @warning This does not create #AKGL_REGISTRY_PROPERTIES -- see the warning on
|
||||||
* this file.
|
* this file.
|
||||||
*/
|
*/
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init();
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init(void);
|
||||||
/**
|
/**
|
||||||
* @brief Create the music registry.
|
* @brief Create the music registry.
|
||||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
* @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
|
* @note Overwrites the existing id without destroying the old set, so calling it
|
||||||
* twice leaks the first one along with everything registered in 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.
|
* @brief Create the font registry.
|
||||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
* @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
|
* @note Overwrites the existing id without destroying the old set. See
|
||||||
* akgl_registry_init_music.
|
* 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.
|
* @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
|
* @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.
|
* 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.
|
* @brief Create the sprite registry.
|
||||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
* @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
|
* @note Overwrites the existing id without destroying the old set. See
|
||||||
* akgl_registry_init_music.
|
* 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.
|
* @brief Create the spritesheet registry.
|
||||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
* @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
|
* @note Overwrites the existing id without destroying the old set. See
|
||||||
* akgl_registry_init_music.
|
* 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.
|
* @brief Create the character registry.
|
||||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
* @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
|
* @note Overwrites the existing id without destroying the old set. See
|
||||||
* akgl_registry_init_music.
|
* 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.
|
* @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
|
* @note Overwrites the existing id without destroying the old set, so every
|
||||||
* property set before a second call is lost.
|
* 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.
|
* @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.
|
* and `MOVING_OUT`, so those two states cannot be named from JSON at all.
|
||||||
* TODO.md items 24-26.
|
* 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.
|
* @brief Set one configuration property.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -28,8 +28,6 @@
|
|||||||
#define AKGL_SPRITE_MAX_REGISTRY_SIZE 1024
|
#define AKGL_SPRITE_MAX_REGISTRY_SIZE 1024
|
||||||
#define AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH 512
|
#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. */
|
/** @brief Stores a loaded spritesheet texture and frame geometry. */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -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.
|
* 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
|
* @param map The map to draw from. Required.
|
||||||
* despite the name.
|
|
||||||
* @param viewport The rectangle of the map, in map pixels, that is on screen.
|
* @param viewport The rectangle of the map, in map pixels, that is on screen.
|
||||||
* Required. Usually the global `akgl_camera`.
|
* Required. Usually the global `akgl_camera`.
|
||||||
* @param layeridx Which layer to draw, 0 to `numlayers - 1`. **Not**
|
* @param layeridx Which layer to draw, 0 to `numlayers - 1`. **Not**
|
||||||
* bounds-checked: an index past the end reads a neighbouring
|
* bounds-checked: an index past the end reads a neighbouring
|
||||||
* layer, or past the array.
|
* layer, or past the array.
|
||||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
* @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
|
* @throws AKERR_* Whatever the renderer's `draw_texture` raises. The first
|
||||||
* failure abandons the rest of the layer.
|
* 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.
|
* @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
|
* matches the source image the offset table is right -- and if tiles are shifted
|
||||||
* or duplicated, it is not. The `charviewer` utility uses it.
|
* or duplicated, it is not. The `charviewer` utility uses it.
|
||||||
*
|
*
|
||||||
* @param dest The map holding the tileset. Required. Not an output
|
* @param map The map holding the tileset. Required.
|
||||||
* parameter despite the name.
|
|
||||||
* @param tilesetidx Which tileset to draw, 0 to `numtilesets - 1`.
|
* @param tilesetidx Which tileset to draw, 0 to `numtilesets - 1`.
|
||||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
* @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
|
* @throws AKERR_OUTOFBOUNDS If @p tilesetidx is at or above `numtilesets`. A
|
||||||
* negative index is not rejected.
|
* negative index is not rejected.
|
||||||
* @throws AKERR_* Whatever the renderer's `draw_texture` raises.
|
* @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
|
* tileset's, so a tileset whose tiles are a different size from the map's
|
||||||
* is reconstructed at the wrong pitch.
|
* 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.
|
* 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);
|
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_
|
#endif //_AKGL_TILEMAP_H_
|
||||||
|
|||||||
86
scripts/check_api_surface.sh
Executable file
86
scripts/check_api_surface.sh
Executable file
@@ -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 <libakgl.so> <header-dir> [<header-dir> ...]
|
||||||
|
#
|
||||||
|
# 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 <libakgl.so> <header-dir> [<header-dir> ...]" >&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
|
||||||
16
src/actor.c
16
src/actor.c
@@ -328,7 +328,7 @@ void akgl_registry_iterate_actor(void *userdata, SDL_PropertiesID registry, cons
|
|||||||
} FINISH_NORETURN(errctx);
|
} 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);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "actor");
|
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);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
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);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
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);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
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);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
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);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
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);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
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);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
||||||
|
|||||||
20
src/audio.c
20
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;
|
SDL_AudioSpec spec;
|
||||||
|
|
||||||
@@ -322,7 +322,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_init(void)
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_shutdown(void)
|
akerr_ErrorContext *akgl_audio_shutdown(void)
|
||||||
{
|
{
|
||||||
SDL_AudioStream *closing = audiostream;
|
SDL_AudioStream *closing = audiostream;
|
||||||
|
|
||||||
@@ -337,7 +337,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_shutdown(void)
|
|||||||
SUCCEED_RETURN(errctx);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
PASS(errctx, check_voice(voice));
|
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);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
PASS(errctx, check_voice(voice));
|
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);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_stop(int voice)
|
akerr_ErrorContext *akgl_audio_stop(int voice)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
PASS(errctx, check_voice(voice));
|
PASS(errctx, check_voice(voice));
|
||||||
@@ -382,7 +382,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_stop(int voice)
|
|||||||
SUCCEED_RETURN(errctx);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
PASS(errctx, check_voice(voice));
|
PASS(errctx, check_voice(voice));
|
||||||
@@ -401,7 +401,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_waveform(int voice, akgl_AudioWave
|
|||||||
SUCCEED_RETURN(errctx);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
PASS(errctx, check_voice(voice));
|
PASS(errctx, check_voice(voice));
|
||||||
@@ -422,7 +422,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_envelope(int voice, uint32_t attac
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_volume(float32_t level)
|
akerr_ErrorContext *akgl_audio_volume(float32_t level)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_NONZERO_RETURN(
|
FAIL_NONZERO_RETURN(
|
||||||
@@ -439,7 +439,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_volume(float32_t level)
|
|||||||
SUCCEED_RETURN(errctx);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
PASS(errctx, check_voice(voice));
|
PASS(errctx, check_voice(voice));
|
||||||
@@ -452,7 +452,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_audio_voice_active(int voice, bool *acti
|
|||||||
SUCCEED_RETURN(errctx);
|
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;
|
akgl_AudioVoice *voice = NULL;
|
||||||
float32_t sum = 0.0f;
|
float32_t sum = 0.0f;
|
||||||
|
|||||||
@@ -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
|
// 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
|
// 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);
|
PREPARE_ERROR(errctx);
|
||||||
akgl_Sprite *spriteptr;
|
akgl_Sprite *spriteptr;
|
||||||
@@ -80,7 +80,7 @@ void akgl_character_state_sprites_iterate(void *userdata, SDL_PropertiesID regis
|
|||||||
ATTEMPT {
|
ATTEMPT {
|
||||||
FAIL_ZERO_BREAK(errctx, opflags, AKERR_NULLPOINTER, "Character state sprite iterator received null iterator op pointer");
|
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");
|
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);
|
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) ) {
|
if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_RELEASE) ) {
|
||||||
CATCH(errctx, akgl_heap_release_sprite(spriteptr));
|
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 AKERR_OUTOFBOUNDS If the array is indexed past its end.
|
||||||
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
* @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;
|
int i = 0;
|
||||||
long newstate = 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 AKERR_OUTOFBOUNDS If an array is indexed past its end.
|
||||||
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
* @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);
|
PREPARE_ERROR(errctx);
|
||||||
json_t *mappings = NULL;
|
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_string_value((json_t *)json, "name", &tmpstr2));
|
||||||
|
|
||||||
CATCH(errctx, akgl_get_json_array_value((json_t *)curmapping, "state", &statearray));
|
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));
|
CATCH(errctx, akgl_get_json_string_value((json_t *)curmapping, "sprite", &tmpstr));
|
||||||
FAIL_ZERO_BREAK(
|
FAIL_ZERO_BREAK(
|
||||||
@@ -236,7 +236,7 @@ akerr_ErrorContext *akgl_character_load_json(char *filename)
|
|||||||
AKERR_NULLPOINTER,
|
AKERR_NULLPOINTER,
|
||||||
"Error while loading character from %s on line %d: %s", filename, error.line, error.text
|
"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));
|
CATCH(errctx, akgl_get_json_integer_value(json, "speedtime", (int *)&obj->speedtime));
|
||||||
obj->speedtime = obj->speedtime * AKGL_TIME_ONEMS_NS;
|
obj->speedtime = obj->speedtime * AKGL_TIME_ONEMS_NS;
|
||||||
CATCH(errctx, akgl_get_json_number_value(json, "speed_x", &obj->sx));
|
CATCH(errctx, akgl_get_json_number_value(json, "speed_x", &obj->sx));
|
||||||
|
|||||||
102
src/controller.c
102
src/controller.c
@@ -182,7 +182,7 @@ static bool keybuffer_take(akgl_Keystroke *dest)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_list_keyboards(void)
|
akerr_ErrorContext *akgl_controller_list_keyboards(void)
|
||||||
{
|
{
|
||||||
int count;
|
int count;
|
||||||
SDL_KeyboardID *keyboards = SDL_GetKeyboards(&count);
|
SDL_KeyboardID *keyboards = SDL_GetKeyboards(&count);
|
||||||
@@ -206,7 +206,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_list_keyboards(void)
|
|||||||
SUCCEED_RETURN(e);
|
SUCCEED_RETURN(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_open_gamepads(void)
|
akerr_ErrorContext *akgl_controller_open_gamepads(void)
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@@ -302,32 +302,7 @@ akerr_ErrorContext *akgl_controller_handle_event(void *appstate, SDL_Event *even
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
akerr_ErrorContext *akgl_controller_handle_button_down(void *appstate, SDL_Event *event)
|
||||||
* @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)
|
|
||||||
{
|
{
|
||||||
akgl_Actor *player = NULL;
|
akgl_Actor *player = NULL;
|
||||||
|
|
||||||
@@ -378,23 +353,7 @@ akerr_ErrorContext *gamepad_handle_button_down(void *appstate, SDL_Event *event)
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
akerr_ErrorContext *akgl_controller_handle_button_up(void *appstate, SDL_Event *event)
|
||||||
* @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)
|
|
||||||
{
|
{
|
||||||
akgl_Actor *player = NULL;
|
akgl_Actor *player = NULL;
|
||||||
|
|
||||||
@@ -433,31 +392,7 @@ akerr_ErrorContext *gamepad_handle_button_up(void *appstate, SDL_Event *event)
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
akerr_ErrorContext *akgl_controller_handle_added(void *appstate, SDL_Event *event)
|
||||||
* @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)
|
|
||||||
{
|
{
|
||||||
SDL_JoystickID which;
|
SDL_JoystickID which;
|
||||||
SDL_Gamepad *gamepad = NULL;
|
SDL_Gamepad *gamepad = NULL;
|
||||||
@@ -487,22 +422,7 @@ akerr_ErrorContext *gamepad_handle_added(void *appstate, SDL_Event *event)
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
akerr_ErrorContext *akgl_controller_handle_removed(void *appstate, SDL_Event *event)
|
||||||
* @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)
|
|
||||||
{
|
{
|
||||||
SDL_JoystickID which;
|
SDL_JoystickID which;
|
||||||
SDL_Gamepad *gamepad = NULL;
|
SDL_Gamepad *gamepad = NULL;
|
||||||
@@ -521,7 +441,7 @@ akerr_ErrorContext *gamepad_handle_removed(void *appstate, SDL_Event *event)
|
|||||||
SUCCEED_RETURN(errctx);
|
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;
|
int newmapid = 0;
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
@@ -538,7 +458,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akg
|
|||||||
SUCCEED_RETURN(errctx);
|
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_ControlMap *controlmap;
|
||||||
akgl_Control control;
|
akgl_Control control;
|
||||||
@@ -630,7 +550,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
|
|||||||
} FINISH(errctx, true);
|
} 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;
|
akgl_Keystroke keystroke;
|
||||||
|
|
||||||
@@ -655,7 +575,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_key(int *keycode, bool *
|
|||||||
SUCCEED_RETURN(errctx);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL keystroke destination");
|
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);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_flush_keys(void)
|
akerr_ErrorContext *akgl_controller_flush_keys(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
keybuffer_head = 0;
|
keybuffer_head = 0;
|
||||||
|
|||||||
16
src/draw.c
16
src/draw.c
@@ -236,7 +236,7 @@ static akerr_ErrorContext *flood_region(SDL_Surface *surface, int x, int y, uint
|
|||||||
SUCCEED_RETURN(errctx);
|
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;
|
SDL_Color previous;
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_point(akgl_RenderBackend *self, flo
|
|||||||
SUCCEED_RETURN(errctx);
|
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;
|
SDL_Color previous;
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_line(akgl_RenderBackend *self, floa
|
|||||||
SUCCEED_RETURN(errctx);
|
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;
|
SDL_Color previous;
|
||||||
|
|
||||||
@@ -303,7 +303,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_rect(akgl_RenderBackend *self, SDL_
|
|||||||
SUCCEED_RETURN(errctx);
|
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;
|
SDL_Color previous;
|
||||||
|
|
||||||
@@ -326,7 +326,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_filled_rect(akgl_RenderBackend *sel
|
|||||||
SUCCEED_RETURN(errctx);
|
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_Color previous;
|
||||||
SDL_FPoint octants[8];
|
SDL_FPoint octants[8];
|
||||||
@@ -393,7 +393,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_circle(akgl_RenderBackend *self, fl
|
|||||||
SUCCEED_RETURN(errctx);
|
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 *target = NULL;
|
||||||
SDL_Surface *rgba = NULL;
|
SDL_Surface *rgba = NULL;
|
||||||
@@ -481,7 +481,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_flood_fill(akgl_RenderBackend *self
|
|||||||
SUCCEED_RETURN(errctx);
|
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;
|
SDL_Surface *saved = NULL;
|
||||||
int width = 0;
|
int width = 0;
|
||||||
@@ -551,7 +551,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_copy_region(akgl_RenderBackend *sel
|
|||||||
SUCCEED_RETURN(errctx);
|
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_Texture *patch = NULL;
|
||||||
SDL_FRect dest;
|
SDL_FRect dest;
|
||||||
|
|||||||
97
src/game.c
97
src/game.c
@@ -129,7 +129,7 @@ void akgl_game_lowfps(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
|
akerr_ErrorContext *akgl_game_init(void)
|
||||||
{
|
{
|
||||||
int screenwidth = 0;
|
int screenwidth = 0;
|
||||||
int screenheight = 0;
|
int screenheight = 0;
|
||||||
@@ -213,7 +213,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
|
|||||||
SUCCEED_RETURN(e);
|
SUCCEED_RETURN(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_lock(void)
|
akerr_ErrorContext *akgl_game_state_lock(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(e);
|
PREPARE_ERROR(e);
|
||||||
// Milliseconds, which is what SDL_Delay takes. This used to count against
|
// 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());
|
SDL_GetError());
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void)
|
akerr_ErrorContext *akgl_game_state_unlock(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(e);
|
PREPARE_ERROR(e);
|
||||||
SDL_UnlockMutex(akgl_game.statelock);
|
SDL_UnlockMutex(akgl_game.statelock);
|
||||||
SUCCEED_RETURN(e);
|
SUCCEED_RETURN(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void akgl_game_update_fps()
|
void akgl_game_update_fps(void)
|
||||||
{
|
{
|
||||||
SDL_Time curTime;
|
SDL_Time curTime;
|
||||||
curTime = SDL_GetTicksNS();
|
curTime = SDL_GetTicksNS();
|
||||||
@@ -287,7 +287,7 @@ void akgl_game_update_fps()
|
|||||||
* **exits the process**. A failed write during a save terminates the
|
* **exits the process**. A failed write during a save terminates the
|
||||||
* game rather than returning an error.
|
* 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;
|
FILE *fp = (FILE *)userdata;
|
||||||
akgl_Actor *actor = NULL;
|
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.
|
* @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.
|
* #AKGL_SPRITE_MAX_NAME_LENGTH wide.
|
||||||
*
|
*
|
||||||
* @param userdata The open output stream, as a `FILE *`. Required.
|
* @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.
|
* @param name The sprite's registry key, written at fixed width.
|
||||||
*
|
*
|
||||||
* @warning Terminates the process on an unhandled error. See
|
* @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;
|
FILE *fp = (FILE *)userdata;
|
||||||
akgl_Sprite *sprite = NULL;
|
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.
|
* @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
|
* #AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH wide -- the widest of the four, since
|
||||||
* a spritesheet is keyed by its resolved path.
|
* 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.
|
* @param name The spritesheet's registry key, written at fixed width.
|
||||||
*
|
*
|
||||||
* @warning Terminates the process on an unhandled error. See
|
* @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
|
* @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
|
* #AKGL_ACTOR_MAX_NAME_LENGTH, so a save containing any spritesheet cannot
|
||||||
* be read back. TODO.md, "Known and still open" item 7.
|
* 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;
|
FILE *fp = (FILE *)userdata;
|
||||||
akgl_SpriteSheet *spritesheet = NULL;
|
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.
|
* @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.
|
* #AKGL_CHARACTER_MAX_NAME_LENGTH wide.
|
||||||
*
|
*
|
||||||
* @param userdata The open output stream, as a `FILE *`. Required.
|
* @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.
|
* @param name The character's registry key, written at fixed width.
|
||||||
*
|
*
|
||||||
* @warning Terminates the process on an unhandled error. See
|
* @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;
|
FILE *fp = (FILE *)userdata;
|
||||||
PREPARE_ERROR(e);
|
PREPARE_ERROR(e);
|
||||||
@@ -390,30 +390,11 @@ void akgl_game_save_charactername_iterator(void *userdata, SDL_PropertiesID prop
|
|||||||
} FINISH_NORETURN(e);
|
} FINISH_NORETURN(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
akerr_ErrorContext *akgl_game_save_actors(FILE *fp)
|
||||||
* @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)
|
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(e);
|
PREPARE_ERROR(e);
|
||||||
// Each name table ends with a zeroed name field and a zeroed pointer, which
|
// 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
|
// 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
|
// 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
|
// 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
|
// write the actor name pointer table
|
||||||
SDL_EnumerateProperties(
|
SDL_EnumerateProperties(
|
||||||
AKGL_REGISTRY_ACTOR,
|
AKGL_REGISTRY_ACTOR,
|
||||||
&akgl_game_save_actorname_iterator,
|
&save_actorname_iterator,
|
||||||
(void *)fp);
|
(void *)fp);
|
||||||
CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_ACTOR_MAX_NAME_LENGTH, 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));
|
CATCH(e, write_exact((void *)&nullbuf, 1, sizeof(akgl_Actor *), fp));
|
||||||
// write the sprite name pointer table
|
// write the sprite name pointer table
|
||||||
SDL_EnumerateProperties(
|
SDL_EnumerateProperties(
|
||||||
AKGL_REGISTRY_SPRITE,
|
AKGL_REGISTRY_SPRITE,
|
||||||
&akgl_game_save_spritename_iterator,
|
&save_spritename_iterator,
|
||||||
(void *)fp);
|
(void *)fp);
|
||||||
CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_SPRITE_MAX_NAME_LENGTH, 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));
|
CATCH(e, write_exact((void *)&nullbuf, 1, sizeof(akgl_Sprite *), fp));
|
||||||
// write the spritesheet name pointer table
|
// write the spritesheet name pointer table
|
||||||
SDL_EnumerateProperties(
|
SDL_EnumerateProperties(
|
||||||
AKGL_REGISTRY_SPRITESHEET,
|
AKGL_REGISTRY_SPRITESHEET,
|
||||||
&akgl_game_save_spritesheetname_iterator,
|
&save_spritesheetname_iterator,
|
||||||
(void *)fp);
|
(void *)fp);
|
||||||
CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, 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));
|
CATCH(e, write_exact((void *)&nullbuf, 1, sizeof(akgl_SpriteSheet *), fp));
|
||||||
// write the character name pointer table
|
// write the character name pointer table
|
||||||
SDL_EnumerateProperties(
|
SDL_EnumerateProperties(
|
||||||
AKGL_REGISTRY_CHARACTER,
|
AKGL_REGISTRY_CHARACTER,
|
||||||
&akgl_game_save_charactername_iterator,
|
&save_charactername_iterator,
|
||||||
(void *)fp);
|
(void *)fp);
|
||||||
CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_CHARACTER_MAX_NAME_LENGTH, 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));
|
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);
|
SUCCEED_RETURN(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath)
|
akerr_ErrorContext *akgl_game_save(char *fpath)
|
||||||
{
|
{
|
||||||
FILE *fp = NULL;
|
FILE *fp = NULL;
|
||||||
PREPARE_ERROR(e);
|
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
|
* rather than being reported: the entry is written with whatever
|
||||||
* `SDL_GetPointerProperty` returned.
|
* `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;
|
void *ptr = NULL;
|
||||||
char ptrstring[32];
|
char ptrstring[32];
|
||||||
@@ -561,27 +542,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_objectnamemap(FILE *fp, SDL_Pr
|
|||||||
SUCCEED_RETURN(e);
|
SUCCEED_RETURN(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
akerr_ErrorContext *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion)
|
||||||
* @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)
|
|
||||||
{
|
{
|
||||||
semver_t current_version = {};
|
semver_t current_version = {};
|
||||||
semver_t compare_version = {};
|
semver_t compare_version = {};
|
||||||
@@ -622,7 +583,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype,
|
|||||||
SUCCEED_RETURN(e);
|
SUCCEED_RETURN(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath)
|
akerr_ErrorContext *akgl_game_load(char *fpath)
|
||||||
{
|
{
|
||||||
akgl_Game savegame;
|
akgl_Game savegame;
|
||||||
SDL_PropertiesID actormap;
|
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));
|
memcpy((void *)&akgl_game, (void *)&savegame, sizeof(akgl_Game));
|
||||||
// Load actor name map
|
// Load actor name map
|
||||||
actormap = SDL_CreateProperties();
|
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
|
// Load sprite name map
|
||||||
spritemap = SDL_CreateProperties();
|
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
|
// Load spritesheet name map
|
||||||
spritesheetmap = SDL_CreateProperties();
|
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
|
// Load character name map
|
||||||
charactermap = SDL_CreateProperties();
|
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
|
// Now that we have all of our pointer maps built, we can load the actual binary objects and reset their pointers
|
||||||
} CLEANUP {
|
} CLEANUP {
|
||||||
if ( fp != NULL ) {
|
if ( fp != NULL ) {
|
||||||
@@ -674,7 +635,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath)
|
|||||||
SUCCEED_RETURN(e);
|
SUCCEED_RETURN(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags)
|
akerr_ErrorContext *akgl_game_update(akgl_Iterator *opflags)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(e);
|
PREPARE_ERROR(e);
|
||||||
akgl_Iterator defflags = {
|
akgl_Iterator defflags = {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ akgl_SpriteSheet akgl_heap_spritesheets[AKGL_MAX_HEAP_SPRITESHEET];
|
|||||||
akgl_Character akgl_heap_characters[AKGL_MAX_HEAP_CHARACTER];
|
akgl_Character akgl_heap_characters[AKGL_MAX_HEAP_CHARACTER];
|
||||||
akgl_String akgl_heap_strings[AKGL_MAX_HEAP_STRING];
|
akgl_String akgl_heap_strings[AKGL_MAX_HEAP_STRING];
|
||||||
|
|
||||||
akerr_ErrorContext *akgl_heap_init()
|
akerr_ErrorContext *akgl_heap_init(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ akerr_ErrorContext *akgl_get_json_number_value(json_t *obj, char *key, float *de
|
|||||||
SUCCEED_RETURN(errctx);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
|
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);
|
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);
|
// Two contexts, and the names now say which is which: `e` is the incoming
|
||||||
if ( err == NULL ) {
|
// one being inspected, `errctx` is this function's own. They were `err` and
|
||||||
SUCCEED_RETURN(e);
|
// `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(errctx, defval, AKERR_NULLPOINTER, "defval");
|
||||||
FAIL_ZERO_RETURN(e, defval, AKERR_NULLPOINTER, "defval");
|
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "dest");
|
||||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dest");
|
|
||||||
|
|
||||||
ATTEMPT {
|
ATTEMPT {
|
||||||
} CLEANUP {
|
} CLEANUP {
|
||||||
} PROCESS(err) {
|
} PROCESS(e) {
|
||||||
} HANDLE_GROUP(err, AKERR_KEY) {
|
} HANDLE_GROUP(e, AKERR_KEY) {
|
||||||
} HANDLE_GROUP(err, AKERR_INDEX) {
|
} HANDLE_GROUP(e, AKERR_INDEX) {
|
||||||
memcpy(dest, defval, defsize);
|
memcpy(dest, defval, defsize);
|
||||||
} FINISH(err, true);
|
} FINISH(e, true);
|
||||||
|
|
||||||
SUCCEED_RETURN(e);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,28 +12,28 @@
|
|||||||
#include <akgl/heap.h>
|
#include <akgl/heap.h>
|
||||||
#include <akgl/registry.h>
|
#include <akgl/registry.h>
|
||||||
|
|
||||||
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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
||||||
SUCCEED_RETURN(e);
|
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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
||||||
SUCCEED_RETURN(e);
|
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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
||||||
SUCCEED_RETURN(e);
|
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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
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);
|
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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
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);
|
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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
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);
|
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;
|
akgl_String *tmp;
|
||||||
PREPARE_ERROR(e);
|
PREPARE_ERROR(e);
|
||||||
@@ -121,7 +121,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_arcade(akgl_PhysicsBackend
|
|||||||
SUCCEED_RETURN(e);
|
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);
|
PREPARE_ERROR(e);
|
||||||
akgl_Iterator defflags = {
|
akgl_Iterator defflags = {
|
||||||
@@ -227,7 +227,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *se
|
|||||||
SUCCEED_RETURN(e);
|
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;
|
uint32_t hashval;
|
||||||
PREPARE_ERROR(e);
|
PREPARE_ERROR(e);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ SDL_PropertiesID AKGL_REGISTRY_MUSIC = 0;
|
|||||||
SDL_PropertiesID AKGL_REGISTRY_FONT = 0;
|
SDL_PropertiesID AKGL_REGISTRY_FONT = 0;
|
||||||
SDL_PropertiesID AKGL_REGISTRY_PROPERTIES = 0;
|
SDL_PropertiesID AKGL_REGISTRY_PROPERTIES = 0;
|
||||||
|
|
||||||
akerr_ErrorContext *akgl_registry_init()
|
akerr_ErrorContext *akgl_registry_init(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
ATTEMPT {
|
ATTEMPT {
|
||||||
@@ -42,7 +42,7 @@ akerr_ErrorContext *akgl_registry_init()
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext *akgl_registry_init_actor()
|
akerr_ErrorContext *akgl_registry_init_actor(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
if ( AKGL_REGISTRY_ACTOR != 0 ) {
|
if ( AKGL_REGISTRY_ACTOR != 0 ) {
|
||||||
@@ -53,7 +53,7 @@ akerr_ErrorContext *akgl_registry_init_actor()
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_font()
|
akerr_ErrorContext *akgl_registry_init_font(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
AKGL_REGISTRY_FONT = SDL_CreateProperties();
|
AKGL_REGISTRY_FONT = SDL_CreateProperties();
|
||||||
@@ -61,7 +61,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_font()
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext *akgl_registry_init_music()
|
akerr_ErrorContext *akgl_registry_init_music(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
AKGL_REGISTRY_MUSIC = SDL_CreateProperties();
|
AKGL_REGISTRY_MUSIC = SDL_CreateProperties();
|
||||||
@@ -69,7 +69,7 @@ akerr_ErrorContext *akgl_registry_init_music()
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext *akgl_registry_init_properties()
|
akerr_ErrorContext *akgl_registry_init_properties(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
AKGL_REGISTRY_PROPERTIES = SDL_CreateProperties();
|
AKGL_REGISTRY_PROPERTIES = SDL_CreateProperties();
|
||||||
@@ -77,7 +77,7 @@ akerr_ErrorContext *akgl_registry_init_properties()
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext *akgl_registry_init_actor_state_strings()
|
akerr_ErrorContext *akgl_registry_init_actor_state_strings(void)
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int flag = 0;
|
int flag = 0;
|
||||||
@@ -94,7 +94,7 @@ akerr_ErrorContext *akgl_registry_init_actor_state_strings()
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext *akgl_registry_init_sprite()
|
akerr_ErrorContext *akgl_registry_init_sprite(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
AKGL_REGISTRY_SPRITE = SDL_CreateProperties();
|
AKGL_REGISTRY_SPRITE = SDL_CreateProperties();
|
||||||
@@ -102,7 +102,7 @@ akerr_ErrorContext *akgl_registry_init_sprite()
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext *akgl_registry_init_spritesheet()
|
akerr_ErrorContext *akgl_registry_init_spritesheet(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
AKGL_REGISTRY_SPRITESHEET = SDL_CreateProperties();
|
AKGL_REGISTRY_SPRITESHEET = SDL_CreateProperties();
|
||||||
@@ -110,7 +110,7 @@ akerr_ErrorContext *akgl_registry_init_spritesheet()
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext *akgl_registry_init_character()
|
akerr_ErrorContext *akgl_registry_init_character(void)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
AKGL_REGISTRY_CHARACTER = SDL_CreateProperties();
|
AKGL_REGISTRY_CHARACTER = SDL_CreateProperties();
|
||||||
@@ -118,7 +118,7 @@ akerr_ErrorContext *akgl_registry_init_character()
|
|||||||
SUCCEED_RETURN(errctx);
|
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 *json = NULL;
|
||||||
json_t *props = NULL;
|
json_t *props = NULL;
|
||||||
@@ -175,16 +175,16 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_load_properties(char *fname)
|
|||||||
SUCCEED_RETURN(errctx);
|
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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_ZERO_RETURN(e, name, AKERR_NULLPOINTER, "NULL char *");
|
FAIL_ZERO_RETURN(e, name, AKERR_NULLPOINTER, "NULL char *");
|
||||||
FAIL_ZERO_RETURN(e, src, AKERR_NULLPOINTER, "NULL char *");
|
FAIL_ZERO_RETURN(e, value, AKERR_NULLPOINTER, "NULL char *");
|
||||||
SDL_SetStringProperty(AKGL_REGISTRY_PROPERTIES, name, src);
|
SDL_SetStringProperty(AKGL_REGISTRY_PROPERTIES, name, value);
|
||||||
SUCCEED_RETURN(e);
|
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;
|
const char *value = NULL;
|
||||||
size_t valuelen = 0;
|
size_t valuelen = 0;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#include <akerror.h>
|
#include <akerror.h>
|
||||||
#include <akstdlib.h>
|
#include <akstdlib.h>
|
||||||
|
|
||||||
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 *width = NULL;
|
||||||
akgl_String *height = NULL;
|
akgl_String *height = NULL;
|
||||||
@@ -47,7 +47,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_init(akgl_RenderBackend *self)
|
|||||||
SUCCEED_RETURN(e);
|
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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
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);
|
PREPARE_ERROR(e);
|
||||||
SUCCEED_RETURN(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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_ZERO_RETURN(e, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
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);
|
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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_ZERO_RETURN(e, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
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);
|
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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
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);
|
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);
|
PREPARE_ERROR(e);
|
||||||
FAIL_RETURN(e, AKERR_API, "Not implemented");
|
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);
|
PREPARE_ERROR(e);
|
||||||
akgl_Iterator defflags;
|
akgl_Iterator defflags;
|
||||||
|
|||||||
@@ -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_SDL If the image cannot be decoded.
|
||||||
* @throws AKGL_ERR_HEAP If the spritesheet or string pool is exhausted.
|
* @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);
|
PREPARE_ERROR(errctx);
|
||||||
json_t *spritesheet_json = NULL;
|
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
|
"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_get_json_string_value((json_t *)json, "name", &spritename));
|
||||||
CATCH(errctx,
|
CATCH(errctx,
|
||||||
akgl_sprite_initialize(
|
akgl_sprite_initialize(
|
||||||
|
|||||||
10
src/text.c
10
src/text.c
@@ -10,7 +10,7 @@
|
|||||||
#include <akgl/registry.h>
|
#include <akgl/registry.h>
|
||||||
#include <akgl/game.h>
|
#include <akgl/game.h>
|
||||||
|
|
||||||
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;
|
TTF_Font *font = NULL;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_loadfont(char *name, char *filepath
|
|||||||
SUCCEED_RETURN(errctx);
|
SUCCEED_RETURN(errctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_text_unloadfont(char *name)
|
akerr_ErrorContext *akgl_text_unloadfont(char *name)
|
||||||
{
|
{
|
||||||
TTF_Font *font = NULL;
|
TTF_Font *font = NULL;
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_unloadfont(char *name)
|
|||||||
SUCCEED_RETURN(errctx);
|
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_Surface *textsurf = NULL;
|
||||||
SDL_Texture *texture = NULL;
|
SDL_Texture *texture = NULL;
|
||||||
@@ -96,7 +96,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_rendertextat(TTF_Font *font, char *
|
|||||||
SUCCEED_RETURN(errctx);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, font, AKERR_NULLPOINTER, "NULL font");
|
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);
|
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);
|
PREPARE_ERROR(errctx);
|
||||||
FAIL_ZERO_RETURN(errctx, font, AKERR_NULLPOINTER, "NULL font");
|
FAIL_ZERO_RETURN(errctx, font, AKERR_NULLPOINTER, "NULL font");
|
||||||
|
|||||||
145
src/tilemap.c
145
src/tilemap.c
@@ -87,23 +87,6 @@ akerr_ErrorContext *akgl_get_json_properties_integer(json_t *obj, char *key, int
|
|||||||
SUCCEED_RETURN(errctx);
|
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)
|
akerr_ErrorContext *akgl_get_json_properties_number(json_t *obj, char *key, float *dest)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
@@ -114,22 +97,6 @@ akerr_ErrorContext *akgl_get_json_properties_number(json_t *obj, char *key, floa
|
|||||||
SUCCEED_RETURN(errctx);
|
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)
|
akerr_ErrorContext *akgl_get_json_properties_float(json_t *obj, char *key, float *dest)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
@@ -140,24 +107,6 @@ akerr_ErrorContext *akgl_get_json_properties_float(json_t *obj, char *key, float
|
|||||||
SUCCEED_RETURN(errctx);
|
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)
|
akerr_ErrorContext *akgl_get_json_properties_double(json_t *obj, char *key, double *dest)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
@@ -288,41 +237,6 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root,
|
|||||||
SUCCEED_RETURN(errctx);
|
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)
|
akerr_ErrorContext *akgl_tilemap_load_layer_object_actor(akgl_TilemapObject *curobj, json_t *layerdatavalue, int layerid, akgl_String *dirname)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
@@ -444,34 +358,6 @@ akerr_ErrorContext *akgl_tilemap_load_layer_tile(akgl_Tilemap *dest, json_t *roo
|
|||||||
SUCCEED_RETURN(errctx);
|
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)
|
akerr_ErrorContext *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
@@ -558,33 +444,6 @@ akerr_ErrorContext *akgl_tilemap_load_layers(akgl_Tilemap *dest, json_t *root, a
|
|||||||
SUCCEED_RETURN(errctx);
|
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)
|
akerr_ErrorContext *akgl_tilemap_load_physics(akgl_Tilemap *dest, json_t *root)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(e);
|
PREPARE_ERROR(e);
|
||||||
@@ -913,7 +772,7 @@ akerr_ErrorContext *akgl_tilemap_draw_tileset(akgl_Tilemap *map, int tilesetidx)
|
|||||||
SUCCEED_RETURN(errctx);
|
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);
|
PREPARE_ERROR(e);
|
||||||
|
|
||||||
@@ -930,7 +789,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_scale_actor(akgl_Tilemap *map, a
|
|||||||
SUCCEED_RETURN(e);
|
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 tileset textures
|
||||||
// Release all image layer textures
|
// Release all image layer textures
|
||||||
|
|||||||
43
src/util.c
43
src/util.c
@@ -27,7 +27,8 @@
|
|||||||
* the join, so symlinks and `..` are folded out and the result is absolute. It
|
* 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.
|
* 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
|
* @param root Directory to resolve against, normally `dirname` of the file that
|
||||||
* named @p path. Required. A trailing `/` is harmless; the join adds
|
* 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
|
* scratch strings are never released. This is exactly the hazard AGENTS.md
|
||||||
* warns about; it wants a `FAIL_BREAK`.
|
* 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);
|
PREPARE_ERROR(e);
|
||||||
akgl_String *pathbuf;
|
akgl_String *pathbuf;
|
||||||
@@ -128,47 +129,11 @@ akerr_ErrorContext *akgl_path_relative(char *root, char *path, akgl_String *dst)
|
|||||||
} FINISH(e, true);
|
} FINISH(e, true);
|
||||||
|
|
||||||
if ( relative_to_root == 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);
|
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)
|
akerr_ErrorContext *akgl_rectangle_points(akgl_RectanglePoints *dest, SDL_FRect *rect)
|
||||||
{
|
{
|
||||||
PREPARE_ERROR(errctx);
|
PREPARE_ERROR(errctx);
|
||||||
|
|||||||
@@ -24,16 +24,6 @@
|
|||||||
|
|
||||||
#include "testutil.h"
|
#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. */
|
/** @brief Keyboard id the tests bind their control maps to. */
|
||||||
#define TEST_KBID 11
|
#define TEST_KBID 11
|
||||||
/** @brief Gamepad id the tests bind their control maps to. */
|
/** @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->state = 0;
|
||||||
player->movement_controls_face = false;
|
player->movement_controls_face = false;
|
||||||
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
|
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");
|
"dpad down press handler");
|
||||||
TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_DOWN),
|
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);
|
"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),
|
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);
|
"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");
|
"dpad down release handler");
|
||||||
TEST_ASSERT(e, AKGL_BITMASK_HASNOT(player->state, AKGL_ACTOR_STATE_MOVING_DOWN),
|
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);
|
"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;
|
player->state = 0;
|
||||||
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
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),
|
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);
|
"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;
|
player->state = 0;
|
||||||
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
|
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),
|
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);
|
"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;
|
player->state = 0;
|
||||||
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
|
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),
|
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);
|
"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
|
// With automatic facing on, the handler leaves facing to the actor's own
|
||||||
// face function.
|
// face function.
|
||||||
player->state = 0;
|
player->state = 0;
|
||||||
player->movement_controls_face = true;
|
player->movement_controls_face = true;
|
||||||
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
|
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");
|
"dpad down press with automatic facing on");
|
||||||
TEST_ASSERT(e, AKGL_BITMASK_HASNOT(player->state, AKGL_ACTOR_STATE_FACE_DOWN),
|
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)",
|
"the handler set facing even though the actor faces automatically (state %d)",
|
||||||
player->state);
|
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");
|
"dpad press handler with a NULL appstate");
|
||||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER,
|
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");
|
"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");
|
"dpad release handler with a NULL appstate");
|
||||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER,
|
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");
|
"dpad release handler with a NULL event");
|
||||||
|
|
||||||
// With no actor named "player" registered there is nothing to drive.
|
// With no actor named "player" registered there is nothing to drive.
|
||||||
CATCH(e, akgl_registry_init_actor());
|
CATCH(e, akgl_registry_init_actor());
|
||||||
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
|
make_button_event(&event, SDL_EVENT_GAMEPAD_BUTTON_DOWN, TEST_JSID, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
|
||||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER,
|
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");
|
"dpad press handler with no player actor registered");
|
||||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER,
|
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");
|
"dpad release handler with no player actor registered");
|
||||||
} CLEANUP {
|
} CLEANUP {
|
||||||
} PROCESS(e) {
|
} 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
|
// The dummy drivers present no gamepads, so the add and remove handlers
|
||||||
// take their "unknown device" paths. Neither may crash.
|
// take their "unknown device" paths. Neither may crash.
|
||||||
make_button_event(&event, SDL_EVENT_GAMEPAD_ADDED, 999, 0);
|
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");
|
"handling an add for a device that cannot be opened");
|
||||||
|
|
||||||
make_button_event(&event, SDL_EVENT_GAMEPAD_REMOVED, 999, 0);
|
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");
|
"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");
|
"add handler with a NULL appstate");
|
||||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER,
|
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");
|
"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");
|
"remove handler with a NULL appstate");
|
||||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER,
|
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");
|
"remove handler with a NULL event");
|
||||||
} CLEANUP {
|
} CLEANUP {
|
||||||
} PROCESS(e) {
|
} PROCESS(e) {
|
||||||
|
|||||||
@@ -24,14 +24,6 @@
|
|||||||
|
|
||||||
#include "testutil.h"
|
#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. */
|
/** @brief Scratch savegame path, created and removed by the tests that use it. */
|
||||||
static char savepath[] = "akgl_test_savegame.bin";
|
static char savepath[] = "akgl_test_savegame.bin";
|
||||||
/** @brief Scratch path for deliberately malformed savegames. */
|
/** @brief Scratch path for deliberately malformed savegames. */
|
||||||
|
|||||||
42
tests/header_pool_override.c
Normal file
42
tests/header_pool_override.c
Normal file
@@ -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 <akgl/heap.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user