Namespace every exported symbol, and bump to 0.5.0

Closes internal-consistency items 1 through 6, 12 and 13. Every include guard
is _AKGL_<FILE>_H_, every in-project header include is angled, and every
exported function, type and global carries the akgl_ prefix. This is an ABI
break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename
table.

The renames were driven by renaming each declaration and letting the compiler
find the uses, not by pattern substitution: renderer, physics and camera are
also parameter and struct-member names, and a sed would have rewritten
map->physics and every akgl_RenderBackend *renderer parameter without a word.

Item 4 turned out not to be cosmetic. The library exported a global called
renderer and tests/character.c defined an SDL_Renderer *renderer of its own;
the executable's definition preempted the library's, akgl_sprite_load_json
read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load
in that suite failed. The suite reported success anyway, because libakerror's
unhandled-error handler ends in exit(errctx->status), exit keeps only the low
byte, and AKGL_ERR_SDL is exactly 256. So character had been green while
running one of its four tests, and every suite in the tree was unable to fail
on the most common status in a library built on SDL.

Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which
collapses any status a byte cannot carry onto 1, and every suite installs it.
character binds a real backend with akgl_render_2d_bind. Its fourth test then
runs for the first time and fails on a defect it has asserted all along, so
akgl_heap_release_character now walks state_sprites with
AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot
-- TODO.md Defects item 21 and half of Carried over item 1.

AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so
akgl_game_state_lock waited roughly sixteen minutes rather than one second. It
is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and
tests/game.c holds the mutex from a second thread to assert the wait -- the
contended path had no coverage at all.

Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both
install() and a generated translation unit per header, so a header that ships
is a header that is checked. Writing that found registry.h, which used
SDL_PropertiesID in eight declarations and included no SDL header.

23/23 suites pass, memcheck is clean, reindent --check is clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
This commit is contained in:
2026-07-31 23:32:21 -04:00
parent e30f3bce03
commit e8cdca6fde
60 changed files with 1199 additions and 860 deletions

View File

@@ -187,12 +187,9 @@ For reference, `cc-mode` defines the style as:
- `case` labels sit at the same column as their `switch` (`label . 0`). - `case` labels sit at the same column as their `switch` (`label . 0`).
- Continuation lines of a wrapped expression indent one level (`statement-cont . +`). - Continuation lines of a wrapped expression indent one level (`statement-cont . +`).
Most of `src/` already conforms. The known non-conforming files are The whole tree conforms: `scripts/reindent.sh --check` is clean, and the
`src/json_helpers.c`, `src/util.c` (from `akgl_rectangle_points` onward), pre-commit hook keeps it that way. Convert a file to the canonical style in its
`src/assets.c`, `src/staticstring.c`, parts of `src/actor.c`, and the headers own commit, not mixed into a behavioral change.
`include/akgl/util.h` and `include/akgl/staticstring.h` — all of which use a
2-column offset. Convert a file to the canonical style in its own commit, not
mixed into a behavioral change.
### Braces ### Braces
@@ -256,8 +253,9 @@ style drifted in the first place.
embed a type name (`akgl_Actor_cmhf_left_on` is wrong; it should be embed a type name (`akgl_Actor_cmhf_left_on` is wrong; it should be
`akgl_actor_cmhf_left_on`). `akgl_actor_cmhf_left_on`).
- **Public types**: `akgl_TypeName` — `akgl_Actor`, `akgl_SpriteSheet`, - **Public types**: `akgl_TypeName` — `akgl_Actor`, `akgl_SpriteSheet`,
`akgl_PhysicsBackend`. Every type exported from a header takes the prefix; `akgl_PhysicsBackend`. Every type exported from a header takes the prefix.
bare names like `point` and `RectanglePoints` are defects, not precedent. `point` and `RectanglePoints` shipped bare until 0.5.0; they are
`akgl_Point` and `akgl_RectanglePoints` now, and neither is precedent.
- **Constants and macros**: `AKGL_UPPER_SNAKE_CASE`. A constant belongs to the - **Constants and macros**: `AKGL_UPPER_SNAKE_CASE`. A constant belongs to the
subsystem it describes: a character limit is `AKGL_CHARACTER_MAX_*`, not subsystem it describes: a character limit is `AKGL_CHARACTER_MAX_*`, not
`AKGL_SPRITE_MAX_CHARACTER_*`. Name a constant for what its value *is* — a `AKGL_SPRITE_MAX_CHARACTER_*`. Name a constant for what its value *is* — a
@@ -266,6 +264,15 @@ style drifted in the first place.
not add bare names (`renderer`, `camera`, `window`), leading-underscore names not add bare names (`renderer`, `camera`, `window`), leading-underscore names
(reserved at file scope), or SCREAMING_SNAKE names for mutable objects — (reserved at file scope), or SCREAMING_SNAKE names for mutable objects —
`AKGL_UPPER_SNAKE_CASE` is for constants. `AKGL_UPPER_SNAKE_CASE` is for constants.
This one is not cosmetic and there is a scar to prove it. Until 0.5.0 the
library exported `renderer`, and `tests/character.c` defined a
`SDL_Renderer *renderer` of its own. Both had external linkage and the same
spelling, so the executable's definition preempted the shared library's: the
library read a `SDL_Renderer *` through an `akgl_RenderBackend *`, every
texture load in that suite failed, and the suite reported success anyway. A
consuming game with a variable called `renderer` would have hit the same
thing with no test to notice.
- **`static` helpers drop the `akgl_` prefix**, which exists only to avoid - **`static` helpers drop the `akgl_` prefix**, which exists only to avoid
external collisions. external collisions.
- **Include guards**: `_AKGL_<FILE>_H_`, matching the file name. Guard names - **Include guards**: `_AKGL_<FILE>_H_`, matching the file name. Guard names
@@ -321,6 +328,15 @@ Headers must be self-contained: include what you use, so that a translation unit
including a single `akgl` header compiles without a particular include order. including a single `akgl` header compiles without a particular include order.
Within the project use the angled form, `#include <akgl/sibling.h>`. Within the project use the angled form, `#include <akgl/sibling.h>`.
This is enforced. `AKGL_PUBLIC_HEADERS` in `CMakeLists.txt` lists every header
installed into `include/akgl/`, and drives two things from that one list: what
`install()` ships, and a generated translation unit per header — each including
exactly that header and nothing before it — linked into the `headers` suite. A
new header is covered by adding it to that list. One header per translation
unit is the only shape that proves anything: a second `#include` after the
first proves nothing about the second, because by then the first has dragged
its dependencies in.
Declare no-argument functions as `(void)`, not `()`. Declare no-argument functions as `(void)`, not `()`.
## Rules ## Rules
@@ -330,7 +346,15 @@ Declare no-argument functions as `(void)`, not `()`.
## Testing Guidelines ## Testing Guidelines
Tests use simple executable return codes and are registered through CTest in `CMakeLists.txt`; there is no declared coverage threshold. Add focused tests as `tests/<feature>.c`, create a matching `test_<feature>` target, and register it with `add_test`. Put reusable fixtures in `tests/assets/` and keep paths compatible with tests launched from the build tree. Coverage mode wraps the suite in a CTest fixture so counters are reset before tests and reports are generated afterward. Tests use simple executable return codes and are registered through CTest in `CMakeLists.txt`; there is no declared coverage threshold.
**Every suite's `main()` must call `TEST_TRAP_UNHANDLED_ERRORS()` immediately
after `akgl_error_init()`.** libakerror's default unhandled-error handler ends
in `exit(errctx->status)`, `exit` keeps only the low byte, and libakgl's status
band starts at 256 — so a suite that failed with `AKGL_ERR_SDL` exited 0 and
CTest recorded a pass. That is not hypothetical: `tests/character.c` aborted at
its second of four tests on a bad renderer and was green until 0.5.0. The trap
in `tests/testutil.h` collapses any status a byte cannot carry onto 1. Add focused tests as `tests/<feature>.c`, create a matching `test_<feature>` target, and register it with `add_test`. Put reusable fixtures in `tests/assets/` and keep paths compatible with tests launched from the build tree. Coverage mode wraps the suite in a CTest fixture so counters are reset before tests and reports are generated afterward.
### Performance suites ### Performance suites

View File

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)
# The single source of truth for the library version. It drives the generated # The single source of truth for the library version. It drives the generated
# include/akgl/version.h, the shared library's VERSION and SOVERSION, and the # include/akgl/version.h, the shared library's VERSION and SOVERSION, and the
# Version field in akgl.pc. Bump it here and nowhere else. # Version field in akgl.pc. Bump it here and nowhere else.
project(akgl VERSION 0.4.0 LANGUAGES C) project(akgl VERSION 0.5.0 LANGUAGES C)
# Memory checking reuses the suites that already exist -- `ctest -T memcheck` # Memory checking reuses the suites that already exist -- `ctest -T memcheck`
# runs every registered test under valgrind -- rather than adding programs of its # runs every registered test under valgrind -- rather than adding programs of its
@@ -154,6 +154,34 @@ if(NOT TARGET jansson::jansson)
find_package(jansson) find_package(jansson)
endif() endif()
# Every header installed into include/akgl/, by base name. This list is the
# single source of truth for two things that must not drift apart: what gets
# installed, and what the `headers` suite proves is self-contained. Adding a
# header here is all a new one needs. SDL_GameControllerDB.h and the generated
# version.h are installed separately -- neither is an API header.
set(AKGL_PUBLIC_HEADERS
actor
assets
audio
character
controller
draw
error
game
heap
iterator
json_helpers
physics
registry
renderer
sprite
staticstring
text
tilemap
types
util
)
set(GAMECONTROLLERDB_H "include/akgl/SDL_GameControllerDB.h") set(GAMECONTROLLERDB_H "include/akgl/SDL_GameControllerDB.h")
set(prefix ${CMAKE_INSTALL_PREFIX}) set(prefix ${CMAKE_INSTALL_PREFIX})
set(exec_prefix "\${prefix}") set(exec_prefix "\${prefix}")
@@ -274,6 +302,30 @@ endforeach()
add_test(NAME semver_unit COMMAND akgl_test_semver_unit) add_test(NAME semver_unit COMMAND akgl_test_semver_unit)
# One translation unit per public header, each including exactly that header and
# nothing before it. This is the only shape that proves a header is
# self-contained: a second #include in the same file proves nothing about the
# second, because by then the first has dragged its dependencies in. Generated
# rather than written by hand so the check cannot fall behind
# AKGL_PUBLIC_HEADERS -- the failure being guarded against is a header nobody
# remembered to cover.
set(AKGL_HEADER_SELFTEST_SOURCES "")
foreach(header IN LISTS AKGL_PUBLIC_HEADERS)
set(generated "${CMAKE_CURRENT_BINARY_DIR}/tests/header_${header}.c")
file(WRITE "${generated}"
"/* Generated by CMakeLists.txt from AKGL_PUBLIC_HEADERS. Do not edit. */\n\
#include <akgl/${header}.h>\n\
\n\
/* ISO C forbids an empty translation unit; the compile is the assertion. */\n\
int akgl_header_selftest_${header}(void);\n\
int akgl_header_selftest_${header}(void)\n\
{\n\
return 0;\n\
}\n")
list(APPEND AKGL_HEADER_SELFTEST_SOURCES "${generated}")
endforeach()
target_sources(akgl_test_headers PRIVATE ${AKGL_HEADER_SELFTEST_SOURCES})
# 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
# times its normal cost. The unit suites finish in well under a second each # times its normal cost. The unit suites finish in well under a second each
@@ -455,24 +507,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akgl.pc DESTINATION "lib/pkgconfig/")
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/akgl/version.h DESTINATION "include/akgl/") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/akgl/version.h DESTINATION "include/akgl/")
install(TARGETS akgl DESTINATION "lib/") install(TARGETS akgl DESTINATION "lib/")
install(FILES "deps/semver/semver.h" DESTINATION "include/") install(FILES "deps/semver/semver.h" DESTINATION "include/")
install(FILES "include/akgl/actor.h" DESTINATION "include/akgl/") foreach(header IN LISTS AKGL_PUBLIC_HEADERS)
install(FILES "include/akgl/types.h" DESTINATION "include/akgl/") install(FILES "include/akgl/${header}.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/text.h" DESTINATION "include/akgl/") endforeach()
install(FILES "include/akgl/assets.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/audio.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/character.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/error.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/draw.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/game.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/controller.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/heap.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/iterator.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/json_helpers.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/renderer.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/physics.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/registry.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/sprite.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/staticstring.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/tilemap.h" DESTINATION "include/akgl/")
install(FILES "include/akgl/util.h" DESTINATION "include/akgl/")
install(FILES ${GAMECONTROLLERDB_H} DESTINATION "include/akgl/") install(FILES ${GAMECONTROLLERDB_H} DESTINATION "include/akgl/")

View File

@@ -84,11 +84,11 @@ start whether the game uses one slot or all of them.
| Pool | Slots | Bytes each | Total | | Pool | Slots | Bytes each | Total |
|---|---:|---:|---:| |---|---:|---:|---:|
| `HEAP_ACTOR` | 64 | 400 | 25,600 | | `akgl_heap_actors` | 64 | 400 | 25,600 |
| `HEAP_SPRITE` | 1024 | 176 | 180,224 | | `HEAP_SPRITE` | 1024 | 176 | 180,224 |
| `HEAP_SPRITESHEET` | 1024 | 536 | 548,864 | | `HEAP_SPRITESHEET` | 1024 | 536 | 548,864 |
| `HEAP_CHARACTER` | 256 | 184 | 47,104 | | `HEAP_CHARACTER` | 256 | 184 | 47,104 |
| `HEAP_STRING` | 256 | 4,100 | 1,049,600 | | `akgl_heap_strings` | 256 | 4,100 | 1,049,600 |
| **pools, total** | | | **1,851,392** | | **pools, total** | | | **1,851,392** |
| `akgl_Tilemap` (one, as `_akgl_gamemap`) | 1 | 26,388,008 | 26,388,008 | | `akgl_Tilemap` (one, as `_akgl_gamemap`) | 1 | 26,388,008 | 26,388,008 |
| — of which layers | 16 | 1,120,296 | 17,924,736 | | — of which layers | 16 | 1,120,296 | 17,924,736 |

View File

@@ -24,8 +24,8 @@ PASS(e, akgl_registry_load_properties(YOUR_REGISTR_FILEPATH));
Initialize your physics engine and renderer of choice Initialize your physics engine and renderer of choice
```c ```c
PASS(e, akgl_render_init2d(renderer)); PASS(e, akgl_render_2d_init(akgl_renderer));
PASS(e, akgl_physics_init_arcade(physics)); PASS(e, akgl_physics_init_arcade(akgl_physics));
``` ```
Unlock the game state Unlock the game state
@@ -55,10 +55,10 @@ In your game loop (or in your `SDL_AppIterate` method), lock the game state, cal
```c ```c
PASS(e, akgl_game_state_lock()); PASS(e, akgl_game_state_lock());
PASS(e, renderer->frame_start(renderer)); PASS(e, akgl_renderer->frame_start(akgl_renderer));
SDL_RenderClear(renderer->sdl_renderer); SDL_RenderClear(akgl_renderer->sdl_renderer);
PASS(e, akgl_game_update(NULL)); PASS(e, akgl_game_update(NULL));
PASS(e, renderer->frame_end(renderer)); PASS(e, akgl_renderer->frame_end(akgl_renderer));
PASS(e, akgl_game_state_unlock()); PASS(e, akgl_game_state_unlock());
``` ```
@@ -190,7 +190,7 @@ Character files:
The engine ONLY supports TilED TMJ tilemaps with tileset external references. Load a tilemap into the global `akgl_Tilemap *gamemap` object. The engine ONLY supports TilED TMJ tilemaps with tileset external references. Load a tilemap into the global `akgl_Tilemap *gamemap` object.
```c ```c
PASS(e, akgl_tilemap_load(PATHSTRING, gamemap)); PASS(e, akgl_tilemap_load(PATHSTRING, akgl_gamemap));
``` ```
Actors will be automatically populated from objects in the tilemap object layers. Actor state flags here must be expressed as an integer, you can't (yet) use the same array of strings that is used in character json files. Actors will be automatically populated from objects in the tilemap object layers. Actor state flags here must be expressed as an integer, you can't (yet) use the same array of strings that is used in character json files.
@@ -225,8 +225,8 @@ Actors will be automatically populated from objects in the tilemap object layers
Check if the tilemap wants to use its own physics, and if you want to allow that, override the global physics simulation Check if the tilemap wants to use its own physics, and if you want to allow that, override the global physics simulation
```c ```c
if ( gamemap->use_own_physics == true ) { if ( akgl_gamemap->use_own_physics == true ) {
physics = &gamemap->physics; akgl_physics = &akgl_gamemap->physics;
} }
``` ```
@@ -251,13 +251,13 @@ Tilemap physics specification follows. A map can specify its own physics propert
}], }],
``` ```
The global `gamemap` object is automatically displayed if it is populated. Actors are drawn at the appropriate map layer depending on the actor's `layer` property (warning: this may be replaced with a `z` property soon.) The global `akgl_gamemap` object is automatically displayed if it is populated. Actors are drawn at the appropriate map layer depending on the actor's `layer` property (warning: this may be replaced with a `z` property soon.)
## How do I get the screen width and height ## How do I get the screen width and height
The most direct is to call `SDL_GetCurrentDisplayMode` to get the parameters from the returned `SDL_DisplayMode` structure (`->w` and `->h`). The most direct is to call `SDL_GetCurrentDisplayMode` to get the parameters from the returned `SDL_DisplayMode` structure (`->w` and `->h`).
The simplest way is to check the global `camera` object's `camera->w` and `camera->h` object. You may have more than one camera on a scene, and it's theoretically possible that the global camera object has been overriden and no longer represents the full screen. The simplest way is to check the global `akgl_camera` object's `akgl_camera->w` and `akgl_camera->h` object. You may have more than one camera on a scene, and it's theoretically possible that the global camera object has been overriden and no longer represents the full screen.
The most reliable engine-centric way is to use `akgl_get_property` to get the property from the engine. Properties are read and stored as strings, so if you need to do these kinds of things a lot, cache the integer value somewhere. The most reliable engine-centric way is to use `akgl_get_property` to get the property from the engine. Properties are read and stored as strings, so if you need to do these kinds of things a lot, cache the integer value somewhere.

224
TODO.md
View File

