Namespace every exported symbol, and bump to 0.5.0
Closes internal-consistency items 1 through 6, 12 and 13. Every include guard is _AKGL_<FILE>_H_, every in-project header include is angled, and every exported function, type and global carries the akgl_ prefix. This is an ABI break; the soname goes to libakgl.so.0.5. TODO.md carries the full rename table. The renames were driven by renaming each declaration and letting the compiler find the uses, not by pattern substitution: renderer, physics and camera are also parameter and struct-member names, and a sed would have rewritten map->physics and every akgl_RenderBackend *renderer parameter without a word. Item 4 turned out not to be cosmetic. The library exported a global called renderer and tests/character.c defined an SDL_Renderer *renderer of its own; the executable's definition preempted the library's, akgl_sprite_load_json read a SDL_Renderer * through an akgl_RenderBackend *, and every texture load in that suite failed. The suite reported success anyway, because libakerror's unhandled-error handler ends in exit(errctx->status), exit keeps only the low byte, and AKGL_ERR_SDL is exactly 256. So character had been green while running one of its four tests, and every suite in the tree was unable to fail on the most common status in a library built on SDL. Both are fixed. tests/testutil.h gains TEST_TRAP_UNHANDLED_ERRORS(), which collapses any status a byte cannot carry onto 1, and every suite installs it. character binds a real backend with akgl_render_2d_bind. Its fourth test then runs for the first time and fails on a defect it has asserted all along, so akgl_heap_release_character now walks state_sprites with AKGL_ITERATOR_OP_RELEASE and destroys the property set before zeroing the slot -- TODO.md Defects item 21 and half of Carried over item 1. AKGL_TIME_ONESEC_MS said "one second in milliseconds" and held 1000000, so akgl_game_state_lock waited roughly sixteen minutes rather than one second. It is AKGL_TIME_ONEMS_NS now, the budget is its own named constant, and tests/game.c holds the mutex from a second thread to assert the wait -- the contended path had no coverage at all. Headers are self-contained and it is enforced: AKGL_PUBLIC_HEADERS drives both install() and a generated translation unit per header, so a header that ships is a header that is checked. Writing that found registry.h, which used SDL_PropertiesID in eight declarations and included no SDL header. 23/23 suites pass, memcheck is clean, reindent --check is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
42
AGENTS.md
42
AGENTS.md
@@ -187,12 +187,9 @@ For reference, `cc-mode` defines the style as:
|
||||
- `case` labels sit at the same column as their `switch` (`label . 0`).
|
||||
- Continuation lines of a wrapped expression indent one level (`statement-cont . +`).
|
||||
|
||||
Most of `src/` already conforms. The known non-conforming files are
|
||||
`src/json_helpers.c`, `src/util.c` (from `akgl_rectangle_points` onward),
|
||||
`src/assets.c`, `src/staticstring.c`, parts of `src/actor.c`, and the headers
|
||||
`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.
|
||||
The whole tree conforms: `scripts/reindent.sh --check` is clean, and the
|
||||
pre-commit hook keeps it that way. Convert a file to the canonical style in its
|
||||
own commit, not mixed into a behavioral change.
|
||||
|
||||
### 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
|
||||
`akgl_actor_cmhf_left_on`).
|
||||
- **Public types**: `akgl_TypeName` — `akgl_Actor`, `akgl_SpriteSheet`,
|
||||
`akgl_PhysicsBackend`. Every type exported from a header takes the prefix;
|
||||
bare names like `point` and `RectanglePoints` are defects, not precedent.
|
||||
`akgl_PhysicsBackend`. Every type exported from a header takes the prefix.
|
||||
`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
|
||||
subsystem it describes: a character limit is `AKGL_CHARACTER_MAX_*`, not
|
||||
`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
|
||||
(reserved at file scope), or SCREAMING_SNAKE names for mutable objects —
|
||||
`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
|
||||
external collisions.
|
||||
- **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.
|
||||
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 `()`.
|
||||
|
||||
## Rules
|
||||
@@ -330,7 +346,15 @@ Declare no-argument functions as `(void)`, not `()`.
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10)
|
||||
# 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
|
||||
# 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`
|
||||
# runs every registered test under valgrind -- rather than adding programs of its
|
||||
@@ -154,6 +154,34 @@ if(NOT TARGET jansson::jansson)
|
||||
find_package(jansson)
|
||||
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(prefix ${CMAKE_INSTALL_PREFIX})
|
||||
set(exec_prefix "\${prefix}")
|
||||
@@ -274,6 +302,30 @@ endforeach()
|
||||
|
||||
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
|
||||
# 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
|
||||
@@ -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(TARGETS akgl DESTINATION "lib/")
|
||||
install(FILES "deps/semver/semver.h" DESTINATION "include/")
|
||||
install(FILES "include/akgl/actor.h" DESTINATION "include/akgl/")
|
||||
install(FILES "include/akgl/types.h" DESTINATION "include/akgl/")
|
||||
install(FILES "include/akgl/text.h" DESTINATION "include/akgl/")
|
||||
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/")
|
||||
foreach(header IN LISTS AKGL_PUBLIC_HEADERS)
|
||||
install(FILES "include/akgl/${header}.h" DESTINATION "include/akgl/")
|
||||
endforeach()
|
||||
install(FILES ${GAMECONTROLLERDB_H} DESTINATION "include/akgl/")
|
||||
|
||||
@@ -84,11 +84,11 @@ start whether the game uses one slot or all of them.
|
||||
|
||||
| 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_SPRITESHEET` | 1024 | 536 | 548,864 |
|
||||
| `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** |
|
||||
| `akgl_Tilemap` (one, as `_akgl_gamemap`) | 1 | 26,388,008 | 26,388,008 |
|
||||
| — of which layers | 16 | 1,120,296 | 17,924,736 |
|
||||
|
||||
20
README.md
20
README.md
@@ -24,8 +24,8 @@ PASS(e, akgl_registry_load_properties(YOUR_REGISTR_FILEPATH));
|
||||
Initialize your physics engine and renderer of choice
|
||||
|
||||
```c
|
||||
PASS(e, akgl_render_init2d(renderer));
|
||||
PASS(e, akgl_physics_init_arcade(physics));
|
||||
PASS(e, akgl_render_2d_init(akgl_renderer));
|
||||
PASS(e, akgl_physics_init_arcade(akgl_physics));
|
||||
```
|
||||
|
||||
Unlock the game state
|
||||
@@ -55,10 +55,10 @@ In your game loop (or in your `SDL_AppIterate` method), lock the game state, cal
|
||||
|
||||
```c
|
||||
PASS(e, akgl_game_state_lock());
|
||||
PASS(e, renderer->frame_start(renderer));
|
||||
SDL_RenderClear(renderer->sdl_renderer);
|
||||
PASS(e, akgl_renderer->frame_start(akgl_renderer));
|
||||
SDL_RenderClear(akgl_renderer->sdl_renderer);
|
||||
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());
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
```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.
|
||||
@@ -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
|
||||
|
||||
```c
|
||||
if ( gamemap->use_own_physics == true ) {
|
||||
physics = &gamemap->physics;
|
||||
if ( akgl_gamemap->use_own_physics == true ) {
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
224
TODO.md
224
TODO.md
@@ -9,67 +9,62 @@ overlap the existing **Defects** list are cross-referenced rather than repeated.
|
||||
|
||||
### 1. Public naming conventions
|
||||
|
||||
1. **Include guards use three different schemes.** `_AKGL_ACTOR_H_`,
|
||||
`_AKGL_CHARACTER_H_`, `_AKGL_GAME_H_`, `_AKGL_HEAP_H_`, `_AKGL_ITERATOR_H_`,
|
||||
`_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.
|
||||
**Items 1 through 6 are resolved in 0.5.0**, which is an ABI break and carries
|
||||
the soname to `libakgl.so.0.5`. What changed, and what a consumer has to rename:
|
||||
|
||||
The worst case is `include/akgl/staticstring.h:6`, which guards with
|
||||
`_STRING_H_` — a name several libc implementations use for their own
|
||||
`<string.h>`. The same file then does `#include "string.h"` (line 9) with
|
||||
quotes, which is a relative-first lookup that only reaches the system header
|
||||
by accident. Rename the guard to `_AKGL_STATICSTRING_H_` and use
|
||||
`#include <string.h>`.
|
||||
| Was | Is |
|
||||
|---|---|
|
||||
| `_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_` |
|
||||
| `akgl_Actor_cmhf_*` (8 functions) | `akgl_actor_cmhf_*` |
|
||||
| `akgl_game_updateFPS` | `akgl_game_update_fps` |
|
||||
| `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
|
||||
symbols use `akgl_` and types use `akgl_TypeName`. Exceptions:
|
||||
- `akgl_Actor_cmhf_left_on` and its seven siblings (`include/akgl/actor.h:196-252`)
|
||||
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`.
|
||||
`include/akgl/staticstring.h` also stopped guarding with `_STRING_H_` — a name
|
||||
several libc implementations use for their own `<string.h>` — and its
|
||||
`#include "string.h"` is now `#include <string.h>`.
|
||||
|
||||
3. **Two public types have no prefix at all.** `point` and `RectanglePoints`
|
||||
(`include/akgl/util.h:13-25`) are unprefixed, and `RectanglePoints` is
|
||||
PascalCase with no namespace. Both are dumped into every translation unit
|
||||
that includes `util.h`. Rename to `akgl_Point` / `akgl_RectanglePoints`.
|
||||
`akgl_render_bind2d` was not on the original list. It is the same defect as
|
||||
`akgl_render_init2d`, `2d` trailing where the six functions it installs carry it
|
||||
in the middle, and renaming one without the other would have been worse than
|
||||
renaming neither.
|
||||
|
||||
4. **Global variables use four different conventions.** `window`, `bgm`, `game`,
|
||||
`gamemap`, `renderer`, `physics`, and `camera` (`src/game.c:26-43`) are
|
||||
unprefixed single common words exported from a shared library — `renderer`
|
||||
and `camera` in particular are very likely to collide with a consuming game.
|
||||
Alongside them the same file exports `akgl_mixer` and `akgl_tracks` (prefixed),
|
||||
`_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.
|
||||
The renames were done by renaming each declaration and letting the compiler find
|
||||
every use, rather than by pattern substitution. That distinction matters for
|
||||
`renderer`, `physics` and `camera`, which are also parameter and struct-member
|
||||
names: a `sed` would have rewritten `map->physics` and every
|
||||
`akgl_RenderBackend *renderer` parameter, and nothing would have complained.
|
||||
|
||||
5. **`AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH`** (`include/akgl/character.h:13`) is a
|
||||
character constant carrying the `SPRITE` prefix, and lives in `character.h`.
|
||||
Rename to `AKGL_CHARACTER_MAX_NAME_LENGTH`.
|
||||
**Item 6 had a live bug behind the name**, now fixed. `akgl_game_state_lock`
|
||||
counted its retry loop 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 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.**
|
||||
`include/akgl/game.h:22` defines it as `1000000`. One second in milliseconds
|
||||
is `1000`; `1000000` is the number of nanoseconds in a *millisecond*. The name
|
||||
says "one second" but the value means "one millisecond", so:
|
||||
- `src/character.c:209` and `src/sprite.c:141` use it correctly as a
|
||||
milliseconds-to-nanoseconds scale factor.
|
||||
- `src/game.c:136` uses it as documented — a one-second budget for the state
|
||||
lock — in a loop that advances `totaltime += 100` alongside `SDL_Delay(100)`.
|
||||
The loop therefore runs 10,000 iterations of 100 ms, so `akgl_game_state_lock`
|
||||
blocks for roughly 16 minutes rather than 1 second before reporting failure.
|
||||
|
||||
Rename to `AKGL_TIME_ONEMS_NS` to match `AKGL_TIME_ONESEC_NS`, and give
|
||||
`akgl_game_state_lock` a real one-second budget.
|
||||
**Item 4 was not cosmetic, and the proof was sitting in the test suite.**
|
||||
`renderer` was exported from the shared library, and `tests/character.c` defined
|
||||
an `SDL_Renderer *renderer` of its own. Both had external linkage and the same
|
||||
spelling, so the executable's definition preempted the library's:
|
||||
`akgl_sprite_load_json` read a `SDL_Renderer *` through an
|
||||
`akgl_RenderBackend *`, every texture load in that suite failed with
|
||||
`Parameter 'renderer' is invalid`, and the suite still reported success — see
|
||||
"Test suites that could not fail" below. It now binds a real backend with
|
||||
`akgl_render_2d_bind`, the way `tests/sprite.c` and `tests/text.c` do. A
|
||||
consuming game with a variable called `renderer` would have hit the same thing
|
||||
with no test to notice.
|
||||
|
||||
### 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: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` |
|
||||
| `json_helpers.h:134` `e` | `json_helpers.c:149` `err` |
|
||||
| `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
|
||||
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`
|
||||
without `<stdint.h>`; `json_helpers.h` uses `json_t` without `<jansson.h>`;
|
||||
`util.h` uses `SDL_FRect` and `bool` without any SDL include; `text.h` uses
|
||||
`SDL_Color` and `akerr_ErrorContext` without including SDL or `akerror.h`;
|
||||
`controller.h` uses `akgl_Actor` without including `actor.h`. Each compiles
|
||||
only because of `.c`-file include ordering. Headers should be self-contained.
|
||||
12. **Headers rely on their includers for types.** `iterator.h` used `uint32_t`
|
||||
without `<stdint.h>`; `json_helpers.h` used `json_t` without `<jansson.h>`;
|
||||
`util.h` used `SDL_FRect` and `bool` without any SDL include. Each compiled
|
||||
only because of `.c`-file include ordering.
|
||||
|
||||
**Resolved, and enforced rather than merely fixed.** `AKGL_PUBLIC_HEADERS`
|
||||
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
|
||||
directory.** `actor.h:10-11`, `character.h:10-11`, `controller.h:11`,
|
||||
`game.h:11-14`, `json_helpers.h:10`, and `staticstring.h:9` use
|
||||
`#include "sibling.h"`; `physics.h:11-13`, `registry.h:9-10`, `renderer.h:13`,
|
||||
`tilemap.h:10-12`, and `util.h:10` use `#include <akgl/sibling.h>`. The
|
||||
angled form is correct for an installed library.
|
||||
directory.** **Resolved.** Every in-project include in a public header uses
|
||||
`#include <akgl/sibling.h>`. `tests/*.c` still use `#include "testutil.h"`,
|
||||
correctly: that one is a test-local header rather than an installed one.
|
||||
|
||||
14. **Empty parameter lists.** `akgl_heap_init()`, `akgl_heap_init_actor()`,
|
||||
`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
|
||||
`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
|
||||
|
||||
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`.
|
||||
|
||||
21. **`akgl_heap_release_character` abandons the whole state-to-sprite map.**
|
||||
`src/heap.c:150` zeroes the character without walking `state_sprites`, so
|
||||
every sprite reference the character took in `akgl_character_sprite_add` is
|
||||
lost and the `SDL_PropertiesID` holding the map is never destroyed. Loading
|
||||
and releasing characters in a loop — level to level — exhausts the sprite
|
||||
pool and leaks 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.
|
||||
`src/heap.c:150` zeroed the character without walking `state_sprites`, so
|
||||
every sprite reference the character took in `akgl_character_sprite_add` was
|
||||
lost and the `SDL_PropertiesID` holding the map was never destroyed. Loading
|
||||
and releasing characters in a loop — level to level — exhausted the sprite
|
||||
pool and leaked an SDL property set each time.
|
||||
|
||||
Fix: enumerate `state_sprites` with that iterator, then
|
||||
`SDL_DestroyProperties`, before the `memset`. Touches `src/heap.c:145-151`.
|
||||
**Fixed in 0.5.0.** At a refcount of zero it enumerates `state_sprites` with
|
||||
`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.**
|
||||
`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`.
|
||||
|
||||
33. **`akgl_heap_release_character` leaks its `state_sprites` property set.**
|
||||
Already documented in `heap.h` and in **Carried over** item 1; the perf
|
||||
suite makes it measurable rather than theoretical. A benchmark that loads
|
||||
the fixture character 10,000 times leaks 10,000 SDL property sets and 20,000
|
||||
sprite references. Cross-referenced here only because the character-load
|
||||
benchmark had to be written around it.
|
||||
**Fixed in 0.5.0**; see item 21, which is the same defect. The
|
||||
character-load benchmark in `tests/perf_render.c` was written around it and
|
||||
no longer has to be.
|
||||
|
||||
### Targets
|
||||
|
||||
@@ -1745,9 +1791,19 @@ this library and told it is compatible.
|
||||
clear the character. Add tests for removal, replacement, duplicate sprite
|
||||
bindings across multiple states, and final character release.
|
||||
|
||||
`akgl_character_state_sprites_iterate` is still uncovered and is the natural
|
||||
place to start; `tests/actor.c` now covers `akgl_character_sprite_get` and the
|
||||
composite-state binding behavior it depends on.
|
||||
**The final-release half is done in 0.5.0** — see **Defects** item 21.
|
||||
`akgl_heap_release_character` now enumerates every remaining binding, releases
|
||||
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
|
||||
to both `dest.w` and `dest.h`, so there is no way to express "twice as wide, the same
|
||||
|
||||
@@ -33,8 +33,8 @@
|
||||
#define _AKGL_ACTOR_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include "types.h"
|
||||
#include "character.h"
|
||||
#include <akgl/types.h>
|
||||
#include <akgl/character.h>
|
||||
|
||||
// ---- LOW WORD STATUSES ----
|
||||
|
||||
@@ -225,7 +225,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_set_character(akgl_Actor *obj, cha
|
||||
* @return `NULL` on success -- including every skipped case above -- otherwise
|
||||
* an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
|
||||
* @throws AKERR_* Whatever akgl_sprite_sheet_coords_for_frame or the renderer's
|
||||
* @throws AKERR_* Whatever akgl_spritesheet_coords_for_frame or the renderer's
|
||||
* `draw_texture` raises.
|
||||
*
|
||||
* @note The destination height is computed from the sprite's *width*, so a
|
||||
@@ -368,7 +368,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_add_child(akgl_Actor *obj, akgl_Ac
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_on(akgl_Actor *obj, SDL_Event *event);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_on(akgl_Actor *obj, SDL_Event *event);
|
||||
/**
|
||||
* @brief Stop the actor moving left, zeroing everything on the x axis.
|
||||
* @param obj The actor to stop. Required. `basechar` is not needed here.
|
||||
@@ -378,7 +378,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_on(akgl_Actor *obj, SDL_
|
||||
* @note It clears the x axis whichever way the actor was going, so releasing
|
||||
* left while holding right also stops the rightward movement.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_off(akgl_Actor *obj, SDL_Event *event);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_off(akgl_Actor *obj, SDL_Event *event);
|
||||
/**
|
||||
* @brief Start the actor moving right, and turn it to face right.
|
||||
* @param obj The actor to move. Required, along with its `basechar`.
|
||||
@@ -386,7 +386,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_off(akgl_Actor *obj, SDL
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_on(akgl_Actor *obj, SDL_Event *event);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_on(akgl_Actor *obj, SDL_Event *event);
|
||||
/**
|
||||
* @brief Stop the actor moving right, zeroing everything on the x axis.
|
||||
* @param obj The actor to stop. Required.
|
||||
@@ -394,7 +394,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_on(akgl_Actor *obj, SDL
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_off(akgl_Actor *obj, SDL_Event *event);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_off(akgl_Actor *obj, SDL_Event *event);
|
||||
/**
|
||||
* @brief Start the actor moving up the screen, and turn it to face up.
|
||||
* @param obj The actor to move. Required, along with its `basechar`, whose y
|
||||
@@ -403,7 +403,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_off(akgl_Actor *obj, SD
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_on(akgl_Actor *obj, SDL_Event *event);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_on(akgl_Actor *obj, SDL_Event *event);
|
||||
/**
|
||||
* @brief Stop the actor moving up, zeroing everything on the y axis.
|
||||
* @param obj The actor to stop. Required.
|
||||
@@ -413,7 +413,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_on(akgl_Actor *obj, SDL_Ev
|
||||
* @note This also zeroes `ey`, so an actor under gravity has its accumulated
|
||||
* fall cancelled by releasing a movement key.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_off(akgl_Actor *obj, SDL_Event *event);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_off(akgl_Actor *obj, SDL_Event *event);
|
||||
/**
|
||||
* @brief Start the actor moving down the screen, and turn it to face down.
|
||||
* @param obj The actor to move. Required, along with its `basechar`.
|
||||
@@ -421,16 +421,16 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_off(akgl_Actor *obj, SDL_E
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_down_on(akgl_Actor *obj, SDL_Event *event);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_on(akgl_Actor *obj, SDL_Event *event);
|
||||
/**
|
||||
* @brief Stop the actor moving down, zeroing everything on the y axis.
|
||||
* @param obj The actor to stop. Required.
|
||||
* @param event The event that triggered this. Required, but not read.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
|
||||
* @note Zeroes `ey` as well; see akgl_Actor_cmhf_up_off.
|
||||
* @note Zeroes `ey` as well; see akgl_actor_cmhf_up_off.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_down_off(akgl_Actor *obj, SDL_Event *event);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_off(akgl_Actor *obj, SDL_Event *event);
|
||||
|
||||
/**
|
||||
* @brief `SDL_EnumerateProperties` callback that applies an akgl_Iterator to one registered actor.
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* @brief Loads the game's startup assets into the global mixer and track table.
|
||||
*/
|
||||
|
||||
#ifndef _ASSETS_H_
|
||||
#define _ASSETS_H_
|
||||
#ifndef _AKGL_ASSETS_H_
|
||||
#define _AKGL_ASSETS_H_
|
||||
|
||||
#include <akerror.h>
|
||||
|
||||
@@ -41,4 +41,4 @@
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_load_start_bgm(char *fname);
|
||||
|
||||
#endif //_ASSETS_H_
|
||||
#endif //_AKGL_ASSETS_H_
|
||||
|
||||
@@ -16,18 +16,18 @@
|
||||
#define _AKGL_CHARACTER_H_
|
||||
|
||||
#include <SDL3/SDL_properties.h>
|
||||
#include "types.h"
|
||||
#include "sprite.h"
|
||||
#include <akgl/types.h>
|
||||
#include <akgl/sprite.h>
|
||||
|
||||
#define AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH 128
|
||||
#define AKGL_CHARACTER_MAX_NAME_LENGTH 128
|
||||
#define AKGL_MAX_HEAP_CHARACTER 256
|
||||
|
||||
/** @brief Defines reusable movement parameters and actor-state sprite bindings. */
|
||||
typedef struct akgl_Character {
|
||||
uint8_t refcount; /**< Pool bookkeeping; 0 means the slot is free. */
|
||||
char name[AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH]; /**< Registry key. Truncated, not rejected, if the source name is longer. */
|
||||
char name[AKGL_CHARACTER_MAX_NAME_LENGTH]; /**< Registry key. Truncated, not rejected, if the source name is longer. */
|
||||
SDL_PropertiesID state_sprites; /**< State bitmask (decimal, as a string) -> akgl_Sprite *. */
|
||||
uint64_t speedtime; /**< Nanoseconds one sprite frame is held before advancing. Read from JSON in milliseconds and scaled by #AKGL_TIME_ONESEC_MS, which despite its name is nanoseconds-per-millisecond. TODO.md item 6. */
|
||||
uint64_t speedtime; /**< Nanoseconds one sprite frame is held before advancing. Read from JSON in milliseconds and scaled by #AKGL_TIME_ONEMS_NS. */
|
||||
float32_t ax; /**< Acceleration along x, world units per second squared. Copied into an actor by akgl_actor_set_character. */
|
||||
float32_t ay; /**< Acceleration along y. */
|
||||
float32_t az; /**< Acceleration along z. Not read from JSON; stays 0 unless set by hand. */
|
||||
@@ -52,7 +52,7 @@ typedef struct akgl_Character {
|
||||
* akgl_heap_next_character. Required. Any previous contents are
|
||||
* discarded without releasing the sprites they referenced.
|
||||
* @param name Registry key, NUL-terminated. Required. Truncated at
|
||||
* #AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH. A name already in the
|
||||
* #AKGL_CHARACTER_MAX_NAME_LENGTH. A name already in the
|
||||
* registry is silently replaced, and the character it displaced
|
||||
* becomes unreachable rather than being released.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
|
||||
@@ -24,12 +24,12 @@
|
||||
* akgl_controller_poll_keystroke().
|
||||
*/
|
||||
|
||||
#ifndef _CONTROLLER_H_
|
||||
#define _CONTROLLER_H_
|
||||
#ifndef _AKGL_CONTROLLER_H_
|
||||
#define _AKGL_CONTROLLER_H_
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <akerror.h>
|
||||
#include "types.h"
|
||||
#include <akgl/types.h>
|
||||
// The binding handlers below take an akgl_Actor *, so this header cannot be
|
||||
// included before akgl/actor.h without one. akgl_Actor is a typedef of a named
|
||||
// struct, and repeating a typedef is not C99, so it is included rather than
|
||||
@@ -94,7 +94,7 @@ typedef struct {
|
||||
} akgl_ControlMap;
|
||||
|
||||
/** @brief Every control map. Zeroed by akgl_game_init; index it by the same id the functions below take. */
|
||||
extern akgl_ControlMap GAME_ControlMaps[AKGL_MAX_CONTROL_MAPS];
|
||||
extern akgl_ControlMap akgl_controlmaps[AKGL_MAX_CONTROL_MAPS];
|
||||
|
||||
/**
|
||||
* @brief Log every attached keyboard and its SDL id.
|
||||
@@ -190,7 +190,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_removed(void *appstate
|
||||
* The binding is copied, so the caller's `akgl_Control` can be a stack local
|
||||
* reused across pushes -- which is exactly what akgl_controller_default does.
|
||||
* Bindings can only be appended; there is no remove, and no way to reset a map
|
||||
* short of zeroing it in ::GAME_ControlMaps directly.
|
||||
* short of zeroing it in ::akgl_controlmaps directly.
|
||||
*
|
||||
* @param controlmapid Which map to append to, 0 through
|
||||
* #AKGL_MAX_CONTROL_MAPS - 1.
|
||||
@@ -203,7 +203,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_removed(void *appstate
|
||||
*
|
||||
* @warning A **negative** @p controlmapid is not rejected -- only the upper
|
||||
* bound is checked -- and indexes before the start of
|
||||
* ::GAME_ControlMaps. TODO.md, "Known and still open" item 11.
|
||||
* ::akgl_controlmaps. TODO.md, "Known and still open" item 11.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akgl_Control *control);
|
||||
|
||||
@@ -212,7 +212,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akg
|
||||
*
|
||||
* Points the map at the named actor and pushes eight bindings: the four arrow
|
||||
* keys and the four D-pad directions, each wired to the matching
|
||||
* `akgl_Actor_cmhf_*_on`/`_off` pair. It is the "just give me something that
|
||||
* `akgl_actor_cmhf_*_on`/`_off` pair. It is the "just give me something that
|
||||
* works" path -- a game wanting different keys builds its own bindings with
|
||||
* akgl_controller_pushmap.
|
||||
*
|
||||
@@ -334,4 +334,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_keystroke(akgl_Keystroke
|
||||
* @return `NULL`. There is no failure path -- it resets two counters.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_flush_keys(void);
|
||||
#endif // _CONTROLLER_H_
|
||||
#endif // _AKGL_CONTROLLER_H_
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
* what the next SDL_RenderClear() paints.
|
||||
*/
|
||||
|
||||
#ifndef _DRAW_H_
|
||||
#define _DRAW_H_
|
||||
#ifndef _AKGL_DRAW_H_
|
||||
#define _AKGL_DRAW_H_
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
* A diagnostic backdrop, not a general primitive -- `charviewer` uses it so a
|
||||
* sprite's transparent pixels are visible rather than blending into black. It
|
||||
* is the one function in this file that does not follow the file's conventions:
|
||||
* it draws through the *global* `renderer` rather than a backend the caller
|
||||
* it draws through the *global* `akgl_renderer` rather than a backend the caller
|
||||
* passes in, it leaves the renderer's draw colour changed, and it reports
|
||||
* nothing.
|
||||
*
|
||||
@@ -233,4 +233,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_copy_region(akgl_RenderBackend *sel
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_paste_region(akgl_RenderBackend *self, SDL_Surface *src, float32_t x, float32_t y);
|
||||
|
||||
#endif //_DRAW_H_
|
||||
#endif //_AKGL_DRAW_H_
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* @brief Declares the public error API.
|
||||
*/
|
||||
|
||||
#ifndef _ERROR_H_
|
||||
#define _ERROR_H_
|
||||
#ifndef _AKGL_ERROR_H_
|
||||
#define _AKGL_ERROR_H_
|
||||
|
||||
#include <akerror.h>
|
||||
|
||||
@@ -75,4 +75,4 @@
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_error_init(void);
|
||||
|
||||
#endif // _ERROR_H_
|
||||
#endif // _AKGL_ERROR_H_
|
||||
|
||||
@@ -6,24 +6,24 @@
|
||||
* registries and the audio and font engines; `akgl_game_update` is one frame --
|
||||
* update every actor, step the physics, draw the world.
|
||||
*
|
||||
* There is exactly one of everything. `renderer`, `physics`, `camera`, and
|
||||
* `gamemap` are globals pointing at the `_akgl_*` storage below them, so a
|
||||
* There is exactly one of everything. `akgl_renderer`, `akgl_physics`, `akgl_camera`, and
|
||||
* `akgl_gamemap` are globals pointing at the `akgl_default_*` storage below them, so a
|
||||
* program can swap in its own instance by reassigning the pointer without the
|
||||
* rest of the library knowing. That is the whole extent of the indirection:
|
||||
* there is no notion of two worlds at once.
|
||||
*
|
||||
* The startup order that actually works:
|
||||
*
|
||||
* 1. fill in `game.name`, `game.version`, and `game.uri` -- akgl_game_init
|
||||
* 1. fill in `akgl_game.name`, `akgl_game.version`, and `akgl_game.uri` -- akgl_game_init
|
||||
* refuses to run without them;
|
||||
* 2. akgl_game_init();
|
||||
* 3. akgl_registry_load_properties() or akgl_set_property(), to configure
|
||||
* screen size, physics constants and so on;
|
||||
* 4. akgl_render_init2d(renderer) and akgl_physics_factory(physics, ...), both
|
||||
* 4. akgl_render_2d_init(renderer) and akgl_physics_factory(physics, ...), both
|
||||
* of which read that configuration;
|
||||
* 5. load assets, then loop on akgl_game_update().
|
||||
*
|
||||
* @warning None of this is thread-safe beyond the `game.state` mutex, and that
|
||||
* @warning None of this is thread-safe beyond the `akgl_game.state` mutex, and that
|
||||
* mutex protects the state flags, not the pools or the registries.
|
||||
*/
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <SDL3_mixer/SDL_mixer.h>
|
||||
#include "types.h"
|
||||
#include "tilemap.h"
|
||||
#include "renderer.h"
|
||||
#include "physics.h"
|
||||
#include <akgl/types.h>
|
||||
#include <akgl/tilemap.h>
|
||||
#include <akgl/renderer.h>
|
||||
#include <akgl/physics.h>
|
||||
// AKGL_VERSION used to be defined here by hand, which is how akgl.pc came to
|
||||
// ship an empty Version field: nothing tied the two together.
|
||||
#include <akgl/version.h>
|
||||
@@ -48,15 +48,29 @@
|
||||
/** @brief Nanoseconds in one second. The unit `SDL_GetTicksNS` reports in. */
|
||||
#define AKGL_TIME_ONESEC_NS 1000000000
|
||||
/**
|
||||
* @brief Misnamed: this is nanoseconds per **millisecond**, not milliseconds per second.
|
||||
* @brief Nanoseconds in one millisecond. The scale factor from JSON durations to internal ones.
|
||||
*
|
||||
* One second in milliseconds is 1000. 1000000 is one millisecond in
|
||||
* nanoseconds, which is how sprite and character load actually use it -- as a
|
||||
* milliseconds-to-nanoseconds scale factor -- and is not how
|
||||
* akgl_game_state_lock uses it. TODO.md item 6 proposes renaming it
|
||||
* `AKGL_TIME_ONEMS_NS`.
|
||||
* Sprite and character definitions state frame durations in milliseconds
|
||||
* because that is what a human writing one wants to type; everything that
|
||||
* compares them against `SDL_GetTicksNS` needs nanoseconds.
|
||||
*
|
||||
* This was called `AKGL_TIME_ONESEC_MS` until 0.5.0, which said "one second in
|
||||
* milliseconds" and held 1000000 -- a name and a value that described two
|
||||
* different quantities. Both callers that meant the scale factor were right;
|
||||
* the one that read it as a one-second budget was wrong by a factor of a
|
||||
* thousand. See akgl_game_state_lock.
|
||||
*/
|
||||
#define AKGL_TIME_ONESEC_MS 1000000
|
||||
#define AKGL_TIME_ONEMS_NS 1000000
|
||||
|
||||
/**
|
||||
* @brief How long akgl_game_state_lock keeps trying for the state mutex, in milliseconds.
|
||||
*
|
||||
* A deliberate ceiling rather than a blocking wait: a deadlock here reports an
|
||||
* error the caller can act on instead of hanging the process.
|
||||
*/
|
||||
#define AKGL_GAME_STATE_LOCK_BUDGET_MS 1000
|
||||
/** @brief How long akgl_game_state_lock sleeps between attempts, in milliseconds. */
|
||||
#define AKGL_GAME_STATE_LOCK_RETRY_MS 100
|
||||
|
||||
/* ==================== GAME STATE VARIABLES =================== */
|
||||
|
||||
@@ -82,39 +96,39 @@ typedef struct {
|
||||
SDL_Mutex *statelock; /**< Guards `state`. Created by akgl_game_init. */
|
||||
int16_t fps; /**< Frames drawn during the last completed second. Recomputed once per second, not per frame. */
|
||||
SDL_Time gameStartTime; /**< `SDL_GetTicksNS()` at akgl_game_init. */
|
||||
SDL_Time lastIterTime; /**< Timestamp of the most recent akgl_game_updateFPS call. */
|
||||
SDL_Time lastIterTime; /**< Timestamp of the most recent akgl_game_update_fps call. */
|
||||
SDL_Time lastFPSTime; /**< When `fps` was last recomputed. */
|
||||
int16_t framesSinceUpdate; /**< Frames counted so far in the current second. */
|
||||
void (*lowfpsfunc)(void); /**< Called every frame while `fps` is under 30. Defaults to akgl_game_lowfps; replace it to do something more useful than log. */
|
||||
} akgl_Game;
|
||||
|
||||
/** @brief The SDL window, created by akgl_render_init2d. `NULL` until then. */
|
||||
extern SDL_Window *window;
|
||||
/** @brief The SDL window, created by akgl_render_2d_init. `NULL` until then. */
|
||||
extern SDL_Window *akgl_window;
|
||||
/** @brief The background music, loaded by akgl_load_start_bgm. `NULL` until then. */
|
||||
extern MIX_Audio *bgm;
|
||||
extern MIX_Audio *akgl_bgm;
|
||||
/** @brief The mixer device, created by akgl_game_init. Everything audio goes through it. */
|
||||
extern MIX_Mixer *akgl_mixer;
|
||||
/** @brief Playback tracks by slot. #AKGL_GAME_AUDIO_TRACK_BGM is the music track; the rest are the application's to assign. */
|
||||
extern MIX_Track *akgl_tracks[AKGL_GAME_AUDIO_MAX_TRACKS];
|
||||
/** @brief Storage behind the default `camera`. Point `camera` elsewhere rather than reaching for this. */
|
||||
extern SDL_FRect _akgl_camera;
|
||||
/** @brief Storage behind the default `akgl_camera`. Point `akgl_camera` elsewhere rather than reaching for this. */
|
||||
extern SDL_FRect akgl_default_camera;
|
||||
/** @brief The one game object: metadata, timing, and FPS accounting. */
|
||||
extern akgl_Game game;
|
||||
/** @brief Storage behind the default `renderer`. */
|
||||
extern akgl_RenderBackend _akgl_renderer;
|
||||
/** @brief Storage behind the default `physics`. */
|
||||
extern akgl_PhysicsBackend _akgl_physics;
|
||||
/** @brief Storage behind the default `gamemap`. */
|
||||
extern akgl_Tilemap _akgl_gamemap;
|
||||
extern akgl_Game akgl_game;
|
||||
/** @brief Storage behind the default `akgl_renderer`. */
|
||||
extern akgl_RenderBackend akgl_default_renderer;
|
||||
/** @brief Storage behind the default `akgl_physics`. */
|
||||
extern akgl_PhysicsBackend akgl_default_physics;
|
||||
/** @brief Storage behind the default `akgl_gamemap`. */
|
||||
extern akgl_Tilemap akgl_default_gamemap;
|
||||
|
||||
/** @brief Currently active tilemap. */
|
||||
extern akgl_Tilemap *gamemap;
|
||||
extern akgl_Tilemap *akgl_gamemap;
|
||||
/** @brief Currently active renderer. */
|
||||
extern akgl_RenderBackend *renderer;
|
||||
extern akgl_RenderBackend *akgl_renderer;
|
||||
/** @brief Currently active physics backend. */
|
||||
extern akgl_PhysicsBackend *physics;
|
||||
extern akgl_PhysicsBackend *akgl_physics;
|
||||
/** @brief Currently active camera. */
|
||||
extern SDL_FRect *camera;
|
||||
extern SDL_FRect *akgl_camera;
|
||||
|
||||
/**
|
||||
* @brief True when every bit of `y` is set in `x`. Not "any of them" -- all of them.
|
||||
@@ -141,14 +155,14 @@ extern SDL_FRect *camera;
|
||||
* pools, create the registries, hand SDL the app metadata, clear the control
|
||||
* maps, `SDL_Init` video/gamepad/audio, load the bundled controller database,
|
||||
* open any attached gamepads, start SDL_mixer and SDL_ttf, and finally point
|
||||
* `renderer`, `physics`, `camera`, and `gamemap` at their default storage.
|
||||
* `akgl_renderer`, `akgl_physics`, `akgl_camera`, and `akgl_gamemap` at their default storage.
|
||||
*
|
||||
* What it does *not* do: create the window, choose a physics backend, or load
|
||||
* any configuration. Those read properties, so they come after the caller has
|
||||
* set them. See the sequence at the top of this file.
|
||||
*
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If `game.name`, `game.version`, or `game.uri` is
|
||||
* @throws AKERR_NULLPOINTER If `akgl_game.name`, `akgl_game.version`, or `akgl_game.uri` is
|
||||
* empty. All three are required and there are no defaults -- the
|
||||
* window title, SDL's app metadata, and the savegame compatibility
|
||||
* check are all built from them.
|
||||
@@ -168,7 +182,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init();
|
||||
* @brief Declared but never defined. Do not call.
|
||||
*
|
||||
* There is no definition anywhere in the library, so a translation unit that
|
||||
* calls this compiles and then fails to link. Screen setup is akgl_render_init2d.
|
||||
* calls this compiles and then fails to link. Screen setup is akgl_render_2d_init.
|
||||
* Tracked in TODO.md under header/implementation surface drift.
|
||||
*
|
||||
* @return Nothing; it cannot be called.
|
||||
@@ -178,13 +192,13 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init_screen();
|
||||
* @brief Count this frame, and recompute the frame rate once a second has passed.
|
||||
*
|
||||
* Called at the top of akgl_game_update, so a caller running its own loop needs
|
||||
* to call it itself. `game.fps` is only refreshed when a full second has
|
||||
* to call it itself. `akgl_game.fps` is only refreshed when a full second has
|
||||
* elapsed, so it is a completed-second average rather than an instantaneous
|
||||
* figure -- and it reads 0 for the first second of the process, which is under
|
||||
* the low-FPS threshold and so fires `lowfpsfunc` on every frame until the first
|
||||
* second is up.
|
||||
*/
|
||||
void akgl_game_updateFPS();
|
||||
void akgl_game_update_fps();
|
||||
/**
|
||||
* @brief Write the game state and the name-to-pointer tables to a save file.
|
||||
*
|
||||
@@ -236,9 +250,9 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath);
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath);
|
||||
/**
|
||||
* @brief The default `game.lowfpsfunc`: log the current frame rate.
|
||||
* @brief The default `akgl_game.lowfpsfunc`: log the current frame rate.
|
||||
*
|
||||
* Called from akgl_game_updateFPS on every frame where `game.fps` is under 30.
|
||||
* Called from akgl_game_update_fps on every frame where `akgl_game.fps` is under 30.
|
||||
* It is a placeholder -- the point of the hook is that a game can replace it
|
||||
* with something that actually sheds work.
|
||||
*/
|
||||
@@ -251,15 +265,16 @@ void akgl_game_lowfps(void);
|
||||
*
|
||||
* @return `NULL` once the lock is held, otherwise an error context owned by the
|
||||
* caller.
|
||||
* @throws AKGL_ERR_SDL If the budget runs out with the lock still held
|
||||
* elsewhere. The message carries `SDL_GetError()`, which after a failed
|
||||
* `SDL_TryLockMutex` is usually stale or empty -- the status is the
|
||||
* signal, not the text.
|
||||
* @throws AKGL_ERR_SDL If #AKGL_GAME_STATE_LOCK_BUDGET_MS elapses with the lock
|
||||
* still held elsewhere. `SDL_GetError()` is appended for whatever it is
|
||||
* worth, but after a failed `SDL_TryLockMutex` it is usually stale or
|
||||
* empty -- contention is reported by the return value, not by an error
|
||||
* string. The status is the signal, not the text.
|
||||
*
|
||||
* @note The budget is meant to be one second but is not: the loop counts against
|
||||
* #AKGL_TIME_ONESEC_MS, which is nanoseconds-per-millisecond (1000000)
|
||||
* rather than milliseconds-per-second, so it retries 10,000 times at 100 ms
|
||||
* and gives up after roughly 16 minutes. TODO.md item 6.
|
||||
* @note Before 0.5.0 the loop counted against a constant named
|
||||
* `AKGL_TIME_ONESEC_MS` that held 1000000, so it retried 10,000 times at
|
||||
* 100 ms and blocked for roughly sixteen minutes rather than the one
|
||||
* second documented here.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_lock(void);
|
||||
/**
|
||||
@@ -276,8 +291,8 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void);
|
||||
*
|
||||
* Takes the state lock, counts the frame, then walks layers 0 through
|
||||
* #AKGL_TILEMAP_MAX_LAYERS calling each live actor's `updatefunc`, optionally
|
||||
* rescaling it to the tilemap first. Then it steps `physics` and draws through
|
||||
* `renderer`, and releases the lock.
|
||||
* rescaling it to the tilemap first. Then it steps `akgl_physics` and draws through
|
||||
* `akgl_renderer`, and releases the lock.
|
||||
*
|
||||
* @param opflags Iterator flags. Optional -- `NULL` selects a default set that
|
||||
* sweeps one layer at a time, in which case `layerid` is advanced
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
#ifndef _AKGL_HEAP_H_
|
||||
#define _AKGL_HEAP_H_
|
||||
|
||||
#include "sprite.h"
|
||||
#include "actor.h"
|
||||
#include "character.h"
|
||||
#include "staticstring.h"
|
||||
#include <akgl/sprite.h>
|
||||
#include <akgl/actor.h>
|
||||
#include <akgl/character.h>
|
||||
#include <akgl/staticstring.h>
|
||||
#include <akerror.h>
|
||||
|
||||
#ifndef AKGL_MAX_HEAP_ACTOR
|
||||
@@ -52,15 +52,15 @@
|
||||
#endif
|
||||
|
||||
/** @brief The actor pool. Public so the render and physics sweeps can walk it directly instead of going through the registry. */
|
||||
extern akgl_Actor HEAP_ACTOR[AKGL_MAX_HEAP_ACTOR];
|
||||
extern akgl_Actor akgl_heap_actors[AKGL_MAX_HEAP_ACTOR];
|
||||
/** @brief The sprite pool. 16 per actor, on the assumption of one sprite per state combination. */
|
||||
extern akgl_Sprite HEAP_SPRITE[AKGL_MAX_HEAP_SPRITE];
|
||||
extern akgl_Sprite akgl_heap_sprites[AKGL_MAX_HEAP_SPRITE];
|
||||
/** @brief The spritesheet pool. Sized like the sprite pool, though sharing means far fewer are used in practice. */
|
||||
extern akgl_SpriteSheet HEAP_SPRITESHEET[AKGL_MAX_HEAP_SPRITESHEET];
|
||||
extern akgl_SpriteSheet akgl_heap_spritesheets[AKGL_MAX_HEAP_SPRITESHEET];
|
||||
/** @brief The character pool. */
|
||||
extern akgl_Character HEAP_CHARACTER[AKGL_MAX_HEAP_CHARACTER];
|
||||
extern akgl_Character akgl_heap_characters[AKGL_MAX_HEAP_CHARACTER];
|
||||
/** @brief The string pool. Every entry is PATH_MAX bytes, so this is the largest of the five by a wide margin. */
|
||||
extern akgl_String HEAP_STRING[AKGL_MAX_HEAP_STRING];
|
||||
extern akgl_String akgl_heap_strings[AKGL_MAX_HEAP_STRING];
|
||||
|
||||
/**
|
||||
* @brief Zero every pool, marking every slot free.
|
||||
@@ -195,11 +195,12 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_release_spritesheet(akgl_SpriteShee
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p ptr is `NULL`.
|
||||
*
|
||||
* @note It does not walk the state-to-sprite map, so the references the
|
||||
* character took in akgl_character_sprite_add are never given back, and
|
||||
* the SDL property set holding the map is leaked. Release the sprites
|
||||
* first with akgl_character_state_sprites_iterate and
|
||||
* #AKGL_ITERATOR_OP_RELEASE if you need them back.
|
||||
* @note At zero it also walks the state-to-sprite map with
|
||||
* akgl_character_state_sprites_iterate and #AKGL_ITERATOR_OP_RELEASE,
|
||||
* giving back every reference akgl_character_sprite_add took, and then
|
||||
* destroys the `SDL_PropertiesID` holding the map. Before 0.5.0 it did
|
||||
* neither, so loading and releasing characters level by level exhausted
|
||||
* the sprite pool and leaked a property set per character.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_release_character(akgl_Character *ptr);
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#ifndef _AKGL_ITERATOR_H_
|
||||
#define _AKGL_ITERATOR_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/** @brief Selects operations and an optional layer for actor traversal. */
|
||||
typedef struct {
|
||||
uint32_t flags; /**< Bitwise OR of the `AKGL_ITERATOR_OP_*` values below. */
|
||||
|
||||
@@ -26,11 +26,12 @@
|
||||
* - **The document itself is never modified.**
|
||||
*/
|
||||
|
||||
#ifndef _JSON_HELPERS_H_
|
||||
#define _JSON_HELPERS_H_
|
||||
#ifndef _AKGL_JSON_HELPERS_H_
|
||||
#define _AKGL_JSON_HELPERS_H_
|
||||
|
||||
#include <akerror.h>
|
||||
#include "staticstring.h"
|
||||
#include <jansson.h>
|
||||
#include <akgl/staticstring.h>
|
||||
|
||||
/**
|
||||
* @brief Read a nested object out of a JSON object.
|
||||
@@ -233,4 +234,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_array_index_string(json_t *arra
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_with_default(akerr_ErrorContext *e, void *defval, void *dest, uint32_t defsize);
|
||||
|
||||
#endif // _JSON_HELPERS_H_
|
||||
#endif // _AKGL_JSON_HELPERS_H_
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
* falling one.
|
||||
*/
|
||||
|
||||
#ifndef _PHYSICS_H_
|
||||
#define _PHYSICS_H_
|
||||
#ifndef _AKGL_PHYSICS_H_
|
||||
#define _AKGL_PHYSICS_H_
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <akerror.h>
|
||||
@@ -235,4 +235,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_factory(akgl_PhysicsBackend *sel
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *self, akgl_Iterator *opflags);
|
||||
|
||||
#endif // _PHYSICS_H_
|
||||
#endif // _AKGL_PHYSICS_H_
|
||||
|
||||
@@ -21,14 +21,15 @@
|
||||
* separate call, made by akgl_game_init but by nothing else. A program
|
||||
* that builds its own startup path and skips it gets a silently
|
||||
* no-op akgl_set_property and an akgl_get_property that always hands
|
||||
* back the caller's default, which in turn means akgl_render_init2d
|
||||
* back the caller's default, which in turn means akgl_render_2d_init
|
||||
* and akgl_physics_init_arcade quietly ignore their configuration.
|
||||
* TODO.md, "Known and still open" item 3.
|
||||
*/
|
||||
|
||||
#ifndef _REGISTRY_H_
|
||||
#define _REGISTRY_H_
|
||||
#ifndef _AKGL_REGISTRY_H_
|
||||
#define _AKGL_REGISTRY_H_
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <akgl/error.h>
|
||||
#include <akgl/staticstring.h>
|
||||
|
||||
@@ -124,7 +125,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_character();
|
||||
* Deliberately separate from akgl_registry_init, because configuration has to be
|
||||
* in place before the subsystems that read it start up. akgl_game_init creates
|
||||
* it; filling it in -- with akgl_registry_load_properties or akgl_set_property --
|
||||
* is the caller's job, and has to happen before akgl_render_init2d or
|
||||
* is the caller's job, and has to happen before akgl_render_2d_init or
|
||||
* akgl_physics_init_arcade run.
|
||||
*
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
@@ -138,7 +139,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_init_properties();
|
||||
*
|
||||
* Expects a document with a top-level `properties` object whose members are all
|
||||
* strings; each becomes one entry. Numbers are configured as strings here and
|
||||
* parsed by whoever reads them -- `game.screenwidth` is `"800"`, not `800`.
|
||||
* parsed by whoever reads them -- `akgl_game.screenwidth` is `"800"`, not `800`.
|
||||
*
|
||||
* @param fname Path to the JSON document. Required. Used verbatim.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
@@ -215,4 +216,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_set_property(char *name, char *value);
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_property(char *name, akgl_String **dest, char *def);
|
||||
|
||||
#endif //_REGISTRY_H_
|
||||
#endif //_AKGL_REGISTRY_H_
|
||||
|
||||
@@ -2,27 +2,27 @@
|
||||
* @file renderer.h
|
||||
* @brief The pluggable rendering backend: a record of function pointers plus an initializer.
|
||||
*
|
||||
* There is one backend shipped, the 2D SDL one, and `akgl_render_init2d` is its
|
||||
* There is one backend shipped, the 2D SDL one, and `akgl_render_2d_init` is its
|
||||
* initializer -- it creates the window and the `SDL_Renderer` and then fills in
|
||||
* the six `akgl_render_2d_*` entry points. A different renderer is a different
|
||||
* initializer populating the same struct, not a branch inside these functions.
|
||||
*
|
||||
* Those are two separable jobs, and a host that already owns an `SDL_Renderer`
|
||||
* -- an embedded interpreter, which must not create the window -- wants only the
|
||||
* second. akgl_render_bind2d() is that half on its own; akgl_render_init2d()
|
||||
* second. akgl_render_2d_bind() is that half on its own; akgl_render_2d_init()
|
||||
* makes the window and then calls it.
|
||||
*
|
||||
* Callers do not normally name the `akgl_render_2d_*` functions directly; they
|
||||
* go through the pointers on the backend (`renderer->frame_start(renderer)`),
|
||||
* which is what makes the swap possible. The global `renderer` in game.h is the
|
||||
* which is what makes the swap possible. The global `akgl_renderer` in game.h is the
|
||||
* instance the rest of the library draws through.
|
||||
*
|
||||
* A frame is `frame_start` (clear), any number of `draw_*` calls, then
|
||||
* `frame_end` (present).
|
||||
*/
|
||||
|
||||
#ifndef _RENDERER_H_
|
||||
#define _RENDERER_H_
|
||||
#ifndef _AKGL_RENDERER_H_
|
||||
#define _AKGL_RENDERER_H_
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
@@ -61,7 +61,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_shutdown(akgl_RenderBackend *s
|
||||
* it is checked -- a `NULL` @p self is a crash, not an error.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If `self->sdl_renderer` is `NULL`, which means the
|
||||
* backend was never run through akgl_render_init2d.
|
||||
* backend was never run through akgl_render_2d_init.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_start(akgl_RenderBackend *self);
|
||||
/**
|
||||
@@ -70,7 +70,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_start(akgl_RenderBackend
|
||||
* it is checked -- a `NULL` @p self is a crash, not an error.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If `self->sdl_renderer` is `NULL`, which means the
|
||||
* backend was never run through akgl_render_init2d.
|
||||
* backend was never run through akgl_render_2d_init.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_end(akgl_RenderBackend *self);
|
||||
/**
|
||||
@@ -116,12 +116,12 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_mesh(akgl_RenderBackend *
|
||||
* @brief Draw the whole scene: every tilemap layer, and the actors standing on it.
|
||||
*
|
||||
* Walks layers from 0 to #AKGL_TILEMAP_MAX_LAYERS. For each, it draws that layer
|
||||
* of the global `gamemap` through `camera` (if the map has that many layers),
|
||||
* of the global `akgl_gamemap` through `akgl_camera` (if the map has that many layers),
|
||||
* then sweeps the actor pool and calls `renderfunc` on every live actor whose
|
||||
* `layer` matches. Sweeping in layer order is what puts actors in front of the
|
||||
* scenery they stand on and behind the scenery they walk under.
|
||||
*
|
||||
* It reads the globals `gamemap`, `camera`, and `HEAP_ACTOR` directly rather
|
||||
* It reads the globals `akgl_gamemap`, `akgl_camera`, and `akgl_heap_actors` directly rather
|
||||
* than taking them as arguments, so there is exactly one world to draw.
|
||||
*
|
||||
* @param self The backend to draw through. Required.
|
||||
@@ -137,7 +137,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_mesh(akgl_RenderBackend *
|
||||
* @throws AKERR_* Whatever akgl_tilemap_draw or an actor's own `renderfunc`
|
||||
* raises; the first failure aborts the frame and propagates unchanged.
|
||||
*
|
||||
* @warning Neither the global `gamemap` nor a live actor's `renderfunc` is
|
||||
* @warning Neither the global `akgl_gamemap` nor a live actor's `renderfunc` is
|
||||
* checked before use, so drawing before akgl_tilemap_load, or with a
|
||||
* hand-built actor that was never run through akgl_actor_initialize,
|
||||
* is a crash rather than an error context.
|
||||
@@ -147,7 +147,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend
|
||||
/**
|
||||
* @brief Install the 2D backend's methods on a backend the caller owns.
|
||||
*
|
||||
* The vtable half of akgl_render_init2d(), with no window, no renderer, and no
|
||||
* The vtable half of akgl_render_2d_init(), with no window, no renderer, and no
|
||||
* property registry involved: it points @p self's six function pointers at the
|
||||
* `akgl_render_2d_*` entry points and returns. `sdl_renderer` is not touched,
|
||||
* so a caller who has already put its own `SDL_Renderer` there keeps it, and a
|
||||
@@ -156,7 +156,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend
|
||||
*
|
||||
* This is the entry point for a host that owns its own window -- it can drive
|
||||
* akgl_actor_render(), akgl_tilemap_draw() and the rest through a backend
|
||||
* libakgl never created. akgl_render_init2d() is the same thing with a window
|
||||
* libakgl never created. akgl_render_2d_init() is the same thing with a window
|
||||
* in front of it.
|
||||
*
|
||||
* @param self The backend to bind. Required. Only the method pointers are
|
||||
@@ -164,15 +164,15 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p self is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_bind2d(akgl_RenderBackend *self);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_bind(akgl_RenderBackend *self);
|
||||
|
||||
/**
|
||||
* @brief Create the window and SDL renderer, and bind the 2D backend's methods.
|
||||
*
|
||||
* Reads `game.screenwidth` and `game.screenheight` from the property registry
|
||||
* Reads `akgl_game.screenwidth` and `akgl_game.screenheight` from the property registry
|
||||
* (both defaulting to the string "0", which asks SDL for a zero-sized window),
|
||||
* creates the window and renderer with `game.uri` as the title, points `camera`
|
||||
* at the full screen rectangle, and then calls akgl_render_bind2d() to install
|
||||
* creates the window and renderer with `akgl_game.uri` as the title, points `akgl_camera`
|
||||
* at the full screen rectangle, and then calls akgl_render_2d_bind() to install
|
||||
* the six `akgl_render_2d_*` function pointers on @p self.
|
||||
*
|
||||
* Because the dimensions come from the registry, akgl_registry_init_properties
|
||||
@@ -184,7 +184,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_bind2d(akgl_RenderBackend *self);
|
||||
* pointers are overwritten; anything it held before is not released.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p self is `NULL`.
|
||||
* @throws AKERR_VALUE If `game.screenwidth` or `game.screenheight` is set to
|
||||
* @throws AKERR_VALUE If `akgl_game.screenwidth` or `akgl_game.screenheight` is set to
|
||||
* something that is not a base-10 integer.
|
||||
* @throws ERANGE If either dimension does not fit in an `int`.
|
||||
* @throws AKGL_ERR_SDL If the window and renderer cannot be created. The message
|
||||
@@ -195,6 +195,6 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_bind2d(akgl_RenderBackend *self);
|
||||
* @note The two pooled strings holding the dimensions are only released on the
|
||||
* success path, so each failed initialization leaks two string slots.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_init2d(akgl_RenderBackend *self);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_init(akgl_RenderBackend *self);
|
||||
|
||||
#endif // _RENDERER_H_
|
||||
#endif // _AKGL_RENDERER_H_
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* both publish themselves in a registry under their name, which is how
|
||||
* akgl_character_sprite_add and the tilemap loader find them. Because loading a
|
||||
* sheet uploads a texture, the renderer has to exist first -- so
|
||||
* akgl_render_init2d, or akgl_game_init, before any of this.
|
||||
* akgl_render_2d_init, or akgl_game_init, before any of this.
|
||||
*/
|
||||
|
||||
#ifndef _AKGL_SPRITE_H_
|
||||
@@ -47,7 +47,7 @@ typedef struct {
|
||||
uint32_t frames; /**< How many entries of frameids are in use. */
|
||||
uint32_t width; /**< Frame width in pixels; also the horizontal stride used to find a frame on the sheet. */
|
||||
uint32_t height; /**< Frame height in pixels; the vertical stride once frames wrap to the next row. */
|
||||
uint32_t speed; /**< Nanoseconds one frame is held. Read from JSON in milliseconds and scaled by #AKGL_TIME_ONESEC_MS, which despite its name is nanoseconds-per-millisecond. TODO.md item 6. */
|
||||
uint32_t speed; /**< Nanoseconds one frame is held. Read from JSON in milliseconds and scaled by #AKGL_TIME_ONEMS_NS. */
|
||||
bool loop; /**< Restart from frame 0 when the last frame is reached, instead of holding it. */
|
||||
bool loopReverse; /**< Play back down to frame 0 instead of jumping to it -- a ping-pong loop. */
|
||||
char name[AKGL_SPRITE_MAX_NAME_LENGTH]; /**< Registry key, from the JSON `name` field. */
|
||||
@@ -82,7 +82,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_sprite_initialize(akgl_Sprite *spr, char
|
||||
/**
|
||||
* @brief Load an image file as a texture and publish it as a spritesheet.
|
||||
*
|
||||
* Uploads @p filename through SDL_image using the global `renderer`, so the
|
||||
* Uploads @p filename through SDL_image using the global `akgl_renderer`, so the
|
||||
* renderer must already be initialized. The sheet is registered in
|
||||
* #AKGL_REGISTRY_SPRITESHEET under @p filename, which is the key
|
||||
* akgl_sprite_load_json looks it up by to avoid loading the same image twice.
|
||||
@@ -166,6 +166,6 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_sprite_load_json(char *filename);
|
||||
* `NULL`. Note that `sheet->texture` is dereferenced without a check,
|
||||
* so a registered-but-unloaded sheet is a crash rather than an error.
|
||||
*/
|
||||
akerr_ErrorContext *akgl_sprite_sheet_coords_for_frame(akgl_Sprite *self, SDL_FRect *srccoords, uint8_t frameid);
|
||||
akerr_ErrorContext *akgl_spritesheet_coords_for_frame(akgl_Sprite *self, SDL_FRect *srccoords, uint8_t frameid);
|
||||
|
||||
#endif //_AKGL_SPRITE_H_
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
* these functions truncate rather than grow, and truncation is silent.
|
||||
*/
|
||||
|
||||
#ifndef _STRING_H_
|
||||
#define _STRING_H_
|
||||
#ifndef _AKGL_STATICSTRING_H_
|
||||
#define _AKGL_STATICSTRING_H_
|
||||
|
||||
#include "string.h"
|
||||
#include <string.h>
|
||||
#include <akerror.h>
|
||||
#include <limits.h>
|
||||
|
||||
@@ -68,4 +68,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_string_initialize(akgl_String *obj, char
|
||||
* this path is unreachable rather than merely rare.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_string_copy(akgl_String *src, akgl_String *dst, int count);
|
||||
#endif //_STRING_H_
|
||||
#endif //_AKGL_STATICSTRING_H_
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
* static text redrawn every frame.
|
||||
*/
|
||||
|
||||
#ifndef _TEXT_H_
|
||||
#define _TEXT_H_
|
||||
#ifndef _AKGL_TEXT_H_
|
||||
#define _AKGL_TEXT_H_
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
@@ -73,7 +73,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_unloadfont(char *name);
|
||||
* @brief Rasterize a string and blit it at a screen position, in one call.
|
||||
*
|
||||
* Renders blended (anti-aliased, alpha-blended) through SDL_ttf, uploads the
|
||||
* result to a texture, draws it through the global `renderer`, and destroys both
|
||||
* result to a texture, draws it through the global `akgl_renderer`, and destroys both
|
||||
* the texture and the surface before returning. The text is drawn at its natural
|
||||
* size -- @p x and @p y are the top-left corner, not a centre.
|
||||
*
|
||||
@@ -91,9 +91,9 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_unloadfont(char *name);
|
||||
* @param y Top edge of the text, in screen pixels.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p font or @p text is `NULL`; if the global
|
||||
* `renderer`, its `sdl_renderer`, or its `draw_texture` is `NULL` --
|
||||
* `akgl_renderer`, its `sdl_renderer`, or its `draw_texture` is `NULL` --
|
||||
* that last one is the state a backend is in between being allocated
|
||||
* and being run through akgl_render_bind2d(); if SDL_ttf cannot
|
||||
* and being run through akgl_render_2d_bind(); if SDL_ttf cannot
|
||||
* rasterize the string; or if the surface cannot be uploaded as a
|
||||
* texture. The last two carry `SDL_GetError()` and are a reused status
|
||||
* rather than a pointer problem.
|
||||
@@ -155,4 +155,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure(TTF_Font *font, char *text,
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure_wrapped(TTF_Font *font, char *text, int wraplength, int *w, int *h);
|
||||
|
||||
#endif // _TEXT_H_
|
||||
#endif // _AKGL_TEXT_H_
|
||||
|
||||
@@ -27,12 +27,12 @@
|
||||
*
|
||||
* Note the size of akgl_Tilemap: a layer's tile grid alone is 512x512 ints, and
|
||||
* a tileset's offset table is 65536 pairs. It is a megabytes-large object and
|
||||
* belongs in static storage, which is where `_akgl_gamemap` puts it. Do not put
|
||||
* belongs in static storage, which is where `akgl_default_gamemap` puts it. Do not put
|
||||
* one on the stack.
|
||||
*/
|
||||
|
||||
#ifndef _TILEMAP_H_
|
||||
#define _TILEMAP_H_
|
||||
#ifndef _AKGL_TILEMAP_H_
|
||||
#define _AKGL_TILEMAP_H_
|
||||
|
||||
#include <limits.h>
|
||||
#include <akgl/actor.h>
|
||||
@@ -151,7 +151,7 @@ typedef struct {
|
||||
akgl_TilemapLayer layers[AKGL_TILEMAP_MAX_LAYERS]; /**< The layers, in draw order: index 0 is furthest back. */
|
||||
|
||||
// Different levels may have different physics.
|
||||
bool use_own_physics; /**< Set when the map declared a `physics.model` property. A caller that honours it simulates through `physics` instead of the global backend. */
|
||||
bool use_own_physics; /**< Set when the map declared a `physics.model` property. A caller that honours it simulates through `akgl_physics` instead of the global backend. */
|
||||
akgl_PhysicsBackend physics; /**< This map's own backend, valid only when `use_own_physics` is set. */
|
||||
} akgl_Tilemap;
|
||||
|
||||
@@ -204,12 +204,12 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load(char *fname, akgl_Tilemap *
|
||||
* tile grid. An image layer ignores the viewport entirely and is drawn once at
|
||||
* the origin.
|
||||
*
|
||||
* Drawing goes through the global `renderer`, not through a backend passed in.
|
||||
* Drawing goes through the global `akgl_renderer`, not through a backend passed in.
|
||||
*
|
||||
* @param dest The map to draw from. Required. Not an output parameter
|
||||
* despite the name.
|
||||
* @param viewport The rectangle of the map, in map pixels, that is on screen.
|
||||
* Required. Usually the global `camera`.
|
||||
* Required. Usually the global `akgl_camera`.
|
||||
* @param layeridx Which layer to draw, 0 to `numlayers - 1`. **Not**
|
||||
* bounds-checked: an index past the end reads a neighbouring
|
||||
* layer, or past the array.
|
||||
@@ -490,4 +490,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_release(akgl_Tilemap *dest);
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_scale_actor(akgl_Tilemap *map, akgl_Actor *actor);
|
||||
|
||||
#endif //_TILEMAP_H_
|
||||
#endif //_AKGL_TILEMAP_H_
|
||||
|
||||
@@ -12,18 +12,20 @@
|
||||
* separating-axis test.
|
||||
*/
|
||||
|
||||
#ifndef _UTIL_H_
|
||||
#define _UTIL_H_
|
||||
#ifndef _AKGL_UTIL_H_
|
||||
#define _AKGL_UTIL_H_
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <akerror.h>
|
||||
#include <stdbool.h>
|
||||
#include <akgl/staticstring.h>
|
||||
|
||||
/** @brief An integer point. Carries a `z` the collision routines do not use. */
|
||||
typedef struct point {
|
||||
typedef struct akgl_Point {
|
||||
int x; /**< Horizontal position, in whatever space the caller is working in. */
|
||||
int y; /**< Vertical position. */
|
||||
int z; /**< Depth. Never written by akgl_rectangle_points and never read by the collision tests. */
|
||||
} point;
|
||||
} akgl_Point;
|
||||
|
||||
/**
|
||||
* @brief The four corners of an axis-aligned rectangle, precomputed.
|
||||
@@ -32,12 +34,12 @@ typedef struct point {
|
||||
* spans, so it wants the corners as points. akgl_rectangle_points derives one of
|
||||
* these from an `SDL_FRect`.
|
||||
*/
|
||||
typedef struct RectanglePoints {
|
||||
point topleft; /**< (x, y). */
|
||||
point topright; /**< (x + w, y). */
|
||||
point bottomleft; /**< (x, y + h). */
|
||||
point bottomright; /**< (x + w, y + h). */
|
||||
} RectanglePoints;
|
||||
typedef struct akgl_RectanglePoints {
|
||||
akgl_Point topleft; /**< (x, y). */
|
||||
akgl_Point topright; /**< (x + w, y). */
|
||||
akgl_Point bottomleft; /**< (x, y + h). */
|
||||
akgl_Point bottomright; /**< (x + w, y + h). */
|
||||
} akgl_RectanglePoints;
|
||||
|
||||
/**
|
||||
* @brief Do not use. Three open parentheses, two closes -- any expansion is a
|
||||
@@ -62,7 +64,7 @@ typedef struct RectanglePoints {
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p dest or @p rect is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_rectangle_points(RectanglePoints *dest, SDL_FRect *rect);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_rectangle_points(akgl_RectanglePoints *dest, SDL_FRect *rect);
|
||||
/**
|
||||
* @brief Test whether a point falls inside a rectangle, edges included.
|
||||
*
|
||||
@@ -78,7 +80,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_rectangle_points(RectanglePoints *dest,
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER If @p p, @p r, or @p collide is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_collide_point_rectangle(point *p, RectanglePoints *r, bool *collide);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_collide_point_rectangle(akgl_Point *p, akgl_RectanglePoints *r, bool *collide);
|
||||
/**
|
||||
* @brief Test whether two rectangles overlap, edges included.
|
||||
*
|
||||
@@ -188,4 +190,4 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_compare_sdl_surfaces(SDL_Surface *s1, SD
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_and_compare(SDL_Texture *t1, SDL_Texture *t2, int x, int y, int w, int h, char *writeout);
|
||||
|
||||
#endif // _UTIL_H_
|
||||
#endif // _AKGL_UTIL_H_
|
||||
|
||||
32
src/actor.c
32
src/actor.c
@@ -234,7 +234,7 @@ akerr_ErrorContext *akgl_actor_render(akgl_Actor *obj)
|
||||
|
||||
ATTEMPT {
|
||||
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 {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_KEY) {
|
||||
@@ -256,7 +256,7 @@ akerr_ErrorContext *akgl_actor_render(akgl_Actor *obj)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx,
|
||||
akgl_sprite_sheet_coords_for_frame(
|
||||
akgl_spritesheet_coords_for_frame(
|
||||
curSprite,
|
||||
&src,
|
||||
obj->curSpriteFrameId)
|
||||
@@ -266,16 +266,16 @@ akerr_ErrorContext *akgl_actor_render(akgl_Actor *obj)
|
||||
} FINISH(errctx, true);
|
||||
|
||||
if ( obj->parent != NULL ) {
|
||||
dest.x = (obj->parent->x + obj->x - camera->x);
|
||||
dest.y = (obj->parent->y + obj->y - camera->y);
|
||||
dest.x = (obj->parent->x + obj->x - akgl_camera->x);
|
||||
dest.y = (obj->parent->y + obj->y - akgl_camera->y);
|
||||
} else {
|
||||
dest.x = (obj->x - camera->x);
|
||||
dest.y = (obj->y - camera->y);
|
||||
dest.x = (obj->x - akgl_camera->x);
|
||||
dest.y = (obj->y - akgl_camera->y);
|
||||
}
|
||||
dest.w = 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);
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ void akgl_registry_iterate_actor(void *userdata, SDL_PropertiesID registry, cons
|
||||
CATCH(errctx, obj->updatefunc(obj));
|
||||
}
|
||||
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 {
|
||||
obj->scale = 1.0;
|
||||
}
|
||||
@@ -328,7 +328,7 @@ void akgl_registry_iterate_actor(void *userdata, SDL_PropertiesID registry, cons
|
||||
} FINISH_NORETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_on(akgl_Actor *obj, SDL_Event *event)
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_on(akgl_Actor *obj, SDL_Event *event)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "actor");
|
||||
@@ -342,7 +342,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_on(akgl_Actor *obj, SDL_
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_off(akgl_Actor *obj, SDL_Event *event)
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_off(akgl_Actor *obj, SDL_Event *event)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
||||
@@ -357,7 +357,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_off(akgl_Actor *obj, SDL
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_on(akgl_Actor *obj, SDL_Event *event)
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_on(akgl_Actor *obj, SDL_Event *event)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
||||
@@ -371,7 +371,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_on(akgl_Actor *obj, SDL
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_off(akgl_Actor *obj, SDL_Event *event)
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_off(akgl_Actor *obj, SDL_Event *event)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
||||
@@ -386,7 +386,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_right_off(akgl_Actor *obj, SD
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_on(akgl_Actor *obj, SDL_Event *event)
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_on(akgl_Actor *obj, SDL_Event *event)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
||||
@@ -400,7 +400,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_on(akgl_Actor *obj, SDL_Ev
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_off(akgl_Actor *obj, SDL_Event *event)
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_off(akgl_Actor *obj, SDL_Event *event)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
||||
@@ -415,7 +415,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_up_off(akgl_Actor *obj, SDL_E
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_down_on(akgl_Actor *obj, SDL_Event *event)
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_on(akgl_Actor *obj, SDL_Event *event)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
||||
@@ -429,7 +429,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_down_on(akgl_Actor *obj, SDL_
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_down_off(akgl_Actor *obj, SDL_Event *event)
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_off(akgl_Actor *obj, SDL_Event *event)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
||||
|
||||
10
src/assets.c
10
src/assets.c
@@ -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_Log("Loading music asset from %s", fname);
|
||||
bgm = MIX_LoadAudio(akgl_mixer, fname, true);
|
||||
FAIL_ZERO_BREAK(errctx, bgm, AKERR_NULLPOINTER, "Failed to load music asset %s : %s", fname, SDL_GetError());
|
||||
akgl_bgm = MIX_LoadAudio(akgl_mixer, fname, true);
|
||||
FAIL_ZERO_BREAK(errctx, akgl_bgm, AKERR_NULLPOINTER, "Failed to load music asset %s : %s", fname, SDL_GetError());
|
||||
|
||||
bgmtrack = MIX_CreateTrack(akgl_mixer);
|
||||
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(
|
||||
errctx,
|
||||
MIX_SetTrackAudio(bgmtrack, bgm),
|
||||
MIX_SetTrackAudio(bgmtrack, akgl_bgm),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
@@ -49,8 +49,8 @@ akerr_ErrorContext *akgl_load_start_bgm(char *fname)
|
||||
} CLEANUP {
|
||||
//IGNORE(akgl_heap_release_string(tmpstr));
|
||||
if ( errctx != NULL ) {
|
||||
if ( errctx->status != 0 && bgm != NULL) {
|
||||
MIX_DestroyAudio(bgm);
|
||||
if ( errctx->status != 0 && akgl_bgm != NULL) {
|
||||
MIX_DestroyAudio(akgl_bgm);
|
||||
}
|
||||
}
|
||||
} PROCESS(errctx) {
|
||||
|
||||
@@ -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, name, AKERR_NULLPOINTER, "NULL name string pointer");
|
||||
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();
|
||||
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_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_y", &obj->sy));
|
||||
CATCH(errctx, akgl_get_json_number_value(json, "acceleration_x", &obj->ax));
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <akgl/game.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
|
||||
@@ -258,7 +258,7 @@ akerr_ErrorContext *akgl_controller_handle_event(void *appstate, SDL_Event *even
|
||||
|
||||
ATTEMPT {
|
||||
for ( i = 0 ; i < AKGL_MAX_CONTROL_MAPS; i++ ) {
|
||||
curmap = &GAME_ControlMaps[i];
|
||||
curmap = &akgl_controlmaps[i];
|
||||
if ( curmap->target == NULL ) {
|
||||
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
|
||||
* actor up by the literal name `"player"` in #AKGL_REGISTRY_ACTOR instead of
|
||||
* being told which actor to act on, so it only ever drives one. The
|
||||
* akgl_Actor_cmhf_* handlers are the general form.
|
||||
* akgl_actor_cmhf_* handlers are the general form.
|
||||
*
|
||||
* Facing follows movement unless the actor sets `movement_controls_face`, which
|
||||
* is how an actor that aims independently of the direction it walks opts out.
|
||||
@@ -528,10 +528,10 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akg
|
||||
ATTEMPT {
|
||||
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);
|
||||
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);
|
||||
memcpy((void *)&GAME_ControlMaps[controlmapid].controls[newmapid], control, sizeof(akgl_Control));
|
||||
GAME_ControlMaps[controlmapid].nextMap = newmapid + 1;
|
||||
memcpy((void *)&akgl_controlmaps[controlmapid].controls[newmapid], control, sizeof(akgl_Control));
|
||||
akgl_controlmaps[controlmapid].nextMap = newmapid + 1;
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
@@ -548,7 +548,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
|
||||
// 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);
|
||||
memset((void *)&control, 0x00, sizeof(akgl_Control));
|
||||
controlmap = &GAME_ControlMaps[controlmapid];
|
||||
controlmap = &akgl_controlmaps[controlmapid];
|
||||
controlmap->kbid = kbid;
|
||||
controlmap->jsid = jsid;
|
||||
|
||||
@@ -561,32 +561,32 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
|
||||
control.key = SDLK_DOWN;
|
||||
control.event_on = SDL_EVENT_KEY_DOWN;
|
||||
control.event_off = SDL_EVENT_KEY_UP;
|
||||
control.handler_on = &akgl_Actor_cmhf_down_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_down_off;
|
||||
control.handler_on = &akgl_actor_cmhf_down_on;
|
||||
control.handler_off = &akgl_actor_cmhf_down_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
// Move up
|
||||
control.key = SDLK_UP;
|
||||
control.event_on = SDL_EVENT_KEY_DOWN;
|
||||
control.event_off = SDL_EVENT_KEY_UP;
|
||||
control.handler_on = &akgl_Actor_cmhf_up_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_up_off;
|
||||
control.handler_on = &akgl_actor_cmhf_up_on;
|
||||
control.handler_off = &akgl_actor_cmhf_up_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
// Move left
|
||||
control.key = SDLK_LEFT;
|
||||
control.event_on = SDL_EVENT_KEY_DOWN;
|
||||
control.event_off = SDL_EVENT_KEY_UP;
|
||||
control.handler_on = &akgl_Actor_cmhf_left_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_left_off;
|
||||
control.handler_on = &akgl_actor_cmhf_left_on;
|
||||
control.handler_off = &akgl_actor_cmhf_left_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
// Move right
|
||||
control.key = SDLK_RIGHT;
|
||||
control.event_on = SDL_EVENT_KEY_DOWN;
|
||||
control.event_off = SDL_EVENT_KEY_UP;
|
||||
control.handler_on = &akgl_Actor_cmhf_right_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_right_off;
|
||||
control.handler_on = &akgl_actor_cmhf_right_on;
|
||||
control.handler_off = &akgl_actor_cmhf_right_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
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.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN;
|
||||
control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP;
|
||||
control.handler_on = &akgl_Actor_cmhf_down_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_down_off;
|
||||
control.handler_on = &akgl_actor_cmhf_down_on;
|
||||
control.handler_off = &akgl_actor_cmhf_down_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
// Move up
|
||||
control.button = SDL_GAMEPAD_BUTTON_DPAD_UP;
|
||||
control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN;
|
||||
control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP;
|
||||
control.handler_on = &akgl_Actor_cmhf_up_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_up_off;
|
||||
control.handler_on = &akgl_actor_cmhf_up_on;
|
||||
control.handler_off = &akgl_actor_cmhf_up_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
// Move left
|
||||
control.button = SDL_GAMEPAD_BUTTON_DPAD_LEFT;
|
||||
control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN;
|
||||
control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP;
|
||||
control.handler_on = &akgl_Actor_cmhf_left_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_left_off;
|
||||
control.handler_on = &akgl_actor_cmhf_left_on;
|
||||
control.handler_off = &akgl_actor_cmhf_left_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
// Move right
|
||||
control.button = SDL_GAMEPAD_BUTTON_DPAD_RIGHT;
|
||||
control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN;
|
||||
control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP;
|
||||
control.handler_on = &akgl_Actor_cmhf_right_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_right_off;
|
||||
control.handler_on = &akgl_actor_cmhf_right_on;
|
||||
control.handler_off = &akgl_actor_cmhf_right_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
@@ -45,11 +45,11 @@ void akgl_draw_background(int w, int h)
|
||||
for (x = 0; x < w; x += dx) {
|
||||
/* use an 8x8 checkerboard pattern */
|
||||
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.y = (float)y;
|
||||
SDL_RenderFillRect(renderer->sdl_renderer, &rect);
|
||||
SDL_RenderFillRect(akgl_renderer->sdl_renderer, &rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
128
src/game.c
128
src/game.c
@@ -24,24 +24,24 @@
|
||||
#include <akgl/error.h>
|
||||
#include <akgl/SDL_GameControllerDB.h>
|
||||
|
||||
SDL_Window *window = NULL;
|
||||
SDL_Window *akgl_window = NULL;
|
||||
|
||||
// Currently active objects
|
||||
akgl_RenderBackend *renderer;
|
||||
akgl_PhysicsBackend *physics;
|
||||
SDL_FRect *camera;
|
||||
akgl_Tilemap *gamemap;
|
||||
akgl_RenderBackend *akgl_renderer;
|
||||
akgl_PhysicsBackend *akgl_physics;
|
||||
SDL_FRect *akgl_camera;
|
||||
akgl_Tilemap *akgl_gamemap;
|
||||
|
||||
// Default objects
|
||||
akgl_RenderBackend _akgl_renderer;
|
||||
akgl_PhysicsBackend _akgl_physics;
|
||||
SDL_FRect _akgl_camera;
|
||||
akgl_Tilemap _akgl_gamemap;
|
||||
akgl_RenderBackend akgl_default_renderer;
|
||||
akgl_PhysicsBackend akgl_default_physics;
|
||||
SDL_FRect akgl_default_camera;
|
||||
akgl_Tilemap akgl_default_gamemap;
|
||||
|
||||
MIX_Audio *bgm = NULL;
|
||||
MIX_Audio *akgl_bgm = NULL;
|
||||
MIX_Mixer *akgl_mixer = NULL;
|
||||
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)
|
||||
{
|
||||
@@ -69,7 +69,7 @@ typedef char akgl_game_save_name_field_fits[
|
||||
((AKGL_ACTOR_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_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.
|
||||
@@ -125,7 +125,7 @@ static akerr_ErrorContext *read_exact(void *ptr, size_t size, size_t nmemb, FILE
|
||||
|
||||
void akgl_game_lowfps(void)
|
||||
{
|
||||
SDL_Log("Low FPS! %d", game.fps);
|
||||
SDL_Log("Low FPS! %d", akgl_game.fps);
|
||||
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
|
||||
// as "Unknown Error" in the stack trace the caller is left holding.
|
||||
PASS(e, akgl_error_init());
|
||||
strncpy((char *)&game.libversion, AKGL_VERSION, 32);
|
||||
game.gameStartTime = SDL_GetTicksNS();
|
||||
game.lastIterTime = game.gameStartTime;
|
||||
game.lastFPSTime = game.gameStartTime;
|
||||
game.lowfpsfunc = &akgl_game_lowfps;
|
||||
game.statelock = SDL_CreateMutex();
|
||||
FAIL_ZERO_RETURN(e, game.statelock, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
strncpy((char *)&akgl_game.libversion, AKGL_VERSION, 32);
|
||||
akgl_game.gameStartTime = SDL_GetTicksNS();
|
||||
akgl_game.lastIterTime = akgl_game.gameStartTime;
|
||||
akgl_game.lastFPSTime = akgl_game.gameStartTime;
|
||||
akgl_game.lowfpsfunc = &akgl_game_lowfps;
|
||||
akgl_game.statelock = SDL_CreateMutex();
|
||||
FAIL_ZERO_RETURN(e, akgl_game.statelock, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
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 *)&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.name), AKERR_NULLPOINTER, "Must provide game name");
|
||||
FAIL_ZERO_RETURN(e, strlen((char *)&akgl_game.version), AKERR_NULLPOINTER, "Must provide game version");
|
||||
FAIL_ZERO_RETURN(e, strlen((char *)&akgl_game.uri), AKERR_NULLPOINTER, "Must provide game uri");
|
||||
PASS(e, akgl_heap_init());
|
||||
PASS(e, akgl_registry_init_actor());
|
||||
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_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++ ) {
|
||||
memset(&GAME_ControlMaps[i], 0x00, sizeof(akgl_ControlMap));
|
||||
memset(&akgl_controlmaps[i], 0x00, sizeof(akgl_ControlMap));
|
||||
}
|
||||
|
||||
FAIL_ZERO_RETURN(
|
||||
@@ -204,10 +204,10 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
|
||||
"Couldn't initialize front engine: %s",
|
||||
SDL_GetError());
|
||||
|
||||
camera = &_akgl_camera;
|
||||
renderer = &_akgl_renderer;
|
||||
physics = &_akgl_physics;
|
||||
gamemap = &_akgl_gamemap;
|
||||
akgl_camera = &akgl_default_camera;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
akgl_physics = &akgl_default_physics;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
|
||||
PASS(e, akgl_game_state_unlock());
|
||||
SUCCEED_RETURN(e);
|
||||
@@ -216,39 +216,51 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_lock(void)
|
||||
{
|
||||
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 ) {
|
||||
if ( SDL_TryLockMutex(game.statelock) == true ) {
|
||||
while ( waitedms < AKGL_GAME_STATE_LOCK_BUDGET_MS ) {
|
||||
if ( SDL_TryLockMutex(akgl_game.statelock) == true ) {
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
totaltime += 100;
|
||||
SDL_Delay(100);
|
||||
waitedms += AKGL_GAME_STATE_LOCK_RETRY_MS;
|
||||
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)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
SDL_UnlockMutex(game.statelock);
|
||||
SDL_UnlockMutex(akgl_game.statelock);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
void akgl_game_updateFPS()
|
||||
void akgl_game_update_fps()
|
||||
{
|
||||
SDL_Time curTime;
|
||||
curTime = SDL_GetTicksNS();
|
||||
if ( (curTime - game.lastFPSTime) > AKGL_TIME_ONESEC_NS ) {
|
||||
game.fps = game.framesSinceUpdate;
|
||||
game.framesSinceUpdate = 0;
|
||||
game.lastFPSTime = curTime;
|
||||
if ( (curTime - akgl_game.lastFPSTime) > AKGL_TIME_ONESEC_NS ) {
|
||||
akgl_game.fps = akgl_game.framesSinceUpdate;
|
||||
akgl_game.framesSinceUpdate = 0;
|
||||
akgl_game.lastFPSTime = curTime;
|
||||
}
|
||||
if ( game.fps < 30 ) {
|
||||
game.lowfpsfunc();
|
||||
if ( akgl_game.fps < 30 ) {
|
||||
akgl_game.lowfpsfunc();
|
||||
}
|
||||
game.framesSinceUpdate += 1;
|
||||
game.lastIterTime = curTime;
|
||||
akgl_game.framesSinceUpdate += 1;
|
||||
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.
|
||||
*
|
||||
* 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 props #AKGL_REGISTRY_CHARACTER, supplied by SDL.
|
||||
@@ -371,7 +383,7 @@ void akgl_game_save_charactername_iterator(void *userdata, SDL_PropertiesID prop
|
||||
ATTEMPT {
|
||||
FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer");
|
||||
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));
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
@@ -439,7 +451,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp)
|
||||
AKGL_REGISTRY_CHARACTER,
|
||||
&akgl_game_save_charactername_iterator,
|
||||
(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));
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
@@ -455,7 +467,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath)
|
||||
ATTEMPT {
|
||||
FAIL_ZERO_BREAK(e, fpath, AKERR_NULLPOINTER, "NULL file path");
|
||||
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));
|
||||
} CLEANUP {
|
||||
// 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, 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("game", (char *)&savegame.version, (char *)&game.version));
|
||||
CATCH(e, akgl_game_load_versioncmp("game", (char *)&savegame.version, (char *)&akgl_game.version));
|
||||
FAIL_NONZERO_RETURN(
|
||||
e,
|
||||
strncmp((char *)&savegame.name, (char *)&game.name, 256),
|
||||
strncmp((char *)&savegame.name, (char *)&akgl_game.name, 256),
|
||||
AKERR_API,
|
||||
"Savegame is not compatible with this game");
|
||||
FAIL_NONZERO_RETURN(
|
||||
e,
|
||||
strncmp((char *)&savegame.uri, (char *)&game.uri, 256),
|
||||
strncmp((char *)&savegame.uri, (char *)&akgl_game.uri, 256),
|
||||
AKERR_API,
|
||||
"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
|
||||
actormap = SDL_CreateProperties();
|
||||
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());
|
||||
|
||||
akgl_game_updateFPS();
|
||||
akgl_game_update_fps();
|
||||
|
||||
for ( int i = 0; i < AKGL_TILEMAP_MAX_LAYERS; i++ ) {
|
||||
if ( opflags == &defflags ) {
|
||||
opflags->layerid = i;
|
||||
}
|
||||
for ( int j = 0; j < AKGL_MAX_HEAP_ACTOR; j++ ) {
|
||||
actor = &HEAP_ACTOR[j];
|
||||
actor = &akgl_heap_actors[j];
|
||||
if ( actor->refcount == 0 ) {
|
||||
continue;
|
||||
}
|
||||
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 {
|
||||
actor->scale = 1.0;
|
||||
}
|
||||
PASS(e, actor->updatefunc(actor));
|
||||
}
|
||||
}
|
||||
PASS(e, physics->simulate(physics, NULL));
|
||||
PASS(e, renderer->draw_world(renderer, NULL));
|
||||
PASS(e, akgl_physics->simulate(akgl_physics, NULL));
|
||||
PASS(e, akgl_renderer->draw_world(akgl_renderer, NULL));
|
||||
PASS(e, akgl_game_state_unlock());
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
72
src/heap.c
72
src/heap.c
@@ -14,11 +14,11 @@
|
||||
#include <akgl/iterator.h>
|
||||
#include <akgl/error.h>
|
||||
|
||||
akgl_Actor HEAP_ACTOR[AKGL_MAX_HEAP_ACTOR];
|
||||
akgl_Sprite HEAP_SPRITE[AKGL_MAX_HEAP_SPRITE];
|
||||
akgl_SpriteSheet HEAP_SPRITESHEET[AKGL_MAX_HEAP_SPRITESHEET];
|
||||
akgl_Character HEAP_CHARACTER[AKGL_MAX_HEAP_CHARACTER];
|
||||
akgl_String HEAP_STRING[AKGL_MAX_HEAP_STRING];
|
||||
akgl_Actor akgl_heap_actors[AKGL_MAX_HEAP_ACTOR];
|
||||
akgl_Sprite akgl_heap_sprites[AKGL_MAX_HEAP_SPRITE];
|
||||
akgl_SpriteSheet akgl_heap_spritesheets[AKGL_MAX_HEAP_SPRITESHEET];
|
||||
akgl_Character akgl_heap_characters[AKGL_MAX_HEAP_CHARACTER];
|
||||
akgl_String akgl_heap_strings[AKGL_MAX_HEAP_STRING];
|
||||
|
||||
akerr_ErrorContext *akgl_heap_init()
|
||||
{
|
||||
@@ -26,16 +26,16 @@ akerr_ErrorContext *akgl_heap_init()
|
||||
int i = 0;
|
||||
PASS(errctx, akgl_heap_init_actor());
|
||||
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++) {
|
||||
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++) {
|
||||
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++) {
|
||||
memset(&HEAP_STRING[i], 0x00, sizeof(akgl_String));
|
||||
memset(&akgl_heap_strings[i], 0x00, sizeof(akgl_String));
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -44,7 +44,7 @@ akerr_ErrorContext *akgl_heap_init_actor(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
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);
|
||||
}
|
||||
@@ -53,10 +53,10 @@ akerr_ErrorContext *akgl_heap_next_actor(akgl_Actor **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
for (int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
|
||||
if ( HEAP_ACTOR[i].refcount != 0 ) {
|
||||
if ( akgl_heap_actors[i].refcount != 0 ) {
|
||||
continue;
|
||||
}
|
||||
*dest = &HEAP_ACTOR[i];
|
||||
*dest = &akgl_heap_actors[i];
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
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);
|
||||
for (int i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) {
|
||||
if ( HEAP_SPRITE[i].refcount != 0 ) {
|
||||
if ( akgl_heap_sprites[i].refcount != 0 ) {
|
||||
continue;
|
||||
}
|
||||
*dest = &HEAP_SPRITE[i];
|
||||
*dest = &akgl_heap_sprites[i];
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
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);
|
||||
for (int i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) {
|
||||
if ( HEAP_SPRITESHEET[i].refcount != 0 ) {
|
||||
if ( akgl_heap_spritesheets[i].refcount != 0 ) {
|
||||
continue;
|
||||
}
|
||||
*dest = &HEAP_SPRITESHEET[i];
|
||||
*dest = &akgl_heap_spritesheets[i];
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
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);
|
||||
for (int i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) {
|
||||
if ( HEAP_CHARACTER[i].refcount != 0 ) {
|
||||
if ( akgl_heap_characters[i].refcount != 0 ) {
|
||||
continue;
|
||||
}
|
||||
*dest = &HEAP_CHARACTER[i];
|
||||
*dest = &akgl_heap_characters[i];
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
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);
|
||||
for (int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
|
||||
if ( HEAP_STRING[i].refcount != 0 ) {
|
||||
if ( akgl_heap_strings[i].refcount != 0 ) {
|
||||
continue;
|
||||
}
|
||||
*dest = &HEAP_STRING[i];
|
||||
HEAP_STRING[i].refcount += 1;
|
||||
*dest = &akgl_heap_strings[i];
|
||||
akgl_heap_strings[i].refcount += 1;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_heap_release_character(akgl_Character *basechar)
|
||||
akerr_ErrorContext *akgl_heap_release_character(akgl_Character *ptr)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
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);
|
||||
|
||||
if ( basechar->refcount > 0 ) {
|
||||
basechar->refcount -= 1;
|
||||
if ( ptr->refcount > 0 ) {
|
||||
ptr->refcount -= 1;
|
||||
}
|
||||
if ( basechar->refcount == 0 ) {
|
||||
SDL_ClearProperty(AKGL_REGISTRY_CHARACTER, (char *)&basechar->name);
|
||||
memset(basechar, 0x00, sizeof(akgl_Character));
|
||||
if ( ptr->refcount == 0 ) {
|
||||
// Give back every sprite reference akgl_character_sprite_add took, and
|
||||
// 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);
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *se
|
||||
|
||||
|
||||
for ( int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
|
||||
actor = &HEAP_ACTOR[i];
|
||||
actor = &akgl_heap_actors[i];
|
||||
if ( actor->refcount == 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <akerror.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 *height = NULL;
|
||||
@@ -33,21 +33,21 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_init2d(akgl_RenderBackend *self)
|
||||
|
||||
FAIL_ZERO_RETURN(
|
||||
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,
|
||||
"Couldn't create window/renderer: %s",
|
||||
SDL_GetError());
|
||||
|
||||
camera->x = 0;
|
||||
camera->y = 0;
|
||||
camera->w = screenwidth;
|
||||
camera->h = screenheight;
|
||||
akgl_camera->x = 0;
|
||||
akgl_camera->y = 0;
|
||||
akgl_camera->w = screenwidth;
|
||||
akgl_camera->h = screenheight;
|
||||
|
||||
PASS(e, akgl_render_bind2d(self));
|
||||
PASS(e, akgl_render_2d_bind(self));
|
||||
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);
|
||||
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)));
|
||||
}
|
||||
for ( int i = 0; i < AKGL_TILEMAP_MAX_LAYERS ; i++ ) {
|
||||
if ( i < gamemap->numlayers ) {
|
||||
PASS(e, akgl_tilemap_draw(gamemap, camera, i));
|
||||
if ( i < akgl_gamemap->numlayers ) {
|
||||
PASS(e, akgl_tilemap_draw(akgl_gamemap, akgl_camera, i));
|
||||
}
|
||||
for ( int j = 0; j < AKGL_MAX_HEAP_ACTOR ; j++ ) {
|
||||
actor = &HEAP_ACTOR[j];
|
||||
actor = &akgl_heap_actors[j];
|
||||
if ( actor->refcount == 0 ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include <akgl/iterator.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);
|
||||
|
||||
@@ -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, "height", &obj->height));
|
||||
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, "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);
|
||||
|
||||
//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(
|
||||
|
||||
14
src/text.c
14
src/text.c
@@ -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");
|
||||
// Checked before anything is rasterized, and checked at all because a
|
||||
// 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_init2d used to be the only escape from.
|
||||
FAIL_ZERO_RETURN(errctx, renderer, AKERR_NULLPOINTER, "No renderer backend");
|
||||
FAIL_ZERO_RETURN(errctx, 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");
|
||||
// akgl_render_2d_bind has a NULL draw_texture -- which is exactly the state
|
||||
// akgl_render_2d_init used to be the only escape from.
|
||||
FAIL_ZERO_RETURN(errctx, akgl_renderer, AKERR_NULLPOINTER, "No renderer backend");
|
||||
FAIL_ZERO_RETURN(errctx, akgl_renderer->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
|
||||
FAIL_ZERO_RETURN(errctx, akgl_renderer->draw_texture, AKERR_NULLPOINTER, "Renderer backend has no draw_texture");
|
||||
if ( wraplength > 0 ) {
|
||||
textsurf = TTF_RenderText_Blended_Wrapped(
|
||||
font,
|
||||
@@ -85,12 +85,12 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_rendertextat(TTF_Font *font, char *
|
||||
color);
|
||||
}
|
||||
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());
|
||||
dest.x = x;
|
||||
dest.y = y;
|
||||
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_DestroySurface(textsurf);
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
@@ -201,7 +201,7 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilema
|
||||
} PROCESS(e) {
|
||||
} 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());
|
||||
|
||||
SUCCEED_RETURN(e);
|
||||
@@ -496,7 +496,7 @@ akerr_ErrorContext *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *ro
|
||||
);
|
||||
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());
|
||||
dest->layers[layerid].width = dest->layers[layerid].texture->w;
|
||||
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;
|
||||
dest.w = map->layers[layeridx].width;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -853,7 +853,7 @@ akerr_ErrorContext *akgl_tilemap_draw(akgl_Tilemap *map, SDL_FRect *viewport, in
|
||||
dest.y,
|
||||
dest.w,
|
||||
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;
|
||||
@@ -908,7 +908,7 @@ akerr_ErrorContext *akgl_tilemap_draw_tileset(akgl_Tilemap *map, int tilesetidx)
|
||||
dest.y,
|
||||
dest.w,
|
||||
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);
|
||||
}
|
||||
|
||||
24
src/util.c
24
src/util.c
@@ -169,10 +169,10 @@ akerr_ErrorContext *akgl_path_relative_from(char *path, char *from, akgl_String
|
||||
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);
|
||||
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");
|
||||
dest->topleft.x = rect->x;
|
||||
dest->topleft.y = rect->y;
|
||||
@@ -185,11 +185,11 @@ akerr_ErrorContext *akgl_rectangle_points(RectanglePoints *dest, SDL_FRect *rect
|
||||
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);
|
||||
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");
|
||||
if ( (p->x >= rp->topleft.x) && (p->y >= rp->topleft.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)
|
||||
{
|
||||
RectanglePoints r1p;
|
||||
RectanglePoints r2p;
|
||||
akgl_RectanglePoints r1p;
|
||||
akgl_RectanglePoints r2p;
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, r1, 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");
|
||||
|
||||
CATCH(errctx, akgl_heap_next_string(&tmpstring));
|
||||
SDL_RenderClear(renderer->sdl_renderer);
|
||||
CATCH(errctx, renderer->draw_texture(renderer, t1, &src, &dest, 0, NULL, SDL_FLIP_NONE));
|
||||
s1 = SDL_RenderReadPixels(renderer->sdl_renderer, &read);
|
||||
SDL_RenderClear(akgl_renderer->sdl_renderer);
|
||||
CATCH(errctx, akgl_renderer->draw_texture(akgl_renderer, t1, &src, &dest, 0, NULL, SDL_FLIP_NONE));
|
||||
s1 = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, &read);
|
||||
FAIL_ZERO_BREAK(errctx, s1, AKGL_ERR_SDL, "Failed to read pixels from renderer");
|
||||
|
||||
if ( writeout != NULL ) {
|
||||
@@ -294,10 +294,10 @@ akerr_ErrorContext *akgl_render_and_compare(SDL_Texture *t1, SDL_Texture *t2, in
|
||||
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));
|
||||
s2 = SDL_RenderReadPixels(renderer->sdl_renderer, &read);
|
||||
CATCH(errctx, akgl_renderer->draw_texture(akgl_renderer, t1, &src, &dest, 0, NULL, SDL_FLIP_NONE));
|
||||
s2 = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, &read);
|
||||
FAIL_ZERO_BREAK(errctx, s2, AKGL_ERR_SDL, "Failed to read pixels from renderer");
|
||||
|
||||
CATCH(errctx, akgl_compare_sdl_surfaces(s1, s2));
|
||||
|
||||
@@ -266,10 +266,10 @@ akerr_ErrorContext *test_actor_manage_children(void)
|
||||
CATCH(errctx, akgl_heap_release_actor(parent));
|
||||
// All actor objects on the heap should be empty now
|
||||
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, HEAP_ACTOR[i].parent, 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, akgl_heap_actors[i].parent, AKERR_VALUE, "Actor not properly cleared");
|
||||
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");
|
||||
goto _test_actor_addchild_heaprelease_cleanup;
|
||||
}
|
||||
@@ -375,7 +375,7 @@ akerr_ErrorContext *test_actor_control_handlers_on(void)
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
actor.basechar = &basechar;
|
||||
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),
|
||||
"left on did not set MOVING_LEFT (state %d)", actor.state);
|
||||
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.
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
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),
|
||||
"right on did not set MOVING_RIGHT (state %d)", actor.state);
|
||||
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.
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
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),
|
||||
"up on did not set MOVING_UP (state %d)", actor.state);
|
||||
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));
|
||||
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),
|
||||
"down on did not set MOVING_DOWN (state %d)", actor.state);
|
||||
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.
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
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_right_on(&actor, &event), "right on after left");
|
||||
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_ASSERT(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_LEFT),
|
||||
"turning right left MOVING_LEFT set (state %d)", actor.state);
|
||||
} 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.ay = 5; actor.ey = 5; actor.ty = 5; actor.vy = 5;
|
||||
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.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);
|
||||
@@ -466,7 +466,7 @@ akerr_ErrorContext *test_actor_control_handlers_off(void)
|
||||
memset(&actor, 0x00, sizeof(akgl_Actor));
|
||||
actor.ax = 5; actor.ex = 5; actor.tx = 5; actor.vx = 5;
|
||||
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(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_RIGHT),
|
||||
"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));
|
||||
actor.ay = 5; actor.ey = 5; actor.ty = 5; actor.vy = 5;
|
||||
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(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_UP),
|
||||
"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));
|
||||
actor.ay = 5; actor.ey = 5; actor.ty = 5; actor.vy = 5;
|
||||
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(e, AKGL_BITMASK_HASNOT(actor.state, AKGL_ACTOR_STATE_MOVING_DOWN),
|
||||
"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(&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_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_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_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_off(NULL, &event), "down off, 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_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_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_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_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_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_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_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_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_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_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_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");
|
||||
|
||||
// A movement handler reads acceleration off the base character, so an
|
||||
// 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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
@@ -879,12 +879,12 @@ akerr_ErrorContext *test_actor_sprite_sheet_coords(void)
|
||||
sprite.width = 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");
|
||||
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");
|
||||
// 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");
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
@@ -904,6 +904,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, akgl_registry_init_actor());
|
||||
CATCH(errctx, akgl_registry_init_sprite());
|
||||
CATCH(errctx, akgl_registry_init_spritesheet());
|
||||
|
||||
@@ -602,6 +602,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_Init(SDL_INIT_AUDIO),
|
||||
|
||||
@@ -7,9 +7,21 @@
|
||||
#include <akgl/heap.h>
|
||||
#include <akgl/registry.h>
|
||||
#include <akgl/iterator.h>
|
||||
#include <akgl/game.h>
|
||||
#include <akgl/renderer.h>
|
||||
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
#include "testutil.h"
|
||||
|
||||
/*
|
||||
* 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()
|
||||
{
|
||||
@@ -196,15 +208,19 @@ int main(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
|
||||
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
CATCH(errctx, akgl_render_2d_bind(akgl_renderer));
|
||||
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
|
||||
@@ -76,7 +76,7 @@ int main(void)
|
||||
actorptr->visible = true;
|
||||
|
||||
// set up the control map
|
||||
controlmap = &GAME_ControlMaps[0];
|
||||
controlmap = &akgl_controlmaps[0];
|
||||
controlmap->kbid = 0;
|
||||
controlmap->target = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "player", NULL);
|
||||
// Move down
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <akerror.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.
|
||||
#include <akgl/actor.h>
|
||||
#include <akgl/character.h>
|
||||
@@ -52,7 +52,7 @@ static void reset_control_maps(void)
|
||||
{
|
||||
int i = 0;
|
||||
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.event_on = SDL_EVENT_KEY_DOWN;
|
||||
control.event_off = SDL_EVENT_KEY_UP;
|
||||
control.handler_on = &akgl_Actor_cmhf_left_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_left_off;
|
||||
control.handler_on = &akgl_actor_cmhf_left_on;
|
||||
control.handler_off = &akgl_actor_cmhf_left_off;
|
||||
|
||||
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",
|
||||
GAME_ControlMaps[0].nextMap);
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].controls[0].key == SDLK_SPACE,
|
||||
akgl_controlmaps[0].nextMap);
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].controls[0].key == SDLK_SPACE,
|
||||
"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");
|
||||
|
||||
// Pushes accumulate rather than overwrite.
|
||||
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",
|
||||
GAME_ControlMaps[0].nextMap);
|
||||
akgl_controlmaps[0].nextMap);
|
||||
|
||||
// Maps are independent of each other.
|
||||
TEST_EXPECT_OK(e, akgl_controller_pushmap(3, &control), "pushing into a different map");
|
||||
TEST_ASSERT(e, GAME_ControlMaps[3].nextMap == 1,
|
||||
"map 3 nextMap is %d, expected 1", GAME_ControlMaps[3].nextMap);
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 2,
|
||||
"pushing into map 3 disturbed map 0 (nextMap %d)", GAME_ControlMaps[0].nextMap);
|
||||
TEST_ASSERT(e, akgl_controlmaps[3].nextMap == 1,
|
||||
"map 3 nextMap is %d, expected 1", akgl_controlmaps[3].nextMap);
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].nextMap == 2,
|
||||
"pushing into map 3 disturbed map 0 (nextMap %d)", akgl_controlmaps[0].nextMap);
|
||||
|
||||
// Filling a map exactly to capacity still succeeds.
|
||||
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);
|
||||
}
|
||||
}
|
||||
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",
|
||||
GAME_ControlMaps[0].nextMap, AKGL_MAX_CONTROLS);
|
||||
akgl_controlmaps[0].nextMap, AKGL_MAX_CONTROLS);
|
||||
|
||||
// One past capacity is refused.
|
||||
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),
|
||||
"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");
|
||||
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",
|
||||
GAME_ControlMaps[0].kbid, TEST_KBID);
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].jsid == TEST_JSID,
|
||||
akgl_controlmaps[0].kbid, TEST_KBID);
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].jsid == TEST_JSID,
|
||||
"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.
|
||||
TEST_ASSERT(e, GAME_ControlMaps[0].nextMap == 8,
|
||||
TEST_ASSERT(e, akgl_controlmaps[0].nextMap == 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");
|
||||
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");
|
||||
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");
|
||||
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");
|
||||
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 gamepad half binds buttons and leaves the keycode clear, so a
|
||||
// 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");
|
||||
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");
|
||||
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");
|
||||
|
||||
// An unknown actor name is a registry error, not a crash.
|
||||
@@ -856,6 +856,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD),
|
||||
|
||||
95
tests/draw.c
95
tests/draw.c
@@ -38,13 +38,13 @@ static akerr_ErrorContext *clear_target(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(
|
||||
errctx,
|
||||
SDL_SetRenderDrawColor(renderer->sdl_renderer, 0x00, 0x00, 0x00, 0xff),
|
||||
SDL_SetRenderDrawColor(akgl_renderer->sdl_renderer, 0x00, 0x00, 0x00, 0xff),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
FAIL_ZERO_RETURN(
|
||||
errctx,
|
||||
SDL_RenderClear(renderer->sdl_renderer),
|
||||
SDL_RenderClear(akgl_renderer->sdl_renderer),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
@@ -80,10 +80,10 @@ akerr_ErrorContext *test_draw_point(void)
|
||||
|
||||
ATTEMPT {
|
||||
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");
|
||||
|
||||
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());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 10, 20, testred),
|
||||
"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());
|
||||
// A vertical line, so every pixel of it is known without reasoning
|
||||
// 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");
|
||||
|
||||
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());
|
||||
for ( i = 4; i <= 12; i++ ) {
|
||||
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.
|
||||
CATCH(errctx, clear_target());
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_rect(renderer, &box, testred), "outlining a rectangle");
|
||||
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL);
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_rect(akgl_renderer, &box, testred), "outlining a rectangle");
|
||||
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||
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, 23, 23, testred),
|
||||
@@ -171,8 +171,8 @@ akerr_ErrorContext *test_draw_rects(void)
|
||||
|
||||
// The filled form covers the interior as well.
|
||||
CATCH(errctx, clear_target());
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_filled_rect(renderer, &box, testred), "filling a rectangle");
|
||||
shot = SDL_RenderReadPixels(renderer->sdl_renderer, NULL);
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_filled_rect(akgl_renderer, &box, testred), "filling a rectangle");
|
||||
shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
|
||||
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, 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),
|
||||
"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");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER, akgl_draw_filled_rect(NULL, &box, testred),
|
||||
"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");
|
||||
} CLEANUP {
|
||||
if ( shot != NULL ) {
|
||||
@@ -206,10 +206,10 @@ akerr_ErrorContext *test_draw_circle(void)
|
||||
|
||||
ATTEMPT {
|
||||
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");
|
||||
|
||||
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());
|
||||
// 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");
|
||||
@@ -243,15 +243,15 @@ akerr_ErrorContext *test_draw_circle(void)
|
||||
|
||||
// A zero radius is the degenerate case, not an error: one pixel.
|
||||
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");
|
||||
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());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 5, 5, testred),
|
||||
"a circle of radius zero did not plot its center");
|
||||
|
||||
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");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
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.h = 20.0f;
|
||||
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");
|
||||
|
||||
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());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 20, 20, testgreen), "the seed pixel was not filled");
|
||||
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
|
||||
// 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");
|
||||
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());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 20, 20, testgreen),
|
||||
"refilling a region disturbed it");
|
||||
@@ -313,9 +313,9 @@ akerr_ErrorContext *test_draw_flood_fill(void)
|
||||
shot = NULL;
|
||||
|
||||
// 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");
|
||||
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());
|
||||
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),
|
||||
@@ -324,10 +324,10 @@ akerr_ErrorContext *test_draw_flood_fill(void)
|
||||
"the fill from outside overwrote the boundary");
|
||||
|
||||
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");
|
||||
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");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
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.h = 8.0f;
|
||||
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.y = 0;
|
||||
region.w = 8;
|
||||
region.h = 8;
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_copy_region(renderer, ®ion, &saved),
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_copy_region(akgl_renderer, ®ion, &saved),
|
||||
"saving a region of the target");
|
||||
TEST_ASSERT(errctx, saved != NULL, "akgl_draw_copy_region did not allocate a surface");
|
||||
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.
|
||||
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");
|
||||
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());
|
||||
TEST_ASSERT(errctx, pixel_is(shot, 32, 32, testred),
|
||||
"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());
|
||||
region.x = 32;
|
||||
region.y = 32;
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_copy_region(renderer, ®ion, &reused),
|
||||
TEST_EXPECT_OK(errctx, akgl_draw_copy_region(akgl_renderer, ®ion, &reused),
|
||||
"saving into a caller-owned surface");
|
||||
TEST_ASSERT(errctx, pixel_is(reused, 0, 0, testred),
|
||||
"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.
|
||||
region.w = 4;
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_draw_copy_region(renderer, ®ion, &reused),
|
||||
akgl_draw_copy_region(akgl_renderer, ®ion, &reused),
|
||||
"saving into a destination of the wrong size");
|
||||
region.x = TEST_TARGET_SIZE - 4;
|
||||
region.y = 0;
|
||||
region.w = 8;
|
||||
region.h = 8;
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_draw_copy_region(renderer, ®ion, &reused),
|
||||
akgl_draw_copy_region(akgl_renderer, ®ion, &reused),
|
||||
"saving a region that runs off the right edge");
|
||||
region.x = 0;
|
||||
region.w = 0;
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_OUTOFBOUNDS,
|
||||
akgl_draw_copy_region(renderer, ®ion, &reused),
|
||||
akgl_draw_copy_region(akgl_renderer, ®ion, &reused),
|
||||
"saving a region with no area");
|
||||
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_draw_copy_region(NULL, ®ion, &reused),
|
||||
"saving through a NULL backend");
|
||||
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");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_draw_copy_region(renderer, ®ion, NULL),
|
||||
akgl_draw_copy_region(akgl_renderer, ®ion, NULL),
|
||||
"saving into a NULL destination");
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_draw_paste_region(NULL, saved, 0.0f, 0.0f),
|
||||
"pasting through a NULL backend");
|
||||
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");
|
||||
} CLEANUP {
|
||||
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.
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_SetRenderDrawColor(renderer->sdl_renderer, 0x11, 0x22, 0x33, 0x44),
|
||||
SDL_SetRenderDrawColor(akgl_renderer->sdl_renderer, 0x11, 0x22, 0x33, 0x44),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
CATCH(errctx, akgl_draw_point(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_rect(renderer, &box, testred));
|
||||
CATCH(errctx, akgl_draw_filled_rect(renderer, &box, testred));
|
||||
CATCH(errctx, akgl_draw_circle(renderer, 20.0f, 20.0f, 4.0f, testred));
|
||||
CATCH(errctx, akgl_draw_point(akgl_renderer, 1.0f, 1.0f, testred));
|
||||
CATCH(errctx, akgl_draw_line(akgl_renderer, 0.0f, 0.0f, 3.0f, 3.0f, testred));
|
||||
CATCH(errctx, akgl_draw_rect(akgl_renderer, &box, testred));
|
||||
CATCH(errctx, akgl_draw_filled_rect(akgl_renderer, &box, testred));
|
||||
CATCH(errctx, akgl_draw_circle(akgl_renderer, 20.0f, 20.0f, 4.0f, testred));
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_GetRenderDrawColor(renderer->sdl_renderer, &r, &g, &b, &a),
|
||||
SDL_GetRenderDrawColor(akgl_renderer->sdl_renderer, &r, &g, &b, &a),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
@@ -564,7 +564,8 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
renderer = &_akgl_renderer;
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
@@ -579,8 +580,8 @@ int main(void)
|
||||
TEST_TARGET_SIZE,
|
||||
TEST_TARGET_SIZE,
|
||||
0,
|
||||
&window,
|
||||
&renderer->sdl_renderer),
|
||||
&akgl_window,
|
||||
&akgl_renderer->sdl_renderer),
|
||||
AKGL_ERR_SDL,
|
||||
"Couldn't create window/renderer: %s",
|
||||
SDL_GetError());
|
||||
|
||||
@@ -94,7 +94,13 @@ int main(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
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());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, test_error_init_is_idempotent());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
|
||||
178
tests/game.c
178
tests/game.c
@@ -40,11 +40,11 @@ static char truncatedpath[] = "akgl_test_truncated.bin";
|
||||
/** @brief Populate the process-wide game record with a valid identity. */
|
||||
static void set_game_identity(void)
|
||||
{
|
||||
memset(&game, 0x00, sizeof(akgl_Game));
|
||||
strncpy((char *)&game.libversion, AKGL_VERSION, 31);
|
||||
strncpy((char *)&game.version, "1.2.3", 31);
|
||||
strncpy((char *)&game.name, "libakgl test game", 255);
|
||||
strncpy((char *)&game.uri, "https://example.invalid/akgl-test", 255);
|
||||
memset(&akgl_game, 0x00, sizeof(akgl_Game));
|
||||
strncpy((char *)&akgl_game.libversion, AKGL_VERSION, 31);
|
||||
strncpy((char *)&akgl_game.version, "1.2.3", 31);
|
||||
strncpy((char *)&akgl_game.name, "libakgl test game", 255);
|
||||
strncpy((char *)&akgl_game.uri, "https://example.invalid/akgl-test", 255);
|
||||
}
|
||||
|
||||
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_heap_init());
|
||||
set_game_identity();
|
||||
game.fps = 60;
|
||||
game.framesSinceUpdate = 7;
|
||||
memcpy(&expected, &game, sizeof(akgl_Game));
|
||||
akgl_game.fps = 60;
|
||||
akgl_game.framesSinceUpdate = 7;
|
||||
memcpy(&expected, &akgl_game, sizeof(akgl_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.
|
||||
game.fps = 0;
|
||||
game.framesSinceUpdate = 0;
|
||||
akgl_game.fps = 0;
|
||||
akgl_game.framesSinceUpdate = 0;
|
||||
|
||||
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, game.framesSinceUpdate == 7,
|
||||
"framesSinceUpdate restored as %d, expected 7", game.framesSinceUpdate);
|
||||
TEST_ASSERT(e, strncmp((char *)&game.name, (char *)&expected.name, 256) == 0,
|
||||
TEST_ASSERT(e, akgl_game.fps == 60, "fps restored as %d, expected 60", akgl_game.fps);
|
||||
TEST_ASSERT(e, akgl_game.framesSinceUpdate == 7,
|
||||
"framesSinceUpdate restored as %d, expected 7", akgl_game.framesSinceUpdate);
|
||||
TEST_ASSERT(e, strncmp((char *)&akgl_game.name, (char *)&expected.name, 256) == 0,
|
||||
"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");
|
||||
} CLEANUP {
|
||||
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.
|
||||
set_game_identity();
|
||||
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),
|
||||
"a savegame with a foreign game name must be refused");
|
||||
unlink((char *)&savepath);
|
||||
@@ -178,14 +178,14 @@ akerr_ErrorContext *test_game_load_rejects_foreign_saves(void)
|
||||
// Same for a differing URI.
|
||||
set_game_identity();
|
||||
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),
|
||||
"a savegame with a foreign URI must be refused");
|
||||
unlink((char *)&savepath);
|
||||
|
||||
// A save written against a different library version must be refused.
|
||||
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));
|
||||
set_game_identity();
|
||||
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.
|
||||
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));
|
||||
set_game_identity();
|
||||
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));
|
||||
fp = fopen((char *)&truncatedpath, "wb");
|
||||
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");
|
||||
FAIL_ZERO_BREAK(e, fwrite(&partial, 1, sizeof(partial), fp), AKERR_IO,
|
||||
"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_SPRITE_MAX_NAME_LENGTH + sizeof(akgl_Sprite *))
|
||||
+ (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,
|
||||
"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 {
|
||||
set_game_identity();
|
||||
game.statelock = SDL_CreateMutex();
|
||||
FAIL_ZERO_BREAK(e, game.statelock, AKGL_ERR_SDL, "unable to create the state mutex");
|
||||
akgl_game.statelock = SDL_CreateMutex();
|
||||
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_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_unlock(), "releasing the state lock again");
|
||||
} CLEANUP {
|
||||
if ( game.statelock != NULL ) {
|
||||
SDL_DestroyMutex(game.statelock);
|
||||
game.statelock = NULL;
|
||||
if ( akgl_game.statelock != NULL ) {
|
||||
SDL_DestroyMutex(akgl_game.statelock);
|
||||
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) {
|
||||
} FINISH(e, true);
|
||||
@@ -352,41 +436,41 @@ akerr_ErrorContext *test_game_updateFPS(void)
|
||||
|
||||
ATTEMPT {
|
||||
set_game_identity();
|
||||
game.lowfpsfunc = &stub_lowfps;
|
||||
akgl_game.lowfpsfunc = &stub_lowfps;
|
||||
|
||||
// Below the 30 FPS floor, every update notifies the callback.
|
||||
game.fps = 10;
|
||||
game.lastFPSTime = SDL_GetTicksNS();
|
||||
akgl_game.fps = 10;
|
||||
akgl_game.lastFPSTime = SDL_GetTicksNS();
|
||||
lowfps_calls = 0;
|
||||
framesbefore = game.framesSinceUpdate;
|
||||
akgl_game_updateFPS();
|
||||
framesbefore = akgl_game.framesSinceUpdate;
|
||||
akgl_game_update_fps();
|
||||
TEST_ASSERT(e, lowfps_calls == 1,
|
||||
"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)",
|
||||
game.framesSinceUpdate, framesbefore + 1);
|
||||
TEST_ASSERT(e, game.lastIterTime != 0, "updateFPS did not stamp lastIterTime");
|
||||
akgl_game.framesSinceUpdate, framesbefore + 1);
|
||||
TEST_ASSERT(e, akgl_game.lastIterTime != 0, "updateFPS did not stamp lastIterTime");
|
||||
|
||||
// At or above the floor, the callback stays quiet.
|
||||
game.fps = 60;
|
||||
game.lastFPSTime = SDL_GetTicksNS();
|
||||
akgl_game.fps = 60;
|
||||
akgl_game.lastFPSTime = SDL_GetTicksNS();
|
||||
lowfps_calls = 0;
|
||||
akgl_game_updateFPS();
|
||||
akgl_game_update_fps();
|
||||
TEST_ASSERT(e, lowfps_calls == 0,
|
||||
"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.
|
||||
game.fps = 60;
|
||||
game.framesSinceUpdate = 45;
|
||||
game.lastFPSTime = SDL_GetTicksNS() - (2 * (SDL_Time)AKGL_TIME_ONESEC_NS);
|
||||
akgl_game_updateFPS();
|
||||
TEST_ASSERT(e, game.fps == 45,
|
||||
"after a second elapsed, fps rolled over as %d, expected 45", game.fps);
|
||||
TEST_ASSERT(e, game.framesSinceUpdate == 1,
|
||||
"the frame counter restarted at %d, expected 1", game.framesSinceUpdate);
|
||||
akgl_game.fps = 60;
|
||||
akgl_game.framesSinceUpdate = 45;
|
||||
akgl_game.lastFPSTime = SDL_GetTicksNS() - (2 * (SDL_Time)AKGL_TIME_ONESEC_NS);
|
||||
akgl_game_update_fps();
|
||||
TEST_ASSERT(e, akgl_game.fps == 45,
|
||||
"after a second elapsed, fps rolled over as %d, expected 45", akgl_game.fps);
|
||||
TEST_ASSERT(e, akgl_game.framesSinceUpdate == 1,
|
||||
"the frame counter restarted at %d, expected 1", akgl_game.framesSinceUpdate);
|
||||
|
||||
// The shipped default callback only logs, so it just has to not crash.
|
||||
game.fps = 1;
|
||||
akgl_game.fps = 1;
|
||||
akgl_game_lowfps();
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
@@ -403,6 +487,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
|
||||
@@ -415,6 +500,7 @@ int main(void)
|
||||
CATCH(errctx, test_game_load_truncated_table());
|
||||
CATCH(errctx, test_game_save_writes_name_tables());
|
||||
CATCH(errctx, test_game_state_lock());
|
||||
CATCH(errctx, test_game_state_lock_budget());
|
||||
CATCH(errctx, test_game_updateFPS());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
|
||||
@@ -10,7 +10,13 @@
|
||||
*
|
||||
* One header per file, deliberately. A second `#include` after the first proves
|
||||
* 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>
|
||||
@@ -26,11 +32,11 @@ int main(void)
|
||||
// one by value.
|
||||
memset(&control, 0x00, sizeof(akgl_Control));
|
||||
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 ) {
|
||||
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 0;
|
||||
|
||||
45
tests/heap.c
45
tests/heap.c
@@ -50,57 +50,57 @@ akerr_ErrorContext *test_heap_init_clears_every_pool(void)
|
||||
ATTEMPT {
|
||||
// Dirty every pool, then require that init scrubs all of them.
|
||||
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++ ) {
|
||||
HEAP_SPRITE[i].refcount = 5;
|
||||
akgl_heap_sprites[i].refcount = 5;
|
||||
}
|
||||
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++ ) {
|
||||
HEAP_CHARACTER[i].refcount = 5;
|
||||
akgl_heap_characters[i].refcount = 5;
|
||||
}
|
||||
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());
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
// akgl_heap_init_actor clears only the actor pool.
|
||||
HEAP_ACTOR[0].refcount = 9;
|
||||
HEAP_SPRITE[0].refcount = 9;
|
||||
akgl_heap_actors[0].refcount = 9;
|
||||
akgl_heap_sprites[0].refcount = 9;
|
||||
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");
|
||||
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");
|
||||
HEAP_SPRITE[0].refcount = 0;
|
||||
akgl_heap_sprites[0].refcount = 0;
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} 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.
|
||||
CATCH(e, reset_all_heaps());
|
||||
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),
|
||||
"akgl_heap_next_actor with every slot claimed");
|
||||
|
||||
CATCH(e, reset_all_heaps());
|
||||
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),
|
||||
"akgl_heap_next_sprite with every slot claimed");
|
||||
|
||||
CATCH(e, reset_all_heaps());
|
||||
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),
|
||||
"akgl_heap_next_spritesheet with every slot claimed");
|
||||
|
||||
CATCH(e, reset_all_heaps());
|
||||
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),
|
||||
"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");
|
||||
|
||||
// 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),
|
||||
"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");
|
||||
|
||||
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
|
||||
// the recursive release should have brought every one of them back to 1.
|
||||
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,
|
||||
"releasing a parent did not decrement every child exactly once");
|
||||
@@ -362,6 +362,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
|
||||
|
||||
@@ -348,6 +348,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
CATCH(errctx, load_fixture());
|
||||
|
||||
27
tests/perf.c
27
tests/perf.c
@@ -69,21 +69,21 @@ static void bench_report_footprint(void)
|
||||
{
|
||||
size_t total = 0;
|
||||
|
||||
total = sizeof(HEAP_ACTOR) + sizeof(HEAP_SPRITE) + sizeof(HEAP_SPRITESHEET) +
|
||||
sizeof(HEAP_CHARACTER) + sizeof(HEAP_STRING);
|
||||
total = sizeof(akgl_heap_actors) + sizeof(akgl_heap_sprites) + sizeof(akgl_heap_spritesheets) +
|
||||
sizeof(akgl_heap_characters) + sizeof(akgl_heap_strings);
|
||||
|
||||
printf("\n");
|
||||
printf("static footprint, fixed at compile time:\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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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 %5d x %7s = %10zu bytes\n",
|
||||
"akgl_Tilemap", 1, "", sizeof(akgl_Tilemap));
|
||||
@@ -263,7 +263,7 @@ static akerr_ErrorContext *bench_heap_actor_claim(void)
|
||||
PASS(errctx, inner);
|
||||
|
||||
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_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
|
||||
// reference count read per 4 KiB page.
|
||||
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);
|
||||
for ( i = 0; i < count; i++ ) {
|
||||
@@ -470,7 +470,7 @@ static akerr_ErrorContext *bench_registry_lookup(void)
|
||||
PASS(errctx, akgl_registry_init());
|
||||
PASS(errctx, bench_make_character(&basechar, "benchlookupchar", 0));
|
||||
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++ ) {
|
||||
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, bench_make_character(&basechar, "benchupdatechar", 0));
|
||||
PASS(errctx, bench_fill_actor_pool(basechar, 1));
|
||||
actor = &HEAP_ACTOR[0];
|
||||
actor = &akgl_heap_actors[0];
|
||||
AKGL_BITMASK_CLEAR(actor->state);
|
||||
|
||||
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);
|
||||
for ( i = 0; i < count; i++ ) {
|
||||
for ( j = 0; j < AKGL_MAX_HEAP_ACTOR; j++ ) {
|
||||
actor = &HEAP_ACTOR[j];
|
||||
actor = &akgl_heap_actors[j];
|
||||
if ( actor->refcount == 0 ) {
|
||||
continue;
|
||||
}
|
||||
@@ -709,7 +709,7 @@ static akerr_ErrorContext *bench_geometry(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akerr_ErrorContext *inner = NULL;
|
||||
RectanglePoints points;
|
||||
akgl_RectanglePoints points;
|
||||
SDL_FRect rects[BENCH_ACTOR_COUNT];
|
||||
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 };
|
||||
@@ -901,6 +901,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
CATCH(errctx, akgl_registry_init_properties());
|
||||
|
||||
@@ -91,7 +91,7 @@ static SDL_Texture *benchtiles = NULL;
|
||||
*/
|
||||
#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); \
|
||||
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++ ) {
|
||||
bench_start("frame_start + frame_end, 640x480", "frame", 230000.0);
|
||||
for ( i = 0; i < count; i++ ) {
|
||||
inner = renderer->frame_start(renderer);
|
||||
inner = akgl_renderer->frame_start(akgl_renderer);
|
||||
if ( inner != NULL ) {
|
||||
break;
|
||||
}
|
||||
inner = renderer->frame_end(renderer);
|
||||
inner = akgl_renderer->frame_end(akgl_renderer);
|
||||
if ( inner != NULL ) {
|
||||
break;
|
||||
}
|
||||
@@ -268,30 +268,30 @@ static akerr_ErrorContext *bench_primitives(void)
|
||||
int i = 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++ ) {
|
||||
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);
|
||||
PASS(errctx, inner);
|
||||
|
||||
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);
|
||||
PASS(errctx, inner);
|
||||
|
||||
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);
|
||||
PASS(errctx, inner);
|
||||
|
||||
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);
|
||||
PASS(errctx, inner);
|
||||
|
||||
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);
|
||||
PASS(errctx, inner);
|
||||
}
|
||||
@@ -325,14 +325,14 @@ static akerr_ErrorContext *bench_framebuffer(void)
|
||||
int i = 0;
|
||||
int rep = 0;
|
||||
|
||||
PASS(errctx, renderer->frame_start(renderer));
|
||||
PASS(errctx, akgl_draw_copy_region(renderer, ®ion, &saved));
|
||||
PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
|
||||
PASS(errctx, akgl_draw_copy_region(akgl_renderer, ®ion, &saved));
|
||||
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("draw_copy_region, 64x64 readback", "call", 10000.0);
|
||||
for ( i = 0; i < count; i++ ) {
|
||||
SDL_Surface *scratch = NULL;
|
||||
inner = akgl_draw_copy_region(renderer, ®ion, &scratch);
|
||||
inner = akgl_draw_copy_region(akgl_renderer, ®ion, &scratch);
|
||||
if ( inner != NULL ) {
|
||||
break;
|
||||
}
|
||||
@@ -342,20 +342,20 @@ static akerr_ErrorContext *bench_framebuffer(void)
|
||||
PASS(errctx, inner);
|
||||
|
||||
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);
|
||||
PASS(errctx, inner);
|
||||
|
||||
bench_start("draw_flood_fill, full 640x480 target", "call", 20000000.0);
|
||||
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 ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
BENCH_FLUSH_STOP(errctx, fills);
|
||||
PASS(errctx, inner);
|
||||
PASS(errctx, renderer->frame_start(renderer));
|
||||
PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
|
||||
}
|
||||
SDL_DestroySurface(saved);
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -381,7 +381,7 @@ static akerr_ErrorContext *bench_text(void)
|
||||
int i = 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++ ) {
|
||||
bench_start("text_measure, 15 characters", "call", 400.0);
|
||||
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
|
||||
// works around the first so it never reaches the second.
|
||||
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 ) {
|
||||
break;
|
||||
}
|
||||
inner = akgl_tilemap_release(gamemap);
|
||||
inner = akgl_tilemap_release(akgl_gamemap);
|
||||
if ( inner != NULL ) {
|
||||
break;
|
||||
}
|
||||
@@ -523,7 +523,7 @@ static akerr_ErrorContext *bench_map_zero(void)
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("zeroing one akgl_Tilemap", "call", 16000000.0);
|
||||
for ( i = 0; i < count; i++ ) {
|
||||
memset(gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
memset(akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
}
|
||||
BENCH_FLUSH_STOP(errctx, count);
|
||||
}
|
||||
@@ -546,10 +546,10 @@ static akerr_ErrorContext *bench_tileset_offsets(void)
|
||||
int i = 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++ ) {
|
||||
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);
|
||||
PASS(errctx, inner);
|
||||
}
|
||||
@@ -573,19 +573,19 @@ static akerr_ErrorContext *bench_tilemap_draw(void)
|
||||
int i = 0;
|
||||
int rep = 0;
|
||||
|
||||
PASS(errctx, renderer->frame_start(renderer));
|
||||
PASS(errctx, bench_build_map(gamemap, 1));
|
||||
PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
|
||||
PASS(errctx, bench_build_map(akgl_gamemap, 1));
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
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);
|
||||
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++ ) {
|
||||
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);
|
||||
PASS(errctx, inner);
|
||||
}
|
||||
@@ -627,7 +627,7 @@ static akerr_ErrorContext *bench_raw_blit_control(void)
|
||||
columns = benchtiles->w / 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++ ) {
|
||||
// One source tile, blitted 1200 times. The floor: 1200 SDL calls and
|
||||
// 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++ ) {
|
||||
dest.x = (float32_t)(x * 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);
|
||||
dest.x = (float32_t)(x * 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;
|
||||
|
||||
PASS(errctx, bench_populate_scene());
|
||||
PASS(errctx, bench_build_map(gamemap, 1));
|
||||
PASS(errctx, renderer->frame_start(renderer));
|
||||
actor = &HEAP_ACTOR[0];
|
||||
PASS(errctx, bench_build_map(akgl_gamemap, 1));
|
||||
PASS(errctx, akgl_renderer->frame_start(akgl_renderer));
|
||||
actor = &akgl_heap_actors[0];
|
||||
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("actor_render, on camera", "actor", 31000.0);
|
||||
@@ -701,7 +701,7 @@ static akerr_ErrorContext *bench_scene(void)
|
||||
PASS(errctx, inner);
|
||||
|
||||
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);
|
||||
PASS(errctx, inner);
|
||||
}
|
||||
@@ -727,8 +727,8 @@ static akerr_ErrorContext *bench_game_update(void)
|
||||
int rep = 0;
|
||||
|
||||
PASS(errctx, bench_populate_scene());
|
||||
PASS(errctx, bench_build_map(gamemap, 1));
|
||||
PASS(errctx, akgl_physics_init_null(physics));
|
||||
PASS(errctx, bench_build_map(akgl_gamemap, 1));
|
||||
PASS(errctx, akgl_physics_init_null(akgl_physics));
|
||||
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("game_update, full frame", "frame", 170000000.0);
|
||||
@@ -750,10 +750,11 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
renderer = &_akgl_renderer;
|
||||
physics = &_akgl_physics;
|
||||
camera = &_akgl_camera;
|
||||
gamemap = &_akgl_gamemap;
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
akgl_physics = &akgl_default_physics;
|
||||
akgl_camera = &akgl_default_camera;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
@@ -774,36 +775,36 @@ int main(void)
|
||||
BENCH_SCREEN_W,
|
||||
BENCH_SCREEN_H,
|
||||
0,
|
||||
&window,
|
||||
&renderer->sdl_renderer),
|
||||
&akgl_window,
|
||||
&akgl_renderer->sdl_renderer),
|
||||
AKGL_ERR_SDL,
|
||||
"Couldn't create window/renderer: %s",
|
||||
SDL_GetError());
|
||||
CATCH(errctx, akgl_render_bind2d(renderer));
|
||||
CATCH(errctx, akgl_render_2d_bind(akgl_renderer));
|
||||
|
||||
camera->x = 0.0;
|
||||
camera->y = 0.0;
|
||||
camera->w = BENCH_SCREEN_W;
|
||||
camera->h = BENCH_SCREEN_H;
|
||||
akgl_camera->x = 0.0;
|
||||
akgl_camera->y = 0.0;
|
||||
akgl_camera->w = BENCH_SCREEN_W;
|
||||
akgl_camera->h = BENCH_SCREEN_H;
|
||||
|
||||
// akgl_game_update takes this before it does anything else. Normally
|
||||
// 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.
|
||||
game.statelock = SDL_CreateMutex();
|
||||
FAIL_ZERO_BREAK(errctx, game.statelock, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
akgl_game.statelock = SDL_CreateMutex();
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// 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);
|
||||
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());
|
||||
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @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
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -723,6 +723,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
CATCH(errctx, akgl_registry_init_properties());
|
||||
|
||||
@@ -163,6 +163,7 @@ int main(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, test_akgl_registry_init_creation_failures());
|
||||
CATCH(errctx, test_akgl_get_property_copies_only_the_value());
|
||||
} CLEANUP {
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
* @file renderer.c
|
||||
* @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
|
||||
* 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
|
||||
* 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
|
||||
* 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));
|
||||
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,
|
||||
"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
|
||||
// inventing one.
|
||||
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");
|
||||
TEST_ASSERT(errctx, backend.sdl_renderer == NULL,
|
||||
"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");
|
||||
} CLEANUP {
|
||||
} 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
|
||||
// has to report that rather than dereference it.
|
||||
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),
|
||||
"starting a frame on a backend with no renderer");
|
||||
@@ -187,6 +187,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
memset(&bound, 0x00, sizeof(akgl_RenderBackend));
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
@@ -202,14 +203,14 @@ int main(void)
|
||||
TEST_TARGET_SIZE,
|
||||
TEST_TARGET_SIZE,
|
||||
0,
|
||||
&window,
|
||||
&akgl_window,
|
||||
&bound.sdl_renderer),
|
||||
AKGL_ERR_SDL,
|
||||
"Couldn't create window/renderer: %s",
|
||||
SDL_GetError());
|
||||
// The host owns the window; libakgl only ever binds to it. This is the
|
||||
// arrangement akgl_render_bind2d exists for.
|
||||
CATCH(errctx, akgl_render_bind2d(&bound));
|
||||
// arrangement akgl_render_2d_bind exists for.
|
||||
CATCH(errctx, akgl_render_2d_bind(&bound));
|
||||
|
||||
CATCH(errctx, test_render_bind2d());
|
||||
CATCH(errctx, test_render_backend_without_a_renderer());
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <akgl/game.h>
|
||||
#include <akgl/renderer.h>
|
||||
|
||||
#include "testutil.h"
|
||||
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
CATCH(
|
||||
@@ -138,7 +140,7 @@ akerr_ErrorContext *test_akgl_sprite_load_json(void)
|
||||
// Is it using the right spritesheet?
|
||||
|
||||
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");
|
||||
|
||||
CATCH(
|
||||
@@ -188,7 +190,8 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
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");
|
||||
|
||||
@@ -196,10 +199,10 @@ int main(void)
|
||||
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());
|
||||
}
|
||||
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_registry_init_sprite());
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <akgl/heap.h>
|
||||
#include <akgl/staticstring.h>
|
||||
|
||||
#include "testutil.h"
|
||||
|
||||
void reset_string_heap(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;
|
||||
PREPARE_ERROR(errctx);
|
||||
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++ ) {
|
||||
ATTEMPT {
|
||||
@@ -45,8 +47,8 @@ akerr_ErrorContext *test_string_heap_error_when_no_strings_left(void)
|
||||
|
||||
akerr_ErrorContext *test_string_heap_honors_refcount(void)
|
||||
{
|
||||
akgl_String *firstptr = &HEAP_STRING[0];
|
||||
akgl_String *secondptr = &HEAP_STRING[1];
|
||||
akgl_String *firstptr = &akgl_heap_strings[0];
|
||||
akgl_String *secondptr = &akgl_heap_strings[1];
|
||||
akgl_String *testptr = NULL;
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
@@ -55,7 +57,7 @@ akerr_ErrorContext *test_string_heap_honors_refcount(void)
|
||||
FAIL_RETURN(
|
||||
errctx,
|
||||
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,
|
||||
testptr
|
||||
);
|
||||
@@ -68,7 +70,7 @@ akerr_ErrorContext *test_string_heap_honors_refcount(void)
|
||||
FAIL_RETURN(
|
||||
errctx,
|
||||
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,
|
||||
testptr
|
||||
);
|
||||
@@ -124,7 +126,7 @@ akerr_ErrorContext *test_akgl_string_initialize(void)
|
||||
void reset_string_heap(void)
|
||||
{
|
||||
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);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
printf("test_fresh_heap_gives_string ....\n");
|
||||
test_fresh_heap_gives_strings();
|
||||
reset_string_heap();
|
||||
|
||||
@@ -16,10 +16,45 @@
|
||||
#define _AKGL_TESTUTIL_H_
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <akerror.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. */
|
||||
#define TEST_ASSERT(e, cond, ...) \
|
||||
if ( ! (cond) ) { \
|
||||
|
||||
23
tests/text.c
23
tests/text.c
@@ -256,7 +256,7 @@ akerr_ErrorContext *test_text_render_backend_guards(void)
|
||||
ATTEMPT {
|
||||
// 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.
|
||||
renderer = NULL;
|
||||
akgl_renderer = NULL;
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_text_rendertextat(testfont, "hello", white, 0, 0, 0),
|
||||
"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
|
||||
// state a host is in between allocating one and initializing it.
|
||||
memset(&backend, 0x00, sizeof(akgl_RenderBackend));
|
||||
renderer = &backend;
|
||||
akgl_renderer = &backend;
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_text_rendertextat(testfont, "hello", white, 0, 0, 0),
|
||||
"drawing text through a backend with no SDL renderer");
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
@@ -279,7 +279,7 @@ akerr_ErrorContext *test_text_render_backend_guards(void)
|
||||
akgl_text_rendertextat(testfont, "hello", white, 0, 0, 0),
|
||||
"drawing text through a backend that was never bound");
|
||||
|
||||
renderer = &testbackend;
|
||||
akgl_renderer = &testbackend;
|
||||
TEST_EXPECT_STATUS(errctx, AKERR_NULLPOINTER,
|
||||
akgl_text_rendertextat(NULL, "hello", white, 0, 0, 0),
|
||||
"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),
|
||||
"drawing a NULL string");
|
||||
} CLEANUP {
|
||||
renderer = &testbackend;
|
||||
akgl_renderer = &testbackend;
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
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:
|
||||
// no display, no window shown, and the whole path from rasterizing to
|
||||
// blitting runs.
|
||||
renderer = &testbackend;
|
||||
TEST_EXPECT_OK(errctx, renderer->frame_start(renderer), "starting a frame");
|
||||
akgl_renderer = &testbackend;
|
||||
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),
|
||||
"drawing a line of text");
|
||||
// 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
|
||||
// failure rather than drawing nothing. That is filed rather than pinned
|
||||
// -- 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 {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
@@ -331,6 +331,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
memset(&testbackend, 0x00, sizeof(akgl_RenderBackend));
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
@@ -355,7 +356,7 @@ int main(void)
|
||||
SDL_GetError());
|
||||
|
||||
// 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.
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
@@ -364,12 +365,12 @@ int main(void)
|
||||
TEST_TARGET_SIZE,
|
||||
TEST_TARGET_SIZE,
|
||||
0,
|
||||
&window,
|
||||
&akgl_window,
|
||||
&testbackend.sdl_renderer),
|
||||
AKGL_ERR_SDL,
|
||||
"Couldn't create window/renderer: %s",
|
||||
SDL_GetError());
|
||||
CATCH(errctx, akgl_render_bind2d(&testbackend));
|
||||
CATCH(errctx, akgl_render_2d_bind(&testbackend));
|
||||
|
||||
CATCH(errctx, test_text_loadfont());
|
||||
CATCH(errctx, test_text_measure());
|
||||
|
||||
181
tests/tilemap.c
181
tests/tilemap.c
@@ -11,6 +11,8 @@
|
||||
#include <akgl/game.h>
|
||||
#include <akgl/json_helpers.h>
|
||||
|
||||
#include "testutil.h"
|
||||
|
||||
akerr_ErrorContext *test_tilemap_akgl_get_json_tilemap_property(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
@@ -20,8 +22,8 @@ akerr_ErrorContext *test_tilemap_akgl_get_json_tilemap_property(void)
|
||||
int propnum;
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
||||
snprintf(
|
||||
(char *)&tmpstr->data,
|
||||
@@ -87,34 +89,34 @@ akerr_ErrorContext *test_akgl_tilemap_compute_tileset_offsets(void)
|
||||
10 // Tile 2 Y
|
||||
};
|
||||
int *comparison_ptrs[8] = {
|
||||
&gamemap->tilesets[0].tile_offsets[0][0], // Tile 0 X
|
||||
&gamemap->tilesets[0].tile_offsets[0][1], // Tile 0 Y
|
||||
&gamemap->tilesets[0].tile_offsets[1][0], // Tile 1 X
|
||||
&gamemap->tilesets[0].tile_offsets[1][1], // Tile 0 Y
|
||||
&gamemap->tilesets[0].tile_offsets[2][0], // Tile 2 X
|
||||
&gamemap->tilesets[0].tile_offsets[2][1], // Tile 2 Y
|
||||
&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[0][0], // Tile 0 X
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[0][1], // Tile 0 Y
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[1][0], // Tile 1 X
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[1][1], // Tile 0 Y
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[2][0], // Tile 2 X
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[2][1], // Tile 2 Y
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[3][0], // Tile 3 X
|
||||
&akgl_gamemap->tilesets[0].tile_offsets[3][1], // Tile 3 Y
|
||||
};
|
||||
int i = 0;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
gamemap->tilesets[0].tilecount = 4;
|
||||
gamemap->tilesets[0].columns = 2;
|
||||
gamemap->tilesets[0].firstgid = 1;
|
||||
gamemap->tilesets[0].imageheight = 20;
|
||||
gamemap->tilesets[0].imagewidth = 20;
|
||||
gamemap->tilesets[0].tilecount = 4;
|
||||
gamemap->tilesets[0].tileheight = 10;
|
||||
gamemap->tilesets[0].tilewidth = 10;
|
||||
gamemap->tilesets[0].spacing = 0;
|
||||
gamemap->tilesets[0].margin = 0;
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
akgl_gamemap->tilesets[0].tilecount = 4;
|
||||
akgl_gamemap->tilesets[0].columns = 2;
|
||||
akgl_gamemap->tilesets[0].firstgid = 1;
|
||||
akgl_gamemap->tilesets[0].imageheight = 20;
|
||||
akgl_gamemap->tilesets[0].imagewidth = 20;
|
||||
akgl_gamemap->tilesets[0].tilecount = 4;
|
||||
akgl_gamemap->tilesets[0].tileheight = 10;
|
||||
akgl_gamemap->tilesets[0].tilewidth = 10;
|
||||
akgl_gamemap->tilesets[0].spacing = 0;
|
||||
akgl_gamemap->tilesets[0].margin = 0;
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
CATCH(errctx, akgl_tilemap_compute_tileset_offsets(gamemap, 0));
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_tilemap_compute_tileset_offsets(akgl_gamemap, 0));
|
||||
// Tile 0 X offset
|
||||
for ( i = 0; i < 8; i++ ) {
|
||||
FAIL_NONZERO_BREAK(
|
||||
@@ -143,10 +145,10 @@ akerr_ErrorContext *test_akgl_tilemap_load_layer_objects(void)
|
||||
json_error_t errdata;
|
||||
akgl_Actor *testactor;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_heap_next_string(&pathstr));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
|
||||
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);
|
||||
CATCH(errctx, akgl_get_json_array_value(doc, "layers", &layers));
|
||||
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);
|
||||
FAIL_ZERO_BREAK(
|
||||
@@ -193,11 +195,11 @@ akerr_ErrorContext *test_akgl_tilemap_load_layer_tile(void)
|
||||
json_t *tiledata = NULL;
|
||||
json_error_t errdata;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_heap_next_string(&pathstr));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
|
||||
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_index_object(layers, 0, &tilelayer));
|
||||
CATCH(errctx, akgl_get_json_array_value(tilelayer, "data", &tiledata));
|
||||
CATCH(errctx, akgl_tilemap_load_layer_tile(gamemap, tilelayer, 0, pathstr));
|
||||
if ( (gamemap->layers[0].data[0] != 1) ||
|
||||
(gamemap->layers[0].data[1] != 2) ||
|
||||
(gamemap->layers[0].data[2] != 3) ||
|
||||
(gamemap->layers[0].data[3] != 4) ) {
|
||||
CATCH(errctx, akgl_tilemap_load_layer_tile(akgl_gamemap, tilelayer, 0, pathstr));
|
||||
if ( (akgl_gamemap->layers[0].data[0] != 1) ||
|
||||
(akgl_gamemap->layers[0].data[1] != 2) ||
|
||||
(akgl_gamemap->layers[0].data[2] != 3) ||
|
||||
(akgl_gamemap->layers[0].data[3] != 4) ) {
|
||||
FAIL_BREAK(errctx, AKERR_VALUE, "Test tilemap layer 0 tiles loaded with incorrect values (check gdb)");
|
||||
}
|
||||
} CLEANUP {
|
||||
@@ -233,61 +235,61 @@ akerr_ErrorContext *test_akgl_tilemap_load_layers(void)
|
||||
json_error_t errdata;
|
||||
int i = 0;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_heap_next_string(&pathstr));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
|
||||
doc = json_load_file((char *)&pathstr->data, 0, &errdata);
|
||||
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);
|
||||
CATCH(errctx, akgl_tilemap_load_layers(gamemap, doc, pathstr));
|
||||
CATCH(errctx, akgl_tilemap_load_layers(akgl_gamemap, doc, pathstr));
|
||||
FAIL_NONZERO_BREAK(
|
||||
errctx,
|
||||
(gamemap->numlayers != 3),
|
||||
(akgl_gamemap->numlayers != 3),
|
||||
AKERR_VALUE,
|
||||
"Map layer count incorrect"
|
||||
);
|
||||
for ( i = 0; i < gamemap->numlayers; i++ ) {
|
||||
if ( (gamemap->layers[i].opacity != 1) ||
|
||||
(gamemap->layers[i].visible != true) ||
|
||||
(gamemap->layers[i].id != (i + 1)) ||
|
||||
(gamemap->layers[i].x != 0) ||
|
||||
(gamemap->layers[i].y != 0) ) {
|
||||
for ( i = 0; i < akgl_gamemap->numlayers; i++ ) {
|
||||
if ( (akgl_gamemap->layers[i].opacity != 1) ||
|
||||
(akgl_gamemap->layers[i].visible != true) ||
|
||||
(akgl_gamemap->layers[i].id != (i + 1)) ||
|
||||
(akgl_gamemap->layers[i].x != 0) ||
|
||||
(akgl_gamemap->layers[i].y != 0) ) {
|
||||
FAIL(errctx, AKERR_VALUE, "Map layer data loaded incorrectly (see gdb)");
|
||||
goto _test_akgl_tilemap_load_layers_cleanup;
|
||||
}
|
||||
}
|
||||
// Layer 2 should have 1 object loaded
|
||||
if ( (gamemap->layers[1].objects[0].actorptr != SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "testactor", NULL)) ||
|
||||
(gamemap->layers[1].objects[1].name[0] != '\0' ) ||
|
||||
(gamemap->layers[1].objects[1].id != 0) ) {
|
||||
if ( (akgl_gamemap->layers[1].objects[0].actorptr != SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "testactor", NULL)) ||
|
||||
(akgl_gamemap->layers[1].objects[1].name[0] != '\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)");
|
||||
}
|
||||
// Layer 1 and 3 should have no objects
|
||||
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");
|
||||
goto _test_akgl_tilemap_load_layers_cleanup;
|
||||
}
|
||||
}
|
||||
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");
|
||||
goto _test_akgl_tilemap_load_layers_cleanup;
|
||||
}
|
||||
}
|
||||
// Layers 1 and 3 should have tile data
|
||||
if ( (gamemap->layers[0].data[0] != 1) ||
|
||||
(gamemap->layers[0].data[1] != 2) ||
|
||||
(gamemap->layers[0].data[2] != 3) ||
|
||||
(gamemap->layers[0].data[3] != 4) ||
|
||||
(gamemap->layers[2].data[0] != 0) ||
|
||||
(gamemap->layers[2].data[1] != 5) ||
|
||||
(gamemap->layers[2].data[2] != 0) ||
|
||||
(gamemap->layers[2].data[3] != 6)
|
||||
if ( (akgl_gamemap->layers[0].data[0] != 1) ||
|
||||
(akgl_gamemap->layers[0].data[1] != 2) ||
|
||||
(akgl_gamemap->layers[0].data[2] != 3) ||
|
||||
(akgl_gamemap->layers[0].data[3] != 4) ||
|
||||
(akgl_gamemap->layers[2].data[0] != 0) ||
|
||||
(akgl_gamemap->layers[2].data[1] != 5) ||
|
||||
(akgl_gamemap->layers[2].data[2] != 0) ||
|
||||
(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");
|
||||
}
|
||||
@@ -312,43 +314,43 @@ akerr_ErrorContext *test_akgl_tilemap_load_tilesets(void)
|
||||
json_error_t errdata;
|
||||
SDL_Texture *image = NULL;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_heap_next_string(&pathstr));
|
||||
snprintf((char *)&pathstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testmap.tmj");
|
||||
doc = json_load_file((char *)&pathstr->data, 0, &errdata);
|
||||
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);
|
||||
CATCH(errctx, akgl_tilemap_load_tilesets(gamemap, doc, pathstr));
|
||||
FAIL_NONZERO_BREAK(errctx, (gamemap->numtilesets != 1), AKERR_VALUE, "Incorrect number of tilesets loaded for map");
|
||||
if ( (gamemap->tilesets[0].columns != 48 ) ||
|
||||
(gamemap->tilesets[0].firstgid != 1) ||
|
||||
(gamemap->tilesets[0].imageheight != 576) ||
|
||||
(gamemap->tilesets[0].imagewidth != 768) ||
|
||||
(gamemap->tilesets[0].margin != 0) ||
|
||||
(gamemap->tilesets[0].spacing != 0) ||
|
||||
(gamemap->tilesets[0].tilecount != 1728) ||
|
||||
(gamemap->tilesets[0].tileheight != 16) ||
|
||||
(gamemap->tilesets[0].tilewidth != 16) ) {
|
||||
CATCH(errctx, akgl_tilemap_load_tilesets(akgl_gamemap, doc, pathstr));
|
||||
FAIL_NONZERO_BREAK(errctx, (akgl_gamemap->numtilesets != 1), AKERR_VALUE, "Incorrect number of tilesets loaded for map");
|
||||
if ( (akgl_gamemap->tilesets[0].columns != 48 ) ||
|
||||
(akgl_gamemap->tilesets[0].firstgid != 1) ||
|
||||
(akgl_gamemap->tilesets[0].imageheight != 576) ||
|
||||
(akgl_gamemap->tilesets[0].imagewidth != 768) ||
|
||||
(akgl_gamemap->tilesets[0].margin != 0) ||
|
||||
(akgl_gamemap->tilesets[0].spacing != 0) ||
|
||||
(akgl_gamemap->tilesets[0].tilecount != 1728) ||
|
||||
(akgl_gamemap->tilesets[0].tileheight != 16) ||
|
||||
(akgl_gamemap->tilesets[0].tilewidth != 16) ) {
|
||||
FAIL_BREAK(errctx, AKERR_VALUE, "Tileset loaded with incorrect values");
|
||||
}
|
||||
FAIL_NONZERO_BREAK(
|
||||
errctx,
|
||||
strcmp((char *)&gamemap->tilesets[0].name, "World_A1"),
|
||||
strcmp((char *)&akgl_gamemap->tilesets[0].name, "World_A1"),
|
||||
AKERR_VALUE,
|
||||
"Tileset loaded with incorrect name");
|
||||
|
||||
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");
|
||||
|
||||
CATCH(
|
||||
errctx,
|
||||
akgl_render_and_compare(
|
||||
gamemap->tilesets[0].texture,
|
||||
akgl_gamemap->tilesets[0].texture,
|
||||
image,
|
||||
0, 0, 768, 576,
|
||||
"test_akgl_tilemap_loaded_tileset.png")
|
||||
@@ -370,12 +372,12 @@ akerr_ErrorContext *test_akgl_tilemap_load(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
memset((void *)akgl_gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
CATCH(errctx, akgl_tilemap_load("assets/testmap.tmj", gamemap));
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
CATCH(errctx, akgl_tilemap_load("assets/testmap.tmj", akgl_gamemap));
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
@@ -387,8 +389,8 @@ akerr_ErrorContext *test_akgl_tilemap_draw(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
@@ -401,8 +403,8 @@ akerr_ErrorContext *test_akgl_tilemap_draw_tileset(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
@@ -415,18 +417,19 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
akgl_gamemap = &akgl_default_gamemap;
|
||||
akgl_renderer = &akgl_default_renderer;
|
||||
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
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_heap_init());
|
||||
|
||||
23
tests/util.c
23
tests/util.c
@@ -6,6 +6,8 @@
|
||||
#include <akgl/staticstring.h>
|
||||
#include <akgl/util.h>
|
||||
|
||||
#include "testutil.h"
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
RectanglePoints points;
|
||||
akgl_RectanglePoints points;
|
||||
// Zeroed for the same reason as the fixtures in
|
||||
// test_akgl_collide_point_rectangle_nullpointers: the last case here is a
|
||||
// real call, and feeding it stack garbage is noise under `memcheck`.
|
||||
SDL_FRect testrect = {.x = 0, .y = 0, .w = 0, .h = 0};
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
memset((void *)&points, 0x00, sizeof(RectanglePoints));
|
||||
memset((void *)&points, 0x00, sizeof(akgl_RectanglePoints));
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(NULL, NULL));
|
||||
@@ -57,7 +59,7 @@ akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(void)
|
||||
|
||||
ATTEMPT {
|
||||
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 {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
@@ -75,9 +77,9 @@ akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(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};
|
||||
memset((void *)&points, 0x00, sizeof(RectanglePoints));
|
||||
memset((void *)&points, 0x00, sizeof(akgl_RectanglePoints));
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
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
|
||||
// floats out of it is sixteen findings under `memcheck` for a test that is
|
||||
// not about coordinates at all.
|
||||
point testpoint = { .x = 0, .y = 0 };
|
||||
RectanglePoints testrectpoints;
|
||||
akgl_Point testpoint = { .x = 0, .y = 0 };
|
||||
akgl_RectanglePoints testrectpoints;
|
||||
bool testcollide = false;
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
memset(&testrectpoints, 0x00, sizeof(RectanglePoints));
|
||||
memset(&testrectpoints, 0x00, sizeof(akgl_RectanglePoints));
|
||||
|
||||
ATTEMPT {
|
||||
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)
|
||||
{
|
||||
point testpoint = {.x = 16, .y = 16};
|
||||
akgl_Point testpoint = {.x = 16, .y = 16};
|
||||
SDL_FRect testrect = { .x = 0, .y = 0, .w = 32, .h = 32};
|
||||
RectanglePoints testrectpoints;
|
||||
akgl_RectanglePoints testrectpoints;
|
||||
bool testcollide = false;
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
@@ -392,6 +394,7 @@ int main(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, test_akgl_rectangle_points_nullpointers());
|
||||
CATCH(errctx, test_akgl_rectangle_points_math());
|
||||
CATCH(errctx, test_akgl_collide_point_rectangle_nullpointers());
|
||||
|
||||
@@ -99,6 +99,7 @@ int main(void)
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
TEST_TRAP_UNHANDLED_ERRORS();
|
||||
CATCH(errctx, test_version_string_matches_components());
|
||||
CATCH(errctx, test_version_linked_matches_compiled());
|
||||
CATCH(errctx, test_version_at_least_boundaries());
|
||||
|
||||
@@ -35,7 +35,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
*appstate = (void *)&game.state;
|
||||
*appstate = (void *)&akgl_game.state;
|
||||
characterjson = argv[1];
|
||||
memset((char *)&pathbuf, 0x00, 4096);
|
||||
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, getcwd((char *)&cwdbuf, 1024), AKERR_NULLPOINTER, "Couldn't get current working directory");
|
||||
|
||||
strcpy((char *)&game.name, "charviewer");
|
||||
strcpy((char *)&game.version, "0.0.1");
|
||||
strcpy((char *)&game.uri, "net.aklabs.libakgl.charviewer");
|
||||
strcpy((char *)&akgl_game.name, "charviewer");
|
||||
strcpy((char *)&akgl_game.version, "0.0.1");
|
||||
strcpy((char *)&akgl_game.uri, "net.aklabs.libakgl.charviewer");
|
||||
CATCH(errctx, akgl_game_init());
|
||||
CATCH(errctx, akgl_set_property("game.screenwidth", "640"));
|
||||
CATCH(errctx, akgl_set_property("game.screenheight", "480"));
|
||||
CATCH(errctx, akgl_render_init2d(renderer));
|
||||
CATCH(errctx, akgl_physics_init_null(physics));
|
||||
CATCH(errctx, akgl_render_2d_init(akgl_renderer));
|
||||
CATCH(errctx, akgl_physics_init_null(akgl_physics));
|
||||
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
@@ -144,9 +144,9 @@ SDL_AppResult SDL_AppIterate(void *appstate)
|
||||
AKGL_BITMASK_CLEAR(opflags.flags);
|
||||
AKGL_BITMASK_ADD(opflags.flags, AKGL_ITERATOR_OP_UPDATE);
|
||||
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 {
|
||||
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);
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
@@ -154,7 +154,7 @@ SDL_AppResult SDL_AppIterate(void *appstate)
|
||||
LOG_ERROR(errctx);
|
||||
return SDL_APP_FAILURE;
|
||||
} FINISH_NORETURN(errctx);
|
||||
SDL_RenderPresent(renderer->sdl_renderer);
|
||||
SDL_RenderPresent(akgl_renderer->sdl_renderer);
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user