@@ -9,67 +9,62 @@ overlap the existing **Defects** list are cross-referenced rather than repeated.
### 1. Public naming conventions ### 1. Public naming conventions
1. **Include guards use three different schemes.** `_AKGL_ACTOR_H_`, **Items 1 through 6 are resolved in 0.5.0**, which is an ABI break and carries
`_AKGL_CHARACTER_H_`, `_AKGL_GAME_H_`, `_AKGL_HEAP_H_`, `_AKGL_ITERATOR_H_`, the soname to `libakgl.so.0.5`. What changed, and what a consumer has to rename:
`_AKGL_SPRITE_H_`, and `_AKGL_TYPES_H_` carry the project prefix; `_ASSETS_H_`,
`_CONTROLLER_H_`, `_DRAW_H_`, `_ERROR_H_`, `_JSON_HELPERS_H_`, `_PHYSICS_H_`,
`_REGISTRY_H_`, `_RENDERER_H_`, `_TEXT_H_`, `_TILEMAP_H_`, and `_UTIL_H_` do
not. Pick `_AKGL_<FILE>_H_` everywhere.
The worst case is `include/akgl/staticstring.h:6`, which guards with | Was | Is |
`_STRING_H_` — a name several libc implementations use for their own |---|---|
`<string.h>`. The same file then does `#include "string.h"` (line 9) with | `_ASSETS_H_`, `_CONTROLLER_H_`, `_DRAW_H_`, `_ERROR_H_`, `_JSON_HELPERS_H_`, `_PHYSICS_H_`, `_REGISTRY_H_`, `_RENDERER_H_`, `_TEXT_H_`, `_TILEMAP_H_`, `_UTIL_H_`, `_STRING_H_` | `_AKGL_<FILE>_H_` |
quotes, which is a relative-first lookup that only reaches the system header | `akgl_Actor_cmhf_*` (8 functions) | `akgl_actor_cmhf_*` |
by accident. Rename the guard to `_AKGL_STATICSTRING_H_` and use | `akgl_game_updateFPS` | `akgl_game_update_fps` |
`#include <string.h>`. | `akgl_render_init2d`, `akgl_render_bind2d` | `akgl_render_2d_init`, `akgl_render_2d_bind` |
| `akgl_sprite_sheet_coords_for_frame` | `akgl_spritesheet_coords_for_frame` |
| `point`, `RectanglePoints` | `akgl_Point`, `akgl_RectanglePoints` |
| `window`, `bgm`, `game`, `gamemap`, `renderer`, `physics`, `camera` | the same, `akgl_`-prefixed |
| `_akgl_renderer`, `_akgl_physics`, `_akgl_camera`, `_akgl_gamemap` | `akgl_default_renderer`, `akgl_default_physics`, `akgl_default_camera`, `akgl_default_gamemap` |
| `HEAP_ACTOR`, `HEAP_SPRITE`, `HEAP_SPRITESHEET`, `HEAP_CHARACTER`, `HEAP_STRING` | `akgl_heap_actors`, `akgl_heap_sprites`, `akgl_heap_spritesheets`, `akgl_heap_characters`, `akgl_heap_strings` |
| `GAME_ControlMaps` | `akgl_controlmaps` |
| `AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH` | `AKGL_CHARACTER_MAX_NAME_LENGTH` |
| `AKGL_TIME_ONESEC_MS` | `AKGL_TIME_ONEMS_NS` |
2. **Function names contradict the stated convention.** `AGENTS.md` says public `include/akgl/staticstring.h` also stopped guarding with `_STRING_H_` — a name
symbols use `akgl_` and types use `akgl_TypeName`. Exceptions: several libc implementations use for their own `<string.h>` — and its
- `akgl_Actor_cmhf_left_on` and its seven siblings (`include/akgl/actor.h:196-252`) `#include "string.h"` is now `#include <string.h>`.
embed the *type* name in a *function* name, unlike every other actor entry
point (`akgl_actor_*`). Rename to `akgl_actor_cmhf_*`.
- `akgl_game_updateFPS` (`include/akgl/game.h:104`) is camelCase; every other
function is snake_case.
- `akgl_render_init2d` (`include/akgl/renderer.h:83`) puts the `2d` at the end
while the six functions it installs are `akgl_render_2d_*`. Make it
`akgl_render_2d_init`.
- `akgl_sprite_sheet_coords_for_frame` (`include/akgl/sprite.h:86`) spells it
`sprite_sheet`; `akgl_spritesheet_initialize` and `akgl_heap_next_spritesheet`
spell it `spritesheet`.
3. **Two public types have no prefix at all.** `point` and `RectanglePoints` `akgl_render_bind2d` was not on the original list. It is the same defect as
(`include/akgl/util.h:13-25`) are unprefixed, and `RectanglePoints` is `akgl_render_init2d`, `2d` trailing where the six functions it installs carry it
PascalCase with no namespace. Both are dumped into every translation unit in the middle, and renaming one without the other would have been worse than
that includes `util.h`. Rename to `akgl_Point` / `akgl_RectanglePoints`. renaming neither.
4. **Global variables use four different conventions.** `window`, `bgm`, `game`, The renames were done by renaming each declaration and letting the compiler find
`gamemap`, `renderer`, `physics`, and `camera` (`src/game.c:26-43`) are every use, rather than by pattern substitution. That distinction matters for
unprefixed single common words exported from a shared library — `renderer` `renderer`, `physics` and `camera`, which are also parameter and struct-member
and `camera` in particular are very likely to collide with a consuming game. names: a `sed` would have rewritten `map->physics` and every
Alongside them the same file exports `akgl_mixer` and `akgl_tracks` (prefixed), `akgl_RenderBackend *renderer` parameter, and nothing would have complained.
`_akgl_renderer` / `_akgl_camera` / `_akgl_physics` / `_akgl_gamemap`
(underscore-prefixed, which is reserved at file scope), and elsewhere
`HEAP_ACTOR``HEAP_STRING` (`src/heap.c:17-21`) and `GAME_ControlMaps`
(`src/controller.c:13`) are SCREAMING_SNAKE, which the convention reserves for
constants and macros. Settle on `akgl_` for all exported objects.
5. **`AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH`** (`include/akgl/character.h:13`) is a **Item 6 had a live bug behind the name**, now fixed. `akgl_game_state_lock`
character constant carrying the `SPRITE` prefix, and lives in `character.h`. counted its retry loop against a constant named "one second in milliseconds"
Rename to `AKGL_CHARACTER_MAX_NAME_LENGTH`. that held `1000000`, so it retried 10,000 times at 100 ms and blocked for
roughly sixteen minutes before reporting failure. The budget is now
`AKGL_GAME_STATE_LOCK_BUDGET_MS` (1000), with the cadence named separately as
`AKGL_GAME_STATE_LOCK_RETRY_MS`. `tests/game.c` carries
`test_game_state_lock_budget`, which holds the mutex from a second thread and
asserts the call gives up between half a second and five. The old code fails it
by timing the suite out; a build that gave up without waiting at all fails the
lower bound. Nothing exercised the contended path before — the existing lock
test only ever took an uncontended mutex, which never reaches the retry loop.
6. **`AKGL_TIME_ONESEC_MS` is misnamed and the error is live.** **Item 4 was not cosmetic, and the proof was sitting in the test suite.**
`include/akgl/game.h:22` defines it as `1000000`. One second in milliseconds `renderer` was exported from the shared library, and `tests/character.c` defined
is `1000`; `1000000` is the number of nanoseconds in a *millisecond*. The name an `SDL_Renderer *renderer` of its own. Both had external linkage and the same
says "one second" but the value means "one millisecond", so: spelling, so the executable's definition preempted the library's:
- `src/character.c:209` and `src/sprite.c:141` use it correctly as a `akgl_sprite_load_json` read a `SDL_Renderer *` through an
milliseconds-to-nanoseconds scale factor. `akgl_RenderBackend *`, every texture load in that suite failed with
- `src/game.c:136` uses it as documented — a one-second budget for the state `Parameter 'renderer' is invalid`, and the suite still reported success — see
lock — in a loop that advances `totaltime += 100` alongside `SDL_Delay(100)`. "Test suites that could not fail" below. It now binds a real backend with
The loop therefore runs 10,000 iterations of 100 ms, so `akgl_game_state_lock` `akgl_render_2d_bind`, the way `tests/sprite.c` and `tests/text.c` do. A
blocks for roughly 16 minutes rather than 1 second before reporting failure. consuming game with a variable called `renderer` would have hit the same thing
with no test to notice.
Rename to `AKGL_TIME_ONEMS_NS` to match `AKGL_TIME_ONESEC_NS`, and give
`akgl_game_state_lock` a real one-second budget.
### 2. Header/implementation surface drift ### 2. Header/implementation surface drift
@@ -114,7 +109,7 @@ overlap the existing **Defects** list are cross-referenced rather than repeated.
|---|---| |---|---|
| `character.h:41` `basechar` | `character.c:21` `obj` | | `character.h:41` `basechar` | `character.c:21` `obj` |
| `character.h:69` `props` | `character.c:75` `registry` | | `character.h:69` `props` | `character.c:75` `registry` |
| `heap.h:121` `ptr` | `heap.c:143` `basechar` | | ~~`heap.h:121` `ptr`~~ | ~~`heap.c:143` `basechar`~~ — fixed in 0.5.0 |
| `registry.h:97` `value` | `registry.c:163` `src` | | `registry.h:97` `value` | `registry.c:163` `src` |
| `json_helpers.h:134` `e` | `json_helpers.c:149` `err` | | `json_helpers.h:134` `e` | `json_helpers.c:149` `err` |
| `tilemap.h:134,143` `dest` | `tilemap.c:646,767` `map` | | `tilemap.h:134,143` `dest` | `tilemap.c:646,767` `map` |
@@ -131,19 +126,27 @@ overlap the existing **Defects** list are cross-referenced rather than repeated.
lands first wins and the `#ifndef` never fires, so the override mechanism 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. cannot work. Define each pool size once — `heap.h` is the natural home.
12. **Headers rely on their includers for types.** `iterator.h` uses `uint32_t` 12. **Headers rely on their includers for types.** `iterator.h` used `uint32_t`
without `<stdint.h>`; `json_helpers.h` uses `json_t` without `<jansson.h>`; without `<stdint.h>`; `json_helpers.h` used `json_t` without `<jansson.h>`;
`util.h` uses `SDL_FRect` and `bool` without any SDL include; `text.h` uses `util.h` used `SDL_FRect` and `bool` without any SDL include. Each compiled
`SDL_Color` and `akerr_ErrorContext` without including SDL or `akerror.h`; only because of `.c`-file include ordering.
`controller.h` uses `akgl_Actor` without including `actor.h`. Each compiles
only because of `.c`-file include ordering. Headers should be self-contained. **Resolved, and enforced rather than merely fixed.** `AKGL_PUBLIC_HEADERS`
in `CMakeLists.txt` is now the single list behind both `install()` and a
generated translation unit per header — each including exactly that header
and nothing before it — linked into the `headers` suite. A header that ships
is a header that is checked, and adding one to that list is all a new header
needs.
Writing the check found a case this item had missed: `registry.h` uses
`SDL_PropertiesID` in eight declarations and included no SDL header at all.
That is the argument for generating the check off the install list instead
of hand-listing the headers thought to be at risk.
13. **Include spelling is split between quoted and angled forms for the same 13. **Include spelling is split between quoted and angled forms for the same
directory.** `actor.h:10-11`, `character.h:10-11`, `controller.h:11`, directory.** **Resolved.** Every in-project include in a public header uses
`game.h:11-14`, `json_helpers.h:10`, and `staticstring.h:9` use `#include <akgl/sibling.h>`. `tests/*.c` still use `#include "testutil.h"`,
`#include "sibling.h"`; `physics.h:11-13`, `registry.h:9-10`, `renderer.h:13`, correctly: that one is a test-local header rather than an installed one.
`tilemap.h:10-12`, and `util.h:10` use `#include <akgl/sibling.h>`. The
angled form is correct for an installed library.
14. **Empty parameter lists.** `akgl_heap_init()`, `akgl_heap_init_actor()`, 14. **Empty parameter lists.** `akgl_heap_init()`, `akgl_heap_init_actor()`,
`akgl_registry_init*()`, `akgl_game_init()`, `akgl_game_init_screen()`, and `akgl_registry_init*()`, `akgl_game_init()`, `akgl_game_init_screen()`, and
@@ -367,6 +370,42 @@ overlap the existing **Defects** list are cross-referenced rather than repeated.
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`.
## Test suites that could not fail
Found while renaming the exported globals, and the reason item 4 above is filed
as a real defect rather than a style complaint. Both are fixed.
**Every suite reported success on any status whose low byte is zero.**
libakerror's default unhandled-error handler ends in `exit(errctx->status)`.
`exit` keeps only the low byte of what it is given, and libakgl's status band
starts at `AKERR_FIRST_CONSUMER_STATUS`, which is 256. `AKGL_ERR_SDL` is
therefore exactly 256, `exit(256)` is a wait status of 0, and CTest recorded a
pass. The most common failure status in a library built on SDL was the one
status that could not fail a test.
**`tests/character.c` was green while running one of its four tests.** It
aborted in `test_character_sprite_mgmt` with
`Failed loading asset .../spritesheet.png : Parameter 'renderer' is invalid`
the symbol collision described in item 4 — and exited 0 because that status was
`AKGL_ERR_SDL`. Two independent defects had to line up, and they did, for long
enough that `TODO.md` recorded the suite as passing and wondered which change
had fixed it. Nothing had; it had stopped running.
**Fixed** in `tests/testutil.h`: `TEST_TRAP_UNHANDLED_ERRORS()` installs a
handler that collapses any status a byte cannot carry onto 1, and every suite's
`main()` calls it immediately after `akgl_error_init()`. `tests/error.c` calls
it after its first test instead, because that suite has no `akgl_error_init()`
in `main()` and libakerror's lazy `akerr_init()` would overwrite the handler.
Once installed, no other suite changed colour, so this was not masking anything
beyond `character` — but it could have been at any time, and nothing would have
said so.
Worth knowing: the fix belongs here rather than in libakerror only because
`exit(status)` is a reasonable thing for a library to do when statuses fit in a
byte. libakerror's own band does. Consumers' bands start at 256 by construction,
so any consumer's test suites have this problem. That is worth raising upstream.
## Coverage status ## Coverage status
Generated with: Generated with:
@@ -833,16 +872,25 @@ without coming here first. Ordered by blast radius.
check `SDL_SetPointerProperty`'s return. Touches `src/character.c:43-55`. check `SDL_SetPointerProperty`'s return. Touches `src/character.c:43-55`.
21. **`akgl_heap_release_character` abandons the whole state-to-sprite map.** 21. **`akgl_heap_release_character` abandons the whole state-to-sprite map.**
`src/heap.c:150` zeroes the character without walking `state_sprites`, so `src/heap.c:150` zeroed the character without walking `state_sprites`, so
every sprite reference the character took in `akgl_character_sprite_add` is every sprite reference the character took in `akgl_character_sprite_add` was
lost and the `SDL_PropertiesID` holding the map is never destroyed. Loading lost and the `SDL_PropertiesID` holding the map was never destroyed. Loading
and releasing characters in a loop — level to level — exhausts the sprite and releasing characters in a loop — level to level — exhausted the sprite
pool and leaks an SDL property set each time. pool and leaked an SDL property set each time.
`akgl_character_state_sprites_iterate` with `AKGL_ITERATOR_OP_RELEASE` exists
precisely to do this walk and is not called from here.
Fix: enumerate `state_sprites` with that iterator, then **Fixed in 0.5.0.** At a refcount of zero it enumerates `state_sprites` with
`SDL_DestroyProperties`, before the `memset`. Touches `src/heap.c:145-151`. `akgl_character_state_sprites_iterate` and `AKGL_ITERATOR_OP_RELEASE`, then
`SDL_DestroyProperties`, then zeroes the slot. The iterator existed for
exactly this walk and simply was never called from here.
The test was already written. `tests/character.c` has asserted this contract
all along — "character did not reduce reference count of its child sprites
when released" — and had never once run, for the two reasons under "Test
suites that could not fail". It runs now, and it fails against the old code.
Still open and separate: **item 20**, `akgl_character_sprite_add` leaking the
displaced sprite when a state is *remapped*. Releasing at character teardown
does not cover a binding replaced while the character is alive.
22. **`akgl_path_relative_root` uses `FAIL_RETURN` inside its `ATTEMPT` block.** 22. **`akgl_path_relative_root` uses `FAIL_RETURN` inside its `ATTEMPT` block.**
`src/util.c:75` returns past `CLEANUP` on the over-long-path branch, so the `src/util.c:75` returns past `CLEANUP` on the over-long-path branch, so the
@@ -1016,11 +1064,9 @@ Ordered by blast radius. Numbering continues the **Defects** list above.
per actor per `akgl_game_update`. per actor per `akgl_game_update`.
33. **`akgl_heap_release_character` leaks its `state_sprites` property set.** 33. **`akgl_heap_release_character` leaks its `state_sprites` property set.**
Already documented in `heap.h` and in **Carried over** item 1; the perf **Fixed in 0.5.0**; see item 21, which is the same defect. The
suite makes it measurable rather than theoretical. A benchmark that loads character-load benchmark in `tests/perf_render.c` was written around it and
the fixture character 10,000 times leaks 10,000 SDL property sets and 20,000 no longer has to be.
sprite references. Cross-referenced here only because the character-load
benchmark had to be written around it.
### Targets ### Targets
@@ -1745,9 +1791,19 @@ this library and told it is compatible.
clear the character. Add tests for removal, replacement, duplicate sprite clear the character. Add tests for removal, replacement, duplicate sprite
bindings across multiple states, and final character release. bindings across multiple states, and final character release.
`akgl_character_state_sprites_iterate` is still uncovered and is the natural **The final-release half is done in 0.5.0** — see **Defects** item 21.
place to start; `tests/actor.c` now covers `akgl_character_sprite_get` and the `akgl_heap_release_character` now enumerates every remaining binding, releases
composite-state binding behavior it depends on. each reference, destroys `state_sprites`, and then clears the character, and
`akgl_character_state_sprites_iterate` is covered by that path.
**Still open: removal and replacement.** There is no API to unbind one state,
and `akgl_character_sprite_add` still overwrites an existing entry without
releasing the sprite it displaces (**Defects** item 20). A character that
rebinds a state while alive still leaks one reference per rebind; teardown
only gives back whatever the map happens to hold at the end. Wants
`akgl_character_sprite_del`, a release of the displaced entry inside
`akgl_character_sprite_add`, and tests for removal, replacement, and
duplicate sprite bindings across multiple states.
2. **An actor cannot be scaled per axis.** `akgl_Actor::scale` is one `float32_t` applied 2. **An actor cannot be scaled per axis.** `akgl_Actor::scale` is one `float32_t` applied
to both `dest.w` and `dest.h`, so there is no way to express "twice as wide, the same to both `dest.w` and `dest.h`, so there is no way to express "twice as wide, the same

View File

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

View File

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

View File

@@ -16,18 +16,18 @@
#define _AKGL_CHARACTER_H_ #define _AKGL_CHARACTER_H_
#include <SDL3/SDL_properties.h> #include <SDL3/SDL_properties.h>
#include "types.h" #include <akgl/types.h>
#include "sprite.h" #include <akgl/sprite.h>
#define AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH 128 #define AKGL_CHARACTER_MAX_NAME_LENGTH 128
#define AKGL_MAX_HEAP_CHARACTER 256 #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 {
uint8_t refcount; /**< Pool bookkeeping; 0 means the slot is free. */ uint8_t refcount; /**< Pool bookkeeping; 0 means the slot is free. */
char name[AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH]; /**< Registry key. Truncated, not rejected, if the source name is longer. */ char name[AKGL_CHARACTER_MAX_NAME_LENGTH]; /**< Registry key. Truncated, not rejected, if the source name is longer. */
SDL_PropertiesID state_sprites; /**< State bitmask (decimal, as a string) -> akgl_Sprite *. */ SDL_PropertiesID state_sprites; /**< State bitmask (decimal, as a string) -> akgl_Sprite *. */
uint64_t speedtime; /**< Nanoseconds one sprite frame is held before advancing. Read from JSON in milliseconds and scaled by #AKGL_TIME_ONESEC_MS, which despite its name is nanoseconds-per-millisecond. TODO.md item 6. */ uint64_t speedtime; /**< Nanoseconds one sprite frame is held before advancing. Read from JSON in milliseconds and scaled by #AKGL_TIME_ONEMS_NS. */
float32_t ax; /**< Acceleration along x, world units per second squared. Copied into an actor by akgl_actor_set_character. */ float32_t ax; /**< Acceleration along x, world units per second squared. Copied into an actor by akgl_actor_set_character. */
float32_t ay; /**< Acceleration along y. */ float32_t ay; /**< Acceleration along y. */
float32_t az; /**< Acceleration along z. Not read from JSON; stays 0 unless set by hand. */ float32_t az; /**< Acceleration along z. Not read from JSON; stays 0 unless set by hand. */
@@ -52,7 +52,7 @@ typedef struct akgl_Character {
* akgl_heap_next_character. Required. Any previous contents are * akgl_heap_next_character. Required. Any previous contents are
* discarded without releasing the sprites they referenced. * discarded without releasing the sprites they referenced.
* @param name Registry key, NUL-terminated. Required. Truncated at * @param name Registry key, NUL-terminated. Required. Truncated at
* #AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH. A name already in the * #AKGL_CHARACTER_MAX_NAME_LENGTH. A name already in the
* registry is silently replaced, and the character it displaced * registry is silently replaced, and the character it displaced
* becomes unreachable rather than being released. * becomes unreachable rather than being released.
* @return `NULL` on success, otherwise an error context owned by the caller. * @return `NULL` on success, otherwise an error context owned by the caller.

View File

@@ -24,12 +24,12 @@
* akgl_controller_poll_keystroke(). * akgl_controller_poll_keystroke().
*/ */
#ifndef _CONTROLLER_H_ #ifndef _AKGL_CONTROLLER_H_
#define _CONTROLLER_H_ #define _AKGL_CONTROLLER_H_
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <akerror.h> #include <akerror.h>
#include "types.h" #include <akgl/types.h>
// The binding handlers below take an akgl_Actor *, so this header cannot be // The binding handlers below take an akgl_Actor *, so this header cannot be
// included before akgl/actor.h without one. akgl_Actor is a typedef of a named // included before akgl/actor.h without one. akgl_Actor is a typedef of a named
// struct, and repeating a typedef is not C99, so it is included rather than // struct, and repeating a typedef is not C99, so it is included rather than
@@ -94,7 +94,7 @@ typedef struct {
} akgl_ControlMap; } akgl_ControlMap;
/** @brief Every control map. Zeroed by akgl_game_init; index it by the same id the functions below take. */ /** @brief Every control map. Zeroed by akgl_game_init; index it by the same id the functions below take. */
extern akgl_ControlMap GAME_ControlMaps[AKGL_MAX_CONTROL_MAPS]; extern akgl_ControlMap akgl_controlmaps[AKGL_MAX_CONTROL_MAPS];
/** /**
* @brief Log every attached keyboard and its SDL id. * @brief Log every attached keyboard and its SDL id.
@@ -190,7 +190,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_removed(void *appstate
* The binding is copied, so the caller's `akgl_Control` can be a stack local * The binding is copied, so the caller's `akgl_Control` can be a stack local
* reused across pushes -- which is exactly what akgl_controller_default does. * reused across pushes -- which is exactly what akgl_controller_default does.
* Bindings can only be appended; there is no remove, and no way to reset a map * Bindings can only be appended; there is no remove, and no way to reset a map
* short of zeroing it in ::GAME_ControlMaps directly. * short of zeroing it in ::akgl_controlmaps directly.
* *
* @param controlmapid Which map to append to, 0 through * @param controlmapid Which map to append to, 0 through
* #AKGL_MAX_CONTROL_MAPS - 1. * #AKGL_MAX_CONTROL_MAPS - 1.
@@ -203,7 +203,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_removed(void *appstate
* *
* @warning A **negative** @p controlmapid is not rejected -- only the upper * @warning A **negative** @p controlmapid is not rejected -- only the upper
* bound is checked -- and indexes before the start of * bound is checked -- and indexes before the start of
* ::GAME_ControlMaps. TODO.md, "Known and still open" item 11. * ::akgl_controlmaps. TODO.md, "Known and still open" item 11.
*/ */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akgl_Control *control); akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akgl_Control *control);
@@ -212,7 +212,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akg
* *
* Points the map at the named actor and pushes eight bindings: the four arrow * Points the map at the named actor and pushes eight bindings: the four arrow
* keys and the four D-pad directions, each wired to the matching * keys and the four D-pad directions, each wired to the matching
* `akgl_Actor_cmhf_*_on`/`_off` pair. It is the "just give me something that * `akgl_actor_cmhf_*_on`/`_off` pair. It is the "just give me something that
* works" path -- a game wanting different keys builds its own bindings with * works" path -- a game wanting different keys builds its own bindings with
* akgl_controller_pushmap. * akgl_controller_pushmap.
* *
@@ -334,4 +334,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_keystroke(akgl_Keystroke
* @return `NULL`. There is no failure path -- it resets two counters. * @return `NULL`. There is no failure path -- it resets two counters.
*/ */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_flush_keys(void); akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_flush_keys(void);
#endif // _CONTROLLER_H_ #endif // _AKGL_CONTROLLER_H_

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,14 +21,15 @@
* separate call, made by akgl_game_init but by nothing else. A program * separate call, made by akgl_game_init but by nothing else. A program
* that builds its own startup path and skips it gets a silently * that builds its own startup path and skips it gets a silently
* no-op akgl_set_property and an akgl_get_property that always hands * no-op akgl_set_property and an akgl_get_property that always hands
* back the caller's default, which in turn means akgl_render_init2d * back the caller's default, which in turn means akgl_render_2d_init
* and akgl_physics_init_arcade quietly ignore their configuration. * and akgl_physics_init_arcade quietly ignore their configuration.
* TODO.md, "Known and still open" item 3. * TODO.md, "Known and still open" item 3.
*/ */
#ifndef _REGISTRY_H_ #ifndef _AKGL_REGISTRY_H_
#define _REGISTRY_H_ #define _AKGL_REGISTRY_H_
#include <SDL3/SDL.h>
#include <akgl/error.h> #include <akgl/error.h>
#include <akgl/staticstring.h> #include <akgl/staticstring.h>
@@ -124,7 +125,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_character();
* Deliberately separate from akgl_registry_init, because configuration has to be * Deliberately separate from akgl_registry_init, because configuration has to be
* in place before the subsystems that read it start up. akgl_game_init creates * in place before the subsystems that read it start up. akgl_game_init creates
* it; filling it in -- with akgl_registry_load_properties or akgl_set_property -- * it; filling it in -- with akgl_registry_load_properties or akgl_set_property --
* is the caller's job, and has to happen before akgl_render_init2d or * is the caller's job, and has to happen before akgl_render_2d_init or
* akgl_physics_init_arcade run. * akgl_physics_init_arcade run.
* *
* @return `NULL` on success, otherwise an error context owned by the caller. * @return `NULL` on success, otherwise an error context owned by the caller.
@@ -138,7 +139,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_properties();
* *
* Expects a document with a top-level `properties` object whose members are all * Expects a document with a top-level `properties` object whose members are all
* strings; each becomes one entry. Numbers are configured as strings here and * strings; each becomes one entry. Numbers are configured as strings here and
* parsed by whoever reads them -- `game.screenwidth` is `"800"`, not `800`. * parsed by whoever reads them -- `akgl_game.screenwidth` is `"800"`, not `800`.
* *
* @param fname Path to the JSON document. Required. Used verbatim. * @param fname Path to the JSON document. Required. Used verbatim.
* @return `NULL` on success, otherwise an error context owned by the caller. * @return `NULL` on success, otherwise an error context owned by the caller.
@@ -215,4 +216,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_set_property(char *name, char *value);
*/ */
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_property(char *name, akgl_String **dest, char *def); akerr_ErrorContext AKERR_NOIGNORE *akgl_get_property(char *name, akgl_String **dest, char *def);
#endif //_REGISTRY_H_ #endif //_AKGL_REGISTRY_H_

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -234,7 +234,7 @@ akerr_ErrorContext *akgl_actor_render(akgl_Actor *obj)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_character_sprite_get(obj->basechar, obj->state, &curSprite)); CATCH(errctx, akgl_character_sprite_get(obj->basechar, obj->state, &curSprite));
CATCH(errctx, actor_visible(obj, camera, &visible)); CATCH(errctx, actor_visible(obj, akgl_camera, &visible));
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {
} HANDLE(errctx, AKERR_KEY) { } HANDLE(errctx, AKERR_KEY) {
@@ -256,7 +256,7 @@ akerr_ErrorContext *akgl_actor_render(akgl_Actor *obj)
ATTEMPT { ATTEMPT {
CATCH(errctx, CATCH(errctx,
akgl_sprite_sheet_coords_for_frame( akgl_spritesheet_coords_for_frame(
curSprite, curSprite,
&src, &src,
obj->curSpriteFrameId) obj->curSpriteFrameId)
@@ -266,16 +266,16 @@ akerr_ErrorContext *akgl_actor_render(akgl_Actor *obj)
} FINISH(errctx, true); } FINISH(errctx, true);
if ( obj->parent != NULL ) { if ( obj->parent != NULL ) {
dest.x = (obj->parent->x + obj->x - camera->x); dest.x = (obj->parent->x + obj->x - akgl_camera->x);
dest.y = (obj->parent->y + obj->y - camera->y); dest.y = (obj->parent->y + obj->y - akgl_camera->y);
} else { } else {
dest.x = (obj->x - camera->x); dest.x = (obj->x - akgl_camera->x);
dest.y = (obj->y - camera->y); dest.y = (obj->y - akgl_camera->y);
} }
dest.w = curSprite->width * obj->scale; dest.w = curSprite->width * obj->scale;
dest.h = curSprite->width * obj->scale; dest.h = curSprite->width * obj->scale;
PASS(errctx, renderer->draw_texture(renderer, curSprite->sheet->texture, &src, &dest, 0, NULL, SDL_FLIP_NONE)); PASS(errctx, akgl_renderer->draw_texture(akgl_renderer, curSprite->sheet->texture, &src, &dest, 0, NULL, SDL_FLIP_NONE));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -316,7 +316,7 @@ void akgl_registry_iterate_actor(void *userdata, SDL_PropertiesID registry, cons
CATCH(errctx, obj->updatefunc(obj)); CATCH(errctx, obj->updatefunc(obj));
} }
if (AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_TILEMAPSCALE)) { if (AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_TILEMAPSCALE)) {
CATCH(errctx, akgl_tilemap_scale_actor(gamemap, obj)); CATCH(errctx, akgl_tilemap_scale_actor(akgl_gamemap, obj));
} else { } else {
obj->scale = 1.0; obj->scale = 1.0;
} }
@@ -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 AKERR_NOIGNORE *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 AKERR_NOIGNORE *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 AKERR_NOIGNORE *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 AKERR_NOIGNORE *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 AKERR_NOIGNORE *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 AKERR_NOIGNORE *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 AKERR_NOIGNORE *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 AKERR_NOIGNORE *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");

View File

@@ -26,8 +26,8 @@ akerr_ErrorContext *akgl_load_start_bgm(char *fname)
//SDL_snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), fname); //SDL_snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), fname);
SDL_Log("Loading music asset from %s", fname); SDL_Log("Loading music asset from %s", fname);
bgm = MIX_LoadAudio(akgl_mixer, fname, true); akgl_bgm = MIX_LoadAudio(akgl_mixer, fname, true);
FAIL_ZERO_BREAK(errctx, bgm, AKERR_NULLPOINTER, "Failed to load music asset %s : %s", fname, SDL_GetError()); FAIL_ZERO_BREAK(errctx, akgl_bgm, AKERR_NULLPOINTER, "Failed to load music asset %s : %s", fname, SDL_GetError());
bgmtrack = MIX_CreateTrack(akgl_mixer); bgmtrack = MIX_CreateTrack(akgl_mixer);
FAIL_ZERO_BREAK(errctx, bgmtrack, AKERR_NULLPOINTER, "Failed to create audio track for background music: %s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, bgmtrack, AKERR_NULLPOINTER, "Failed to create audio track for background music: %s", SDL_GetError());
@@ -36,7 +36,7 @@ akerr_ErrorContext *akgl_load_start_bgm(char *fname)
FAIL_ZERO_BREAK( FAIL_ZERO_BREAK(
errctx, errctx,
MIX_SetTrackAudio(bgmtrack, bgm), MIX_SetTrackAudio(bgmtrack, akgl_bgm),
AKGL_ERR_SDL, AKGL_ERR_SDL,
"%s", "%s",
SDL_GetError()); SDL_GetError());
@@ -49,8 +49,8 @@ akerr_ErrorContext *akgl_load_start_bgm(char *fname)
} CLEANUP { } CLEANUP {
//IGNORE(akgl_heap_release_string(tmpstr)); //IGNORE(akgl_heap_release_string(tmpstr));
if ( errctx != NULL ) { if ( errctx != NULL ) {
if ( errctx->status != 0 && bgm != NULL) { if ( errctx->status != 0 && akgl_bgm != NULL) {
MIX_DestroyAudio(bgm); MIX_DestroyAudio(akgl_bgm);
} }
} }
} PROCESS(errctx) { } PROCESS(errctx) {

View File

@@ -24,7 +24,7 @@ akerr_ErrorContext *akgl_character_initialize(akgl_Character *obj, char *name)
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL akgl_Character reference"); FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL akgl_Character reference");
FAIL_ZERO_RETURN(errctx, name, AKERR_NULLPOINTER, "NULL name string pointer"); FAIL_ZERO_RETURN(errctx, name, AKERR_NULLPOINTER, "NULL name string pointer");
memset(obj, 0x00, sizeof(akgl_Character)); memset(obj, 0x00, sizeof(akgl_Character));
strncpy(obj->name, name, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH); strncpy(obj->name, name, AKGL_CHARACTER_MAX_NAME_LENGTH);
obj->state_sprites = SDL_CreateProperties(); obj->state_sprites = SDL_CreateProperties();
FAIL_ZERO_RETURN(errctx, obj->state_sprites, AKERR_NULLPOINTER, "Unable to initialize SDL_PropertiesID for character state map"); FAIL_ZERO_RETURN(errctx, obj->state_sprites, AKERR_NULLPOINTER, "Unable to initialize SDL_PropertiesID for character state map");
@@ -238,7 +238,7 @@ akerr_ErrorContext *akgl_character_load_json(char *filename)
); );
CATCH(errctx, akgl_character_load_json_inner(json, obj)); CATCH(errctx, akgl_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_ONESEC_MS; 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));
CATCH(errctx, akgl_get_json_number_value(json, "speed_y", &obj->sy)); CATCH(errctx, akgl_get_json_number_value(json, "speed_y", &obj->sy));
CATCH(errctx, akgl_get_json_number_value(json, "acceleration_x", &obj->ax)); CATCH(errctx, akgl_get_json_number_value(json, "acceleration_x", &obj->ax));

View File

@@ -10,7 +10,7 @@
#include <akgl/game.h> #include <akgl/game.h>
#include <akgl/controller.h> #include <akgl/controller.h>
akgl_ControlMap GAME_ControlMaps[AKGL_MAX_CONTROL_MAPS]; akgl_ControlMap akgl_controlmaps[AKGL_MAX_CONTROL_MAPS];
/* /*
* Keystrokes waiting for akgl_controller_poll_key() and * Keystrokes waiting for akgl_controller_poll_key() and
@@ -258,7 +258,7 @@ akerr_ErrorContext *akgl_controller_handle_event(void *appstate, SDL_Event *even
ATTEMPT { ATTEMPT {
for ( i = 0 ; i < AKGL_MAX_CONTROL_MAPS; i++ ) { for ( i = 0 ; i < AKGL_MAX_CONTROL_MAPS; i++ ) {
curmap = &GAME_ControlMaps[i]; curmap = &akgl_controlmaps[i];
if ( curmap->target == NULL ) { if ( curmap->target == NULL ) {
continue; continue;
} }
@@ -308,7 +308,7 @@ akerr_ErrorContext *akgl_controller_handle_event(void *appstate, SDL_Event *even
* A whole-application shortcut rather than a control-map binding: it looks the * 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 * 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 * being told which actor to act on, so it only ever drives one. The
* akgl_Actor_cmhf_* handlers are the general form. * akgl_actor_cmhf_* handlers are the general form.
* *
* Facing follows movement unless the actor sets `movement_controls_face`, which * 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. * is how an actor that aims independently of the direction it walks opts out.
@@ -528,10 +528,10 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akg
ATTEMPT { ATTEMPT {
FAIL_ZERO_RETURN(errctx, control, AKERR_NULLPOINTER, "NULL Control"); FAIL_ZERO_RETURN(errctx, control, AKERR_NULLPOINTER, "NULL Control");
FAIL_NONZERO_RETURN(errctx, (controlmapid >= AKGL_MAX_CONTROL_MAPS), AKERR_OUTOFBOUNDS, "ID %d exceeds maximum %d", controlmapid, AKGL_MAX_CONTROL_MAPS); FAIL_NONZERO_RETURN(errctx, (controlmapid >= AKGL_MAX_CONTROL_MAPS), AKERR_OUTOFBOUNDS, "ID %d exceeds maximum %d", controlmapid, AKGL_MAX_CONTROL_MAPS);
newmapid = GAME_ControlMaps[controlmapid].nextMap; newmapid = akgl_controlmaps[controlmapid].nextMap;
FAIL_ZERO_RETURN(errctx, (AKGL_MAX_CONTROLS - newmapid), AKERR_OUTOFBOUNDS, "Control map ID %d is full", controlmapid); FAIL_ZERO_RETURN(errctx, (AKGL_MAX_CONTROLS - newmapid), AKERR_OUTOFBOUNDS, "Control map ID %d is full", controlmapid);
memcpy((void *)&GAME_ControlMaps[controlmapid].controls[newmapid], control, sizeof(akgl_Control)); memcpy((void *)&akgl_controlmaps[controlmapid].controls[newmapid], control, sizeof(akgl_Control));
GAME_ControlMaps[controlmapid].nextMap = newmapid + 1; akgl_controlmaps[controlmapid].nextMap = newmapid + 1;
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {
} FINISH(errctx, true); } FINISH(errctx, true);
@@ -548,7 +548,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
// set up the control map // set up the control map
FAIL_NONZERO_RETURN(errctx, (controlmapid >= AKGL_MAX_CONTROL_MAPS), AKERR_OUTOFBOUNDS, "ID %d exceeds maximum %d", controlmapid, AKGL_MAX_CONTROL_MAPS); FAIL_NONZERO_RETURN(errctx, (controlmapid >= AKGL_MAX_CONTROL_MAPS), AKERR_OUTOFBOUNDS, "ID %d exceeds maximum %d", controlmapid, AKGL_MAX_CONTROL_MAPS);
memset((void *)&control, 0x00, sizeof(akgl_Control)); memset((void *)&control, 0x00, sizeof(akgl_Control));
controlmap = &GAME_ControlMaps[controlmapid]; controlmap = &akgl_controlmaps[controlmapid];
controlmap->kbid = kbid; controlmap->kbid = kbid;
controlmap->jsid = jsid; controlmap->jsid = jsid;
@@ -561,32 +561,32 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
control.key = SDLK_DOWN; control.key = SDLK_DOWN;
control.event_on = SDL_EVENT_KEY_DOWN; control.event_on = SDL_EVENT_KEY_DOWN;
control.event_off = SDL_EVENT_KEY_UP; control.event_off = SDL_EVENT_KEY_UP;
control.handler_on = &akgl_Actor_cmhf_down_on; control.handler_on = &akgl_actor_cmhf_down_on;
control.handler_off = &akgl_Actor_cmhf_down_off; control.handler_off = &akgl_actor_cmhf_down_off;
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
// Move up // Move up
control.key = SDLK_UP; control.key = SDLK_UP;
control.event_on = SDL_EVENT_KEY_DOWN; control.event_on = SDL_EVENT_KEY_DOWN;
control.event_off = SDL_EVENT_KEY_UP; control.event_off = SDL_EVENT_KEY_UP;
control.handler_on = &akgl_Actor_cmhf_up_on; control.handler_on = &akgl_actor_cmhf_up_on;
control.handler_off = &akgl_Actor_cmhf_up_off; control.handler_off = &akgl_actor_cmhf_up_off;
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
// Move left // Move left
control.key = SDLK_LEFT; control.key = SDLK_LEFT;
control.event_on = SDL_EVENT_KEY_DOWN; control.event_on = SDL_EVENT_KEY_DOWN;
control.event_off = SDL_EVENT_KEY_UP; control.event_off = SDL_EVENT_KEY_UP;
control.handler_on = &akgl_Actor_cmhf_left_on; control.handler_on = &akgl_actor_cmhf_left_on;
control.handler_off = &akgl_Actor_cmhf_left_off; control.handler_off = &akgl_actor_cmhf_left_off;
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
// Move right // Move right
control.key = SDLK_RIGHT; control.key = SDLK_RIGHT;
control.event_on = SDL_EVENT_KEY_DOWN; control.event_on = SDL_EVENT_KEY_DOWN;
control.event_off = SDL_EVENT_KEY_UP; control.event_off = SDL_EVENT_KEY_UP;
control.handler_on = &akgl_Actor_cmhf_right_on; control.handler_on = &akgl_actor_cmhf_right_on;
control.handler_off = &akgl_Actor_cmhf_right_off; control.handler_off = &akgl_actor_cmhf_right_off;
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
control.key = 0; control.key = 0;
@@ -595,32 +595,32 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
control.button = SDL_GAMEPAD_BUTTON_DPAD_DOWN; control.button = SDL_GAMEPAD_BUTTON_DPAD_DOWN;
control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN; control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN;
control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP; control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP;
control.handler_on = &akgl_Actor_cmhf_down_on; control.handler_on = &akgl_actor_cmhf_down_on;
control.handler_off = &akgl_Actor_cmhf_down_off; control.handler_off = &akgl_actor_cmhf_down_off;
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
// Move up // Move up
control.button = SDL_GAMEPAD_BUTTON_DPAD_UP; control.button = SDL_GAMEPAD_BUTTON_DPAD_UP;
control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN; control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN;
control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP; control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP;
control.handler_on = &akgl_Actor_cmhf_up_on; control.handler_on = &akgl_actor_cmhf_up_on;
control.handler_off = &akgl_Actor_cmhf_up_off; control.handler_off = &akgl_actor_cmhf_up_off;
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
// Move left // Move left
control.button = SDL_GAMEPAD_BUTTON_DPAD_LEFT; control.button = SDL_GAMEPAD_BUTTON_DPAD_LEFT;
control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN; control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN;
control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP; control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP;
control.handler_on = &akgl_Actor_cmhf_left_on; control.handler_on = &akgl_actor_cmhf_left_on;
control.handler_off = &akgl_Actor_cmhf_left_off; control.handler_off = &akgl_actor_cmhf_left_off;
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
// Move right // Move right
control.button = SDL_GAMEPAD_BUTTON_DPAD_RIGHT; control.button = SDL_GAMEPAD_BUTTON_DPAD_RIGHT;
control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN; control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN;
control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP; control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP;
control.handler_on = &akgl_Actor_cmhf_right_on; control.handler_on = &akgl_actor_cmhf_right_on;
control.handler_off = &akgl_Actor_cmhf_right_off; control.handler_off = &akgl_actor_cmhf_right_off;
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);

View File

@@ -45,11 +45,11 @@ void akgl_draw_background(int w, int h)
for (x = 0; x < w; x += dx) { for (x = 0; x < w; x += dx) {
/* use an 8x8 checkerboard pattern */ /* use an 8x8 checkerboard pattern */
i = (((x ^ y) >> 3) & 1); i = (((x ^ y) >> 3) & 1);
SDL_SetRenderDrawColor(renderer->sdl_renderer, col[i].r, col[i].g, col[i].b, col[i].a); SDL_SetRenderDrawColor(akgl_renderer->sdl_renderer, col[i].r, col[i].g, col[i].b, col[i].a);
rect.x = (float)x; rect.x = (float)x;
rect.y = (float)y; rect.y = (float)y;
SDL_RenderFillRect(renderer->sdl_renderer, &rect); SDL_RenderFillRect(akgl_renderer->sdl_renderer, &rect);
} }
} }
} }

View File

@@ -24,24 +24,24 @@
#include <akgl/error.h> #include <akgl/error.h>
#include <akgl/SDL_GameControllerDB.h> #include <akgl/SDL_GameControllerDB.h>
SDL_Window *window = NULL; SDL_Window *akgl_window = NULL;
// Currently active objects // Currently active objects
akgl_RenderBackend *renderer; akgl_RenderBackend *akgl_renderer;
akgl_PhysicsBackend *physics; akgl_PhysicsBackend *akgl_physics;
SDL_FRect *camera; SDL_FRect *akgl_camera;
akgl_Tilemap *gamemap; akgl_Tilemap *akgl_gamemap;
// Default objects // Default objects
akgl_RenderBackend _akgl_renderer; akgl_RenderBackend akgl_default_renderer;
akgl_PhysicsBackend _akgl_physics; akgl_PhysicsBackend akgl_default_physics;
SDL_FRect _akgl_camera; SDL_FRect akgl_default_camera;
akgl_Tilemap _akgl_gamemap; akgl_Tilemap akgl_default_gamemap;
MIX_Audio *bgm = NULL; MIX_Audio *akgl_bgm = NULL;
MIX_Mixer *akgl_mixer = NULL; MIX_Mixer *akgl_mixer = NULL;
MIX_Track *akgl_tracks[AKGL_GAME_AUDIO_MAX_TRACKS]; MIX_Track *akgl_tracks[AKGL_GAME_AUDIO_MAX_TRACKS];
akgl_Game game; akgl_Game akgl_game;
static akerr_ErrorContext *write_exact(const void *ptr, size_t size, size_t nmemb, FILE *fp) static akerr_ErrorContext *write_exact(const void *ptr, size_t size, size_t nmemb, FILE *fp)
{ {
@@ -69,7 +69,7 @@ typedef char akgl_game_save_name_field_fits[
((AKGL_ACTOR_MAX_NAME_LENGTH <= AKGL_GAME_SAVE_MAX_NAME_FIELD) && ((AKGL_ACTOR_MAX_NAME_LENGTH <= AKGL_GAME_SAVE_MAX_NAME_FIELD) &&
(AKGL_SPRITE_MAX_NAME_LENGTH <= AKGL_GAME_SAVE_MAX_NAME_FIELD) && (AKGL_SPRITE_MAX_NAME_LENGTH <= AKGL_GAME_SAVE_MAX_NAME_FIELD) &&
(AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH <= AKGL_GAME_SAVE_MAX_NAME_FIELD) && (AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH <= AKGL_GAME_SAVE_MAX_NAME_FIELD) &&
(AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH <= AKGL_GAME_SAVE_MAX_NAME_FIELD)) ? 1 : -1]; (AKGL_CHARACTER_MAX_NAME_LENGTH <= AKGL_GAME_SAVE_MAX_NAME_FIELD)) ? 1 : -1];
/** /**
* @brief Write a registry key as a fixed-width, zero-padded field. * @brief Write a registry key as a fixed-width, zero-padded field.
@@ -125,7 +125,7 @@ static akerr_ErrorContext *read_exact(void *ptr, size_t size, size_t nmemb, FILE
void akgl_game_lowfps(void) void akgl_game_lowfps(void)
{ {
SDL_Log("Low FPS! %d", game.fps); SDL_Log("Low FPS! %d", akgl_game.fps);
return; return;
} }
@@ -140,17 +140,17 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
// AKGL_ERR_* codes, and a code raised before its name is registered prints // AKGL_ERR_* codes, and a code raised before its name is registered prints
// as "Unknown Error" in the stack trace the caller is left holding. // as "Unknown Error" in the stack trace the caller is left holding.
PASS(e, akgl_error_init()); PASS(e, akgl_error_init());
strncpy((char *)&game.libversion, AKGL_VERSION, 32); strncpy((char *)&akgl_game.libversion, AKGL_VERSION, 32);
game.gameStartTime = SDL_GetTicksNS(); akgl_game.gameStartTime = SDL_GetTicksNS();
game.lastIterTime = game.gameStartTime; akgl_game.lastIterTime = akgl_game.gameStartTime;
game.lastFPSTime = game.gameStartTime; akgl_game.lastFPSTime = akgl_game.gameStartTime;
game.lowfpsfunc = &akgl_game_lowfps; akgl_game.lowfpsfunc = &akgl_game_lowfps;
game.statelock = SDL_CreateMutex(); akgl_game.statelock = SDL_CreateMutex();
FAIL_ZERO_RETURN(e, game.statelock, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_RETURN(e, akgl_game.statelock, AKGL_ERR_SDL, "%s", SDL_GetError());
PASS(e, akgl_game_state_lock()); PASS(e, akgl_game_state_lock());
FAIL_ZERO_RETURN(e, strlen((char *)&game.name), AKERR_NULLPOINTER, "Must provide game name"); FAIL_ZERO_RETURN(e, strlen((char *)&akgl_game.name), AKERR_NULLPOINTER, "Must provide game name");
FAIL_ZERO_RETURN(e, strlen((char *)&game.version), AKERR_NULLPOINTER, "Must provide game version"); FAIL_ZERO_RETURN(e, strlen((char *)&akgl_game.version), AKERR_NULLPOINTER, "Must provide game version");
FAIL_ZERO_RETURN(e, strlen((char *)&game.uri), AKERR_NULLPOINTER, "Must provide game uri"); FAIL_ZERO_RETURN(e, strlen((char *)&akgl_game.uri), AKERR_NULLPOINTER, "Must provide game uri");
PASS(e, akgl_heap_init()); PASS(e, akgl_heap_init());
PASS(e, akgl_registry_init_actor()); PASS(e, akgl_registry_init_actor());
PASS(e, akgl_registry_init_sprite()); PASS(e, akgl_registry_init_sprite());
@@ -161,10 +161,10 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
PASS(e, akgl_registry_init_properties()); PASS(e, akgl_registry_init_properties());
PASS(e, akgl_registry_init_actor_state_strings()); PASS(e, akgl_registry_init_actor_state_strings());
SDL_SetAppMetadata(game.name, game.version, game.uri); SDL_SetAppMetadata(akgl_game.name, akgl_game.version, akgl_game.uri);
for ( i = 0 ; i < AKGL_MAX_CONTROL_MAPS; i++ ) { for ( i = 0 ; i < AKGL_MAX_CONTROL_MAPS; i++ ) {
memset(&GAME_ControlMaps[i], 0x00, sizeof(akgl_ControlMap)); memset(&akgl_controlmaps[i], 0x00, sizeof(akgl_ControlMap));
} }
FAIL_ZERO_RETURN( FAIL_ZERO_RETURN(
@@ -204,10 +204,10 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
"Couldn't initialize front engine: %s", "Couldn't initialize front engine: %s",
SDL_GetError()); SDL_GetError());
camera = &_akgl_camera; akgl_camera = &akgl_default_camera;
renderer = &_akgl_renderer; akgl_renderer = &akgl_default_renderer;
physics = &_akgl_physics; akgl_physics = &akgl_default_physics;
gamemap = &_akgl_gamemap; akgl_gamemap = &akgl_default_gamemap;
PASS(e, akgl_game_state_unlock()); PASS(e, akgl_game_state_unlock());
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
@@ -216,39 +216,51 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_lock(void) akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_lock(void)
{ {
PREPARE_ERROR(e); PREPARE_ERROR(e);
SDL_Time totaltime = 0; // Milliseconds, which is what SDL_Delay takes. This used to count against
// AKGL_TIME_ONESEC_MS, a constant whose name said "one second in
// milliseconds" and whose value was a thousand times that, so the budget
// was roughly sixteen minutes rather than the one second documented.
Uint32 waitedms = 0;
while ( totaltime < AKGL_TIME_ONESEC_MS ) { while ( waitedms < AKGL_GAME_STATE_LOCK_BUDGET_MS ) {
if ( SDL_TryLockMutex(game.statelock) == true ) { if ( SDL_TryLockMutex(akgl_game.statelock) == true ) {
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
} }
totaltime += 100; waitedms += AKGL_GAME_STATE_LOCK_RETRY_MS;
SDL_Delay(100); SDL_Delay(AKGL_GAME_STATE_LOCK_RETRY_MS);
} }
FAIL_RETURN(e, AKGL_ERR_SDL, "%s", SDL_GetError()); // SDL_GetError() after a failed SDL_TryLockMutex is stale or empty -- the
// call reports contention by returning false, not by setting an error -- so
// say what actually happened and keep SDL's text as trailing context.
FAIL_RETURN(
e,
AKGL_ERR_SDL,
"Gave up waiting %d ms for the game state lock: %s",
AKGL_GAME_STATE_LOCK_BUDGET_MS,
SDL_GetError());
} }
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void) akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void)
{ {
PREPARE_ERROR(e); PREPARE_ERROR(e);
SDL_UnlockMutex(game.statelock); SDL_UnlockMutex(akgl_game.statelock);
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
} }
void akgl_game_updateFPS() void akgl_game_update_fps()
{ {
SDL_Time curTime; SDL_Time curTime;
curTime = SDL_GetTicksNS(); curTime = SDL_GetTicksNS();
if ( (curTime - game.lastFPSTime) > AKGL_TIME_ONESEC_NS ) { if ( (curTime - akgl_game.lastFPSTime) > AKGL_TIME_ONESEC_NS ) {
game.fps = game.framesSinceUpdate; akgl_game.fps = akgl_game.framesSinceUpdate;
game.framesSinceUpdate = 0; akgl_game.framesSinceUpdate = 0;
game.lastFPSTime = curTime; akgl_game.lastFPSTime = curTime;
} }
if ( game.fps < 30 ) { if ( akgl_game.fps < 30 ) {
game.lowfpsfunc(); akgl_game.lowfpsfunc();
} }
game.framesSinceUpdate += 1; akgl_game.framesSinceUpdate += 1;
game.lastIterTime = curTime; akgl_game.lastIterTime = curTime;
} }
/* /*
@@ -354,7 +366,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 akgl_game_save_actorname_iterator, but the name field is
* #AKGL_SPRITE_MAX_CHARACTER_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.
* @param props #AKGL_REGISTRY_CHARACTER, supplied by SDL. * @param props #AKGL_REGISTRY_CHARACTER, supplied by SDL.
@@ -371,7 +383,7 @@ void akgl_game_save_charactername_iterator(void *userdata, SDL_PropertiesID prop
ATTEMPT { ATTEMPT {
FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer"); FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer");
character = SDL_GetPointerProperty(props, name, NULL); character = SDL_GetPointerProperty(props, name, NULL);
CATCH(e, write_name_field(name, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp)); CATCH(e, write_name_field(name, AKGL_CHARACTER_MAX_NAME_LENGTH, fp));
CATCH(e, write_exact(&character, 1, sizeof(akgl_Character *), fp)); CATCH(e, write_exact(&character, 1, sizeof(akgl_Character *), fp));
} CLEANUP { } CLEANUP {
} PROCESS(e) { } PROCESS(e) {
@@ -439,7 +451,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp)
AKGL_REGISTRY_CHARACTER, AKGL_REGISTRY_CHARACTER,
&akgl_game_save_charactername_iterator, &akgl_game_save_charactername_iterator,
(void *)fp); (void *)fp);
CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_SPRITE_MAX_CHARACTER_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));
} CLEANUP { } CLEANUP {
} PROCESS(e) { } PROCESS(e) {
@@ -455,7 +467,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath)
ATTEMPT { ATTEMPT {
FAIL_ZERO_BREAK(e, fpath, AKERR_NULLPOINTER, "NULL file path"); FAIL_ZERO_BREAK(e, fpath, AKERR_NULLPOINTER, "NULL file path");
CATCH(e, aksl_fopen(fpath, "wb", &fp)); CATCH(e, aksl_fopen(fpath, "wb", &fp));
CATCH(e, write_exact(&game, 1, sizeof(akgl_Game), fp)); CATCH(e, write_exact(&akgl_game, 1, sizeof(akgl_Game), fp));
CATCH(e, akgl_game_save_actors(fp)); CATCH(e, akgl_game_save_actors(fp));
} CLEANUP { } CLEANUP {
// CLEANUP must precede PROCESS: with the two transposed, the fclose // CLEANUP must precede PROCESS: with the two transposed, the fclose
@@ -626,19 +638,19 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath)
CATCH(e, aksl_fopen(fpath, "rb", &fp)); CATCH(e, aksl_fopen(fpath, "rb", &fp));
CATCH(e, read_exact((void *)&savegame, 1, sizeof(akgl_Game), fp)); CATCH(e, read_exact((void *)&savegame, 1, sizeof(akgl_Game), fp));
CATCH(e, akgl_game_load_versioncmp("library", (char *)&savegame.libversion, (char *)AKGL_VERSION)); CATCH(e, akgl_game_load_versioncmp("library", (char *)&savegame.libversion, (char *)AKGL_VERSION));
CATCH(e, akgl_game_load_versioncmp("game", (char *)&savegame.version, (char *)&game.version)); CATCH(e, akgl_game_load_versioncmp("game", (char *)&savegame.version, (char *)&akgl_game.version));
FAIL_NONZERO_RETURN( FAIL_NONZERO_RETURN(
e, e,
strncmp((char *)&savegame.name, (char *)&game.name, 256), strncmp((char *)&savegame.name, (char *)&akgl_game.name, 256),
AKERR_API, AKERR_API,
"Savegame is not compatible with this game"); "Savegame is not compatible with this game");
FAIL_NONZERO_RETURN( FAIL_NONZERO_RETURN(
e, e,
strncmp((char *)&savegame.uri, (char *)&game.uri, 256), strncmp((char *)&savegame.uri, (char *)&akgl_game.uri, 256),
AKERR_API, AKERR_API,
"Savegame is not compatible with this game"); "Savegame is not compatible with this game");
memcpy((void *)&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, akgl_game_load_objectnamemap(fp, actormap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Actor *), AKGL_REGISTRY_ACTOR));
@@ -678,27 +690,27 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags)
PASS(e, akgl_game_state_lock()); PASS(e, akgl_game_state_lock());
akgl_game_updateFPS(); akgl_game_update_fps();
for ( int i = 0; i < AKGL_TILEMAP_MAX_LAYERS; i++ ) { for ( int i = 0; i < AKGL_TILEMAP_MAX_LAYERS; i++ ) {
if ( opflags == &defflags ) { if ( opflags == &defflags ) {
opflags->layerid = i; opflags->layerid = i;
} }
for ( int j = 0; j < AKGL_MAX_HEAP_ACTOR; j++ ) { for ( int j = 0; j < AKGL_MAX_HEAP_ACTOR; j++ ) {
actor = &HEAP_ACTOR[j]; actor = &akgl_heap_actors[j];
if ( actor->refcount == 0 ) { if ( actor->refcount == 0 ) {
continue; continue;
} }
if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_TILEMAPSCALE) ) { if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_TILEMAPSCALE) ) {
PASS(e, akgl_tilemap_scale_actor(gamemap, actor)); PASS(e, akgl_tilemap_scale_actor(akgl_gamemap, actor));
} else { } else {
actor->scale = 1.0; actor->scale = 1.0;
} }
PASS(e, actor->updatefunc(actor)); PASS(e, actor->updatefunc(actor));
} }
} }
PASS(e, physics->simulate(physics, NULL)); PASS(e, akgl_physics->simulate(akgl_physics, NULL));
PASS(e, renderer->draw_world(renderer, NULL)); PASS(e, akgl_renderer->draw_world(akgl_renderer, NULL));
PASS(e, akgl_game_state_unlock()); PASS(e, akgl_game_state_unlock());
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
} }

View File

@@ -14,11 +14,11 @@
#include <akgl/iterator.h> #include <akgl/iterator.h>
#include <akgl/error.h> #include <akgl/error.h>
akgl_Actor HEAP_ACTOR[AKGL_MAX_HEAP_ACTOR]; akgl_Actor akgl_heap_actors[AKGL_MAX_HEAP_ACTOR];
akgl_Sprite HEAP_SPRITE[AKGL_MAX_HEAP_SPRITE]; akgl_Sprite akgl_heap_sprites[AKGL_MAX_HEAP_SPRITE];
akgl_SpriteSheet HEAP_SPRITESHEET[AKGL_MAX_HEAP_SPRITESHEET]; akgl_SpriteSheet akgl_heap_spritesheets[AKGL_MAX_HEAP_SPRITESHEET];
akgl_Character HEAP_CHARACTER[AKGL_MAX_HEAP_CHARACTER]; akgl_Character akgl_heap_characters[AKGL_MAX_HEAP_CHARACTER];
akgl_String HEAP_STRING[AKGL_MAX_HEAP_STRING]; akgl_String akgl_heap_strings[AKGL_MAX_HEAP_STRING];
akerr_ErrorContext *akgl_heap_init() akerr_ErrorContext *akgl_heap_init()
{ {
@@ -26,16 +26,16 @@ akerr_ErrorContext *akgl_heap_init()
int i = 0; int i = 0;
PASS(errctx, akgl_heap_init_actor()); PASS(errctx, akgl_heap_init_actor());
for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++) { for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++) {
memset(&HEAP_SPRITE[i], 0x00, sizeof(akgl_Sprite)); memset(&akgl_heap_sprites[i], 0x00, sizeof(akgl_Sprite));
} }
for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++) { for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++) {
memset(&HEAP_SPRITESHEET[i], 0x00, sizeof(akgl_SpriteSheet)); memset(&akgl_heap_spritesheets[i], 0x00, sizeof(akgl_SpriteSheet));
} }
for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++) { for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++) {
memset(&HEAP_CHARACTER[i], 0x00, sizeof(akgl_Character)); memset(&akgl_heap_characters[i], 0x00, sizeof(akgl_Character));
} }
for ( i = 0; i < AKGL_MAX_HEAP_STRING; i++) { for ( i = 0; i < AKGL_MAX_HEAP_STRING; i++) {
memset(&HEAP_STRING[i], 0x00, sizeof(akgl_String)); memset(&akgl_heap_strings[i], 0x00, sizeof(akgl_String));
} }
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -44,7 +44,7 @@ akerr_ErrorContext *akgl_heap_init_actor(void)
{ {
PREPARE_ERROR(e); PREPARE_ERROR(e);
for ( int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++) { for ( int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++) {
memset(&HEAP_ACTOR[i], 0x00, sizeof(akgl_Actor)); memset(&akgl_heap_actors[i], 0x00, sizeof(akgl_Actor));
} }
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
} }
@@ -53,10 +53,10 @@ akerr_ErrorContext *akgl_heap_next_actor(akgl_Actor **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
for (int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) { for (int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
if ( HEAP_ACTOR[i].refcount != 0 ) { if ( akgl_heap_actors[i].refcount != 0 ) {
continue; continue;
} }
*dest = &HEAP_ACTOR[i]; *dest = &akgl_heap_actors[i];
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused actor on the heap"); FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused actor on the heap");
@@ -66,10 +66,10 @@ akerr_ErrorContext *akgl_heap_next_sprite(akgl_Sprite **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
for (int i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) { for (int i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) {
if ( HEAP_SPRITE[i].refcount != 0 ) { if ( akgl_heap_sprites[i].refcount != 0 ) {
continue; continue;
} }
*dest = &HEAP_SPRITE[i]; *dest = &akgl_heap_sprites[i];
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused sprite on the heap"); FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused sprite on the heap");
@@ -79,10 +79,10 @@ akerr_ErrorContext *akgl_heap_next_spritesheet(akgl_SpriteSheet **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
for (int i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) { for (int i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) {
if ( HEAP_SPRITESHEET[i].refcount != 0 ) { if ( akgl_heap_spritesheets[i].refcount != 0 ) {
continue; continue;
} }
*dest = &HEAP_SPRITESHEET[i]; *dest = &akgl_heap_spritesheets[i];
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused spritesheet on the heap"); FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused spritesheet on the heap");
@@ -92,10 +92,10 @@ akerr_ErrorContext *akgl_heap_next_character(akgl_Character **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
for (int i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) { for (int i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) {
if ( HEAP_CHARACTER[i].refcount != 0 ) { if ( akgl_heap_characters[i].refcount != 0 ) {
continue; continue;
} }
*dest = &HEAP_CHARACTER[i]; *dest = &akgl_heap_characters[i];
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused character on the heap"); FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused character on the heap");
@@ -105,11 +105,11 @@ akerr_ErrorContext *akgl_heap_next_string(akgl_String **dest)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
for (int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) { for (int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
if ( HEAP_STRING[i].refcount != 0 ) { if ( akgl_heap_strings[i].refcount != 0 ) {
continue; continue;
} }
*dest = &HEAP_STRING[i]; *dest = &akgl_heap_strings[i];
HEAP_STRING[i].refcount += 1; akgl_heap_strings[i].refcount += 1;
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused string on the heap"); FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused string on the heap");
@@ -135,19 +135,35 @@ akerr_ErrorContext *akgl_heap_release_actor(akgl_Actor *ptr)
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
akerr_ErrorContext *akgl_heap_release_character(akgl_Character *basechar) akerr_ErrorContext *akgl_heap_release_character(akgl_Character *ptr)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
akgl_Iterator opflags; akgl_Iterator opflags;
FAIL_ZERO_RETURN(errctx, basechar, AKERR_NULLPOINTER, "NULL character reference"); FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "NULL character reference");
AKGL_BITMASK_CLEAR(opflags.flags); AKGL_BITMASK_CLEAR(opflags.flags);
if ( basechar->refcount > 0 ) { if ( ptr->refcount > 0 ) {
basechar->refcount -= 1; ptr->refcount -= 1;
} }
if ( basechar->refcount == 0 ) { if ( ptr->refcount == 0 ) {
SDL_ClearProperty(AKGL_REGISTRY_CHARACTER, (char *)&basechar->name); // Give back every sprite reference akgl_character_sprite_add took, and
memset(basechar, 0x00, sizeof(akgl_Character)); // the property set holding the map, before the slot is zeroed. Without
// this a game that loads and releases characters level by level
// exhausts the sprite pool and leaks one SDL_PropertiesID per
// character. akgl_character_state_sprites_iterate exists for exactly
// this walk and simply was never called from here.
if ( ptr->state_sprites != 0 ) {
AKGL_BITMASK_ADD(opflags.flags, AKGL_ITERATOR_OP_RELEASE);
SDL_EnumerateProperties(
ptr->state_sprites,
&akgl_character_state_sprites_iterate,
(void *)&opflags
);
SDL_DestroyProperties(ptr->state_sprites);
ptr->state_sprites = 0;
}
SDL_ClearProperty(AKGL_REGISTRY_CHARACTER, (char *)&ptr->name);
memset(ptr, 0x00, sizeof(akgl_Character));
} }
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }

View File

@@ -146,7 +146,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *se
for ( int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) { for ( int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
actor = &HEAP_ACTOR[i]; actor = &akgl_heap_actors[i];
if ( actor->refcount == 0 ) { if ( actor->refcount == 0 ) {
continue; continue;
} }

View File

@@ -14,7 +14,7 @@
#include <akerror.h> #include <akerror.h>
#include <akstdlib.h> #include <akstdlib.h>
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_init2d(akgl_RenderBackend *self) akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_init(akgl_RenderBackend *self)
{ {
akgl_String *width = NULL; akgl_String *width = NULL;
akgl_String *height = NULL; akgl_String *height = NULL;
@@ -33,21 +33,21 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_init2d(akgl_RenderBackend *self)
FAIL_ZERO_RETURN( FAIL_ZERO_RETURN(
e, e,
SDL_CreateWindowAndRenderer(game.uri, screenwidth, screenheight, 0, &window, &self->sdl_renderer), SDL_CreateWindowAndRenderer(akgl_game.uri, screenwidth, screenheight, 0, &akgl_window, &self->sdl_renderer),
AKGL_ERR_SDL, AKGL_ERR_SDL,
"Couldn't create window/renderer: %s", "Couldn't create window/renderer: %s",
SDL_GetError()); SDL_GetError());
camera->x = 0; akgl_camera->x = 0;
camera->y = 0; akgl_camera->y = 0;
camera->w = screenwidth; akgl_camera->w = screenwidth;
camera->h = screenheight; akgl_camera->h = screenheight;
PASS(e, akgl_render_bind2d(self)); PASS(e, akgl_render_2d_bind(self));
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
} }
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_bind2d(akgl_RenderBackend *self) akerr_ErrorContext AKERR_NOIGNORE *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");
@@ -134,11 +134,11 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend
PASS(e, aksl_memset((void *)opflags, 0x00, sizeof(akgl_Iterator))); PASS(e, aksl_memset((void *)opflags, 0x00, sizeof(akgl_Iterator)));
} }
for ( int i = 0; i < AKGL_TILEMAP_MAX_LAYERS ; i++ ) { for ( int i = 0; i < AKGL_TILEMAP_MAX_LAYERS ; i++ ) {
if ( i < gamemap->numlayers ) { if ( i < akgl_gamemap->numlayers ) {
PASS(e, akgl_tilemap_draw(gamemap, camera, i)); PASS(e, akgl_tilemap_draw(akgl_gamemap, akgl_camera, i));
} }
for ( int j = 0; j < AKGL_MAX_HEAP_ACTOR ; j++ ) { for ( int j = 0; j < AKGL_MAX_HEAP_ACTOR ; j++ ) {
actor = &HEAP_ACTOR[j]; actor = &akgl_heap_actors[j];
if ( actor->refcount == 0 ) { if ( actor->refcount == 0 ) {
continue; continue;
} }

View File

@@ -19,7 +19,7 @@
#include <akgl/iterator.h> #include <akgl/iterator.h>
#include <akgl/util.h> #include <akgl/util.h>
akerr_ErrorContext *akgl_sprite_sheet_coords_for_frame(akgl_Sprite *self, SDL_FRect *srccoords, uint8_t frameid) akerr_ErrorContext *akgl_spritesheet_coords_for_frame(akgl_Sprite *self, SDL_FRect *srccoords, uint8_t frameid)
{ {
PREPARE_ERROR(e); PREPARE_ERROR(e);
@@ -157,7 +157,7 @@ akerr_ErrorContext *akgl_sprite_load_json(char *filename)
CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "width", &obj->width)); CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "width", &obj->width));
CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "height", &obj->height)); CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "height", &obj->height));
CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "speed", &obj->speed)); CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "speed", &obj->speed));
obj->speed = obj->speed * AKGL_TIME_ONESEC_MS; obj->speed = obj->speed * AKGL_TIME_ONEMS_NS;
CATCH(errctx, akgl_get_json_boolean_value((json_t *)json, "loop", &obj->loop)); CATCH(errctx, akgl_get_json_boolean_value((json_t *)json, "loop", &obj->loop));
CATCH(errctx, akgl_get_json_boolean_value((json_t *)json, "loopReverse", &obj->loopReverse)); CATCH(errctx, akgl_get_json_boolean_value((json_t *)json, "loopReverse", &obj->loopReverse));
@@ -222,7 +222,7 @@ akerr_ErrorContext *akgl_spritesheet_initialize(akgl_SpriteSheet *sheet, int spr
strncpy((char *)&sheet->name, filename, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH); strncpy((char *)&sheet->name, filename, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH);
//snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), filename); //snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), filename);
sheet->texture = IMG_LoadTexture(renderer->sdl_renderer, filename); sheet->texture = IMG_LoadTexture(akgl_renderer->sdl_renderer, filename);
FAIL_ZERO_BREAK(errctx, sheet->texture, AKGL_ERR_SDL, "Failed loading asset %s : %s", filename, SDL_GetError()); FAIL_ZERO_BREAK(errctx, sheet->texture, AKGL_ERR_SDL, "Failed loading asset %s : %s", filename, SDL_GetError());
FAIL_ZERO_BREAK( FAIL_ZERO_BREAK(

View File

@@ -65,11 +65,11 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_rendertextat(TTF_Font *font, char *
FAIL_ZERO_RETURN(errctx, text, AKERR_NULLPOINTER, "NULL text string"); FAIL_ZERO_RETURN(errctx, text, AKERR_NULLPOINTER, "NULL text string");
// Checked before anything is rasterized, and checked at all because a // Checked before anything is rasterized, and checked at all because a
// backend that has an SDL_Renderer but was never run through // backend that has an SDL_Renderer but was never run through
// akgl_render_bind2d has a NULL draw_texture -- which is exactly the state // akgl_render_2d_bind has a NULL draw_texture -- which is exactly the state
// akgl_render_init2d used to be the only escape from. // akgl_render_2d_init used to be the only escape from.
FAIL_ZERO_RETURN(errctx, renderer, AKERR_NULLPOINTER, "No renderer backend"); FAIL_ZERO_RETURN(errctx, akgl_renderer, AKERR_NULLPOINTER, "No renderer backend");
FAIL_ZERO_RETURN(errctx, renderer->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend"); FAIL_ZERO_RETURN(errctx, akgl_renderer->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
FAIL_ZERO_RETURN(errctx, renderer->draw_texture, AKERR_NULLPOINTER, "Renderer backend has no draw_texture"); FAIL_ZERO_RETURN(errctx, akgl_renderer->draw_texture, AKERR_NULLPOINTER, "Renderer backend has no draw_texture");
if ( wraplength > 0 ) { if ( wraplength > 0 ) {
textsurf = TTF_RenderText_Blended_Wrapped( textsurf = TTF_RenderText_Blended_Wrapped(
font, font,
@@ -85,12 +85,12 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_rendertextat(TTF_Font *font, char *
color); color);
} }
FAIL_ZERO_RETURN(errctx, textsurf, AKERR_NULLPOINTER, "%s", SDL_GetError()); FAIL_ZERO_RETURN(errctx, textsurf, AKERR_NULLPOINTER, "%s", SDL_GetError());
texture = SDL_CreateTextureFromSurface(renderer->sdl_renderer, textsurf); texture = SDL_CreateTextureFromSurface(akgl_renderer->sdl_renderer, textsurf);
FAIL_ZERO_RETURN(errctx, texture, AKERR_NULLPOINTER, "%s", SDL_GetError()); FAIL_ZERO_RETURN(errctx, texture, AKERR_NULLPOINTER, "%s", SDL_GetError());
dest.x = x; dest.x = x;
dest.y = y; dest.y = y;
SDL_GetTextureSize(texture, &dest.w, &dest.h); SDL_GetTextureSize(texture, &dest.w, &dest.h);
PASS(errctx, renderer->draw_texture(renderer, texture, NULL, &dest, 0, NULL, SDL_FLIP_NONE)); PASS(errctx, akgl_renderer->draw_texture(akgl_renderer, texture, NULL, &dest, 0, NULL, SDL_FLIP_NONE));
SDL_DestroyTexture(texture); SDL_DestroyTexture(texture);
SDL_DestroySurface(textsurf); SDL_DestroySurface(textsurf);
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);

View File

@@ -201,7 +201,7 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilema
} PROCESS(e) { } PROCESS(e) {
} FINISH(e, true); } FINISH(e, true);
dest->tilesets[tsidx].texture = IMG_LoadTexture(renderer->sdl_renderer, (char *)&dest->tilesets[tsidx].imagefilename); dest->tilesets[tsidx].texture = IMG_LoadTexture(akgl_renderer->sdl_renderer, (char *)&dest->tilesets[tsidx].imagefilename);
FAIL_ZERO_RETURN(e, dest->tilesets[tsidx].texture, AKERR_NULLPOINTER, "Failed loading tileset image : %s", SDL_GetError()); FAIL_ZERO_RETURN(e, dest->tilesets[tsidx].texture, AKERR_NULLPOINTER, "Failed loading tileset image : %s", SDL_GetError());
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
@@ -496,7 +496,7 @@ akerr_ErrorContext *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *ro
); );
RESTORE_GCC_WARNINGS RESTORE_GCC_WARNINGS
dest->layers[layerid].texture = IMG_LoadTexture(renderer->sdl_renderer, (char *)fpath->data); dest->layers[layerid].texture = IMG_LoadTexture(akgl_renderer->sdl_renderer, (char *)fpath->data);
FAIL_ZERO_BREAK(errctx, dest->layers[layerid].texture, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, dest->layers[layerid].texture, AKGL_ERR_SDL, "%s", SDL_GetError());
dest->layers[layerid].width = dest->layers[layerid].texture->w; dest->layers[layerid].width = dest->layers[layerid].texture->w;
dest->layers[layerid].height = dest->layers[layerid].texture->h; dest->layers[layerid].height = dest->layers[layerid].texture->h;
@@ -798,7 +798,7 @@ akerr_ErrorContext *akgl_tilemap_draw(akgl_Tilemap *map, SDL_FRect *viewport, in
src.h = map->layers[layeridx].height; src.h = map->layers[layeridx].height;
dest.w = map->layers[layeridx].width; dest.w = map->layers[layeridx].width;
dest.h = map->layers[layeridx].height; dest.h = map->layers[layeridx].height;
PASS(errctx, renderer->draw_texture(renderer, map->layers[layeridx].texture, &src, &dest, 0, NULL, SDL_FLIP_NONE)); PASS(errctx, akgl_renderer->draw_texture(akgl_renderer, map->layers[layeridx].texture, &src, &dest, 0, NULL, SDL_FLIP_NONE));
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
@@ -853,7 +853,7 @@ akerr_ErrorContext *akgl_tilemap_draw(akgl_Tilemap *map, SDL_FRect *viewport, in
dest.y, dest.y,
dest.w, dest.w,
dest.h);*/ dest.h);*/
PASS(errctx, renderer->draw_texture(renderer, map->tilesets[tilesetidx].texture, &src, &dest, 0, NULL, SDL_FLIP_NONE)); PASS(errctx, akgl_renderer->draw_texture(akgl_renderer, map->tilesets[tilesetidx].texture, &src, &dest, 0, NULL, SDL_FLIP_NONE));
} }
} }
dest.x += map->tilewidth; dest.x += map->tilewidth;
@@ -908,7 +908,7 @@ akerr_ErrorContext *akgl_tilemap_draw_tileset(akgl_Tilemap *map, int tilesetidx)
dest.y, dest.y,
dest.w, dest.w,
dest.h);*/ dest.h);*/
PASS(errctx, renderer->draw_texture(renderer, map->tilesets[tilesetidx].texture, &src, &dest, 0, NULL, SDL_FLIP_NONE)); PASS(errctx, akgl_renderer->draw_texture(akgl_renderer, map->tilesets[tilesetidx].texture, &src, &dest, 0, NULL, SDL_FLIP_NONE));
} }
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }

View File

@@ -169,10 +169,10 @@ akerr_ErrorContext *akgl_path_relative_from(char *path, char *from, akgl_String
SUCCEED_RETURN(e); SUCCEED_RETURN(e);
} }
akerr_ErrorContext *akgl_rectangle_points(RectanglePoints *dest, SDL_FRect *rect) akerr_ErrorContext *akgl_rectangle_points(akgl_RectanglePoints *dest, SDL_FRect *rect)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL RectanglePoints reference"); FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL akgl_RectanglePoints reference");
FAIL_ZERO_RETURN(errctx, rect, AKERR_NULLPOINTER, "NULL Rectangle reference"); FAIL_ZERO_RETURN(errctx, rect, AKERR_NULLPOINTER, "NULL Rectangle reference");
dest->topleft.x = rect->x; dest->topleft.x = rect->x;
dest->topleft.y = rect->y; dest->topleft.y = rect->y;
@@ -185,11 +185,11 @@ akerr_ErrorContext *akgl_rectangle_points(RectanglePoints *dest, SDL_FRect *rect
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
} }
akerr_ErrorContext *akgl_collide_point_rectangle(point *p, RectanglePoints *rp, bool *collide) akerr_ErrorContext *akgl_collide_point_rectangle(akgl_Point *p, akgl_RectanglePoints *rp, bool *collide)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, p, AKERR_NULLPOINTER, "NULL Point reference"); FAIL_ZERO_RETURN(errctx, p, AKERR_NULLPOINTER, "NULL Point reference");
FAIL_ZERO_RETURN(errctx, rp, AKERR_NULLPOINTER, "NULL RectanglePoints reference"); FAIL_ZERO_RETURN(errctx, rp, AKERR_NULLPOINTER, "NULL akgl_RectanglePoints reference");
FAIL_ZERO_RETURN(errctx, collide, AKERR_NULLPOINTER, "NULL boolean reference"); FAIL_ZERO_RETURN(errctx, collide, AKERR_NULLPOINTER, "NULL boolean reference");
if ( (p->x >= rp->topleft.x) && (p->y >= rp->topleft.y) && if ( (p->x >= rp->topleft.x) && (p->y >= rp->topleft.y) &&
(p->x <= rp->bottomright.x) && (p->y <= rp->bottomright.y) ) { (p->x <= rp->bottomright.x) && (p->y <= rp->bottomright.y) ) {
@@ -202,8 +202,8 @@ akerr_ErrorContext *akgl_collide_point_rectangle(point *p, RectanglePoints *rp,
akerr_ErrorContext *akgl_collide_rectangles(SDL_FRect *r1, SDL_FRect *r2, bool *collide) akerr_ErrorContext *akgl_collide_rectangles(SDL_FRect *r1, SDL_FRect *r2, bool *collide)
{ {
RectanglePoints r1p; akgl_RectanglePoints r1p;
RectanglePoints r2p; akgl_RectanglePoints r2p;
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, r1, AKERR_NULLPOINTER, "NULL rectangle reference"); FAIL_ZERO_RETURN(errctx, r1, AKERR_NULLPOINTER, "NULL rectangle reference");
FAIL_ZERO_RETURN(errctx, r2, AKERR_NULLPOINTER, "NULL rectangle reference"); FAIL_ZERO_RETURN(errctx, r2, AKERR_NULLPOINTER, "NULL rectangle reference");
@@ -278,9 +278,9 @@ akerr_ErrorContext *akgl_render_and_compare(SDL_Texture *t1, SDL_Texture *t2, in
FAIL_ZERO_BREAK(errctx, t2, AKERR_NULLPOINTER, "NULL texture"); FAIL_ZERO_BREAK(errctx, t2, AKERR_NULLPOINTER, "NULL texture");
CATCH(errctx, akgl_heap_next_string(&tmpstring)); CATCH(errctx, akgl_heap_next_string(&tmpstring));
SDL_RenderClear(renderer->sdl_renderer); SDL_RenderClear(akgl_renderer->sdl_renderer);
CATCH(errctx, renderer->draw_texture(renderer, t1, &src, &dest, 0, NULL, SDL_FLIP_NONE)); CATCH(errctx, akgl_renderer->draw_texture(akgl_renderer, t1, &src, &dest, 0, NULL, SDL_FLIP_NONE));
s1 = SDL_RenderReadPixels(renderer->sdl_renderer, &read); s1 = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, &read);
FAIL_ZERO_BREAK(errctx, s1, AKGL_ERR_SDL, "Failed to read pixels from renderer"); FAIL_ZERO_BREAK(errctx, s1, AKGL_ERR_SDL, "Failed to read pixels from renderer");
if ( writeout != NULL ) { if ( writeout != NULL ) {
@@ -294,10 +294,10 @@ akerr_ErrorContext *akgl_render_and_compare(SDL_Texture *t1, SDL_Texture *t2, in
SDL_GetError()); SDL_GetError());
} }
SDL_RenderClear(renderer->sdl_renderer); SDL_RenderClear(akgl_renderer->sdl_renderer);
CATCH(errctx, renderer->draw_texture(renderer, t1, &src, &dest, 0, NULL, SDL_FLIP_NONE)); CATCH(errctx, akgl_renderer->draw_texture(akgl_renderer, t1, &src, &dest, 0, NULL, SDL_FLIP_NONE));
s2 = SDL_RenderReadPixels(renderer->sdl_renderer, &read); s2 = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, &read);
FAIL_ZERO_BREAK(errctx, s2, AKGL_ERR_SDL, "Failed to read pixels from renderer"); FAIL_ZERO_BREAK(errctx, s2, AKGL_ERR_SDL, "Failed to read pixels from renderer");
CATCH(errctx, akgl_compare_sdl_surfaces(s1, s2)); CATCH(errctx, akgl_compare_sdl_surfaces(s1, s2));

View File

@@ -266,10 +266,10 @@ akerr_ErrorContext *test_actor_manage_children(void)
CATCH(errctx, akgl_heap_release_actor(parent)); CATCH(errctx, akgl_heap_release_actor(parent));
// All actor objects on the heap should be empty now // All actor objects on the heap should be empty now
for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++) { for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++) {
FAIL_NONZERO_BREAK(errctx, HEAP_ACTOR[i].refcount, AKERR_VALUE, "Actor not properly cleared"); FAIL_NONZERO_BREAK(errctx, akgl_heap_actors[i].refcount, AKERR_VALUE, "Actor not properly cleared");
FAIL_NONZERO_BREAK(errctx, HEAP_ACTOR[i].parent, AKERR_VALUE, "Actor not properly cleared"); FAIL_NONZERO_BREAK(errctx, akgl_heap_actors[i].parent, AKERR_VALUE, "Actor not properly cleared");
for ( j = 0 ; j < AKGL_ACTOR_MAX_CHILDREN; j++) { for ( j = 0 ; j < AKGL_ACTOR_MAX_CHILDREN; j++) {
if ( HEAP_ACTOR[i].children[j] != NULL ) { if ( akgl_heap_actors[i].children[j] != NULL ) {
FAIL(errctx, AKERR_VALUE, "Actor not properly cleared"); FAIL(errctx, AKERR_VALUE, "Actor not properly cleared");
goto _test_actor_addchild_heaprelease_cleanup; goto _test_actor_addchild_heaprelease_cleanup;
} }
@@ -375,7 +375,7 @@ akerr_ErrorContext *test_actor_control_handlers_on(void)
memset(&actor, 0x00, sizeof(akgl_Actor)); memset(&actor, 0x00, sizeof(akgl_Actor));
actor.basechar = &basechar; actor.basechar = &basechar;
actor.state = (AKGL_ACTOR_STATE_FACE_RIGHT | AKGL_ACTOR_STATE_MOVING_RIGHT | AKGL_ACTOR_STATE_ALIVE); actor.state = (AKGL_ACTOR_STATE_FACE_RIGHT | AKGL_ACTOR_STATE_MOVING_RIGHT | AKGL_ACTOR_STATE_ALIVE);
TEST_EXPECT_OK(e, akgl_Actor_cmhf_left_on(&actor, &event), "left on"); TEST_EXPECT_OK(e, akgl_actor_cmhf_left_on(&actor, &event), "left on");
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_MOVING_LEFT), TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_MOVING_LEFT),
"left on did not set MOVING_LEFT (state %d)", actor.state); "left on did not set MOVING_LEFT (state %d)", actor.state);
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_LEFT), TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_LEFT),
@@ -391,7 +391,7 @@ akerr_ErrorContext *test_actor_control_handlers_on(void)
// Right: same, with the sign preserved. // Right: same, with the sign preserved.
memset(&actor, 0x00, sizeof(akgl_Actor)); memset(&actor, 0x00, sizeof(akgl_Actor));
actor.basechar = &basechar; actor.basechar = &basechar;
TEST_EXPECT_OK(e, akgl_Actor_cmhf_right_on(&actor, &event), "right on"); TEST_EXPECT_OK(e, akgl_actor_cmhf_right_on(&actor, &event), "right on");
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_MOVING_RIGHT), TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_MOVING_RIGHT),
"right on did not set MOVING_RIGHT (state %d)", actor.state); "right on did not set MOVING_RIGHT (state %d)", actor.state);
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_RIGHT), TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_RIGHT),
@@ -401,7 +401,7 @@ akerr_ErrorContext *test_actor_control_handlers_on(void)
// Up reverses the Y acceleration, because Y grows downward. // Up reverses the Y acceleration, because Y grows downward.
memset(&actor, 0x00, sizeof(akgl_Actor)); memset(&actor, 0x00, sizeof(akgl_Actor));
actor.basechar = &basechar; actor.basechar = &basechar;
TEST_EXPECT_OK(e, akgl_Actor_cmhf_up_on(&actor, &event), "up on"); TEST_EXPECT_OK(e, akgl_actor_cmhf_up_on(&actor, &event), "up on");
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_MOVING_UP), TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_MOVING_UP),
"up on did not set MOVING_UP (state %d)", actor.state); "up on did not set MOVING_UP (state %d)", actor.state);
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_UP), TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_UP),
@@ -410,7 +410,7 @@ akerr_ErrorContext *test_actor_control_handlers_on(void)
memset(&actor, 0x00, sizeof(akgl_Actor)); memset(&actor, 0x00, sizeof(akgl_Actor));
actor.basechar = &basechar; actor.basechar = &basechar;
TEST_EXPECT_OK(e, akgl_Actor_cmhf_down_on(&actor, &event), "down on"); TEST_EXPECT_OK(e, akgl_actor_cmhf_down_on(&actor, &event), "down on");
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_MOVING_DOWN), TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_MOVING_DOWN),
"down on did not set MOVING_DOWN (state %d)", actor.state); "down on did not set MOVING_DOWN (state %d)", actor.state);
TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_DOWN), TEST_ASSERT(e, AKGL_BITMASK_HAS(actor.state, AKGL_ACTOR_STATE_FACE_DOWN),
@@ -420,8 +420,8 @@ akerr_ErrorContext *test_actor_control_handlers_on(void)
// Each direction is exclusive: turning right after left leaves only right. // Each direction is exclusive: turning right after left leaves only right.
memset(&actor, 0x00, sizeof(akgl_Actor)); memset(&actor, 0x00, sizeof(akgl_Actor));
actor.basechar = &basechar; actor.basechar = &basechar;
TEST_EXPECT_OK(e, akgl_Actor_cmhf_left_on(&actor, &event), "left on before turning"); TEST_EXPECT_OK(e, akgl_actor_cmhf_left_on(&actor, &event), "left on before turning");
TEST_EXPECT_OK(e, akgl_Actor_cmhf_right_on(&actor, &event), "right on after left"); TEST_EXPECT_OK(e, akgl_actor_cmhf_right_on(&actor, &event), "right on after left");
TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_LEFT), TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_LEFT),
"turning right left MOVING_LEFT set (state %d)", actor.state); "turning right left MOVING_LEFT set (state %d)", actor.state);
} CLEANUP { } CLEANUP {
@@ -450,7 +450,7 @@ akerr_ErrorContext *test_actor_control_handlers_off(void)
actor.ax = 5; actor.ex = 5; actor.tx = 5; actor.vx = 5; actor.ax = 5; actor.ex = 5; actor.tx = 5; actor.vx = 5;
actor.ay = 5; actor.ey = 5; actor.ty = 5; actor.vy = 5; actor.ay = 5; actor.ey = 5; actor.ty = 5; actor.vy = 5;
actor.state = (AKGL_ACTOR_STATE_MOVING_LEFT | AKGL_ACTOR_STATE_FACE_LEFT); actor.state = (AKGL_ACTOR_STATE_MOVING_LEFT | AKGL_ACTOR_STATE_FACE_LEFT);
TEST_EXPECT_OK(e, akgl_Actor_cmhf_left_off(&actor, &event), "left off"); TEST_EXPECT_OK(e, akgl_actor_cmhf_left_off(&actor, &event), "left off");
TEST_ASSERT_FEQ(e, actor.ax, 0.0f, "left off left ax at %f", actor.ax); TEST_ASSERT_FEQ(e, actor.ax, 0.0f, "left off left ax at %f", actor.ax);
TEST_ASSERT_FEQ(e, actor.ex, 0.0f, "left off left ex at %f", actor.ex); TEST_ASSERT_FEQ(e, actor.ex, 0.0f, "left off left ex at %f", actor.ex);
TEST_ASSERT_FEQ(e, actor.tx, 0.0f, "left off left tx at %f", actor.tx); TEST_ASSERT_FEQ(e, actor.tx, 0.0f, "left off left tx at %f", actor.tx);
@@ -466,7 +466,7 @@ akerr_ErrorContext *test_actor_control_handlers_off(void)
memset(&actor, 0x00, sizeof(akgl_Actor)); memset(&actor, 0x00, sizeof(akgl_Actor));
actor.ax = 5; actor.ex = 5; actor.tx = 5; actor.vx = 5; actor.ax = 5; actor.ex = 5; actor.tx = 5; actor.vx = 5;
actor.state = AKGL_ACTOR_STATE_MOVING_RIGHT; actor.state = AKGL_ACTOR_STATE_MOVING_RIGHT;
TEST_EXPECT_OK(e, akgl_Actor_cmhf_right_off(&actor, &event), "right off"); TEST_EXPECT_OK(e, akgl_actor_cmhf_right_off(&actor, &event), "right off");
TEST_ASSERT_FEQ(e, actor.vx, 0.0f, "right off left vx at %f", actor.vx); TEST_ASSERT_FEQ(e, actor.vx, 0.0f, "right off left vx at %f", actor.vx);
TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_RIGHT), TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_RIGHT),
"right off did not clear MOVING_RIGHT (state %d)", actor.state); "right off did not clear MOVING_RIGHT (state %d)", actor.state);
@@ -474,7 +474,7 @@ akerr_ErrorContext *test_actor_control_handlers_off(void)
memset(&actor, 0x00, sizeof(akgl_Actor)); memset(&actor, 0x00, sizeof(akgl_Actor));
actor.ay = 5; actor.ey = 5; actor.ty = 5; actor.vy = 5; actor.ay = 5; actor.ey = 5; actor.ty = 5; actor.vy = 5;
actor.state = AKGL_ACTOR_STATE_MOVING_UP; actor.state = AKGL_ACTOR_STATE_MOVING_UP;
TEST_EXPECT_OK(e, akgl_Actor_cmhf_up_off(&actor, &event), "up off"); TEST_EXPECT_OK(e, akgl_actor_cmhf_up_off(&actor, &event), "up off");
TEST_ASSERT_FEQ(e, actor.vy, 0.0f, "up off left vy at %f", actor.vy); TEST_ASSERT_FEQ(e, actor.vy, 0.0f, "up off left vy at %f", actor.vy);
TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_UP), TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_UP),
"up off did not clear MOVING_UP (state %d)", actor.state); "up off did not clear MOVING_UP (state %d)", actor.state);
@@ -482,7 +482,7 @@ akerr_ErrorContext *test_actor_control_handlers_off(void)
memset(&actor, 0x00, sizeof(akgl_Actor)); memset(&actor, 0x00, sizeof(akgl_Actor));
actor.ay = 5; actor.ey = 5; actor.ty = 5; actor.vy = 5; actor.ay = 5; actor.ey = 5; actor.ty = 5; actor.vy = 5;
actor.state = AKGL_ACTOR_STATE_MOVING_DOWN; actor.state = AKGL_ACTOR_STATE_MOVING_DOWN;
TEST_EXPECT_OK(e, akgl_Actor_cmhf_down_off(&actor, &event), "down off"); TEST_EXPECT_OK(e, akgl_actor_cmhf_down_off(&actor, &event), "down off");
TEST_ASSERT_FEQ(e, actor.vy, 0.0f, "down off left vy at %f", actor.vy); TEST_ASSERT_FEQ(e, actor.vy, 0.0f, "down off left vy at %f", actor.vy);
TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_DOWN), TEST_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_DOWN),
"down off did not clear MOVING_DOWN (state %d)", actor.state); "down off did not clear MOVING_DOWN (state %d)", actor.state);
@@ -502,33 +502,33 @@ akerr_ErrorContext *test_actor_control_handlers_nullpointers(void)
memset(&event, 0x00, sizeof(SDL_Event)); memset(&event, 0x00, sizeof(SDL_Event));
memset(&actor, 0x00, sizeof(akgl_Actor)); memset(&actor, 0x00, sizeof(akgl_Actor));
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_left_on(NULL, &event), "left on, NULL actor"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_left_on(NULL, &event), "left on, NULL actor");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_left_off(NULL, &event), "left off, NULL actor"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_left_off(NULL, &event), "left off, NULL actor");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_right_on(NULL, &event), "right on, NULL actor"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_right_on(NULL, &event), "right on, NULL actor");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_right_off(NULL, &event), "right off, NULL actor"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_right_off(NULL, &event), "right off, NULL actor");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_up_on(NULL, &event), "up on, NULL actor"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_up_on(NULL, &event), "up on, NULL actor");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_up_off(NULL, &event), "up off, NULL actor"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_up_off(NULL, &event), "up off, NULL actor");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_down_on(NULL, &event), "down on, NULL actor"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_down_on(NULL, &event), "down on, NULL actor");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_down_off(NULL, &event), "down off, NULL actor"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_down_off(NULL, &event), "down off, NULL actor");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_left_on(&actor, NULL), "left on, NULL event"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_left_on(&actor, NULL), "left on, NULL event");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_left_off(&actor, NULL), "left off, NULL event"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_left_off(&actor, NULL), "left off, NULL event");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_right_on(&actor, NULL), "right on, NULL event"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_right_on(&actor, NULL), "right on, NULL event");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_right_off(&actor, NULL), "right off, NULL event"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_right_off(&actor, NULL), "right off, NULL event");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_up_on(&actor, NULL), "up on, NULL event"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_up_on(&actor, NULL), "up on, NULL event");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_up_off(&actor, NULL), "up off, NULL event"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_up_off(&actor, NULL), "up off, NULL event");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_down_on(&actor, NULL), "down on, NULL event"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_down_on(&actor, NULL), "down on, NULL event");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_down_off(&actor, NULL), "down off, NULL event"); TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_down_off(&actor, NULL), "down off, NULL event");
// A movement handler reads acceleration off the base character, so an // A movement handler reads acceleration off the base character, so an
// actor without one has to be reported rather than dereferenced. // actor without one has to be reported rather than dereferenced.
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_left_on(&actor, &event), TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_left_on(&actor, &event),
"left on, actor with no base character"); "left on, actor with no base character");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_right_on(&actor, &event), TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_right_on(&actor, &event),
"right on, actor with no base character"); "right on, actor with no base character");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_up_on(&actor, &event), TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_up_on(&actor, &event),
"up on, actor with no base character"); "up on, actor with no base character");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_Actor_cmhf_down_on(&actor, &event), TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_actor_cmhf_down_on(&actor, &event),
"down on, actor with no base character"); "down on, actor with no base character");
} CLEANUP { } CLEANUP {
} PROCESS(e) { } PROCESS(e) {
@@ -879,12 +879,12 @@ akerr_ErrorContext *test_actor_sprite_sheet_coords(void)
sprite.width = 32; sprite.width = 32;
sprite.height = 32; sprite.height = 32;
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_sprite_sheet_coords_for_frame(NULL, &coords, 0), TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_spritesheet_coords_for_frame(NULL, &coords, 0),
"sheet coords with a NULL sprite"); "sheet coords with a NULL sprite");
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_sprite_sheet_coords_for_frame(&sprite, NULL, 0), TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_spritesheet_coords_for_frame(&sprite, NULL, 0),
"sheet coords with a NULL rectangle"); "sheet coords with a NULL rectangle");
// The sprite has no sheet attached, so there is no texture to measure against. // The sprite has no sheet attached, so there is no texture to measure against.
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_sprite_sheet_coords_for_frame(&sprite, &coords, 0), TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_spritesheet_coords_for_frame(&sprite, &coords, 0),
"sheet coords with a NULL spritesheet"); "sheet coords with a NULL spritesheet");
} CLEANUP { } CLEANUP {
} PROCESS(e) { } PROCESS(e) {
@@ -904,6 +904,7 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
CATCH(errctx, akgl_registry_init_actor()); CATCH(errctx, akgl_registry_init_actor());
CATCH(errctx, akgl_registry_init_sprite()); CATCH(errctx, akgl_registry_init_sprite());
CATCH(errctx, akgl_registry_init_spritesheet()); CATCH(errctx, akgl_registry_init_spritesheet());

View File

@@ -602,6 +602,7 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
FAIL_ZERO_BREAK( FAIL_ZERO_BREAK(
errctx, errctx,
SDL_Init(SDL_INIT_AUDIO), SDL_Init(SDL_INIT_AUDIO),

View File

@@ -7,9 +7,21 @@
#include <akgl/heap.h> #include <akgl/heap.h>
#include <akgl/registry.h> #include <akgl/registry.h>
#include <akgl/iterator.h> #include <akgl/iterator.h>
#include <akgl/game.h>
#include <akgl/renderer.h>
SDL_Window *window; #include "testutil.h"
SDL_Renderer *renderer;
/*
* This suite used to declare its own file-scope `window` and `renderer`, back
* when the library's globals carried those bare names too. Both definitions had
* external linkage and the same spelling, so the executable's preempted the
* shared library's: akgl_sprite_load_json read the test's `SDL_Renderer *`
* through an `akgl_RenderBackend *` and happened not to crash. That is the
* collision the akgl_ prefix on exported globals exists to prevent, and it was
* invisible until they were renamed. The suite now binds a real backend, the
* way tests/sprite.c and tests/text.c do.
*/
akerr_ErrorContext *test_akgl_character_initialize() akerr_ErrorContext *test_akgl_character_initialize()
{ {
@@ -196,15 +208,19 @@ int main(void)
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
akgl_renderer = &akgl_default_renderer;
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest"); SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) { if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) {
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError()); FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
} }
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_character", 640, 480, SDL_WINDOW_HIDDEN, &window, &renderer)) { if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_character", 640, 480, SDL_WINDOW_HIDDEN, &akgl_window, &akgl_renderer->sdl_renderer)) {
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError()); FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
} }
CATCH(errctx, akgl_render_2d_bind(akgl_renderer));
CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_heap_init());
CATCH(errctx, akgl_registry_init()); CATCH(errctx, akgl_registry_init());

View File

@@ -76,7 +76,7 @@ int main(void)
actorptr->visible = true; actorptr->visible = true;
// set up the control map // set up the control map
controlmap = &GAME_ControlMaps[0]; controlmap = &akgl_controlmaps[0];
controlmap->kbid = 0; controlmap->kbid = 0;
controlmap->target = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "player", NULL); controlmap->target = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "player", NULL);
// Move down // Move down

View File

@@ -13,7 +13,7 @@
#include <akerror.h> #include <akerror.h>
#include <akgl/error.h> #include <akgl/error.h>
// For the akgl_Actor_cmhf_* handlers these tests bind. akgl/controller.h pulls // For the akgl_actor_cmhf_* handlers these tests bind. akgl/controller.h pulls
// actor.h in for itself now; tests/headers.c is what keeps it doing so. // actor.h in for itself now; tests/headers.c is what keeps it doing so.
#include <akgl/actor.h> #include <akgl/actor.h>
#include <akgl/character.h> #include <akgl/character.h>
@@ -52,7 +52,7 @@ static void reset_control_maps(void)
{ {
int i = 0; int i = 0;
for ( i = 0; i < AKGL_MAX_CONTROL_MAPS; i++ ) { for ( i = 0; i < AKGL_MAX_CONTROL_MAPS; i++ ) {
memset(&GAME_ControlMaps[i], 0x00, sizeof(akgl_ControlMap)); memset(&akgl_controlmaps[i], 0x00, sizeof(akgl_ControlMap));
} }
} }
@@ -129,30 +129,30 @@ akerr_ErrorContext *test_controller_pushmap(void)
control.key = SDLK_SPACE; control.key = SDLK_SPACE;
control.event_on = SDL_EVENT_KEY_DOWN; control.event_on = SDL_EVENT_KEY_DOWN;
control.event_off = SDL_EVENT_KEY_UP; control.event_off = SDL_EVENT_KEY_UP;
control.handler_on = &akgl_Actor_cmhf_left_on; control.handler_on = &akgl_actor_cmhf_left_on;
control.handler_off = &akgl_Actor_cmhf_left_off; control.handler_off = &akgl_actor_cmhf_left_off;
TEST_EXPECT_OK(e, akgl_controller_pushmap(0, &control), "pushing one control"); TEST_EXPECT_OK(e, akgl_controller_pushmap(0, &control), "pushing one control");
TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 1, TEST_ASSERT(e, akgl_controlmaps[0].nextMap == 1,
"pushing one control left nextMap at %d, expected 1", "pushing one control left nextMap at %d, expected 1",
GAME_ControlMaps[0].nextMap); akgl_controlmaps[0].nextMap);
TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].key == SDLK_SPACE, TEST_ASSERT(e, akgl_controlmaps[0].controls[0].key == SDLK_SPACE,
"the pushed control did not land in slot 0"); "the pushed control did not land in slot 0");
TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].handler_on == &akgl_Actor_cmhf_left_on, TEST_ASSERT(e, akgl_controlmaps[0].controls[0].handler_on == &akgl_actor_cmhf_left_on,
"the pushed control lost its press handler"); "the pushed control lost its press handler");
// Pushes accumulate rather than overwrite. // Pushes accumulate rather than overwrite.
TEST_EXPECT_OK(e, akgl_controller_pushmap(0, &control), "pushing a second control"); TEST_EXPECT_OK(e, akgl_controller_pushmap(0, &control), "pushing a second control");
TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 2, TEST_ASSERT(e, akgl_controlmaps[0].nextMap == 2,
"pushing a second control left nextMap at %d, expected 2", "pushing a second control left nextMap at %d, expected 2",
GAME_ControlMaps[0].nextMap); akgl_controlmaps[0].nextMap);
// Maps are independent of each other. // Maps are independent of each other.
TEST_EXPECT_OK(e, akgl_controller_pushmap(3, &control), "pushing into a different map"); TEST_EXPECT_OK(e, akgl_controller_pushmap(3, &control), "pushing into a different map");
TEST_ASSERT(e, GAME_ControlMaps[3].nextMap == 1, TEST_ASSERT(e, akgl_controlmaps[3].nextMap == 1,
"map 3 nextMap is %d, expected 1", GAME_ControlMaps[3].nextMap); "map 3 nextMap is %d, expected 1", akgl_controlmaps[3].nextMap);
TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 2, TEST_ASSERT(e, akgl_controlmaps[0].nextMap == 2,
"pushing into map 3 disturbed map 0 (nextMap %d)", GAME_ControlMaps[0].nextMap); "pushing into map 3 disturbed map 0 (nextMap %d)", akgl_controlmaps[0].nextMap);
// Filling a map exactly to capacity still succeeds. // Filling a map exactly to capacity still succeeds.
reset_control_maps(); reset_control_maps();
@@ -164,9 +164,9 @@ akerr_ErrorContext *test_controller_pushmap(void)
FAIL_BREAK(e, AKGL_ERR_BEHAVIOR, "pushing control %d of %d failed", i, AKGL_MAX_CONTROLS); FAIL_BREAK(e, AKGL_ERR_BEHAVIOR, "pushing control %d of %d failed", i, AKGL_MAX_CONTROLS);
} }
} }
TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == AKGL_MAX_CONTROLS, TEST_ASSERT(e, akgl_controlmaps[0].nextMap == AKGL_MAX_CONTROLS,
"a full map reports nextMap %d, expected %d", "a full map reports nextMap %d, expected %d",
GAME_ControlMaps[0].nextMap, AKGL_MAX_CONTROLS); akgl_controlmaps[0].nextMap, AKGL_MAX_CONTROLS);
// One past capacity is refused. // One past capacity is refused.
TEST_EXPECT_STATUS(e, AKERR_OUTOFBOUNDS, akgl_controller_pushmap(0, &control), TEST_EXPECT_STATUS(e, AKERR_OUTOFBOUNDS, akgl_controller_pushmap(0, &control),
@@ -198,38 +198,38 @@ akerr_ErrorContext *test_controller_default_bindings(void)
TEST_EXPECT_OK(e, akgl_controller_default(0, "player", TEST_KBID, TEST_JSID), TEST_EXPECT_OK(e, akgl_controller_default(0, "player", TEST_KBID, TEST_JSID),
"installing the default control map"); "installing the default control map");
TEST_ASSERT(e, GAME_ControlMaps[0].target == player, TEST_ASSERT(e, akgl_controlmaps[0].target == player,
"the default map did not target the player actor"); "the default map did not target the player actor");
TEST_ASSERT(e, GAME_ControlMaps[0].kbid == TEST_KBID, TEST_ASSERT(e, akgl_controlmaps[0].kbid == TEST_KBID,
"the default map recorded keyboard %d, expected %d", "the default map recorded keyboard %d, expected %d",
GAME_ControlMaps[0].kbid, TEST_KBID); akgl_controlmaps[0].kbid, TEST_KBID);
TEST_ASSERT(e, GAME_ControlMaps[0].jsid == TEST_JSID, TEST_ASSERT(e, akgl_controlmaps[0].jsid == TEST_JSID,
"the default map recorded gamepad %d, expected %d", "the default map recorded gamepad %d, expected %d",
GAME_ControlMaps[0].jsid, TEST_JSID); akgl_controlmaps[0].jsid, TEST_JSID);
// Four keyboard bindings then four gamepad bindings. // Four keyboard bindings then four gamepad bindings.
TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 8, TEST_ASSERT(e, akgl_controlmaps[0].nextMap == 8,
"the default map installed %d controls, expected 8", "the default map installed %d controls, expected 8",
GAME_ControlMaps[0].nextMap); akgl_controlmaps[0].nextMap);
TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].key == SDLK_DOWN, TEST_ASSERT(e, akgl_controlmaps[0].controls[0].key == SDLK_DOWN,
"the first default binding is not the down arrow"); "the first default binding is not the down arrow");
TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].handler_on == &akgl_Actor_cmhf_down_on, TEST_ASSERT(e, akgl_controlmaps[0].controls[0].handler_on == &akgl_actor_cmhf_down_on,
"the down arrow is not bound to the down handler"); "the down arrow is not bound to the down handler");
TEST_ASSERT(e, GAME_ControlMaps[0].controls[1].key == SDLK_UP, TEST_ASSERT(e, akgl_controlmaps[0].controls[1].key == SDLK_UP,
"the second default binding is not the up arrow"); "the second default binding is not the up arrow");
TEST_ASSERT(e, GAME_ControlMaps[0].controls[2].key == SDLK_LEFT, TEST_ASSERT(e, akgl_controlmaps[0].controls[2].key == SDLK_LEFT,
"the third default binding is not the left arrow"); "the third default binding is not the left arrow");
TEST_ASSERT(e, GAME_ControlMaps[0].controls[3].key == SDLK_RIGHT, TEST_ASSERT(e, akgl_controlmaps[0].controls[3].key == SDLK_RIGHT,
"the fourth default binding is not the right arrow"); "the fourth default binding is not the right arrow");
// The gamepad half binds buttons and leaves the keycode clear, so a // The gamepad half binds buttons and leaves the keycode clear, so a
// keyboard event cannot accidentally match a dpad binding. // keyboard event cannot accidentally match a dpad binding.
TEST_ASSERT(e, GAME_ControlMaps[0].controls[4].button == SDL_GAMEPAD_BUTTON_DPAD_DOWN, TEST_ASSERT(e, akgl_controlmaps[0].controls[4].button == SDL_GAMEPAD_BUTTON_DPAD_DOWN,
"the fifth default binding is not the dpad down button"); "the fifth default binding is not the dpad down button");
TEST_ASSERT(e, GAME_ControlMaps[0].controls[4].key == 0, TEST_ASSERT(e, akgl_controlmaps[0].controls[4].key == 0,
"a gamepad binding also carries a keycode"); "a gamepad binding also carries a keycode");
TEST_ASSERT(e, GAME_ControlMaps[0].controls[7].button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT, TEST_ASSERT(e, akgl_controlmaps[0].controls[7].button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
"the eighth default binding is not the dpad right button"); "the eighth default binding is not the dpad right button");
// An unknown actor name is a registry error, not a crash. // An unknown actor name is a registry error, not a crash.
@@ -856,6 +856,7 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
FAIL_ZERO_BREAK( FAIL_ZERO_BREAK(
errctx, errctx,
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD), SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD),

View File

@@ -38,13 +38,13 @@ static akerr_ErrorContext *clear_target(void)
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN( FAIL_ZERO_RETURN(
errctx, errctx,
SDL_SetRenderDrawColor(renderer->sdl_renderer, 0x00, 0x00, 0x00, 0xff), SDL_SetRenderDrawColor(akgl_renderer->sdl_renderer, 0x00, 0x00, 0x00, 0xff),
AKGL_ERR_SDL, AKGL_ERR_SDL,
"%s", "%s",
SDL_GetError()); SDL_GetError());
FAIL_ZERO_RETURN( FAIL_ZERO_RETURN(
errctx, errctx,
SDL_RenderClear(renderer->sdl_renderer), SDL_RenderClear(akgl_renderer->sdl_renderer),
AKGL_ERR_SDL, AKGL_ERR_SDL,
"%s", "%s",
SDL_GetError()); SDL_GetError());
@@ -80,10 +80,10 @@ akerr_ErrorContext *test_draw_point(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, clear_target()); CATCH(errctx, clear_target());
TEST_EXPECT_OK(errctx, akgl_draw_point(renderer, 10.0f, 20.0f, testred), TEST_EXPECT_OK(errctx, akgl_draw_point(akgl_renderer, 10.0f, 20.0f, testred),
"plotting one pixel"); "plotting one pixel");
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL); shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
TEST_ASSERT(errctx, pixel_is(shot, 10, 20, testred), TEST_ASSERT(errctx, pixel_is(shot, 10, 20, testred),
"the plotted pixel at 10,20 is not the color it was drawn with"); "the plotted pixel at 10,20 is not the color it was drawn with");
@@ -115,10 +115,10 @@ akerr_ErrorContext *test_draw_line(void)
CATCH(errctx, clear_target()); CATCH(errctx, clear_target());
// A vertical line, so every pixel of it is known without reasoning // A vertical line, so every pixel of it is known without reasoning
// about how SDL rasterises a diagonal. // about how SDL rasterises a diagonal.
TEST_EXPECT_OK(errctx, akgl_draw_line(renderer, 5.0f, 4.0f, 5.0f, 12.0f, testred), TEST_EXPECT_OK(errctx, akgl_draw_line(akgl_renderer, 5.0f, 4.0f, 5.0f, 12.0f, testred),
"drawing a vertical line"); "drawing a vertical line");
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL); shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
for ( i = 4; i <= 12; i++ ) { for ( i = 4; i <= 12; i++ ) {
TEST_ASSERT_FLAG(onthe_line, pixel_is(shot, 5, i, testred)); TEST_ASSERT_FLAG(onthe_line, pixel_is(shot, 5, i, testred));
@@ -158,8 +158,8 @@ akerr_ErrorContext *test_draw_rects(void)
// The outline touches the border and leaves the middle alone. // The outline touches the border and leaves the middle alone.
CATCH(errctx, clear_target()); CATCH(errctx, clear_target());
TEST_EXPECT_OK(errctx, akgl_draw_rect(renderer, &box, testred), "outlining a rectangle"); TEST_EXPECT_OK(errctx, akgl_draw_rect(akgl_renderer, &box, testred), "outlining a rectangle");
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL); shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
TEST_ASSERT(errctx, pixel_is(shot, 8, 8, testred), "the outline is missing its top left corner"); TEST_ASSERT(errctx, pixel_is(shot, 8, 8, testred), "the outline is missing its top left corner");
TEST_ASSERT(errctx, pixel_is(shot, 23, 23, testred), TEST_ASSERT(errctx, pixel_is(shot, 23, 23, testred),
@@ -171,8 +171,8 @@ akerr_ErrorContext *test_draw_rects(void)
// The filled form covers the interior as well. // The filled form covers the interior as well.
CATCH(errctx, clear_target()); CATCH(errctx, clear_target());
TEST_EXPECT_OK(errctx, akgl_draw_filled_rect(renderer, &box, testred), "filling a rectangle"); TEST_EXPECT_OK(errctx, akgl_draw_filled_rect(akgl_renderer, &box, testred), "filling a rectangle");
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL); shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
TEST_ASSERT(errctx, pixel_is(shot, 16, 16, testred), "the fill left its interior empty"); TEST_ASSERT(errctx, pixel_is(shot, 16, 16, testred), "the fill left its interior empty");
TEST_ASSERT(errctx, pixel_is(shot, 8, 8, testred), "the fill missed its top left corner"); TEST_ASSERT(errctx, pixel_is(shot, 8, 8, testred), "the fill missed its top left corner");
@@ -181,11 +181,11 @@ akerr_ErrorContext *test_draw_rects(void)
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_rect(NULL, &box, testred), TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_rect(NULL, &box, testred),
"outlining through a NULL backend"); "outlining through a NULL backend");
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_rect(renderer, NULL, testred), TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_rect(akgl_renderer, NULL, testred),
"outlining a NULL rectangle"); "outlining a NULL rectangle");
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_filled_rect(NULL, &box, testred), TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_filled_rect(NULL, &box, testred),
"filling through a NULL backend"); "filling through a NULL backend");
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_filled_rect(renderer, NULL, testred), TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_filled_rect(akgl_renderer, NULL, testred),
"filling a NULL rectangle"); "filling a NULL rectangle");
} CLEANUP { } CLEANUP {
if ( shot != NULL ) { if ( shot != NULL ) {
@@ -206,10 +206,10 @@ akerr_ErrorContext *test_draw_circle(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, clear_target()); CATCH(errctx, clear_target());
TEST_EXPECT_OK(errctx, akgl_draw_circle(renderer, 32.0f, 32.0f, 10.0f, testred), TEST_EXPECT_OK(errctx, akgl_draw_circle(akgl_renderer, 32.0f, 32.0f, 10.0f, testred),
"drawing a circle"); "drawing a circle");
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL); shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
// The four axis points are exact for any correct midpoint circle. // The four axis points are exact for any correct midpoint circle.
TEST_ASSERT(errctx, pixel_is(shot, 42, 32, testred), "the circle is missing its rightmost pixel"); TEST_ASSERT(errctx, pixel_is(shot, 42, 32, testred), "the circle is missing its rightmost pixel");
@@ -243,15 +243,15 @@ akerr_ErrorContext *test_draw_circle(void)
// A zero radius is the degenerate case, not an error: one pixel. // A zero radius is the degenerate case, not an error: one pixel.
CATCH(errctx, clear_target()); CATCH(errctx, clear_target());
TEST_EXPECT_OK(errctx, akgl_draw_circle(renderer, 5.0f, 5.0f, 0.0f, testred), TEST_EXPECT_OK(errctx, akgl_draw_circle(akgl_renderer, 5.0f, 5.0f, 0.0f, testred),
"drawing a circle of radius zero"); "drawing a circle of radius zero");
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL); shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
TEST_ASSERT(errctx, pixel_is(shot, 5, 5, testred), TEST_ASSERT(errctx, pixel_is(shot, 5, 5, testred),
"a circle of radius zero did not plot its center"); "a circle of radius zero did not plot its center");
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS, TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
akgl_draw_circle(renderer, 5.0f, 5.0f, -1.0f, testred), akgl_draw_circle(akgl_renderer, 5.0f, 5.0f, -1.0f, testred),
"drawing a circle of negative radius"); "drawing a circle of negative radius");
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
akgl_draw_circle(NULL, 5.0f, 5.0f, 4.0f, testred), akgl_draw_circle(NULL, 5.0f, 5.0f, 4.0f, testred),
@@ -279,12 +279,12 @@ akerr_ErrorContext *test_draw_flood_fill(void)
box.w = 20.0f; box.w = 20.0f;
box.h = 20.0f; box.h = 20.0f;
CATCH(errctx, clear_target()); CATCH(errctx, clear_target());
CATCH(errctx, akgl_draw_rect(renderer, &box, testred)); CATCH(errctx, akgl_draw_rect(akgl_renderer, &box, testred));
TEST_EXPECT_OK(errctx, akgl_draw_flood_fill(renderer, 20, 20, testgreen), TEST_EXPECT_OK(errctx, akgl_draw_flood_fill(akgl_renderer, 20, 20, testgreen),
"flooding the inside of a box"); "flooding the inside of a box");
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL); shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
TEST_ASSERT(errctx, pixel_is(shot, 20, 20, testgreen), "the seed pixel was not filled"); TEST_ASSERT(errctx, pixel_is(shot, 20, 20, testgreen), "the seed pixel was not filled");
TEST_ASSERT(errctx, pixel_is(shot, 11, 11, testgreen), TEST_ASSERT(errctx, pixel_is(shot, 11, 11, testgreen),
@@ -301,9 +301,9 @@ akerr_ErrorContext *test_draw_flood_fill(void)
// Filling a region that is already the requested color changes nothing // Filling a region that is already the requested color changes nothing
// and is not an error. // and is not an error.
TEST_EXPECT_OK(errctx, akgl_draw_flood_fill(renderer, 20, 20, testgreen), TEST_EXPECT_OK(errctx, akgl_draw_flood_fill(akgl_renderer, 20, 20, testgreen),
"flooding a region that is already that color"); "flooding a region that is already that color");
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL); shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
TEST_ASSERT(errctx, pixel_is(shot, 20, 20, testgreen), TEST_ASSERT(errctx, pixel_is(shot, 20, 20, testgreen),
"refilling a region disturbed it"); "refilling a region disturbed it");
@@ -313,9 +313,9 @@ akerr_ErrorContext *test_draw_flood_fill(void)
shot = NULL; shot = NULL;
// Flooding the outside reaches every pixel that is not the box. // Flooding the outside reaches every pixel that is not the box.
TEST_EXPECT_OK(errctx, akgl_draw_flood_fill(renderer, 0, 0, testgreen), TEST_EXPECT_OK(errctx, akgl_draw_flood_fill(akgl_renderer, 0, 0, testgreen),
"flooding the area around a box"); "flooding the area around a box");
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL); shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
TEST_ASSERT(errctx, pixel_is(shot, 0, 0, testgreen), "the seed pixel was not filled"); TEST_ASSERT(errctx, pixel_is(shot, 0, 0, testgreen), "the seed pixel was not filled");
TEST_ASSERT(errctx, pixel_is(shot, TEST_TARGET_SIZE - 1, TEST_TARGET_SIZE - 1, testgreen), TEST_ASSERT(errctx, pixel_is(shot, TEST_TARGET_SIZE - 1, TEST_TARGET_SIZE - 1, testgreen),
@@ -324,10 +324,10 @@ akerr_ErrorContext *test_draw_flood_fill(void)
"the fill from outside overwrote the boundary"); "the fill from outside overwrote the boundary");
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS, TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
akgl_draw_flood_fill(renderer, -1, 0, testred), akgl_draw_flood_fill(akgl_renderer, -1, 0, testred),
"flooding from a seed left of the target"); "flooding from a seed left of the target");
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS, TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
akgl_draw_flood_fill(renderer, 0, TEST_TARGET_SIZE, testred), akgl_draw_flood_fill(akgl_renderer, 0, TEST_TARGET_SIZE, testred),
"flooding from a seed below the target"); "flooding from a seed below the target");
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
akgl_draw_flood_fill(NULL, 0, 0, testred), akgl_draw_flood_fill(NULL, 0, 0, testred),
@@ -357,13 +357,13 @@ akerr_ErrorContext *test_draw_copy_and_paste_region(void)
box.w = 8.0f; box.w = 8.0f;
box.h = 8.0f; box.h = 8.0f;
CATCH(errctx, clear_target()); CATCH(errctx, clear_target());
CATCH(errctx, akgl_draw_filled_rect(renderer, &box, testred)); CATCH(errctx, akgl_draw_filled_rect(akgl_renderer, &box, testred));
region.x = 0; region.x = 0;
region.y = 0; region.y = 0;
region.w = 8; region.w = 8;
region.h = 8; region.h = 8;
TEST_EXPECT_OK(errctx, akgl_draw_copy_region(renderer, &region, &saved), TEST_EXPECT_OK(errctx, akgl_draw_copy_region(akgl_renderer, &region, &saved),
"saving a region of the target"); "saving a region of the target");
TEST_ASSERT(errctx, saved != NULL, "akgl_draw_copy_region did not allocate a surface"); TEST_ASSERT(errctx, saved != NULL, "akgl_draw_copy_region did not allocate a surface");
TEST_ASSERT(errctx, saved->w == 8 && saved->h == 8, TEST_ASSERT(errctx, saved->w == 8 && saved->h == 8,
@@ -371,9 +371,9 @@ akerr_ErrorContext *test_draw_copy_and_paste_region(void)
// Wipe the screen and put it back somewhere else. // Wipe the screen and put it back somewhere else.
CATCH(errctx, clear_target()); CATCH(errctx, clear_target());
TEST_EXPECT_OK(errctx, akgl_draw_paste_region(renderer, saved, 32.0f, 32.0f), TEST_EXPECT_OK(errctx, akgl_draw_paste_region(akgl_renderer, saved, 32.0f, 32.0f),
"pasting a saved region"); "pasting a saved region");
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL); shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, shot, AKGL_ERR_SDL, "%s", SDL_GetError());
TEST_ASSERT(errctx, pixel_is(shot, 32, 32, testred), TEST_ASSERT(errctx, pixel_is(shot, 32, 32, testred),
"the pasted region did not land at its destination"); "the pasted region did not land at its destination");
@@ -392,7 +392,7 @@ akerr_ErrorContext *test_draw_copy_and_paste_region(void)
FAIL_ZERO_BREAK(errctx, reused, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, reused, AKGL_ERR_SDL, "%s", SDL_GetError());
region.x = 32; region.x = 32;
region.y = 32; region.y = 32;
TEST_EXPECT_OK(errctx, akgl_draw_copy_region(renderer, &region, &reused), TEST_EXPECT_OK(errctx, akgl_draw_copy_region(akgl_renderer, &region, &reused),
"saving into a caller-owned surface"); "saving into a caller-owned surface");
TEST_ASSERT(errctx, pixel_is(reused, 0, 0, testred), TEST_ASSERT(errctx, pixel_is(reused, 0, 0, testred),
"the caller-owned surface did not receive the region"); "the caller-owned surface did not receive the region");
@@ -401,35 +401,35 @@ akerr_ErrorContext *test_draw_copy_and_paste_region(void)
// refused rather than silently clipped. // refused rather than silently clipped.
region.w = 4; region.w = 4;
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS, TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
akgl_draw_copy_region(renderer, &region, &reused), akgl_draw_copy_region(akgl_renderer, &region, &reused),
"saving into a destination of the wrong size"); "saving into a destination of the wrong size");
region.x = TEST_TARGET_SIZE - 4; region.x = TEST_TARGET_SIZE - 4;
region.y = 0; region.y = 0;
region.w = 8; region.w = 8;
region.h = 8; region.h = 8;
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS, TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
akgl_draw_copy_region(renderer, &region, &reused), akgl_draw_copy_region(akgl_renderer, &region, &reused),
"saving a region that runs off the right edge"); "saving a region that runs off the right edge");
region.x = 0; region.x = 0;
region.w = 0; region.w = 0;
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS, TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
akgl_draw_copy_region(renderer, &region, &reused), akgl_draw_copy_region(akgl_renderer, &region, &reused),
"saving a region with no area"); "saving a region with no area");
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
akgl_draw_copy_region(NULL, &region, &reused), akgl_draw_copy_region(NULL, &region, &reused),
"saving through a NULL backend"); "saving through a NULL backend");
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
akgl_draw_copy_region(renderer, NULL, &reused), akgl_draw_copy_region(akgl_renderer, NULL, &reused),
"saving a NULL region"); "saving a NULL region");
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
akgl_draw_copy_region(renderer, &region, NULL), akgl_draw_copy_region(akgl_renderer, &region, NULL),
"saving into a NULL destination"); "saving into a NULL destination");
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
akgl_draw_paste_region(NULL, saved, 0.0f, 0.0f), akgl_draw_paste_region(NULL, saved, 0.0f, 0.0f),
"pasting through a NULL backend"); "pasting through a NULL backend");
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
akgl_draw_paste_region(renderer, NULL, 0.0f, 0.0f), akgl_draw_paste_region(akgl_renderer, NULL, 0.0f, 0.0f),
"pasting a NULL surface"); "pasting a NULL surface");
} CLEANUP { } CLEANUP {
if ( shot != NULL ) { if ( shot != NULL ) {
@@ -465,19 +465,19 @@ akerr_ErrorContext *test_draw_preserves_render_draw_color(void)
// with, or the host's next SDL_RenderClear() paints the wrong color. // with, or the host's next SDL_RenderClear() paints the wrong color.
FAIL_ZERO_BREAK( FAIL_ZERO_BREAK(
errctx, errctx,
SDL_SetRenderDrawColor(renderer->sdl_renderer, 0x11, 0x22, 0x33, 0x44), SDL_SetRenderDrawColor(akgl_renderer->sdl_renderer, 0x11, 0x22, 0x33, 0x44),
AKGL_ERR_SDL, AKGL_ERR_SDL,
"%s", "%s",
SDL_GetError()); SDL_GetError());
CATCH(errctx, akgl_draw_point(renderer, 1.0f, 1.0f, testred)); CATCH(errctx, akgl_draw_point(akgl_renderer, 1.0f, 1.0f, testred));
CATCH(errctx, akgl_draw_line(renderer, 0.0f, 0.0f, 3.0f, 3.0f, testred)); CATCH(errctx, akgl_draw_line(akgl_renderer, 0.0f, 0.0f, 3.0f, 3.0f, testred));
CATCH(errctx, akgl_draw_rect(renderer, &box, testred)); CATCH(errctx, akgl_draw_rect(akgl_renderer, &box, testred));
CATCH(errctx, akgl_draw_filled_rect(renderer, &box, testred)); CATCH(errctx, akgl_draw_filled_rect(akgl_renderer, &box, testred));
CATCH(errctx, akgl_draw_circle(renderer, 20.0f, 20.0f, 4.0f, testred)); CATCH(errctx, akgl_draw_circle(akgl_renderer, 20.0f, 20.0f, 4.0f, testred));
FAIL_ZERO_BREAK( FAIL_ZERO_BREAK(
errctx, errctx,
SDL_GetRenderDrawColor(renderer->sdl_renderer, &r, &g, &b, &a), SDL_GetRenderDrawColor(akgl_renderer->sdl_renderer, &r, &g, &b, &a),
AKGL_ERR_SDL, AKGL_ERR_SDL,
"%s", "%s",
SDL_GetError()); SDL_GetError());
@@ -564,7 +564,8 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
renderer = &_akgl_renderer; TEST_TRAP_UNHANDLED_ERRORS();
akgl_renderer = &akgl_default_renderer;
FAIL_ZERO_BREAK( FAIL_ZERO_BREAK(
errctx, errctx,
@@ -579,8 +580,8 @@ int main(void)
TEST_TARGET_SIZE, TEST_TARGET_SIZE,
TEST_TARGET_SIZE, TEST_TARGET_SIZE,
0, 0,
&window, &akgl_window,
&renderer->sdl_renderer), &akgl_renderer->sdl_renderer),
AKGL_ERR_SDL, AKGL_ERR_SDL,
"Couldn't create window/renderer: %s", "Couldn't create window/renderer: %s",
SDL_GetError()); SDL_GetError());

View File

@@ -94,7 +94,13 @@ int main(void)
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
ATTEMPT { ATTEMPT {
// Unlike every other suite, this one has no akgl_error_init() in
// main() -- the first test is what brings the subsystem up. The trap
// therefore goes after it: libakerror installs its own default handler
// from the lazy akerr_init() inside that first call, and would
// overwrite anything set beforehand.
CATCH(errctx, test_error_init_owns_the_status_band()); CATCH(errctx, test_error_init_owns_the_status_band());
TEST_TRAP_UNHANDLED_ERRORS();
CATCH(errctx, test_error_init_is_idempotent()); CATCH(errctx, test_error_init_is_idempotent());
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {

View File

@@ -40,11 +40,11 @@ static char truncatedpath[] = "akgl_test_truncated.bin";
/** @brief Populate the process-wide game record with a valid identity. */ /** @brief Populate the process-wide game record with a valid identity. */
static void set_game_identity(void) static void set_game_identity(void)
{ {
memset(&game, 0x00, sizeof(akgl_Game)); memset(&akgl_game, 0x00, sizeof(akgl_Game));
strncpy((char *)&game.libversion, AKGL_VERSION, 31); strncpy((char *)&akgl_game.libversion, AKGL_VERSION, 31);
strncpy((char *)&game.version, "1.2.3", 31); strncpy((char *)&akgl_game.version, "1.2.3", 31);
strncpy((char *)&game.name, "libakgl test game", 255); strncpy((char *)&akgl_game.name, "libakgl test game", 255);
strncpy((char *)&game.uri, "https://example.invalid/akgl-test", 255); strncpy((char *)&akgl_game.uri, "https://example.invalid/akgl-test", 255);
} }
akerr_ErrorContext *test_game_load_versioncmp_matching(void) akerr_ErrorContext *test_game_load_versioncmp_matching(void)
@@ -134,23 +134,23 @@ akerr_ErrorContext *test_game_save_roundtrip(void)
CATCH(e, akgl_registry_init()); CATCH(e, akgl_registry_init());
CATCH(e, akgl_heap_init()); CATCH(e, akgl_heap_init());
set_game_identity(); set_game_identity();
game.fps = 60; akgl_game.fps = 60;
game.framesSinceUpdate = 7; akgl_game.framesSinceUpdate = 7;
memcpy(&expected, &game, sizeof(akgl_Game)); memcpy(&expected, &akgl_game, sizeof(akgl_Game));
TEST_EXPECT_OK(e, akgl_game_save((char *)&savepath), "saving a game"); TEST_EXPECT_OK(e, akgl_game_save((char *)&savepath), "saving a game");
// Scribble over the live state so a successful load has to restore it. // Scribble over the live state so a successful load has to restore it.
game.fps = 0; akgl_game.fps = 0;
game.framesSinceUpdate = 0; akgl_game.framesSinceUpdate = 0;
TEST_EXPECT_OK(e, akgl_game_load((char *)&savepath), "loading the game back"); TEST_EXPECT_OK(e, akgl_game_load((char *)&savepath), "loading the game back");
TEST_ASSERT(e, game.fps == 60, "fps restored as %d, expected 60", game.fps); TEST_ASSERT(e, akgl_game.fps == 60, "fps restored as %d, expected 60", akgl_game.fps);
TEST_ASSERT(e, game.framesSinceUpdate == 7, TEST_ASSERT(e, akgl_game.framesSinceUpdate == 7,
"framesSinceUpdate restored as %d, expected 7", game.framesSinceUpdate); "framesSinceUpdate restored as %d, expected 7", akgl_game.framesSinceUpdate);
TEST_ASSERT(e, strncmp((char *)&game.name, (char *)&expected.name, 256) == 0, TEST_ASSERT(e, strncmp((char *)&akgl_game.name, (char *)&expected.name, 256) == 0,
"the game name was not preserved across a save and load"); "the game name was not preserved across a save and load");
TEST_ASSERT(e, strncmp((char *)&game.version, (char *)&expected.version, 32) == 0, TEST_ASSERT(e, strncmp((char *)&akgl_game.version, (char *)&expected.version, 32) == 0,
"the game version was not preserved across a save and load"); "the game version was not preserved across a save and load");
} CLEANUP { } CLEANUP {
unlink((char *)&savepath); unlink((char *)&savepath);
@@ -170,7 +170,7 @@ akerr_ErrorContext *test_game_load_rejects_foreign_saves(void)
// A save written by a different game must not load into this one. // A save written by a different game must not load into this one.
set_game_identity(); set_game_identity();
CATCH(e, akgl_game_save((char *)&savepath)); CATCH(e, akgl_game_save((char *)&savepath));
strncpy((char *)&game.name, "a completely different game", 255); strncpy((char *)&akgl_game.name, "a completely different game", 255);
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath), TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath),
"a savegame with a foreign game name must be refused"); "a savegame with a foreign game name must be refused");
unlink((char *)&savepath); unlink((char *)&savepath);
@@ -178,14 +178,14 @@ akerr_ErrorContext *test_game_load_rejects_foreign_saves(void)
// Same for a differing URI. // Same for a differing URI.
set_game_identity(); set_game_identity();
CATCH(e, akgl_game_save((char *)&savepath)); CATCH(e, akgl_game_save((char *)&savepath));
strncpy((char *)&game.uri, "https://example.invalid/other", 255); strncpy((char *)&akgl_game.uri, "https://example.invalid/other", 255);
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath), TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath),
"a savegame with a foreign URI must be refused"); "a savegame with a foreign URI must be refused");
unlink((char *)&savepath); unlink((char *)&savepath);
// A save written against a different library version must be refused. // A save written against a different library version must be refused.
set_game_identity(); set_game_identity();
strncpy((char *)&game.libversion, "99.98.97", 31); strncpy((char *)&akgl_game.libversion, "99.98.97", 31);
CATCH(e, akgl_game_save((char *)&savepath)); CATCH(e, akgl_game_save((char *)&savepath));
set_game_identity(); set_game_identity();
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath), TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath),
@@ -194,7 +194,7 @@ akerr_ErrorContext *test_game_load_rejects_foreign_saves(void)
// And one written against a different game version. // And one written against a different game version.
set_game_identity(); set_game_identity();
strncpy((char *)&game.version, "4.5.6", 31); strncpy((char *)&akgl_game.version, "4.5.6", 31);
CATCH(e, akgl_game_save((char *)&savepath)); CATCH(e, akgl_game_save((char *)&savepath));
set_game_identity(); set_game_identity();
TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath), TEST_EXPECT_STATUS(e, AKERR_API, akgl_game_load((char *)&savepath),
@@ -246,7 +246,7 @@ akerr_ErrorContext *test_game_load_truncated_table(void)
memset(&partial, 0x00, sizeof(partial)); memset(&partial, 0x00, sizeof(partial));
fp = fopen((char *)&truncatedpath, "wb"); fp = fopen((char *)&truncatedpath, "wb");
FAIL_ZERO_BREAK(e, fp, AKERR_IO, "unable to create the truncated savegame fixture"); FAIL_ZERO_BREAK(e, fp, AKERR_IO, "unable to create the truncated savegame fixture");
FAIL_ZERO_BREAK(e, fwrite(&game, 1, sizeof(akgl_Game), fp), AKERR_IO, FAIL_ZERO_BREAK(e, fwrite(&akgl_game, 1, sizeof(akgl_Game), fp), AKERR_IO,
"unable to write the truncated savegame header"); "unable to write the truncated savegame header");
FAIL_ZERO_BREAK(e, fwrite(&partial, 1, sizeof(partial), fp), AKERR_IO, FAIL_ZERO_BREAK(e, fwrite(&partial, 1, sizeof(partial), fp), AKERR_IO,
"unable to write the truncated savegame body"); "unable to write the truncated savegame body");
@@ -296,7 +296,7 @@ akerr_ErrorContext *test_game_save_writes_name_tables(void)
+ (long)(AKGL_ACTOR_MAX_NAME_LENGTH + sizeof(akgl_Actor *)) * 2 + (long)(AKGL_ACTOR_MAX_NAME_LENGTH + sizeof(akgl_Actor *)) * 2
+ (long)(AKGL_SPRITE_MAX_NAME_LENGTH + sizeof(akgl_Sprite *)) + (long)(AKGL_SPRITE_MAX_NAME_LENGTH + sizeof(akgl_Sprite *))
+ (long)(AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH + sizeof(akgl_SpriteSheet *)) + (long)(AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH + sizeof(akgl_SpriteSheet *))
+ (long)(AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH + sizeof(akgl_Character *)); + (long)(AKGL_CHARACTER_MAX_NAME_LENGTH + sizeof(akgl_Character *));
TEST_ASSERT(e, filesize >= minimum, TEST_ASSERT(e, filesize >= minimum,
"the savegame is %ld bytes, expected at least %ld for the header and four name tables", "the savegame is %ld bytes, expected at least %ld for the header and four name tables",
@@ -317,8 +317,8 @@ akerr_ErrorContext *test_game_state_lock(void)
ATTEMPT { ATTEMPT {
set_game_identity(); set_game_identity();
game.statelock = SDL_CreateMutex(); akgl_game.statelock = SDL_CreateMutex();
FAIL_ZERO_BREAK(e, game.statelock, AKGL_ERR_SDL, "unable to create the state mutex"); FAIL_ZERO_BREAK(e, akgl_game.statelock, AKGL_ERR_SDL, "unable to create the state mutex");
TEST_EXPECT_OK(e, akgl_game_state_lock(), "taking the state lock"); TEST_EXPECT_OK(e, akgl_game_state_lock(), "taking the state lock");
TEST_EXPECT_OK(e, akgl_game_state_unlock(), "releasing the state lock"); TEST_EXPECT_OK(e, akgl_game_state_unlock(), "releasing the state lock");
@@ -327,9 +327,93 @@ akerr_ErrorContext *test_game_state_lock(void)
TEST_EXPECT_OK(e, akgl_game_state_lock(), "retaking the state lock"); TEST_EXPECT_OK(e, akgl_game_state_lock(), "retaking the state lock");
TEST_EXPECT_OK(e, akgl_game_state_unlock(), "releasing the state lock again"); TEST_EXPECT_OK(e, akgl_game_state_unlock(), "releasing the state lock again");
} CLEANUP { } CLEANUP {
if ( game.statelock != NULL ) { if ( akgl_game.statelock != NULL ) {
SDL_DestroyMutex(game.statelock); SDL_DestroyMutex(akgl_game.statelock);
game.statelock = NULL; akgl_game.statelock = NULL;
}
} PROCESS(e) {
} FINISH(e, true);
SUCCEED_RETURN(e);
}
/** @brief Cleared while the helper thread should keep holding the state mutex. */
static SDL_AtomicInt lockholder_release;
/** @brief Takes the state mutex and sits on it until lockholder_release is set. */
static int SDLCALL lockholder_thread(void *userdata)
{
SDL_Mutex *statelock = (SDL_Mutex *)userdata;
SDL_LockMutex(statelock);
while ( SDL_GetAtomicInt(&lockholder_release) == 0 ) {
SDL_Delay(10);
}
SDL_UnlockMutex(statelock);
return 0;
}
/**
* @brief akgl_game_state_lock must give up on a contended mutex in about a second.
*
* The uncontended path above never reaches the retry loop, which is where the
* budget lives and where the defect was: the loop counted against a constant
* named "one second in milliseconds" that held 1000000, so it retried 10,000
* times at 100 ms and blocked for roughly sixteen minutes before reporting
* failure. The upper bound below is the assertion that matters. The lower bound
* is there so a build that gave up immediately -- reporting failure without
* waiting at all -- cannot pass either.
*/
akerr_ErrorContext *test_game_state_lock_budget(void)
{
PREPARE_ERROR(e);
SDL_Thread *holder = NULL;
Uint64 started = 0;
Uint64 elapsed = 0;
ATTEMPT {
set_game_identity();
akgl_game.statelock = SDL_CreateMutex();
FAIL_ZERO_BREAK(e, akgl_game.statelock, AKGL_ERR_SDL, "unable to create the state mutex");
SDL_SetAtomicInt(&lockholder_release, 0);
holder = SDL_CreateThread(lockholder_thread, "akgl_test_lockholder", (void *)akgl_game.statelock);
FAIL_ZERO_BREAK(e, holder, AKGL_ERR_SDL, "unable to start the lock-holding thread");
// Wait until the helper actually owns the mutex. Without this the
// measurement races the thread start and the lock is taken on the first
// try, which measures nothing.
while ( SDL_TryLockMutex(akgl_game.statelock) == true ) {
SDL_UnlockMutex(akgl_game.statelock);
SDL_Delay(1);
}
started = SDL_GetTicksNS();
TEST_EXPECT_STATUS(
e,
AKGL_ERR_SDL,
akgl_game_state_lock(),
"taking a state lock another thread is holding");
elapsed = SDL_GetTicksNS() - started;
TEST_ASSERT(
e,
elapsed >= (AKGL_TIME_ONESEC_NS / 2),
"state lock gave up after %" SDL_PRIu64 " ns without waiting out its budget",
elapsed);
TEST_ASSERT(
e,
elapsed < (5 * (Uint64)AKGL_TIME_ONESEC_NS),
"state lock waited %" SDL_PRIu64 " ns on a %d ms budget",
elapsed,
AKGL_GAME_STATE_LOCK_BUDGET_MS);
} CLEANUP {
SDL_SetAtomicInt(&lockholder_release, 1);
if ( holder != NULL ) {
SDL_WaitThread(holder, NULL);
}
if ( akgl_game.statelock != NULL ) {
SDL_DestroyMutex(akgl_game.statelock);
akgl_game.statelock = NULL;
} }
} PROCESS(e) { } PROCESS(e) {
} FINISH(e, true); } FINISH(e, true);
@@ -352,41 +436,41 @@ akerr_ErrorContext *test_game_updateFPS(void)
ATTEMPT { ATTEMPT {
set_game_identity(); set_game_identity();
game.lowfpsfunc = &stub_lowfps; akgl_game.lowfpsfunc = &stub_lowfps;
// Below the 30 FPS floor, every update notifies the callback. // Below the 30 FPS floor, every update notifies the callback.
game.fps = 10; akgl_game.fps = 10;
game.lastFPSTime = SDL_GetTicksNS(); akgl_game.lastFPSTime = SDL_GetTicksNS();
lowfps_calls = 0; lowfps_calls = 0;
framesbefore = game.framesSinceUpdate; framesbefore = akgl_game.framesSinceUpdate;
akgl_game_updateFPS(); akgl_game_update_fps();
TEST_ASSERT(e, lowfps_calls == 1, TEST_ASSERT(e, lowfps_calls == 1,
"a sub-30 FPS update fired the low-FPS callback %d times, expected 1", lowfps_calls); "a sub-30 FPS update fired the low-FPS callback %d times, expected 1", lowfps_calls);
TEST_ASSERT(e, game.framesSinceUpdate == (framesbefore + 1), TEST_ASSERT(e, akgl_game.framesSinceUpdate == (framesbefore + 1),
"updateFPS did not count the frame (%d, expected %d)", "updateFPS did not count the frame (%d, expected %d)",
game.framesSinceUpdate, framesbefore + 1); akgl_game.framesSinceUpdate, framesbefore + 1);
TEST_ASSERT(e, game.lastIterTime != 0, "updateFPS did not stamp lastIterTime"); TEST_ASSERT(e, akgl_game.lastIterTime != 0, "updateFPS did not stamp lastIterTime");
// At or above the floor, the callback stays quiet. // At or above the floor, the callback stays quiet.
game.fps = 60; akgl_game.fps = 60;
game.lastFPSTime = SDL_GetTicksNS(); akgl_game.lastFPSTime = SDL_GetTicksNS();
lowfps_calls = 0; lowfps_calls = 0;
akgl_game_updateFPS(); akgl_game_update_fps();
TEST_ASSERT(e, lowfps_calls == 0, TEST_ASSERT(e, lowfps_calls == 0,
"a 60 FPS update fired the low-FPS callback %d times, expected 0", lowfps_calls); "a 60 FPS update fired the low-FPS callback %d times, expected 0", lowfps_calls);
// Once a full second has elapsed, the frame counter rolls into fps. // Once a full second has elapsed, the frame counter rolls into fps.
game.fps = 60; akgl_game.fps = 60;
game.framesSinceUpdate = 45; akgl_game.framesSinceUpdate = 45;
game.lastFPSTime = SDL_GetTicksNS() - (2 * (SDL_Time)AKGL_TIME_ONESEC_NS); akgl_game.lastFPSTime = SDL_GetTicksNS() - (2 * (SDL_Time)AKGL_TIME_ONESEC_NS);
akgl_game_updateFPS(); akgl_game_update_fps();
TEST_ASSERT(e, game.fps == 45, TEST_ASSERT(e, akgl_game.fps == 45,
"after a second elapsed, fps rolled over as %d, expected 45", game.fps); "after a second elapsed, fps rolled over as %d, expected 45", akgl_game.fps);
TEST_ASSERT(e, game.framesSinceUpdate == 1, TEST_ASSERT(e, akgl_game.framesSinceUpdate == 1,
"the frame counter restarted at %d, expected 1", game.framesSinceUpdate); "the frame counter restarted at %d, expected 1", akgl_game.framesSinceUpdate);
// The shipped default callback only logs, so it just has to not crash. // The shipped default callback only logs, so it just has to not crash.
game.fps = 1; akgl_game.fps = 1;
akgl_game_lowfps(); akgl_game_lowfps();
} CLEANUP { } CLEANUP {
} PROCESS(e) { } PROCESS(e) {
@@ -403,6 +487,7 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_heap_init());
CATCH(errctx, akgl_registry_init()); CATCH(errctx, akgl_registry_init());
@@ -415,6 +500,7 @@ int main(void)
CATCH(errctx, test_game_load_truncated_table()); CATCH(errctx, test_game_load_truncated_table());
CATCH(errctx, test_game_save_writes_name_tables()); CATCH(errctx, test_game_save_writes_name_tables());
CATCH(errctx, test_game_state_lock()); CATCH(errctx, test_game_state_lock());
CATCH(errctx, test_game_state_lock_budget());
CATCH(errctx, test_game_updateFPS()); CATCH(errctx, test_game_updateFPS());
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {

View File

@@ -10,7 +10,13 @@
* *
* One header per file, deliberately. A second `#include` after the first proves * One header per file, deliberately. A second `#include` after the first proves
* nothing about the second: by then the first has already dragged its * nothing about the second: by then the first has already dragged its
* dependencies in. Covering another header means another file like this one. * dependencies in. So every other public header gets its own generated
* translation unit -- `CMakeLists.txt` writes one per entry in
* `AKGL_PUBLIC_HEADERS` and links them all into this program. That list also
* drives `install()`, so a header that ships is a header that is checked.
*
* This file stays hand-written because it asserts something the generated ones
* cannot: that `akgl_Actor` is *complete* here rather than merely declared.
*/ */
#include <akgl/controller.h> #include <akgl/controller.h>
@@ -26,11 +32,11 @@ int main(void)
// one by value. // one by value.
memset(&control, 0x00, sizeof(akgl_Control)); memset(&control, 0x00, sizeof(akgl_Control));
control.key = SDLK_LEFT; control.key = SDLK_LEFT;
control.handler_on = &akgl_Actor_cmhf_left_on; control.handler_on = &akgl_actor_cmhf_left_on;
if ( control.handler_on == NULL ) { if ( control.handler_on == NULL ) {
return 1; return 1;
} }
if ( sizeof(GAME_ControlMaps) != (sizeof(akgl_ControlMap) * AKGL_MAX_CONTROL_MAPS) ) { if ( sizeof(akgl_controlmaps) != (sizeof(akgl_ControlMap) * AKGL_MAX_CONTROL_MAPS) ) {
return 1; return 1;
} }
return 0; return 0;

View File

@@ -50,57 +50,57 @@ akerr_ErrorContext *test_heap_init_clears_every_pool(void)
ATTEMPT { ATTEMPT {
// Dirty every pool, then require that init scrubs all of them. // Dirty every pool, then require that init scrubs all of them.
for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
HEAP_ACTOR[i].refcount = 5; akgl_heap_actors[i].refcount = 5;
} }
for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) {
HEAP_SPRITE[i].refcount = 5; akgl_heap_sprites[i].refcount = 5;
} }
for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) {
HEAP_SPRITESHEET[i].refcount = 5; akgl_heap_spritesheets[i].refcount = 5;
} }
for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) {
HEAP_CHARACTER[i].refcount = 5; akgl_heap_characters[i].refcount = 5;
} }
for ( i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
HEAP_STRING[i].refcount = 5; akgl_heap_strings[i].refcount = 5;
} }
CATCH(e, akgl_heap_init()); CATCH(e, akgl_heap_init());
for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
TEST_ASSERT_FLAG(clean, HEAP_ACTOR[i].refcount == 0); TEST_ASSERT_FLAG(clean, akgl_heap_actors[i].refcount == 0);
} }
TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the actor pool"); TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the actor pool");
for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) {
TEST_ASSERT_FLAG(clean, HEAP_SPRITE[i].refcount == 0); TEST_ASSERT_FLAG(clean, akgl_heap_sprites[i].refcount == 0);
} }
TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the sprite pool"); TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the sprite pool");
for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) {
TEST_ASSERT_FLAG(clean, HEAP_SPRITESHEET[i].refcount == 0); TEST_ASSERT_FLAG(clean, akgl_heap_spritesheets[i].refcount == 0);
} }
TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the spritesheet pool"); TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the spritesheet pool");
for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) {
TEST_ASSERT_FLAG(clean, HEAP_CHARACTER[i].refcount == 0); TEST_ASSERT_FLAG(clean, akgl_heap_characters[i].refcount == 0);
} }
TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the character pool"); TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the character pool");
for ( i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
TEST_ASSERT_FLAG(clean, HEAP_STRING[i].refcount == 0); TEST_ASSERT_FLAG(clean, akgl_heap_strings[i].refcount == 0);
} }
TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the string pool"); TEST_ASSERT(e, clean, "akgl_heap_init left a nonzero refcount in the string pool");
// akgl_heap_init_actor clears only the actor pool. // akgl_heap_init_actor clears only the actor pool.
HEAP_ACTOR[0].refcount = 9; akgl_heap_actors[0].refcount = 9;
HEAP_SPRITE[0].refcount = 9; akgl_heap_sprites[0].refcount = 9;
CATCH(e, akgl_heap_init_actor()); CATCH(e, akgl_heap_init_actor());
TEST_ASSERT(e, HEAP_ACTOR[0].refcount == 0, TEST_ASSERT(e, akgl_heap_actors[0].refcount == 0,
"akgl_heap_init_actor did not clear the actor pool"); "akgl_heap_init_actor did not clear the actor pool");
TEST_ASSERT(e, HEAP_SPRITE[0].refcount == 9, TEST_ASSERT(e, akgl_heap_sprites[0].refcount == 9,
"akgl_heap_init_actor cleared the sprite pool as well"); "akgl_heap_init_actor cleared the sprite pool as well");
HEAP_SPRITE[0].refcount = 0; akgl_heap_sprites[0].refcount = 0;
} CLEANUP { } CLEANUP {
} PROCESS(e) { } PROCESS(e) {
} FINISH(e, true); } FINISH(e, true);
@@ -151,28 +151,28 @@ akerr_ErrorContext *test_heap_exhaustion(void)
// Actors: the acquire function does not claim the slot, so the test does. // Actors: the acquire function does not claim the slot, so the test does.
CATCH(e, reset_all_heaps()); CATCH(e, reset_all_heaps());
for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
HEAP_ACTOR[i].refcount = 1; akgl_heap_actors[i].refcount = 1;
} }
TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_heap_next_actor(&actor), TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_heap_next_actor(&actor),
"akgl_heap_next_actor with every slot claimed"); "akgl_heap_next_actor with every slot claimed");
CATCH(e, reset_all_heaps()); CATCH(e, reset_all_heaps());
for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) {
HEAP_SPRITE[i].refcount = 1; akgl_heap_sprites[i].refcount = 1;
} }
TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_heap_next_sprite(&sprite), TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_heap_next_sprite(&sprite),
"akgl_heap_next_sprite with every slot claimed"); "akgl_heap_next_sprite with every slot claimed");
CATCH(e, reset_all_heaps()); CATCH(e, reset_all_heaps());
for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) {
HEAP_SPRITESHEET[i].refcount = 1; akgl_heap_spritesheets[i].refcount = 1;
} }
TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_heap_next_spritesheet(&sheet), TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_heap_next_spritesheet(&sheet),
"akgl_heap_next_spritesheet with every slot claimed"); "akgl_heap_next_spritesheet with every slot claimed");
CATCH(e, reset_all_heaps()); CATCH(e, reset_all_heaps());
for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) { for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) {
HEAP_CHARACTER[i].refcount = 1; akgl_heap_characters[i].refcount = 1;
} }
TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_heap_next_character(&basechar), TEST_EXPECT_STATUS(e, AKGL_ERR_HEAP, akgl_heap_next_character(&basechar),
"akgl_heap_next_character with every slot claimed"); "akgl_heap_next_character with every slot claimed");
@@ -186,10 +186,10 @@ akerr_ErrorContext *test_heap_exhaustion(void)
"akgl_heap_next_string with the pool drained"); "akgl_heap_next_string with the pool drained");
// A single release makes exactly one slot available again. // A single release makes exactly one slot available again.
CATCH(e, akgl_heap_release_string(&HEAP_STRING[0])); CATCH(e, akgl_heap_release_string(&akgl_heap_strings[0]));
TEST_EXPECT_OK(e, akgl_heap_next_string(&str), TEST_EXPECT_OK(e, akgl_heap_next_string(&str),
"akgl_heap_next_string after freeing one slot"); "akgl_heap_next_string after freeing one slot");
TEST_ASSERT(e, str == &HEAP_STRING[0], TEST_ASSERT(e, str == &akgl_heap_strings[0],
"the reclaimed string was not the slot that was released"); "the reclaimed string was not the slot that was released");
CATCH(e, reset_all_heaps()); CATCH(e, reset_all_heaps());
@@ -264,7 +264,7 @@ akerr_ErrorContext *test_heap_release_actor_children(void)
// Each child was initialized to 1 and incremented to 2 by add_child, so // Each child was initialized to 1 and incremented to 2 by add_child, so
// the recursive release should have brought every one of them back to 1. // the recursive release should have brought every one of them back to 1.
for ( i = 1; i <= AKGL_ACTOR_MAX_CHILDREN; i++ ) { for ( i = 1; i <= AKGL_ACTOR_MAX_CHILDREN; i++ ) {
TEST_ASSERT_FLAG(released, HEAP_ACTOR[i].refcount == 1); TEST_ASSERT_FLAG(released, akgl_heap_actors[i].refcount == 1);
} }
TEST_ASSERT(e, released, TEST_ASSERT(e, released,
"releasing a parent did not decrement every child exactly once"); "releasing a parent did not decrement every child exactly once");
@@ -362,6 +362,7 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_heap_init());
CATCH(errctx, akgl_registry_init()); CATCH(errctx, akgl_registry_init());

View File

@@ -348,6 +348,7 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_heap_init());
CATCH(errctx, akgl_registry_init()); CATCH(errctx, akgl_registry_init());
CATCH(errctx, load_fixture()); CATCH(errctx, load_fixture());

View File

@@ -69,21 +69,21 @@ static void bench_report_footprint(void)
{ {
size_t total = 0; size_t total = 0;
total = sizeof(HEAP_ACTOR) + sizeof(HEAP_SPRITE) + sizeof(HEAP_SPRITESHEET) + total = sizeof(akgl_heap_actors) + sizeof(akgl_heap_sprites) + sizeof(akgl_heap_spritesheets) +
sizeof(HEAP_CHARACTER) + sizeof(HEAP_STRING); sizeof(akgl_heap_characters) + sizeof(akgl_heap_strings);
printf("\n"); printf("\n");
printf("static footprint, fixed at compile time:\n"); printf("static footprint, fixed at compile time:\n");
printf(" %-24s %5d x %7zu = %10zu bytes\n", printf(" %-24s %5d x %7zu = %10zu bytes\n",
"HEAP_ACTOR", AKGL_MAX_HEAP_ACTOR, sizeof(akgl_Actor), sizeof(HEAP_ACTOR)); "akgl_heap_actors", AKGL_MAX_HEAP_ACTOR, sizeof(akgl_Actor), sizeof(akgl_heap_actors));
printf(" %-24s %5d x %7zu = %10zu bytes\n", printf(" %-24s %5d x %7zu = %10zu bytes\n",
"HEAP_SPRITE", AKGL_MAX_HEAP_SPRITE, sizeof(akgl_Sprite), sizeof(HEAP_SPRITE)); "akgl_heap_sprites", AKGL_MAX_HEAP_SPRITE, sizeof(akgl_Sprite), sizeof(akgl_heap_sprites));
printf(" %-24s %5d x %7zu = %10zu bytes\n", printf(" %-24s %5d x %7zu = %10zu bytes\n",
"HEAP_SPRITESHEET", AKGL_MAX_HEAP_SPRITESHEET, sizeof(akgl_SpriteSheet), sizeof(HEAP_SPRITESHEET)); "akgl_heap_spritesheets", AKGL_MAX_HEAP_SPRITESHEET, sizeof(akgl_SpriteSheet), sizeof(akgl_heap_spritesheets));
printf(" %-24s %5d x %7zu = %10zu bytes\n", printf(" %-24s %5d x %7zu = %10zu bytes\n",
"HEAP_CHARACTER", AKGL_MAX_HEAP_CHARACTER, sizeof(akgl_Character), sizeof(HEAP_CHARACTER)); "akgl_heap_characters", AKGL_MAX_HEAP_CHARACTER, sizeof(akgl_Character), sizeof(akgl_heap_characters));
printf(" %-24s %5d x %7zu = %10zu bytes\n", printf(" %-24s %5d x %7zu = %10zu bytes\n",
"HEAP_STRING", AKGL_MAX_HEAP_STRING, sizeof(akgl_String), sizeof(HEAP_STRING)); "akgl_heap_strings", AKGL_MAX_HEAP_STRING, sizeof(akgl_String), sizeof(akgl_heap_strings));
printf(" %-24s %5s %7s %10zu bytes\n", "pools, total", "", "", total); printf(" %-24s %5s %7s %10zu bytes\n", "pools, total", "", "", total);
printf(" %-24s %5d x %7s = %10zu bytes\n", printf(" %-24s %5d x %7s = %10zu bytes\n",
"akgl_Tilemap", 1, "", sizeof(akgl_Tilemap)); "akgl_Tilemap", 1, "", sizeof(akgl_Tilemap));
@@ -263,7 +263,7 @@ static akerr_ErrorContext *bench_heap_actor_claim(void)
PASS(errctx, inner); PASS(errctx, inner);
for ( i = 0; i < (AKGL_MAX_HEAP_ACTOR - 1); i++ ) { for ( i = 0; i < (AKGL_MAX_HEAP_ACTOR - 1); i++ ) {
HEAP_ACTOR[i].refcount = 1; akgl_heap_actors[i].refcount = 1;
} }
bench_start("heap_next_actor, one slot left", "call", 400.0); bench_start("heap_next_actor, one slot left", "call", 400.0);
BENCH_LOOP(inner, i, count, akgl_heap_next_actor(&actor)); BENCH_LOOP(inner, i, count, akgl_heap_next_actor(&actor));
@@ -328,7 +328,7 @@ static akerr_ErrorContext *bench_heap_string(void)
// The same claim with 255 of the 256 slots taken: a megabyte walked, one // The same claim with 255 of the 256 slots taken: a megabyte walked, one
// reference count read per 4 KiB page. // reference count read per 4 KiB page.
for ( i = 0; i < (AKGL_MAX_HEAP_STRING - 1); i++ ) { for ( i = 0; i < (AKGL_MAX_HEAP_STRING - 1); i++ ) {
HEAP_STRING[i].refcount = 1; akgl_heap_strings[i].refcount = 1;
} }
bench_start("heap_next_string, one slot left", "call", 2500.0); bench_start("heap_next_string, one slot left", "call", 2500.0);
for ( i = 0; i < count; i++ ) { for ( i = 0; i < count; i++ ) {
@@ -470,7 +470,7 @@ static akerr_ErrorContext *bench_registry_lookup(void)
PASS(errctx, akgl_registry_init()); PASS(errctx, akgl_registry_init());
PASS(errctx, bench_make_character(&basechar, "benchlookupchar", 0)); PASS(errctx, bench_make_character(&basechar, "benchlookupchar", 0));
PASS(errctx, bench_fill_actor_pool(basechar, BENCH_ACTOR_COUNT)); PASS(errctx, bench_fill_actor_pool(basechar, BENCH_ACTOR_COUNT));
actor = &HEAP_ACTOR[0]; actor = &akgl_heap_actors[0];
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
bench_start("actor_set_character, registry lookup", "call", 400.0); bench_start("actor_set_character, registry lookup", "call", 400.0);
@@ -569,7 +569,7 @@ static akerr_ErrorContext *bench_actor_update(void)
PASS(errctx, akgl_registry_init()); PASS(errctx, akgl_registry_init());
PASS(errctx, bench_make_character(&basechar, "benchupdatechar", 0)); PASS(errctx, bench_make_character(&basechar, "benchupdatechar", 0));
PASS(errctx, bench_fill_actor_pool(basechar, 1)); PASS(errctx, bench_fill_actor_pool(basechar, 1));
actor = &HEAP_ACTOR[0]; actor = &akgl_heap_actors[0];
AKGL_BITMASK_CLEAR(actor->state); AKGL_BITMASK_CLEAR(actor->state);
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
@@ -674,7 +674,7 @@ static akerr_ErrorContext *bench_logic_frame(void)
bench_start("logic frame, 64 actors updated + simulated", "frame", 60000.0); bench_start("logic frame, 64 actors updated + simulated", "frame", 60000.0);
for ( i = 0; i < count; i++ ) { for ( i = 0; i < count; i++ ) {
for ( j = 0; j < AKGL_MAX_HEAP_ACTOR; j++ ) { for ( j = 0; j < AKGL_MAX_HEAP_ACTOR; j++ ) {
actor = &HEAP_ACTOR[j]; actor = &akgl_heap_actors[j];
if ( actor->refcount == 0 ) { if ( actor->refcount == 0 ) {
continue; continue;
} }
@@ -709,7 +709,7 @@ static akerr_ErrorContext *bench_geometry(void)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
akerr_ErrorContext *inner = NULL; akerr_ErrorContext *inner = NULL;
RectanglePoints points; akgl_RectanglePoints points;
SDL_FRect rects[BENCH_ACTOR_COUNT]; SDL_FRect rects[BENCH_ACTOR_COUNT];
SDL_FRect overlapping = { .x = 8.0, .y = 8.0, .w = 32.0, .h = 32.0 }; SDL_FRect overlapping = { .x = 8.0, .y = 8.0, .w = 32.0, .h = 32.0 };
SDL_FRect disjoint = { .x = 900.0, .y = 900.0, .w = 32.0, .h = 32.0 }; SDL_FRect disjoint = { .x = 900.0, .y = 900.0, .w = 32.0, .h = 32.0 };
@@ -901,6 +901,7 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_heap_init());
CATCH(errctx, akgl_registry_init()); CATCH(errctx, akgl_registry_init());
CATCH(errctx, akgl_registry_init_properties()); CATCH(errctx, akgl_registry_init_properties());

View File

@@ -91,7 +91,7 @@ static SDL_Texture *benchtiles = NULL;
*/ */
#define BENCH_FLUSH_STOP(e, ops) \ #define BENCH_FLUSH_STOP(e, ops) \
{ \ { \
bool __bench_flushed = SDL_FlushRenderer(renderer->sdl_renderer); \ bool __bench_flushed = SDL_FlushRenderer(akgl_renderer->sdl_renderer); \
bench_stop(ops); \ bench_stop(ops); \
FAIL_ZERO_RETURN(e, __bench_flushed, AKGL_ERR_SDL, "SDL_FlushRenderer: %s", SDL_GetError()); \ FAIL_ZERO_RETURN(e, __bench_flushed, AKGL_ERR_SDL, "SDL_FlushRenderer: %s", SDL_GetError()); \
} }
@@ -233,11 +233,11 @@ static akerr_ErrorContext *bench_frame(void)
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
bench_start("frame_start + frame_end, 640x480", "frame", 230000.0); bench_start("frame_start + frame_end, 640x480", "frame", 230000.0);
for ( i = 0; i < count; i++ ) { for ( i = 0; i < count; i++ ) {
inner = renderer->frame_start(renderer); inner = akgl_renderer->frame_start(akgl_renderer);
if ( inner != NULL ) { if ( inner != NULL ) {
break; break;
} }
inner = renderer->frame_end(renderer); inner = akgl_renderer->frame_end(akgl_renderer);
if ( inner != NULL ) { if ( inner != NULL ) {
break; break;
} }
@@ -268,30 +268,30 @@ static akerr_ErrorContext *bench_primitives(void)
int i = 0; int i = 0;
int rep = 0; int rep = 0;
PASS(errctx, renderer->frame_start(renderer)); PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
bench_start("draw_point", "call", 1400.0); bench_start("draw_point", "call", 1400.0);
BENCH_LOOP(inner, i, count, akgl_draw_point(renderer, 320.0, 240.0, red)); BENCH_LOOP(inner, i, count, akgl_draw_point(akgl_renderer, 320.0, 240.0, red));
BENCH_FLUSH_STOP(errctx, count); BENCH_FLUSH_STOP(errctx, count);
PASS(errctx, inner); PASS(errctx, inner);
bench_start("draw_line, screen diagonal", "call", 6200.0); bench_start("draw_line, screen diagonal", "call", 6200.0);
BENCH_LOOP(inner, i, count, akgl_draw_line(renderer, 0.0, 0.0, 639.0, 479.0, red)); BENCH_LOOP(inner, i, count, akgl_draw_line(akgl_renderer, 0.0, 0.0, 639.0, 479.0, red));
BENCH_FLUSH_STOP(errctx, count); BENCH_FLUSH_STOP(errctx, count);
PASS(errctx, inner); PASS(errctx, inner);
bench_start("draw_rect, 200x150 outline", "call", 5000.0); bench_start("draw_rect, 200x150 outline", "call", 5000.0);
BENCH_LOOP(inner, i, count, akgl_draw_rect(renderer, &rect, red)); BENCH_LOOP(inner, i, count, akgl_draw_rect(akgl_renderer, &rect, red));
BENCH_FLUSH_STOP(errctx, count); BENCH_FLUSH_STOP(errctx, count);
PASS(errctx, inner); PASS(errctx, inner);
bench_start("draw_filled_rect, 200x150", "call", 440000.0); bench_start("draw_filled_rect, 200x150", "call", 440000.0);
BENCH_LOOP(inner, i, filled, akgl_draw_filled_rect(renderer, &rect, red)); BENCH_LOOP(inner, i, filled, akgl_draw_filled_rect(akgl_renderer, &rect, red));
BENCH_FLUSH_STOP(errctx, filled); BENCH_FLUSH_STOP(errctx, filled);
PASS(errctx, inner); PASS(errctx, inner);
bench_start("draw_circle, radius 64", "call", 25000.0); bench_start("draw_circle, radius 64", "call", 25000.0);
BENCH_LOOP(inner, i, circles, akgl_draw_circle(renderer, 320.0, 240.0, 64.0, red)); BENCH_LOOP(inner, i, circles, akgl_draw_circle(akgl_renderer, 320.0, 240.0, 64.0, red));
BENCH_FLUSH_STOP(errctx, circles); BENCH_FLUSH_STOP(errctx, circles);
PASS(errctx, inner); PASS(errctx, inner);
} }
@@ -325,14 +325,14 @@ static akerr_ErrorContext *bench_framebuffer(void)
int i = 0; int i = 0;
int rep = 0; int rep = 0;
PASS(errctx, renderer->frame_start(renderer)); PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
PASS(errctx, akgl_draw_copy_region(renderer, &region, &saved)); PASS(errctx, akgl_draw_copy_region(akgl_renderer, &region, &saved));
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
bench_start("draw_copy_region, 64x64 readback", "call", 10000.0); bench_start("draw_copy_region, 64x64 readback", "call", 10000.0);
for ( i = 0; i < count; i++ ) { for ( i = 0; i < count; i++ ) {
SDL_Surface *scratch = NULL; SDL_Surface *scratch = NULL;
inner = akgl_draw_copy_region(renderer, &region, &scratch); inner = akgl_draw_copy_region(akgl_renderer, &region, &scratch);
if ( inner != NULL ) { if ( inner != NULL ) {
break; break;
} }
@@ -342,20 +342,20 @@ static akerr_ErrorContext *bench_framebuffer(void)
PASS(errctx, inner); PASS(errctx, inner);
bench_start("draw_paste_region, 64x64 upload", "call", 55000.0); bench_start("draw_paste_region, 64x64 upload", "call", 55000.0);
BENCH_LOOP(inner, i, count, akgl_draw_paste_region(renderer, saved, 64.0, 64.0)); BENCH_LOOP(inner, i, count, akgl_draw_paste_region(akgl_renderer, saved, 64.0, 64.0));
BENCH_FLUSH_STOP(errctx, count); BENCH_FLUSH_STOP(errctx, count);
PASS(errctx, inner); PASS(errctx, inner);
bench_start("draw_flood_fill, full 640x480 target", "call", 20000000.0); bench_start("draw_flood_fill, full 640x480 target", "call", 20000000.0);
for ( i = 0; i < fills; i++ ) { for ( i = 0; i < fills; i++ ) {
inner = akgl_draw_flood_fill(renderer, 320, 240, ( (i % 2) == 0 ) ? red : blue); inner = akgl_draw_flood_fill(akgl_renderer, 320, 240, ( (i % 2) == 0 ) ? red : blue);
if ( inner != NULL ) { if ( inner != NULL ) {
break; break;
} }
} }
BENCH_FLUSH_STOP(errctx, fills); BENCH_FLUSH_STOP(errctx, fills);
PASS(errctx, inner); PASS(errctx, inner);
PASS(errctx, renderer->frame_start(renderer)); PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
} }
SDL_DestroySurface(saved); SDL_DestroySurface(saved);
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
@@ -381,7 +381,7 @@ static akerr_ErrorContext *bench_text(void)
int i = 0; int i = 0;
int rep = 0; int rep = 0;
PASS(errctx, renderer->frame_start(renderer)); PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
bench_start("text_measure, 15 characters", "call", 400.0); bench_start("text_measure, 15 characters", "call", 400.0);
BENCH_LOOP(inner, i, count, akgl_text_measure(benchfont, BENCH_TEXT, &w, &h)); BENCH_LOOP(inner, i, count, akgl_text_measure(benchfont, BENCH_TEXT, &w, &h));
@@ -486,13 +486,13 @@ static akerr_ErrorContext *bench_asset_load(void)
// pointer it never set. Both defects are filed in TODO.md; this loop // pointer it never set. Both defects are filed in TODO.md; this loop
// works around the first so it never reaches the second. // works around the first so it never reaches the second.
for ( j = 0; j < AKGL_MAX_HEAP_STRING; j++ ) { for ( j = 0; j < AKGL_MAX_HEAP_STRING; j++ ) {
HEAP_STRING[j].refcount = 0; akgl_heap_strings[j].refcount = 0;
} }
inner = akgl_tilemap_load("assets/testmap.tmj", gamemap); inner = akgl_tilemap_load("assets/testmap.tmj", akgl_gamemap);
if ( inner != NULL ) { if ( inner != NULL ) {
break; break;
} }
inner = akgl_tilemap_release(gamemap); inner = akgl_tilemap_release(akgl_gamemap);
if ( inner != NULL ) { if ( inner != NULL ) {
break; break;
} }
@@ -523,7 +523,7 @@ static akerr_ErrorContext *bench_map_zero(void)
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
bench_start("zeroing one akgl_Tilemap", "call", 16000000.0); bench_start("zeroing one akgl_Tilemap", "call", 16000000.0);
for ( i = 0; i < count; i++ ) { for ( i = 0; i < count; i++ ) {
memset(gamemap, 0x00, sizeof(akgl_Tilemap)); memset(akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
} }
BENCH_FLUSH_STOP(errctx, count); BENCH_FLUSH_STOP(errctx, count);
} }
@@ -546,10 +546,10 @@ static akerr_ErrorContext *bench_tileset_offsets(void)
int i = 0; int i = 0;
int rep = 0; int rep = 0;
PASS(errctx, bench_build_map(gamemap, 1)); PASS(errctx, bench_build_map(akgl_gamemap, 1));
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
bench_start("tilemap_compute_tileset_offsets, 1728 tiles", "call", 23000.0); bench_start("tilemap_compute_tileset_offsets, 1728 tiles", "call", 23000.0);
BENCH_LOOP(inner, i, count, akgl_tilemap_compute_tileset_offsets(gamemap, 0)); BENCH_LOOP(inner, i, count, akgl_tilemap_compute_tileset_offsets(akgl_gamemap, 0));
BENCH_FLUSH_STOP(errctx, count); BENCH_FLUSH_STOP(errctx, count);
PASS(errctx, inner); PASS(errctx, inner);
} }
@@ -573,19 +573,19 @@ static akerr_ErrorContext *bench_tilemap_draw(void)
int i = 0; int i = 0;
int rep = 0; int rep = 0;
PASS(errctx, renderer->frame_start(renderer)); PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
PASS(errctx, bench_build_map(gamemap, 1)); PASS(errctx, bench_build_map(akgl_gamemap, 1));
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
bench_start("tilemap_draw, 40x30 tiles, 1 tileset", "frame", 170000000.0); bench_start("tilemap_draw, 40x30 tiles, 1 tileset", "frame", 170000000.0);
BENCH_LOOP(inner, i, count, akgl_tilemap_draw(gamemap, camera, 0)); BENCH_LOOP(inner, i, count, akgl_tilemap_draw(akgl_gamemap, akgl_camera, 0));
BENCH_FLUSH_STOP(errctx, count); BENCH_FLUSH_STOP(errctx, count);
PASS(errctx, inner); PASS(errctx, inner);
} }
PASS(errctx, bench_build_map(gamemap, BENCH_MAP_TILESETS)); PASS(errctx, bench_build_map(akgl_gamemap, BENCH_MAP_TILESETS));
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
bench_start("tilemap_draw, 40x30 tiles, 8 tilesets", "frame", 170000000.0); bench_start("tilemap_draw, 40x30 tiles, 8 tilesets", "frame", 170000000.0);
BENCH_LOOP(inner, i, count, akgl_tilemap_draw(gamemap, camera, 0)); BENCH_LOOP(inner, i, count, akgl_tilemap_draw(akgl_gamemap, akgl_camera, 0));
BENCH_FLUSH_STOP(errctx, count); BENCH_FLUSH_STOP(errctx, count);
PASS(errctx, inner); PASS(errctx, inner);
} }
@@ -627,7 +627,7 @@ static akerr_ErrorContext *bench_raw_blit_control(void)
columns = benchtiles->w / BENCH_TILE_SIZE; columns = benchtiles->w / BENCH_TILE_SIZE;
tilecount = columns * (benchtiles->h / BENCH_TILE_SIZE); tilecount = columns * (benchtiles->h / BENCH_TILE_SIZE);
PASS(errctx, renderer->frame_start(renderer)); PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
// One source tile, blitted 1200 times. The floor: 1200 SDL calls and // One source tile, blitted 1200 times. The floor: 1200 SDL calls and
// 1200 x 256 pixels of copying, with the source rectangle staying in // 1200 x 256 pixels of copying, with the source rectangle staying in
@@ -640,7 +640,7 @@ static akerr_ErrorContext *bench_raw_blit_control(void)
for ( x = 0; x < across; x++ ) { for ( x = 0; x < across; x++ ) {
dest.x = (float32_t)(x * BENCH_TILE_SIZE); dest.x = (float32_t)(x * BENCH_TILE_SIZE);
dest.y = (float32_t)(y * BENCH_TILE_SIZE); dest.y = (float32_t)(y * BENCH_TILE_SIZE);
SDL_RenderTexture(renderer->sdl_renderer, benchtiles, &src, &dest); SDL_RenderTexture(akgl_renderer->sdl_renderer, benchtiles, &src, &dest);
} }
} }
} }
@@ -662,7 +662,7 @@ static akerr_ErrorContext *bench_raw_blit_control(void)
src.y = (float32_t)((tile / columns) * BENCH_TILE_SIZE); src.y = (float32_t)((tile / columns) * BENCH_TILE_SIZE);
dest.x = (float32_t)(x * BENCH_TILE_SIZE); dest.x = (float32_t)(x * BENCH_TILE_SIZE);
dest.y = (float32_t)(y * BENCH_TILE_SIZE); dest.y = (float32_t)(y * BENCH_TILE_SIZE);
SDL_RenderTexture(renderer->sdl_renderer, benchtiles, &src, &dest); SDL_RenderTexture(akgl_renderer->sdl_renderer, benchtiles, &src, &dest);
} }
} }
} }
@@ -690,9 +690,9 @@ static akerr_ErrorContext *bench_scene(void)
int rep = 0; int rep = 0;
PASS(errctx, bench_populate_scene()); PASS(errctx, bench_populate_scene());
PASS(errctx, bench_build_map(gamemap, 1)); PASS(errctx, bench_build_map(akgl_gamemap, 1));
PASS(errctx, renderer->frame_start(renderer)); PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
actor = &HEAP_ACTOR[0]; actor = &akgl_heap_actors[0];
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
bench_start("actor_render, on camera", "actor", 31000.0); bench_start("actor_render, on camera", "actor", 31000.0);
@@ -701,7 +701,7 @@ static akerr_ErrorContext *bench_scene(void)
PASS(errctx, inner); PASS(errctx, inner);
bench_start("draw_world, 1200 tiles + 64 actors", "frame", 170000000.0); bench_start("draw_world, 1200 tiles + 64 actors", "frame", 170000000.0);
BENCH_LOOP(inner, i, frames, renderer->draw_world(renderer, NULL)); BENCH_LOOP(inner, i, frames, akgl_renderer->draw_world(akgl_renderer, NULL));
BENCH_FLUSH_STOP(errctx, frames); BENCH_FLUSH_STOP(errctx, frames);
PASS(errctx, inner); PASS(errctx, inner);
} }
@@ -727,8 +727,8 @@ static akerr_ErrorContext *bench_game_update(void)
int rep = 0; int rep = 0;
PASS(errctx, bench_populate_scene()); PASS(errctx, bench_populate_scene());
PASS(errctx, bench_build_map(gamemap, 1)); PASS(errctx, bench_build_map(akgl_gamemap, 1));
PASS(errctx, akgl_physics_init_null(physics)); PASS(errctx, akgl_physics_init_null(akgl_physics));
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) { for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
bench_start("game_update, full frame", "frame", 170000000.0); bench_start("game_update, full frame", "frame", 170000000.0);
@@ -750,10 +750,11 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
renderer = &_akgl_renderer; TEST_TRAP_UNHANDLED_ERRORS();
physics = &_akgl_physics; akgl_renderer = &akgl_default_renderer;
camera = &_akgl_camera; akgl_physics = &akgl_default_physics;
gamemap = &_akgl_gamemap; akgl_camera = &akgl_default_camera;
akgl_gamemap = &akgl_default_gamemap;
FAIL_ZERO_BREAK( FAIL_ZERO_BREAK(
errctx, errctx,
@@ -774,36 +775,36 @@ int main(void)
BENCH_SCREEN_W, BENCH_SCREEN_W,
BENCH_SCREEN_H, BENCH_SCREEN_H,
0, 0,
&window, &akgl_window,
&renderer->sdl_renderer), &akgl_renderer->sdl_renderer),
AKGL_ERR_SDL, AKGL_ERR_SDL,
"Couldn't create window/renderer: %s", "Couldn't create window/renderer: %s",
SDL_GetError()); SDL_GetError());
CATCH(errctx, akgl_render_bind2d(renderer)); CATCH(errctx, akgl_render_2d_bind(akgl_renderer));
camera->x = 0.0; akgl_camera->x = 0.0;
camera->y = 0.0; akgl_camera->y = 0.0;
camera->w = BENCH_SCREEN_W; akgl_camera->w = BENCH_SCREEN_W;
camera->h = BENCH_SCREEN_H; akgl_camera->h = BENCH_SCREEN_H;
// akgl_game_update takes this before it does anything else. Normally // akgl_game_update takes this before it does anything else. Normally
// akgl_game_init creates it; this suite brings the library up piece by // akgl_game_init creates it; this suite brings the library up piece by
// piece so it can stay headless, so it creates the mutex itself. // piece so it can stay headless, so it creates the mutex itself.
game.statelock = SDL_CreateMutex(); akgl_game.statelock = SDL_CreateMutex();
FAIL_ZERO_BREAK(errctx, game.statelock, AKGL_ERR_SDL, "%s", SDL_GetError()); FAIL_ZERO_BREAK(errctx, akgl_game.statelock, AKGL_ERR_SDL, "%s", SDL_GetError());
// And this one is not optional either. akgl_game_updateFPS calls // And this one is not optional either. akgl_game_update_fps calls
// game.lowfpsfunc through the pointer, unguarded, on every frame under // game.lowfpsfunc through the pointer, unguarded, on every frame under
// 30 fps -- which includes the first frame, before there is a frame rate // 30 fps -- which includes the first frame, before there is a frame rate
// to compare. A host that brings the library up the way renderer.h // to compare. A host that brings the library up the way renderer.h
// documents for an embedder, akgl_render_bind2d over its own window, // documents for an embedder, akgl_render_2d_bind over its own window,
// segfaults on its first akgl_game_update. Filed in TODO.md; installing // segfaults on its first akgl_game_update. Filed in TODO.md; installing
// the default by hand is the workaround. // the default by hand is the workaround.
game.lowfpsfunc = &akgl_game_lowfps; akgl_game.lowfpsfunc = &akgl_game_lowfps;
benchfont = TTF_OpenFont(BENCH_FONT_PATH, BENCH_FONT_SIZE); benchfont = TTF_OpenFont(BENCH_FONT_PATH, BENCH_FONT_SIZE);
FAIL_ZERO_BREAK(errctx, benchfont, AKGL_ERR_SDL, "Couldn't open %s: %s", BENCH_FONT_PATH, SDL_GetError()); FAIL_ZERO_BREAK(errctx, benchfont, AKGL_ERR_SDL, "Couldn't open %s: %s", BENCH_FONT_PATH, SDL_GetError());
benchtiles = IMG_LoadTexture(renderer->sdl_renderer, BENCH_TILESET_IMAGE); benchtiles = IMG_LoadTexture(akgl_renderer->sdl_renderer, BENCH_TILESET_IMAGE);
FAIL_ZERO_BREAK(errctx, benchtiles, AKGL_ERR_SDL, "Couldn't load %s: %s", BENCH_TILESET_IMAGE, SDL_GetError()); FAIL_ZERO_BREAK(errctx, benchtiles, AKGL_ERR_SDL, "Couldn't load %s: %s", BENCH_TILESET_IMAGE, SDL_GetError());
CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_heap_init());

View File

@@ -3,7 +3,7 @@
* @brief Unit tests for the physics backends and the simulation loop. * @brief Unit tests for the physics backends and the simulation loop.
* *
* None of these tests need a renderer or a window. The simulation loop walks * None of these tests need a renderer or a window. The simulation loop walks
* HEAP_ACTOR directly, so each test rebuilds the actor heap rather than relying * akgl_heap_actors directly, so each test rebuilds the actor heap rather than relying
* on state left behind by an earlier one. * on state left behind by an earlier one.
*/ */
@@ -723,6 +723,7 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_heap_init());
CATCH(errctx, akgl_registry_init()); CATCH(errctx, akgl_registry_init());
CATCH(errctx, akgl_registry_init_properties()); CATCH(errctx, akgl_registry_init_properties());

View File

@@ -163,6 +163,7 @@ int main(void)
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
CATCH(errctx, test_akgl_registry_init_creation_failures()); CATCH(errctx, test_akgl_registry_init_creation_failures());
CATCH(errctx, test_akgl_get_property_copies_only_the_value()); CATCH(errctx, test_akgl_get_property_copies_only_the_value());
} CLEANUP { } CLEANUP {

View File

@@ -2,13 +2,13 @@
* @file renderer.c * @file renderer.c
* @brief Unit tests for the 2D render backend's vtable and its entry points. * @brief Unit tests for the 2D render backend's vtable and its entry points.
* *
* None of this needs a display. akgl_render_bind2d() installs the backend's * None of this needs a display. akgl_render_2d_bind() installs the backend's
* methods and nothing else, so it can be checked against a backend that has no * methods and nothing else, so it can be checked against a backend that has no
* SDL_Renderer at all; the entry points that do draw are checked against a * SDL_Renderer at all; the entry points that do draw are checked against a
* software renderer under the dummy video driver, the same way tests/draw.c * software renderer under the dummy video driver, the same way tests/draw.c
* does it. * does it.
* *
* akgl_render_init2d() is not covered here: it creates a window from the * akgl_render_2d_init() is not covered here: it creates a window from the
* property registry and writes the `camera` global, which is what the offscreen * property registry and writes the `camera` global, which is what the offscreen
* harness described in TODO.md exists to make testable. * harness described in TODO.md exists to make testable.
*/ */
@@ -42,7 +42,7 @@ akerr_ErrorContext *test_render_bind2d(void)
memset(&backend, 0x00, sizeof(akgl_RenderBackend)); memset(&backend, 0x00, sizeof(akgl_RenderBackend));
backend.sdl_renderer = (SDL_Renderer *)&bound; backend.sdl_renderer = (SDL_Renderer *)&bound;
TEST_EXPECT_OK(errctx, akgl_render_bind2d(&backend), "binding the 2D backend"); TEST_EXPECT_OK(errctx, akgl_render_2d_bind(&backend), "binding the 2D backend");
TEST_ASSERT(errctx, backend.sdl_renderer == (SDL_Renderer *)&bound, TEST_ASSERT(errctx, backend.sdl_renderer == (SDL_Renderer *)&bound,
"binding replaced the caller's SDL_Renderer"); "binding replaced the caller's SDL_Renderer");
@@ -63,12 +63,12 @@ akerr_ErrorContext *test_render_bind2d(void)
// are separable in both directions -- and leaves it NULL rather than // are separable in both directions -- and leaves it NULL rather than
// inventing one. // inventing one.
memset(&backend, 0x00, sizeof(akgl_RenderBackend)); memset(&backend, 0x00, sizeof(akgl_RenderBackend));
TEST_EXPECT_OK(errctx, akgl_render_bind2d(&backend), TEST_EXPECT_OK(errctx, akgl_render_2d_bind(&backend),
"binding a backend with no SDL renderer"); "binding a backend with no SDL renderer");
TEST_ASSERT(errctx, backend.sdl_renderer == NULL, TEST_ASSERT(errctx, backend.sdl_renderer == NULL,
"binding invented an SDL_Renderer"); "binding invented an SDL_Renderer");
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_render_bind2d(NULL), TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_render_2d_bind(NULL),
"binding a NULL backend"); "binding a NULL backend");
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {
@@ -85,7 +85,7 @@ akerr_ErrorContext *test_render_backend_without_a_renderer(void)
// Bound but never given an SDL_Renderer. Every entry point that draws // Bound but never given an SDL_Renderer. Every entry point that draws
// has to report that rather than dereference it. // has to report that rather than dereference it.
memset(&empty, 0x00, sizeof(akgl_RenderBackend)); memset(&empty, 0x00, sizeof(akgl_RenderBackend));
CATCH(errctx, akgl_render_bind2d(&empty)); CATCH(errctx, akgl_render_2d_bind(&empty));
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, empty.frame_start(&empty), TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, empty.frame_start(&empty),
"starting a frame on a backend with no renderer"); "starting a frame on a backend with no renderer");
@@ -187,6 +187,7 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
memset(&bound, 0x00, sizeof(akgl_RenderBackend)); memset(&bound, 0x00, sizeof(akgl_RenderBackend));
FAIL_ZERO_BREAK( FAIL_ZERO_BREAK(
@@ -202,14 +203,14 @@ int main(void)
TEST_TARGET_SIZE, TEST_TARGET_SIZE,
TEST_TARGET_SIZE, TEST_TARGET_SIZE,
0, 0,
&window, &akgl_window,
&bound.sdl_renderer), &bound.sdl_renderer),
AKGL_ERR_SDL, AKGL_ERR_SDL,
"Couldn't create window/renderer: %s", "Couldn't create window/renderer: %s",
SDL_GetError()); SDL_GetError());
// The host owns the window; libakgl only ever binds to it. This is the // The host owns the window; libakgl only ever binds to it. This is the
// arrangement akgl_render_bind2d exists for. // arrangement akgl_render_2d_bind exists for.
CATCH(errctx, akgl_render_bind2d(&bound)); CATCH(errctx, akgl_render_2d_bind(&bound));
CATCH(errctx, test_render_bind2d()); CATCH(errctx, test_render_bind2d());
CATCH(errctx, test_render_backend_without_a_renderer()); CATCH(errctx, test_render_backend_without_a_renderer());

View File

@@ -13,6 +13,8 @@
#include <akgl/game.h> #include <akgl/game.h>
#include <akgl/renderer.h> #include <akgl/renderer.h>
#include "testutil.h"
akerr_ErrorContext *test_akgl_spritesheet_initialize(void) akerr_ErrorContext *test_akgl_spritesheet_initialize(void)
{ {
@@ -37,7 +39,7 @@ akerr_ErrorContext *test_akgl_spritesheet_initialize(void)
"Loaded texture was not the correct size"); "Loaded texture was not the correct size");
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png"); snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png");
image = IMG_LoadTexture(renderer->sdl_renderer, (char *)&tmpstr->data); image = IMG_LoadTexture(akgl_renderer->sdl_renderer, (char *)&tmpstr->data);
FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image"); FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image");
CATCH( CATCH(
@@ -138,7 +140,7 @@ akerr_ErrorContext *test_akgl_sprite_load_json(void)
// Is it using the right spritesheet? // Is it using the right spritesheet?
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png"); snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png");
image = IMG_LoadTexture(renderer->sdl_renderer, (char *)&tmpstr->data); image = IMG_LoadTexture(akgl_renderer->sdl_renderer, (char *)&tmpstr->data);
FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image"); FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image");
CATCH( CATCH(
@@ -188,7 +190,8 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
renderer = &_akgl_renderer; TEST_TRAP_UNHANDLED_ERRORS();
akgl_renderer = &akgl_default_renderer;
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest"); SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
@@ -196,10 +199,10 @@ int main(void)
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError()); FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
} }
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_sprite", 640, 480, 0, &window, &renderer->sdl_renderer)) { if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_sprite", 640, 480, 0, &akgl_window, &akgl_renderer->sdl_renderer)) {
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError()); FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
} }
renderer->draw_texture = &akgl_render_2d_draw_texture; akgl_renderer->draw_texture = &akgl_render_2d_draw_texture;
CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_heap_init());
CATCH(errctx, akgl_registry_init_sprite()); CATCH(errctx, akgl_registry_init_sprite());

View File

@@ -4,6 +4,8 @@
#include <akgl/heap.h> #include <akgl/heap.h>
#include <akgl/staticstring.h> #include <akgl/staticstring.h>
#include "testutil.h"
void reset_string_heap(void); void reset_string_heap(void);
akerr_ErrorContext *test_fresh_heap_gives_strings(void) akerr_ErrorContext *test_fresh_heap_gives_strings(void)
@@ -28,7 +30,7 @@ akerr_ErrorContext *test_string_heap_error_when_no_strings_left(void)
akgl_String *ptr; akgl_String *ptr;
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
for ( int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) { for ( int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
HEAP_STRING[i].refcount = 1; akgl_heap_strings[i].refcount = 1;
} }
for ( int i = 0; i < AKGL_MAX_HEAP_STRING - 1; i++ ) { for ( int i = 0; i < AKGL_MAX_HEAP_STRING - 1; i++ ) {
ATTEMPT { ATTEMPT {
@@ -45,8 +47,8 @@ akerr_ErrorContext *test_string_heap_error_when_no_strings_left(void)
akerr_ErrorContext *test_string_heap_honors_refcount(void) akerr_ErrorContext *test_string_heap_honors_refcount(void)
{ {
akgl_String *firstptr = &HEAP_STRING[0]; akgl_String *firstptr = &akgl_heap_strings[0];
akgl_String *secondptr = &HEAP_STRING[1]; akgl_String *secondptr = &akgl_heap_strings[1];
akgl_String *testptr = NULL; akgl_String *testptr = NULL;
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
ATTEMPT { ATTEMPT {
@@ -55,7 +57,7 @@ akerr_ErrorContext *test_string_heap_honors_refcount(void)
FAIL_RETURN( FAIL_RETURN(
errctx, errctx,
AKERR_VALUE, AKERR_VALUE,
"Expected testptr to equal (HEAP_STRING[0] = %p) but got %p", "Expected testptr to equal (akgl_heap_strings[0] = %p) but got %p",
firstptr, firstptr,
testptr testptr
); );
@@ -68,7 +70,7 @@ akerr_ErrorContext *test_string_heap_honors_refcount(void)
FAIL_RETURN( FAIL_RETURN(
errctx, errctx,
AKERR_VALUE, AKERR_VALUE,
"Expected testptr to equal (HEAP_STRING[1] = %p) but got %p", "Expected testptr to equal (akgl_heap_strings[1] = %p) but got %p",
secondptr, secondptr,
testptr testptr
); );
@@ -124,7 +126,7 @@ akerr_ErrorContext *test_akgl_string_initialize(void)
void reset_string_heap(void) void reset_string_heap(void)
{ {
for ( int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) { for ( int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
memset(&HEAP_STRING[i], 0x00, sizeof(akgl_String)); memset(&akgl_heap_strings[i], 0x00, sizeof(akgl_String));
} }
} }
@@ -134,6 +136,7 @@ int main(void)
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
printf("test_fresh_heap_gives_string ....\n"); printf("test_fresh_heap_gives_string ....\n");
test_fresh_heap_gives_strings(); test_fresh_heap_gives_strings();
reset_string_heap(); reset_string_heap();

View File

@@ -16,10 +16,45 @@
#define _AKGL_TESTUTIL_H_ #define _AKGL_TESTUTIL_H_
#include <math.h> #include <math.h>
#include <stdlib.h>
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <akerror.h> #include <akerror.h>
#include <akgl/error.h> #include <akgl/error.h>
/**
* @brief Exit with a status the shell can actually see.
*
* libakerror's default unhandled-error handler ends in `exit(errctx->status)`.
* `exit` keeps only the low byte of what it is given, and libakgl's own
* statuses start at `AKERR_FIRST_CONSUMER_STATUS`, which is 256 -- so
* #AKGL_ERR_SDL exits 0, and CTest records a pass. Every suite in this
* directory that failed for the most common reason in this library therefore
* reported success. `tests/character.c` did exactly that: it aborted at its
* second of four tests on a bad renderer and was green for months.
*
* This collapses any status that a byte cannot carry onto 1. It does not log --
* `FINISH_NORETURN` has already logged the context and its stack by the time it
* calls the handler.
*/
static inline void test_handler_unhandled_error(akerr_ErrorContext *errctx)
{
int status = ( errctx == NULL ) ? 1 : (errctx->status & 0xFF);
if ( status == 0 ) {
status = 1;
}
exit(status);
}
/**
* @brief Install test_handler_unhandled_error(). Call once, after akgl_error_init().
*
* akgl_error_init() reaches akerr_init(), which installs libakerror's default
* handler, so this has to come after it rather than before.
*/
#define TEST_TRAP_UNHANDLED_ERRORS() \
(akerr_handler_unhandled_error = &test_handler_unhandled_error)
/** @brief Fail the enclosing ATTEMPT block unless @p cond holds. */ /** @brief Fail the enclosing ATTEMPT block unless @p cond holds. */
#define TEST_ASSERT(e, cond, ...) \ #define TEST_ASSERT(e, cond, ...) \
if ( ! (cond) ) { \ if ( ! (cond) ) { \

View File

@@ -256,7 +256,7 @@ akerr_ErrorContext *test_text_render_backend_guards(void)
ATTEMPT { ATTEMPT {
// Nothing initialized the renderer at all, which is where the global // Nothing initialized the renderer at all, which is where the global
// starts and where a host that has not called akgl_game_init leaves it. // starts and where a host that has not called akgl_game_init leaves it.
renderer = NULL; akgl_renderer = NULL;
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
akgl_text_rendertextat(testfont, "hello", white, 0, 0, 0), akgl_text_rendertextat(testfont, "hello", white, 0, 0, 0),
"drawing text with no renderer backend"); "drawing text with no renderer backend");
@@ -264,13 +264,13 @@ akerr_ErrorContext *test_text_render_backend_guards(void)
// A backend that exists but was never given an SDL_Renderer -- the // A backend that exists but was never given an SDL_Renderer -- the
// state a host is in between allocating one and initializing it. // state a host is in between allocating one and initializing it.
memset(&backend, 0x00, sizeof(akgl_RenderBackend)); memset(&backend, 0x00, sizeof(akgl_RenderBackend));
renderer = &backend; akgl_renderer = &backend;
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
akgl_text_rendertextat(testfont, "hello", white, 0, 0, 0), akgl_text_rendertextat(testfont, "hello", white, 0, 0, 0),
"drawing text through a backend with no SDL renderer"); "drawing text through a backend with no SDL renderer");
// A live SDL_Renderer but no methods: a host that created its own // A live SDL_Renderer but no methods: a host that created its own
// renderer and has not called akgl_render_bind2d() yet. This is the one // renderer and has not called akgl_render_2d_bind() yet. This is the one
// that used to be a segfault rather than an error -- with a real // that used to be a segfault rather than an error -- with a real
// renderer behind it the call gets all the way to a NULL function // renderer behind it the call gets all the way to a NULL function
// pointer, which is why the check has to be here and not left to SDL. // pointer, which is why the check has to be here and not left to SDL.
@@ -279,7 +279,7 @@ akerr_ErrorContext *test_text_render_backend_guards(void)
akgl_text_rendertextat(testfont, "hello", white, 0, 0, 0), akgl_text_rendertextat(testfont, "hello", white, 0, 0, 0),
"drawing text through a backend that was never bound"); "drawing text through a backend that was never bound");
renderer = &testbackend; akgl_renderer = &testbackend;
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
akgl_text_rendertextat(NULL, "hello", white, 0, 0, 0), akgl_text_rendertextat(NULL, "hello", white, 0, 0, 0),
"drawing text with a NULL font"); "drawing text with a NULL font");
@@ -287,7 +287,7 @@ akerr_ErrorContext *test_text_render_backend_guards(void)
akgl_text_rendertextat(testfont, NULL, white, 0, 0, 0), akgl_text_rendertextat(testfont, NULL, white, 0, 0, 0),
"drawing a NULL string"); "drawing a NULL string");
} CLEANUP { } CLEANUP {
renderer = &testbackend; akgl_renderer = &testbackend;
} PROCESS(errctx) { } PROCESS(errctx) {
} FINISH(errctx, true); } FINISH(errctx, true);
SUCCEED_RETURN(errctx); SUCCEED_RETURN(errctx);
@@ -302,8 +302,8 @@ akerr_ErrorContext *test_text_rendertextat(void)
// A bound backend over a software renderer is enough to draw through: // A bound backend over a software renderer is enough to draw through:
// no display, no window shown, and the whole path from rasterizing to // no display, no window shown, and the whole path from rasterizing to
// blitting runs. // blitting runs.
renderer = &testbackend; akgl_renderer = &testbackend;
TEST_EXPECT_OK(errctx, renderer->frame_start(renderer), "starting a frame"); TEST_EXPECT_OK(errctx, akgl_renderer->frame_start(akgl_renderer), "starting a frame");
TEST_EXPECT_OK(errctx, akgl_text_rendertextat(testfont, "hello", white, 0, 4, 4), TEST_EXPECT_OK(errctx, akgl_text_rendertextat(testfont, "hello", white, 0, 4, 4),
"drawing a line of text"); "drawing a line of text");
// Wrapping takes the other rasterizing path. // Wrapping takes the other rasterizing path.
@@ -314,7 +314,7 @@ akerr_ErrorContext *test_text_rendertextat(void)
// has zero width" on both paths, so drawing an empty line reports a // has zero width" on both paths, so drawing an empty line reports a
// failure rather than drawing nothing. That is filed rather than pinned // failure rather than drawing nothing. That is filed rather than pinned
// -- see TODO.md, "Known and still open". // -- see TODO.md, "Known and still open".
TEST_EXPECT_OK(errctx, renderer->frame_end(renderer), "ending the frame"); TEST_EXPECT_OK(errctx, akgl_renderer->frame_end(akgl_renderer), "ending the frame");
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {
} FINISH(errctx, true); } FINISH(errctx, true);
@@ -331,6 +331,7 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
memset(&testbackend, 0x00, sizeof(akgl_RenderBackend)); memset(&testbackend, 0x00, sizeof(akgl_RenderBackend));
FAIL_ZERO_BREAK( FAIL_ZERO_BREAK(
errctx, errctx,
@@ -355,7 +356,7 @@ int main(void)
SDL_GetError()); SDL_GetError());
// The host owns the window and libakgl binds to it, which is the // The host owns the window and libakgl binds to it, which is the
// arrangement akgl_render_bind2d exists for and the one an embedded // arrangement akgl_render_2d_bind exists for and the one an embedded
// interpreter drawing text is in. // interpreter drawing text is in.
FAIL_ZERO_BREAK( FAIL_ZERO_BREAK(
errctx, errctx,
@@ -364,12 +365,12 @@ int main(void)
TEST_TARGET_SIZE, TEST_TARGET_SIZE,
TEST_TARGET_SIZE, TEST_TARGET_SIZE,
0, 0,
&window, &akgl_window,
&testbackend.sdl_renderer), &testbackend.sdl_renderer),
AKGL_ERR_SDL, AKGL_ERR_SDL,
"Couldn't create window/renderer: %s", "Couldn't create window/renderer: %s",
SDL_GetError()); SDL_GetError());
CATCH(errctx, akgl_render_bind2d(&testbackend)); CATCH(errctx, akgl_render_2d_bind(&testbackend));
CATCH(errctx, test_text_loadfont()); CATCH(errctx, test_text_loadfont());
CATCH(errctx, test_text_measure()); CATCH(errctx, test_text_measure());

View File

@@ -11,6 +11,8 @@
#include <akgl/game.h> #include <akgl/game.h>
#include <akgl/json_helpers.h> #include <akgl/json_helpers.h>
#include "testutil.h"
akerr_ErrorContext *test_tilemap_akgl_get_json_tilemap_property(void) akerr_ErrorContext *test_tilemap_akgl_get_json_tilemap_property(void)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
@@ -20,8 +22,8 @@ akerr_ErrorContext *test_tilemap_akgl_get_json_tilemap_property(void)
int propnum; int propnum;
ATTEMPT { ATTEMPT {
gamemap = &_akgl_gamemap; akgl_gamemap = &akgl_default_gamemap;
renderer = &_akgl_renderer; akgl_renderer = &akgl_default_renderer;
CATCH(errctx, akgl_heap_next_string(&tmpstr)); CATCH(errctx, akgl_heap_next_string(&tmpstr));
snprintf( snprintf(
(char *)&tmpstr->data, (char *)&tmpstr->data,
@@ -87,34 +89,34 @@ akerr_ErrorContext *test_akgl_tilemap_compute_tileset_offsets(void)
10 // Tile 2 Y 10 // Tile 2 Y
}; };
int *comparison_ptrs[8] = { int *comparison_ptrs[8] = {
&gamemap->tilesets[0].tile_offsets[0][0], // Tile 0 X &akgl_gamemap->tilesets[0].tile_offsets[0][0], // Tile 0 X
&gamemap->tilesets[0].tile_offsets[0][1], // Tile 0 Y &akgl_gamemap->tilesets[0].tile_offsets[0][1], // Tile 0 Y
&gamemap->tilesets[0].tile_offsets[1][0], // Tile 1 X &akgl_gamemap->tilesets[0].tile_offsets[1][0], // Tile 1 X
&gamemap->tilesets[0].tile_offsets[1][1], // Tile 0 Y &akgl_gamemap->tilesets[0].tile_offsets[1][1], // Tile 0 Y
&gamemap->tilesets[0].tile_offsets[2][0], // Tile 2 X &akgl_gamemap->tilesets[0].tile_offsets[2][0], // Tile 2 X
&gamemap->tilesets[0].tile_offsets[2][1], // Tile 2 Y &akgl_gamemap->tilesets[0].tile_offsets[2][1], // Tile 2 Y
&gamemap->tilesets[0].tile_offsets[3][0], // Tile 3 X &akgl_gamemap->tilesets[0].tile_offsets[3][0], // Tile 3 X
&gamemap->tilesets[0].tile_offsets[3][1], // Tile 3 Y &akgl_gamemap->tilesets[0].tile_offsets[3][1], // Tile 3 Y
}; };
int i = 0; int i = 0;
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap)); memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
gamemap->tilesets[0].tilecount = 4; akgl_gamemap->tilesets[0].tilecount = 4;
gamemap->tilesets[0].columns = 2; akgl_gamemap->tilesets[0].columns = 2;
gamemap->tilesets[0].firstgid = 1; akgl_gamemap->tilesets[0].firstgid = 1;
gamemap->tilesets[0].imageheight = 20; akgl_gamemap->tilesets[0].imageheight = 20;
gamemap->tilesets[0].imagewidth = 20; akgl_gamemap->tilesets[0].imagewidth = 20;
gamemap->tilesets[0].tilecount = 4; akgl_gamemap->tilesets[0].tilecount = 4;
gamemap->tilesets[0].tileheight = 10; akgl_gamemap->tilesets[0].tileheight = 10;
gamemap->tilesets[0].tilewidth = 10; akgl_gamemap->tilesets[0].tilewidth = 10;
gamemap->tilesets[0].spacing = 0; akgl_gamemap->tilesets[0].spacing = 0;
gamemap->tilesets[0].margin = 0; akgl_gamemap->tilesets[0].margin = 0;
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
ATTEMPT { ATTEMPT {
gamemap = &_akgl_gamemap; akgl_gamemap = &akgl_default_gamemap;
renderer = &_akgl_renderer; akgl_renderer = &akgl_default_renderer;
CATCH(errctx, akgl_tilemap_compute_tileset_offsets(gamemap, 0)); CATCH(errctx, akgl_tilemap_compute_tileset_offsets(akgl_gamemap, 0));
// Tile 0 X offset // Tile 0 X offset
for ( i = 0; i < 8; i++ ) { for ( i = 0; i < 8; i++ ) {
FAIL_NONZERO_BREAK( FAIL_NONZERO_BREAK(
@@ -143,10 +145,10 @@ akerr_ErrorContext *test_akgl_tilemap_load_layer_objects(void)
json_error_t errdata; json_error_t errdata;
akgl_Actor *testactor; akgl_Actor *testactor;
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap)); memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
ATTEMPT { ATTEMPT {
gamemap = &_akgl_gamemap; akgl_gamemap = &akgl_default_gamemap;
renderer = &_akgl_renderer; akgl_renderer = &akgl_default_renderer;
CATCH(errctx, akgl_heap_next_string(&pathstr)); CATCH(errctx, akgl_heap_next_string(&pathstr));
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj"); snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
doc = json_load_file((char *)&pathstr->data, 0, &errdata); doc = json_load_file((char *)&pathstr->data, 0, &errdata);
@@ -154,7 +156,7 @@ akerr_ErrorContext *test_akgl_tilemap_load_layer_objects(void)
FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text); FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text);
CATCH(errctx, akgl_get_json_array_value(doc, "layers", &layers)); CATCH(errctx, akgl_get_json_array_value(doc, "layers", &layers));
CATCH(errctx, akgl_get_json_array_index_object(layers, 1, &objectlayer)); CATCH(errctx, akgl_get_json_array_index_object(layers, 1, &objectlayer));
CATCH(errctx, akgl_tilemap_load_layer_objects(gamemap, objectlayer, 1, pathstr)); CATCH(errctx, akgl_tilemap_load_layer_objects(akgl_gamemap, objectlayer, 1, pathstr));
testactor = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "testactor", NULL); testactor = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "testactor", NULL);
FAIL_ZERO_BREAK( FAIL_ZERO_BREAK(
@@ -193,11 +195,11 @@ akerr_ErrorContext *test_akgl_tilemap_load_layer_tile(void)
json_t *tiledata = NULL; json_t *tiledata = NULL;
json_error_t errdata; json_error_t errdata;
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap)); memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
ATTEMPT { ATTEMPT {
gamemap = &_akgl_gamemap; akgl_gamemap = &akgl_default_gamemap;
renderer = &_akgl_renderer; akgl_renderer = &akgl_default_renderer;
CATCH(errctx, akgl_heap_next_string(&pathstr)); CATCH(errctx, akgl_heap_next_string(&pathstr));
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj"); snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
doc = json_load_file((char *)&pathstr->data, 0, &errdata); doc = json_load_file((char *)&pathstr->data, 0, &errdata);
@@ -206,11 +208,11 @@ akerr_ErrorContext *test_akgl_tilemap_load_layer_tile(void)
CATCH(errctx, akgl_get_json_array_value(doc, "layers", &layers)); CATCH(errctx, akgl_get_json_array_value(doc, "layers", &layers));
CATCH(errctx, akgl_get_json_array_index_object(layers, 0, &tilelayer)); CATCH(errctx, akgl_get_json_array_index_object(layers, 0, &tilelayer));
CATCH(errctx, akgl_get_json_array_value(tilelayer, "data", &tiledata)); CATCH(errctx, akgl_get_json_array_value(tilelayer, "data", &tiledata));
CATCH(errctx, akgl_tilemap_load_layer_tile(gamemap, tilelayer, 0, pathstr)); CATCH(errctx, akgl_tilemap_load_layer_tile(akgl_gamemap, tilelayer, 0, pathstr));
if ( (gamemap->layers[0].data[0] != 1) || if ( (akgl_gamemap->layers[0].data[0] != 1) ||
(gamemap->layers[0].data[1] != 2) || (akgl_gamemap->layers[0].data[1] != 2) ||
(gamemap->layers[0].data[2] != 3) || (akgl_gamemap->layers[0].data[2] != 3) ||
(gamemap->layers[0].data[3] != 4) ) { (akgl_gamemap->layers[0].data[3] != 4) ) {
FAIL_BREAK(errctx, AKERR_VALUE, "Test tilemap layer 0 tiles loaded with incorrect values (check gdb)"); FAIL_BREAK(errctx, AKERR_VALUE, "Test tilemap layer 0 tiles loaded with incorrect values (check gdb)");
} }
} CLEANUP { } CLEANUP {
@@ -233,61 +235,61 @@ akerr_ErrorContext *test_akgl_tilemap_load_layers(void)
json_error_t errdata; json_error_t errdata;
int i = 0; int i = 0;
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap)); memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
ATTEMPT { ATTEMPT {
gamemap = &_akgl_gamemap; akgl_gamemap = &akgl_default_gamemap;
renderer = &_akgl_renderer; akgl_renderer = &akgl_default_renderer;
CATCH(errctx, akgl_heap_next_string(&pathstr)); CATCH(errctx, akgl_heap_next_string(&pathstr));
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj"); snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
doc = json_load_file((char *)&pathstr->data, 0, &errdata); doc = json_load_file((char *)&pathstr->data, 0, &errdata);
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets"); snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets");
FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text); FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text);
CATCH(errctx, akgl_tilemap_load_layers(gamemap, doc, pathstr)); CATCH(errctx, akgl_tilemap_load_layers(akgl_gamemap, doc, pathstr));
FAIL_NONZERO_BREAK( FAIL_NONZERO_BREAK(
errctx, errctx,
(gamemap->numlayers != 3), (akgl_gamemap->numlayers != 3),
AKERR_VALUE, AKERR_VALUE,
"Map layer count incorrect" "Map layer count incorrect"
); );
for ( i = 0; i < gamemap->numlayers; i++ ) { for ( i = 0; i < akgl_gamemap->numlayers; i++ ) {
if ( (gamemap->layers[i].opacity != 1) || if ( (akgl_gamemap->layers[i].opacity != 1) ||
(gamemap->layers[i].visible != true) || (akgl_gamemap->layers[i].visible != true) ||
(gamemap->layers[i].id != (i + 1)) || (akgl_gamemap->layers[i].id != (i + 1)) ||
(gamemap->layers[i].x != 0) || (akgl_gamemap->layers[i].x != 0) ||
(gamemap->layers[i].y != 0) ) { (akgl_gamemap->layers[i].y != 0) ) {
FAIL(errctx, AKERR_VALUE, "Map layer data loaded incorrectly (see gdb)"); FAIL(errctx, AKERR_VALUE, "Map layer data loaded incorrectly (see gdb)");
goto _test_akgl_tilemap_load_layers_cleanup; goto _test_akgl_tilemap_load_layers_cleanup;
} }
} }
// Layer 2 should have 1 object loaded // Layer 2 should have 1 object loaded
if ( (gamemap->layers[1].objects[0].actorptr != SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "testactor", NULL)) || if ( (akgl_gamemap->layers[1].objects[0].actorptr != SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "testactor", NULL)) ||
(gamemap->layers[1].objects[1].name[0] != '\0' ) || (akgl_gamemap->layers[1].objects[1].name[0] != '\0' ) ||
(gamemap->layers[1].objects[1].id != 0) ) { (akgl_gamemap->layers[1].objects[1].id != 0) ) {
FAIL_BREAK(errctx, AKERR_VALUE, "Map layer 2 should have 1 loaded object (testactor) and nothing else (see gdb)"); FAIL_BREAK(errctx, AKERR_VALUE, "Map layer 2 should have 1 loaded object (testactor) and nothing else (see gdb)");
} }
// Layer 1 and 3 should have no objects // Layer 1 and 3 should have no objects
for ( i = 0; i < AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER ; i++ ) { for ( i = 0; i < AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER ; i++ ) {
if ( gamemap->layers[0].objects[i].id != 0 ) { if ( akgl_gamemap->layers[0].objects[i].id != 0 ) {
FAIL(errctx, AKERR_VALUE, "Map layers 1 and 3 should have no objects loaded but found objects"); FAIL(errctx, AKERR_VALUE, "Map layers 1 and 3 should have no objects loaded but found objects");
goto _test_akgl_tilemap_load_layers_cleanup; goto _test_akgl_tilemap_load_layers_cleanup;
} }
} }
for ( i = 0; i < AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER ; i++ ) { for ( i = 0; i < AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER ; i++ ) {
if ( gamemap->layers[2].objects[i].id != 0 ) { if ( akgl_gamemap->layers[2].objects[i].id != 0 ) {
FAIL(errctx, AKERR_VALUE, "Map layers 1 and 3 should have no objects loaded but found objects"); FAIL(errctx, AKERR_VALUE, "Map layers 1 and 3 should have no objects loaded but found objects");
goto _test_akgl_tilemap_load_layers_cleanup; goto _test_akgl_tilemap_load_layers_cleanup;
} }
} }
// Layers 1 and 3 should have tile data // Layers 1 and 3 should have tile data
if ( (gamemap->layers[0].data[0] != 1) || if ( (akgl_gamemap->layers[0].data[0] != 1) ||
(gamemap->layers[0].data[1] != 2) || (akgl_gamemap->layers[0].data[1] != 2) ||
(gamemap->layers[0].data[2] != 3) || (akgl_gamemap->layers[0].data[2] != 3) ||
(gamemap->layers[0].data[3] != 4) || (akgl_gamemap->layers[0].data[3] != 4) ||
(gamemap->layers[2].data[0] != 0) || (akgl_gamemap->layers[2].data[0] != 0) ||
(gamemap->layers[2].data[1] != 5) || (akgl_gamemap->layers[2].data[1] != 5) ||
(gamemap->layers[2].data[2] != 0) || (akgl_gamemap->layers[2].data[2] != 0) ||
(gamemap->layers[2].data[3] != 6) (akgl_gamemap->layers[2].data[3] != 6)
) { ) {
FAIL_BREAK(errctx, AKERR_VALUE, "Map layers 1 and 3 should have tile data but it is incorrect"); FAIL_BREAK(errctx, AKERR_VALUE, "Map layers 1 and 3 should have tile data but it is incorrect");
} }
@@ -312,43 +314,43 @@ akerr_ErrorContext *test_akgl_tilemap_load_tilesets(void)
json_error_t errdata; json_error_t errdata;
SDL_Texture *image = NULL; SDL_Texture *image = NULL;
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap)); memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
ATTEMPT { ATTEMPT {
gamemap = &_akgl_gamemap; akgl_gamemap = &akgl_default_gamemap;
renderer = &_akgl_renderer; akgl_renderer = &akgl_default_renderer;
CATCH(errctx, akgl_heap_next_string(&pathstr)); CATCH(errctx, akgl_heap_next_string(&pathstr));
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj"); snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
doc = json_load_file((char *)&pathstr->data, 0, &errdata); doc = json_load_file((char *)&pathstr->data, 0, &errdata);
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets"); snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets");
FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text); FAIL_ZERO_BREAK(errctx, doc, AKERR_NULLPOINTER, "Failed to load testmap: %s", (char *)&errdata.text);
CATCH(errctx, akgl_tilemap_load_tilesets(gamemap, doc, pathstr)); CATCH(errctx, akgl_tilemap_load_tilesets(akgl_gamemap, doc, pathstr));
FAIL_NONZERO_BREAK(errctx, (gamemap->numtilesets != 1), AKERR_VALUE, "Incorrect number of tilesets loaded for map"); FAIL_NONZERO_BREAK(errctx, (akgl_gamemap->numtilesets != 1), AKERR_VALUE, "Incorrect number of tilesets loaded for map");
if ( (gamemap->tilesets[0].columns != 48 ) || if ( (akgl_gamemap->tilesets[0].columns != 48 ) ||
(gamemap->tilesets[0].firstgid != 1) || (akgl_gamemap->tilesets[0].firstgid != 1) ||
(gamemap->tilesets[0].imageheight != 576) || (akgl_gamemap->tilesets[0].imageheight != 576) ||
(gamemap->tilesets[0].imagewidth != 768) || (akgl_gamemap->tilesets[0].imagewidth != 768) ||
(gamemap->tilesets[0].margin != 0) || (akgl_gamemap->tilesets[0].margin != 0) ||
(gamemap->tilesets[0].spacing != 0) || (akgl_gamemap->tilesets[0].spacing != 0) ||
(gamemap->tilesets[0].tilecount != 1728) || (akgl_gamemap->tilesets[0].tilecount != 1728) ||
(gamemap->tilesets[0].tileheight != 16) || (akgl_gamemap->tilesets[0].tileheight != 16) ||
(gamemap->tilesets[0].tilewidth != 16) ) { (akgl_gamemap->tilesets[0].tilewidth != 16) ) {
FAIL_BREAK(errctx, AKERR_VALUE, "Tileset loaded with incorrect values"); FAIL_BREAK(errctx, AKERR_VALUE, "Tileset loaded with incorrect values");
} }
FAIL_NONZERO_BREAK( FAIL_NONZERO_BREAK(
errctx, errctx,
strcmp((char *)&gamemap->tilesets[0].name, "World_A1"), strcmp((char *)&akgl_gamemap->tilesets[0].name, "World_A1"),
AKERR_VALUE, AKERR_VALUE,
"Tileset loaded with incorrect name"); "Tileset loaded with incorrect name");
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/World_A1.png"); snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/World_A1.png");
image = IMG_LoadTexture(renderer->sdl_renderer, (char *)&pathstr->data); image = IMG_LoadTexture(akgl_renderer->sdl_renderer, (char *)&pathstr->data);
FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image"); FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image");
CATCH( CATCH(
errctx, errctx,
akgl_render_and_compare( akgl_render_and_compare(
gamemap->tilesets[0].texture, akgl_gamemap->tilesets[0].texture,
image, image,
0, 0, 768, 576, 0, 0, 768, 576,
"test_akgl_tilemap_loaded_tileset.png") "test_akgl_tilemap_loaded_tileset.png")
@@ -370,12 +372,12 @@ akerr_ErrorContext *test_akgl_tilemap_load(void)
{ {
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap)); memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
ATTEMPT { ATTEMPT {
gamemap = &_akgl_gamemap; akgl_gamemap = &akgl_default_gamemap;
renderer = &_akgl_renderer; akgl_renderer = &akgl_default_renderer;
CATCH(errctx, akgl_tilemap_load("assets/testmap.tmj", gamemap)); CATCH(errctx, akgl_tilemap_load("assets/testmap.tmj", akgl_gamemap));
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {
} FINISH(errctx, true); } FINISH(errctx, true);
@@ -387,8 +389,8 @@ akerr_ErrorContext *test_akgl_tilemap_draw(void)
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
ATTEMPT { ATTEMPT {
gamemap = &_akgl_gamemap; akgl_gamemap = &akgl_default_gamemap;
renderer = &_akgl_renderer; akgl_renderer = &akgl_default_renderer;
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {
} FINISH(errctx, true); } FINISH(errctx, true);
@@ -401,8 +403,8 @@ akerr_ErrorContext *test_akgl_tilemap_draw_tileset(void)
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
ATTEMPT { ATTEMPT {
gamemap = &_akgl_gamemap; akgl_gamemap = &akgl_default_gamemap;
renderer = &_akgl_renderer; akgl_renderer = &akgl_default_renderer;
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {
} FINISH(errctx, true); } FINISH(errctx, true);
@@ -415,18 +417,19 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
gamemap = &_akgl_gamemap; TEST_TRAP_UNHANDLED_ERRORS();
renderer = &_akgl_renderer; akgl_gamemap = &akgl_default_gamemap;
akgl_renderer = &akgl_default_renderer;
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest"); SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) { if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) {
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError()); FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
} }
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_sprite", 768, 576, 0, &window, &renderer->sdl_renderer)) { if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_sprite", 768, 576, 0, &akgl_window, &akgl_renderer->sdl_renderer)) {
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError()); FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
} }
renderer->draw_texture = &akgl_render_2d_draw_texture; akgl_renderer->draw_texture = &akgl_render_2d_draw_texture;
CATCH(errctx, akgl_registry_init()); CATCH(errctx, akgl_registry_init());
CATCH(errctx, akgl_heap_init()); CATCH(errctx, akgl_heap_init());

View File

@@ -6,6 +6,8 @@
#include <akgl/staticstring.h> #include <akgl/staticstring.h>
#include <akgl/util.h> #include <akgl/util.h>
#include "testutil.h"
/** /**
* @brief How many entries of AKERR_ARRAY_ERROR are currently held by somebody. * @brief How many entries of AKERR_ARRAY_ERROR are currently held by somebody.
* *
@@ -28,14 +30,14 @@ static int live_error_contexts(void)
akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(void) akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(void)
{ {
RectanglePoints points; akgl_RectanglePoints points;
// Zeroed for the same reason as the fixtures in // Zeroed for the same reason as the fixtures in
// test_akgl_collide_point_rectangle_nullpointers: the last case here is a // test_akgl_collide_point_rectangle_nullpointers: the last case here is a
// real call, and feeding it stack garbage is noise under `memcheck`. // real call, and feeding it stack garbage is noise under `memcheck`.
SDL_FRect testrect = {.x = 0, .y = 0, .w = 0, .h = 0}; SDL_FRect testrect = {.x = 0, .y = 0, .w = 0, .h = 0};
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
memset((void *)&points, 0x00, sizeof(RectanglePoints)); memset((void *)&points, 0x00, sizeof(akgl_RectanglePoints));
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_rectangle_points(NULL, NULL)); CATCH(errctx, akgl_rectangle_points(NULL, NULL));
@@ -57,7 +59,7 @@ akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_rectangle_points(&points, NULL)); CATCH(errctx, akgl_rectangle_points(&points, NULL));
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_rectangle_points fails to FAIL with NULL RectanglePoints pointer"); FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_rectangle_points fails to FAIL with NULL akgl_RectanglePoints pointer");
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {
} HANDLE(errctx, AKERR_NULLPOINTER) { } HANDLE(errctx, AKERR_NULLPOINTER) {
@@ -75,9 +77,9 @@ akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(void)
akerr_ErrorContext *test_akgl_rectangle_points_math(void) akerr_ErrorContext *test_akgl_rectangle_points_math(void)
{ {
RectanglePoints points; akgl_RectanglePoints points;
SDL_FRect testrect = {.x = 0, .y = 0, .w = 32, .h = 32}; SDL_FRect testrect = {.x = 0, .y = 0, .w = 32, .h = 32};
memset((void *)&points, 0x00, sizeof(RectanglePoints)); memset((void *)&points, 0x00, sizeof(akgl_RectanglePoints));
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
ATTEMPT { ATTEMPT {
@@ -112,13 +114,13 @@ akerr_ErrorContext *test_akgl_collide_point_rectangle_nullpointers(void)
// function is a real call with real arguments, and reading uninitialised // function is a real call with real arguments, and reading uninitialised
// floats out of it is sixteen findings under `memcheck` for a test that is // floats out of it is sixteen findings under `memcheck` for a test that is
// not about coordinates at all. // not about coordinates at all.
point testpoint = { .x = 0, .y = 0 }; akgl_Point testpoint = { .x = 0, .y = 0 };
RectanglePoints testrectpoints; akgl_RectanglePoints testrectpoints;
bool testcollide = false; bool testcollide = false;
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
memset(&testrectpoints, 0x00, sizeof(RectanglePoints)); memset(&testrectpoints, 0x00, sizeof(akgl_RectanglePoints));
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, &testrectpoints, NULL)); CATCH(errctx, akgl_collide_point_rectangle(&testpoint, &testrectpoints, NULL));
@@ -167,9 +169,9 @@ akerr_ErrorContext *test_akgl_collide_point_rectangle_nullpointers(void)
akerr_ErrorContext *test_akgl_collide_point_rectangle_logic(void) akerr_ErrorContext *test_akgl_collide_point_rectangle_logic(void)
{ {
point testpoint = {.x = 16, .y = 16}; akgl_Point testpoint = {.x = 16, .y = 16};
SDL_FRect testrect = { .x = 0, .y = 0, .w = 32, .h = 32}; SDL_FRect testrect = { .x = 0, .y = 0, .w = 32, .h = 32};
RectanglePoints testrectpoints; akgl_RectanglePoints testrectpoints;
bool testcollide = false; bool testcollide = false;
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
@@ -392,6 +394,7 @@ int main(void)
PREPARE_ERROR(errctx); PREPARE_ERROR(errctx);
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
CATCH(errctx, test_akgl_rectangle_points_nullpointers()); CATCH(errctx, test_akgl_rectangle_points_nullpointers());
CATCH(errctx, test_akgl_rectangle_points_math()); CATCH(errctx, test_akgl_rectangle_points_math());
CATCH(errctx, test_akgl_collide_point_rectangle_nullpointers()); CATCH(errctx, test_akgl_collide_point_rectangle_nullpointers());

View File

@@ -99,6 +99,7 @@ int main(void)
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_error_init()); CATCH(errctx, akgl_error_init());
TEST_TRAP_UNHANDLED_ERRORS();
CATCH(errctx, test_version_string_matches_components()); CATCH(errctx, test_version_string_matches_components());
CATCH(errctx, test_version_linked_matches_compiled()); CATCH(errctx, test_version_linked_matches_compiled());
CATCH(errctx, test_version_at_least_boundaries()); CATCH(errctx, test_version_at_least_boundaries());

View File

@@ -35,7 +35,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
return SDL_APP_FAILURE; return SDL_APP_FAILURE;
} }
*appstate = (void *)&game.state; *appstate = (void *)&akgl_game.state;
characterjson = argv[1]; characterjson = argv[1];
memset((char *)&pathbuf, 0x00, 4096); memset((char *)&pathbuf, 0x00, 4096);
memset((char *)&cwdbuf, 0x00, 1024); memset((char *)&cwdbuf, 0x00, 1024);
@@ -45,14 +45,14 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
FAIL_ZERO_BREAK(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate pointer"); FAIL_ZERO_BREAK(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate pointer");
FAIL_ZERO_BREAK(errctx, getcwd((char *)&cwdbuf, 1024), AKERR_NULLPOINTER, "Couldn't get current working directory"); FAIL_ZERO_BREAK(errctx, getcwd((char *)&cwdbuf, 1024), AKERR_NULLPOINTER, "Couldn't get current working directory");
strcpy((char *)&game.name, "charviewer"); strcpy((char *)&akgl_game.name, "charviewer");
strcpy((char *)&game.version, "0.0.1"); strcpy((char *)&akgl_game.version, "0.0.1");
strcpy((char *)&game.uri, "net.aklabs.libakgl.charviewer"); strcpy((char *)&akgl_game.uri, "net.aklabs.libakgl.charviewer");
CATCH(errctx, akgl_game_init()); CATCH(errctx, akgl_game_init());
CATCH(errctx, akgl_set_property("game.screenwidth", "640")); CATCH(errctx, akgl_set_property("game.screenwidth", "640"));
CATCH(errctx, akgl_set_property("game.screenheight", "480")); CATCH(errctx, akgl_set_property("game.screenheight", "480"));
CATCH(errctx, akgl_render_init2d(renderer)); CATCH(errctx, akgl_render_2d_init(akgl_renderer));
CATCH(errctx, akgl_physics_init_null(physics)); CATCH(errctx, akgl_physics_init_null(akgl_physics));
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {
@@ -144,9 +144,9 @@ SDL_AppResult SDL_AppIterate(void *appstate)
AKGL_BITMASK_CLEAR(opflags.flags); AKGL_BITMASK_CLEAR(opflags.flags);
AKGL_BITMASK_ADD(opflags.flags, AKGL_ITERATOR_OP_UPDATE); AKGL_BITMASK_ADD(opflags.flags, AKGL_ITERATOR_OP_UPDATE);
AKGL_BITMASK_ADD(opflags.flags, AKGL_ITERATOR_OP_RENDER); AKGL_BITMASK_ADD(opflags.flags, AKGL_ITERATOR_OP_RENDER);
akgl_draw_background((int)camera->w, (int)camera->h); akgl_draw_background((int)akgl_camera->w, (int)akgl_camera->h);
ATTEMPT { ATTEMPT {
CATCH(errctx, akgl_tilemap_draw(gamemap, camera, i)); CATCH(errctx, akgl_tilemap_draw(akgl_gamemap, akgl_camera, i));
SDL_EnumerateProperties(AKGL_REGISTRY_ACTOR, &akgl_registry_iterate_actor, (void *)&opflags); SDL_EnumerateProperties(AKGL_REGISTRY_ACTOR, &akgl_registry_iterate_actor, (void *)&opflags);
} CLEANUP { } CLEANUP {
} PROCESS(errctx) { } PROCESS(errctx) {
@@ -154,7 +154,7 @@ SDL_AppResult SDL_AppIterate(void *appstate)
LOG_ERROR(errctx); LOG_ERROR(errctx);
return SDL_APP_FAILURE; return SDL_APP_FAILURE;
} FINISH_NORETURN(errctx); } FINISH_NORETURN(errctx);
SDL_RenderPresent(renderer->sdl_renderer); SDL_RenderPresent(akgl_renderer->sdl_renderer);
return SDL_APP_CONTINUE; return SDL_APP_CONTINUE;
} }