2026-07-30 01:10:31 -04:00
|
|
|
/**
|
|
|
|
|
* @file game.c
|
|
|
|
|
* @brief Implements the game subsystem.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
|
#include <SDL3_image/SDL_image.h>
|
|
|
|
|
#include <SDL3_mixer/SDL_mixer.h>
|
2026-05-06 11:12:42 -04:00
|
|
|
#include <SDL3_ttf/SDL_ttf.h>
|
2025-08-03 10:07:35 -04:00
|
|
|
#include <stdio.h>
|
2026-01-05 08:58:06 -05:00
|
|
|
#include <akerror.h>
|
2026-05-08 23:15:11 -04:00
|
|
|
#include <semver.h>
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-10 00:03:45 -04:00
|
|
|
#include <akstdlib.h>
|
2026-05-07 22:20:10 -04:00
|
|
|
#include <akgl/game.h>
|
|
|
|
|
#include <akgl/controller.h>
|
|
|
|
|
#include <akgl/tilemap.h>
|
|
|
|
|
#include <akgl/sprite.h>
|
|
|
|
|
#include <akgl/heap.h>
|
|
|
|
|
#include <akgl/registry.h>
|
|
|
|
|
#include <akgl/staticstring.h>
|
|
|
|
|
#include <akgl/iterator.h>
|
2026-05-25 21:29:18 -04:00
|
|
|
#include <akgl/physics.h>
|
Migrate to the libakerror 1.0.0 status registry
libakerror 1.0.0 replaced the consumer-sized status-name array with a
private registry and made status-code ownership explicit and enforced.
AKERR_MAX_ERR_VALUE and __AKERR_ERROR_NAMES are gone, and the registry
entry points raise akerr_ErrorContext * instead of returning int. See
deps/libakerror/UPGRADING.md.
The break was not only source-level. libakgl's codes sat at
AKERR_LAST_ERRNO_VALUE + 18 through + 22, and 1.0.0 claimed exactly
those five offsets for its own AKERR_STATUS_* registry codes, so every
AKGL_ERR_* was aliasing a libakerror status. HANDLE(e,
AKGL_ERR_LOGICINTERRUPT) at physics.c:222 would have swallowed a
foreign-name refusal.
- Move the band to AKERR_FIRST_CONSUMER_STATUS (256) as fixed offsets,
so a libc that grows an errno cannot move the codes, and add
AKGL_ERR_OWNER, AKGL_ERR_LIMIT and AKGL_ERR_COUNT to describe it.
- Reserve the range and register the names through the owned entry
points, PASS-ing each: these are AKERR_NOIGNORE, and the old
akerr_name_for_status calls discarded failure silently.
- Drop the AKERR_MAX_ERR_VALUE=256 compile definition.
- Guard on AKERR_FIRST_CONSUMER_STATUS in include/akgl/error.h, which
now includes <akerror.h> so the guard is reliable. The embedded build
is fine, but the find_package path can pick up a stale installed
header, and 1.0.0 has an soname, so that pairing is an ABI mismatch
rather than a compile problem. Same guard libakstdlib already carries.
Registration also moves out of akgl_heap_init into a new akgl_error_init
in src/error.c. It was in the heap pool's initializer only because that
was the first thing akgl_game_init called, and the upgrade turned five
fire-and-forget name calls into a library-wide ownership claim that can
fail. That placement was hiding a defect: game.c raises AKGL_ERR_SDL
when SDL_CreateMutex fails, five lines before akgl_heap_init ran, so the
earliest error path in the library was guaranteed to print "Unknown
Error". akgl_error_init is now the first statement in akgl_game_init.
Callers that drive subsystems directly must call akgl_error_init first;
it is idempotent, so ordering it precisely is not required. The eleven
test suites that relied on akgl_heap_init to name their statuses now
call it explicitly, or their failure messages would have degraded to
"Unknown Error".
Add tests/error.c: assert every code reads back its registered name,
that the name table and AKGL_ERR_COUNT agree, that a foreign owner is
refused with AKERR_STATUS_NAME_FOREIGN and AKERR_STATUS_RANGE_OVERLAP,
and that repeating the init is a no-op. That last one is a live
constraint, not a triviality -- libakerror treats only an identical
reservation as a repeat, so a subset or superset raises.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 22:20:28 -04:00
|
|
|
#include <akgl/error.h>
|
2026-05-07 22:20:10 -04:00
|
|
|
#include <akgl/SDL_GameControllerDB.h>
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
SDL_Window *window = NULL;
|
2026-06-02 13:15:26 -04:00
|
|
|
|
|
|
|
|
// Currently active objects
|
|
|
|
|
akgl_RenderBackend *renderer;
|
|
|
|
|
akgl_PhysicsBackend *physics;
|
|
|
|
|
SDL_FRect *camera;
|
|
|
|
|
akgl_Tilemap *gamemap;
|
|
|
|
|
|
|
|
|
|
// Default objects
|
|
|
|
|
akgl_RenderBackend _akgl_renderer;
|
|
|
|
|
akgl_PhysicsBackend _akgl_physics;
|
|
|
|
|
SDL_FRect _akgl_camera;
|
|
|
|
|
akgl_Tilemap _akgl_gamemap;
|
|
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
MIX_Audio *bgm = NULL;
|
2026-05-08 22:16:43 -04:00
|
|
|
MIX_Mixer *akgl_mixer = NULL;
|
|
|
|
|
MIX_Track *akgl_tracks[AKGL_GAME_AUDIO_MAX_TRACKS];
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_Game game;
|
2025-08-03 15:14:36 -04:00
|
|
|
|
2026-07-31 08:27:15 -04:00
|
|
|
static akerr_ErrorContext *write_exact(const void *ptr, size_t size, size_t nmemb, FILE *fp)
|
|
|
|
|
{
|
|
|
|
|
size_t transferred;
|
|
|
|
|
|
|
|
|
|
return aksl_fwrite(ptr, size, nmemb, fp, &transferred);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static akerr_ErrorContext *read_exact(void *ptr, size_t size, size_t nmemb, FILE *fp)
|
|
|
|
|
{
|
|
|
|
|
size_t transferred;
|
|
|
|
|
|
|
|
|
|
return aksl_fread(ptr, size, nmemb, fp, &transferred);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 11:07:31 -04:00
|
|
|
void akgl_game_lowfps(void)
|
|
|
|
|
{
|
|
|
|
|
SDL_Log("Low FPS! %d", game.fps);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
|
2025-08-03 15:14:36 -04:00
|
|
|
{
|
2026-05-13 04:55:19 -04:00
|
|
|
int screenwidth = 0;
|
|
|
|
|
int screenheight = 0;
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2025-08-03 21:42:12 -04:00
|
|
|
int i = 0;
|
2026-05-25 21:29:18 -04:00
|
|
|
PREPARE_ERROR(e);
|
Migrate to the libakerror 1.0.0 status registry
libakerror 1.0.0 replaced the consumer-sized status-name array with a
private registry and made status-code ownership explicit and enforced.
AKERR_MAX_ERR_VALUE and __AKERR_ERROR_NAMES are gone, and the registry
entry points raise akerr_ErrorContext * instead of returning int. See
deps/libakerror/UPGRADING.md.
The break was not only source-level. libakgl's codes sat at
AKERR_LAST_ERRNO_VALUE + 18 through + 22, and 1.0.0 claimed exactly
those five offsets for its own AKERR_STATUS_* registry codes, so every
AKGL_ERR_* was aliasing a libakerror status. HANDLE(e,
AKGL_ERR_LOGICINTERRUPT) at physics.c:222 would have swallowed a
foreign-name refusal.
- Move the band to AKERR_FIRST_CONSUMER_STATUS (256) as fixed offsets,
so a libc that grows an errno cannot move the codes, and add
AKGL_ERR_OWNER, AKGL_ERR_LIMIT and AKGL_ERR_COUNT to describe it.
- Reserve the range and register the names through the owned entry
points, PASS-ing each: these are AKERR_NOIGNORE, and the old
akerr_name_for_status calls discarded failure silently.
- Drop the AKERR_MAX_ERR_VALUE=256 compile definition.
- Guard on AKERR_FIRST_CONSUMER_STATUS in include/akgl/error.h, which
now includes <akerror.h> so the guard is reliable. The embedded build
is fine, but the find_package path can pick up a stale installed
header, and 1.0.0 has an soname, so that pairing is an ABI mismatch
rather than a compile problem. Same guard libakstdlib already carries.
Registration also moves out of akgl_heap_init into a new akgl_error_init
in src/error.c. It was in the heap pool's initializer only because that
was the first thing akgl_game_init called, and the upgrade turned five
fire-and-forget name calls into a library-wide ownership claim that can
fail. That placement was hiding a defect: game.c raises AKGL_ERR_SDL
when SDL_CreateMutex fails, five lines before akgl_heap_init ran, so the
earliest error path in the library was guaranteed to print "Unknown
Error". akgl_error_init is now the first statement in akgl_game_init.
Callers that drive subsystems directly must call akgl_error_init first;
it is idempotent, so ordering it precisely is not required. The eleven
test suites that relied on akgl_heap_init to name their statuses now
call it explicitly, or their failure messages would have degraded to
"Unknown Error".
Add tests/error.c: assert every code reads back its registered name,
that the name table and AKGL_ERR_COUNT agree, that a foreign owner is
refused with AKERR_STATUS_NAME_FOREIGN and AKERR_STATUS_RANGE_OVERLAP,
and that repeating the init is a no-op. That last one is a live
constraint, not a triviality -- libakerror treats only an identical
reservation as a repeat, so a subset or superset raises.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 22:20:28 -04:00
|
|
|
// First, before anything that can raise: everything below reports through
|
|
|
|
|
// 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());
|
2026-06-02 13:15:26 -04:00
|
|
|
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());
|
|
|
|
|
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");
|
|
|
|
|
PASS(e, akgl_heap_init());
|
|
|
|
|
PASS(e, akgl_registry_init_actor());
|
|
|
|
|
PASS(e, akgl_registry_init_sprite());
|
|
|
|
|
PASS(e, akgl_registry_init_spritesheet());
|
|
|
|
|
PASS(e, akgl_registry_init_character());
|
|
|
|
|
PASS(e, akgl_registry_init_font());
|
|
|
|
|
PASS(e, akgl_registry_init_music());
|
|
|
|
|
PASS(e, akgl_registry_init_properties());
|
|
|
|
|
PASS(e, akgl_registry_init_actor_state_strings());
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2025-08-03 15:14:36 -04:00
|
|
|
SDL_SetAppMetadata(game.name, game.version, game.uri);
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
for ( i = 0 ; i < AKGL_MAX_CONTROL_MAPS; i++ ) {
|
|
|
|
|
memset(&GAME_ControlMaps[i], 0x00, sizeof(akgl_ControlMap));
|
2025-08-03 21:42:12 -04:00
|
|
|
}
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2025-08-03 15:14:36 -04:00
|
|
|
FAIL_ZERO_RETURN(
|
2026-05-25 21:29:18 -04:00
|
|
|
e,
|
2025-08-03 15:14:36 -04:00
|
|
|
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD | SDL_INIT_AUDIO),
|
2026-05-06 23:18:42 -04:00
|
|
|
AKGL_ERR_SDL,
|
2025-08-03 15:14:36 -04:00
|
|
|
"Couldn't initialize SDL: %s",
|
|
|
|
|
SDL_GetError());
|
2025-08-09 13:53:37 -04:00
|
|
|
|
|
|
|
|
// Load the Game Controller DB
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
for ( i = 0; i < AKGL_SDL_GAMECONTROLLER_DB_LEN ; i++ ) {
|
2025-08-09 13:53:37 -04:00
|
|
|
if ( SDL_AddGamepadMapping(SDL_GAMECONTROLLER_DB[i]) == -1 ) {
|
2026-05-25 21:29:18 -04:00
|
|
|
FAIL_ZERO_RETURN(e, 0, AKGL_ERR_SDL, "%s", SDL_GetError());
|
2025-08-09 13:53:37 -04:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-25 21:29:18 -04:00
|
|
|
PASS(e, akgl_controller_open_gamepads());
|
2026-05-21 21:43:51 -04:00
|
|
|
|
2025-08-03 15:14:36 -04:00
|
|
|
FAIL_ZERO_RETURN(
|
2026-05-25 21:29:18 -04:00
|
|
|
e,
|
2025-08-03 15:14:36 -04:00
|
|
|
MIX_Init(),
|
2026-05-06 23:18:42 -04:00
|
|
|
AKGL_ERR_SDL,
|
2025-08-03 15:14:36 -04:00
|
|
|
"Couldn't initialize audio: %s",
|
|
|
|
|
SDL_GetError());
|
2026-05-08 22:16:43 -04:00
|
|
|
akgl_mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, 0);
|
2025-08-03 15:14:36 -04:00
|
|
|
FAIL_ZERO_RETURN(
|
2026-05-25 21:29:18 -04:00
|
|
|
e,
|
2026-05-08 22:16:43 -04:00
|
|
|
akgl_mixer,
|
2026-05-06 23:18:42 -04:00
|
|
|
AKGL_ERR_SDL,
|
2025-08-03 15:14:36 -04:00
|
|
|
"Unable to create mixer device: %s",
|
|
|
|
|
SDL_GetError());
|
2025-08-03 21:42:12 -04:00
|
|
|
|
2026-05-06 11:12:42 -04:00
|
|
|
FAIL_ZERO_RETURN(
|
2026-05-25 21:29:18 -04:00
|
|
|
e,
|
2026-05-06 11:12:42 -04:00
|
|
|
TTF_Init(),
|
2026-05-06 23:18:42 -04:00
|
|
|
AKGL_ERR_SDL,
|
2026-05-06 11:12:42 -04:00
|
|
|
"Couldn't initialize front engine: %s",
|
|
|
|
|
SDL_GetError());
|
2026-06-02 13:15:26 -04:00
|
|
|
|
|
|
|
|
camera = &_akgl_camera;
|
|
|
|
|
renderer = &_akgl_renderer;
|
|
|
|
|
physics = &_akgl_physics;
|
|
|
|
|
gamemap = &_akgl_gamemap;
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2026-05-25 21:29:18 -04:00
|
|
|
PASS(e, akgl_game_state_unlock());
|
|
|
|
|
SUCCEED_RETURN(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_lock(void)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(e);
|
|
|
|
|
SDL_Time totaltime = 0;
|
|
|
|
|
|
|
|
|
|
while ( totaltime < AKGL_TIME_ONESEC_MS ) {
|
|
|
|
|
if ( SDL_TryLockMutex(game.statelock) == true ) {
|
|
|
|
|
SUCCEED_RETURN(e);
|
|
|
|
|
}
|
|
|
|
|
totaltime += 100;
|
|
|
|
|
SDL_Delay(100);
|
|
|
|
|
}
|
|
|
|
|
FAIL_RETURN(e, AKGL_ERR_SDL, "%s", SDL_GetError());
|
|
|
|
|
}
|
2026-05-06 11:12:42 -04:00
|
|
|
|
2026-05-25 21:29:18 -04:00
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(e);
|
|
|
|
|
SDL_UnlockMutex(game.statelock);
|
|
|
|
|
SUCCEED_RETURN(e);
|
2026-05-06 11:12:42 -04:00
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
void akgl_game_updateFPS()
|
2026-05-06 11:12:42 -04:00
|
|
|
{
|
|
|
|
|
SDL_Time curTime;
|
|
|
|
|
curTime = SDL_GetTicksNS();
|
2026-05-06 23:18:42 -04:00
|
|
|
if ( (curTime - game.lastFPSTime) > AKGL_TIME_ONESEC_NS ) {
|
2026-05-06 11:12:42 -04:00
|
|
|
game.fps = game.framesSinceUpdate;
|
|
|
|
|
game.framesSinceUpdate = 0;
|
|
|
|
|
game.lastFPSTime = curTime;
|
|
|
|
|
}
|
2026-05-15 11:07:31 -04:00
|
|
|
if ( game.fps < 30 ) {
|
|
|
|
|
game.lowfpsfunc();
|
|
|
|
|
}
|
2026-05-06 11:12:42 -04:00
|
|
|
game.framesSinceUpdate += 1;
|
|
|
|
|
game.lastIterTime = curTime;
|
2025-08-03 15:14:36 -04:00
|
|
|
}
|
2026-05-08 22:01:56 -04:00
|
|
|
|
2026-05-09 14:45:37 -04:00
|
|
|
/*
|
|
|
|
|
* entity name -> pointer map tables
|
|
|
|
|
*/
|
|
|
|
|
|
2026-07-30 01:10:31 -04:00
|
|
|
/**
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:02:20 -04:00
|
|
|
* @brief Write one actor's name and save-time address to the name table.
|
|
|
|
|
*
|
|
|
|
|
* An `SDL_EnumerateProperties` callback. The name is written at a fixed
|
|
|
|
|
* #AKGL_ACTOR_MAX_NAME_LENGTH bytes so the reader can walk the table without a
|
|
|
|
|
* length prefix, and the pointer is written raw -- it is not a pointer the
|
|
|
|
|
* loader will ever dereference, only a key it maps back to the object that has
|
|
|
|
|
* the same name next run.
|
|
|
|
|
*
|
|
|
|
|
* @param userdata The open output stream, as a `FILE *`. Required.
|
|
|
|
|
* @param props #AKGL_REGISTRY_ACTOR, supplied by SDL.
|
|
|
|
|
* @param name The actor's registry key. Read for
|
|
|
|
|
* #AKGL_ACTOR_MAX_NAME_LENGTH bytes regardless of its actual
|
|
|
|
|
* length, so a shorter key is read past its end.
|
|
|
|
|
*
|
|
|
|
|
* @warning Ends in `FINISH_NORETURN`: an unhandled error here logs a stack trace
|
|
|
|
|
* and then calls libakerror's unhandled-error handler, which by default
|
|
|
|
|
* **exits the process**. A failed write during a save terminates the
|
|
|
|
|
* game rather than returning an error.
|
2026-07-30 01:10:31 -04:00
|
|
|
*/
|
2026-05-09 14:45:37 -04:00
|
|
|
void akgl_game_save_actorname_iterator(void *userdata, SDL_PropertiesID props, const char *name)
|
|
|
|
|
{
|
|
|
|
|
FILE *fp = (FILE *)userdata;
|
|
|
|
|
akgl_Actor *actor = NULL;
|
2026-05-25 21:29:18 -04:00
|
|
|
PREPARE_ERROR(e);
|
2026-05-09 14:45:37 -04:00
|
|
|
ATTEMPT {
|
2026-05-25 21:29:18 -04:00
|
|
|
FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer");
|
2026-07-31 08:27:15 -04:00
|
|
|
CATCH(e, write_exact((char *)name, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp));
|
2026-05-09 14:45:37 -04:00
|
|
|
actor = SDL_GetPointerProperty(props, name, NULL);
|
2026-07-31 08:27:15 -04:00
|
|
|
CATCH(e, write_exact(&actor, 1, sizeof(akgl_Actor *), fp));
|
2026-05-09 14:45:37 -04:00
|
|
|
} CLEANUP {
|
2026-05-25 21:29:18 -04:00
|
|
|
} PROCESS(e) {
|
|
|
|
|
} FINISH_NORETURN(e);
|
2026-05-09 14:45:37 -04:00
|
|
|
}
|
|
|
|
|
|
2026-07-30 01:10:31 -04:00
|
|
|
/**
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:02:20 -04:00
|
|
|
* @brief Write one sprite's name and save-time address to the name table.
|
|
|
|
|
*
|
|
|
|
|
* As akgl_game_save_actorname_iterator, but the name field is
|
|
|
|
|
* #AKGL_SPRITE_MAX_NAME_LENGTH wide.
|
|
|
|
|
*
|
|
|
|
|
* @param userdata The open output stream, as a `FILE *`. Required.
|
|
|
|
|
* @param props #AKGL_REGISTRY_SPRITE, supplied by SDL.
|
|
|
|
|
* @param name The sprite's registry key, written at fixed width.
|
|
|
|
|
*
|
|
|
|
|
* @warning Terminates the process on an unhandled error. See
|
|
|
|
|
* akgl_game_save_actorname_iterator.
|
2026-07-30 01:10:31 -04:00
|
|
|
*/
|
2026-05-09 14:45:37 -04:00
|
|
|
void akgl_game_save_spritename_iterator(void *userdata, SDL_PropertiesID props, const char *name)
|
|
|
|
|
{
|
|
|
|
|
FILE *fp = (FILE *)userdata;
|
|
|
|
|
akgl_Sprite *sprite = NULL;
|
2026-05-25 21:29:18 -04:00
|
|
|
PREPARE_ERROR(e);
|
2026-05-09 14:45:37 -04:00
|
|
|
ATTEMPT {
|
2026-05-25 21:29:18 -04:00
|
|
|
FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer");
|
2026-05-09 14:45:37 -04:00
|
|
|
sprite = SDL_GetPointerProperty(props, name, NULL);
|
2026-07-31 08:27:15 -04:00
|
|
|
CATCH(e, write_exact((char *)name, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp));
|
|
|
|
|
CATCH(e, write_exact(&sprite, 1, sizeof(akgl_Sprite *), fp));
|
2026-05-09 14:45:37 -04:00
|
|
|
} CLEANUP {
|
2026-05-25 21:29:18 -04:00
|
|
|
} PROCESS(e) {
|
|
|
|
|
} FINISH_NORETURN(e);
|
2026-05-09 14:45:37 -04:00
|
|
|
}
|
|
|
|
|
|
2026-07-30 01:10:31 -04:00
|
|
|
/**
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:02:20 -04:00
|
|
|
* @brief Write one spritesheet's name and save-time address to the name table.
|
|
|
|
|
*
|
|
|
|
|
* As akgl_game_save_actorname_iterator, but the name field is
|
|
|
|
|
* #AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH wide -- the widest of the four, since
|
|
|
|
|
* a spritesheet is keyed by its resolved path.
|
|
|
|
|
*
|
|
|
|
|
* @param userdata The open output stream, as a `FILE *`. Required.
|
|
|
|
|
* @param props #AKGL_REGISTRY_SPRITESHEET, supplied by SDL.
|
|
|
|
|
* @param name The spritesheet's registry key, written at fixed width.
|
|
|
|
|
*
|
|
|
|
|
* @warning Terminates the process on an unhandled error. See
|
|
|
|
|
* akgl_game_save_actorname_iterator.
|
|
|
|
|
* @note This is the table the loader disagrees with: it reads every table at
|
|
|
|
|
* #AKGL_ACTOR_MAX_NAME_LENGTH, so a save containing any spritesheet cannot
|
|
|
|
|
* be read back. TODO.md, "Known and still open" item 7.
|
2026-07-30 01:10:31 -04:00
|
|
|
*/
|
2026-05-09 14:45:37 -04:00
|
|
|
void akgl_game_save_spritesheetname_iterator(void *userdata, SDL_PropertiesID props, const char *name)
|
|
|
|
|
{
|
|
|
|
|
FILE *fp = (FILE *)userdata;
|
|
|
|
|
akgl_SpriteSheet *spritesheet = NULL;
|
2026-05-25 21:29:18 -04:00
|
|
|
PREPARE_ERROR(e);
|
2026-05-09 14:45:37 -04:00
|
|
|
ATTEMPT {
|
2026-05-25 21:29:18 -04:00
|
|
|
FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer");
|
2026-05-09 14:45:37 -04:00
|
|
|
spritesheet = SDL_GetPointerProperty(props, name, NULL);
|
2026-07-31 08:27:15 -04:00
|
|
|
CATCH(e, write_exact((char *)name, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp));
|
|
|
|
|
CATCH(e, write_exact(&spritesheet, 1, sizeof(akgl_SpriteSheet *), fp));
|
2026-05-09 14:45:37 -04:00
|
|
|
} CLEANUP {
|
2026-05-25 21:29:18 -04:00
|
|
|
} PROCESS(e) {
|
|
|
|
|
} FINISH_NORETURN(e);
|
2026-05-09 14:45:37 -04:00
|
|
|
}
|
|
|
|
|
|
2026-07-30 01:10:31 -04:00
|
|
|
/**
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:02:20 -04:00
|
|
|
* @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.
|
|
|
|
|
*
|
|
|
|
|
* @param userdata The open output stream, as a `FILE *`. Required.
|
|
|
|
|
* @param props #AKGL_REGISTRY_CHARACTER, supplied by SDL.
|
|
|
|
|
* @param name The character's registry key, written at fixed width.
|
|
|
|
|
*
|
|
|
|
|
* @warning Terminates the process on an unhandled error. See
|
|
|
|
|
* akgl_game_save_actorname_iterator.
|
2026-07-30 01:10:31 -04:00
|
|
|
*/
|
2026-05-09 14:45:37 -04:00
|
|
|
void akgl_game_save_charactername_iterator(void *userdata, SDL_PropertiesID props, const char *name)
|
|
|
|
|
{
|
|
|
|
|
FILE *fp = (FILE *)userdata;
|
2026-05-25 21:29:18 -04:00
|
|
|
PREPARE_ERROR(e);
|
2026-05-09 14:45:37 -04:00
|
|
|
akgl_Character *character = NULL;
|
|
|
|
|
ATTEMPT {
|
2026-05-25 21:29:18 -04:00
|
|
|
FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer");
|
2026-05-09 14:45:37 -04:00
|
|
|
character = SDL_GetPointerProperty(props, name, NULL);
|
2026-07-31 08:27:15 -04:00
|
|
|
CATCH(e, write_exact((char *)name, 1, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp));
|
|
|
|
|
CATCH(e, write_exact(&character, 1, sizeof(akgl_Character *), fp));
|
2026-05-09 14:45:37 -04:00
|
|
|
} CLEANUP {
|
2026-05-25 21:29:18 -04:00
|
|
|
} PROCESS(e) {
|
|
|
|
|
} FINISH_NORETURN(e);
|
2026-05-09 14:45:37 -04:00
|
|
|
}
|
|
|
|
|
|
2026-07-30 01:10:31 -04:00
|
|
|
/**
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:02:20 -04:00
|
|
|
* @brief Write all four name-to-address tables, each with its terminator.
|
|
|
|
|
*
|
|
|
|
|
* Actors, sprites, spritesheets, characters, in that order -- which is the order
|
|
|
|
|
* akgl_game_load reads them back in. Each table is the registry enumerated one
|
|
|
|
|
* entry at a time, followed by a zeroed name field and a zeroed pointer that the
|
|
|
|
|
* reader stops on.
|
|
|
|
|
*
|
|
|
|
|
* @param fp The open save-game stream. Required.
|
2026-07-30 01:10:31 -04:00
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:02:20 -04:00
|
|
|
* @throws AKERR_NULLPOINTER If @p fp is `NULL`.
|
|
|
|
|
* @throws AKERR_IO On a stream error or a short write while emitting a
|
|
|
|
|
* terminator.
|
|
|
|
|
*
|
|
|
|
|
* @note Only the terminators are written through the error-reporting path. The
|
|
|
|
|
* entries themselves go through `SDL_EnumerateProperties`, whose callbacks
|
|
|
|
|
* cannot report failure upward and instead terminate the process -- so a
|
|
|
|
|
* write error mid-table never reaches this function's return value.
|
2026-07-30 01:10:31 -04:00
|
|
|
*/
|
2026-05-09 14:45:37 -04:00
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp)
|
|
|
|
|
{
|
2026-05-25 21:29:18 -04:00
|
|
|
PREPARE_ERROR(e);
|
Add physics, heap, json_helpers, game, and actor test suites
Raise line coverage from 39.6 to 61.8 percent with four new suites and an
extension to the actor suite, and register every suite through a single
CMake list so a new test file cannot be left out of the coverage fixture.
Give the test targets a build-tree RPATH and prepend the build tree to
LD_LIBRARY_PATH for CTest, so a developer with a previously installed
libakgl.so exercises the library that was just compiled.
Fix six defects the new tests exposed:
- akgl_physics_simulate read self->gravity_time before its NULL check, so a
NULL backend crashed instead of reporting AKERR_NULLPOINTER.
- akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose
inside the PROCESS switch. An ordinary save never flushed or closed its
stream and produced an empty file.
- akgl_game_save_actors wrote each name table terminator from the address of
a single char, emitting stack contents into the save file and a sentinel
the loader could not recognize.
- akgl_game_load_objectnamemap used CATCH directly inside while(1), where the
break leaves the loop rather than propagating, so a truncated name table
loaded as a successful game.
- akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no
NULL check, unlike their left and right counterparts.
- akgl_actor_logic_movement checked actor twice instead of checking
actor->basechar before dereferencing it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
|
|
|
// Each name table ends with a zeroed name field and a zeroed pointer, which
|
|
|
|
|
// is what akgl_game_load_objectnamemap() looks for to stop reading. The
|
|
|
|
|
// terminator has to come from a buffer at least as long as the longest name
|
|
|
|
|
// field: writing N bytes from the address of a single char would emit N-1
|
|
|
|
|
// bytes of whatever happened to follow it on the stack, which both leaks
|
|
|
|
|
// stack contents into the save file and produces a sentinel the loader
|
|
|
|
|
// cannot recognize.
|
|
|
|
|
char nullbuf[AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH];
|
|
|
|
|
|
|
|
|
|
memset((void *)&nullbuf, 0x00, sizeof(nullbuf));
|
|
|
|
|
|
2026-05-09 14:45:37 -04:00
|
|
|
ATTEMPT {
|
2026-05-25 21:29:18 -04:00
|
|
|
FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer");
|
2026-05-09 14:45:37 -04:00
|
|
|
// write the actor name pointer table
|
|
|
|
|
SDL_EnumerateProperties(
|
|
|
|
|
AKGL_REGISTRY_ACTOR,
|
|
|
|
|
&akgl_game_save_actorname_iterator,
|
|
|
|
|
(void *)fp);
|
2026-07-31 08:27:15 -04:00
|
|
|
CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp));
|
|
|
|
|
CATCH(e, write_exact((void *)&nullbuf, 1, sizeof(akgl_Actor *), fp));
|
2026-05-09 14:45:37 -04:00
|
|
|
// write the sprite name pointer table
|
|
|
|
|
SDL_EnumerateProperties(
|
|
|
|
|
AKGL_REGISTRY_SPRITE,
|
|
|
|
|
&akgl_game_save_spritename_iterator,
|
|
|
|
|
(void *)fp);
|
2026-07-31 08:27:15 -04:00
|
|
|
CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp));
|
|
|
|
|
CATCH(e, write_exact((void *)&nullbuf, 1, sizeof(akgl_Sprite *), fp));
|
2026-05-09 14:45:37 -04:00
|
|
|
// write the spritesheet name pointer table
|
|
|
|
|
SDL_EnumerateProperties(
|
|
|
|
|
AKGL_REGISTRY_SPRITESHEET,
|
|
|
|
|
&akgl_game_save_spritesheetname_iterator,
|
|
|
|
|
(void *)fp);
|
2026-07-31 08:27:15 -04:00
|
|
|
CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp));
|
|
|
|
|
CATCH(e, write_exact((void *)&nullbuf, 1, sizeof(akgl_SpriteSheet *), fp));
|
2026-05-09 14:45:37 -04:00
|
|
|
// write the character name pointer table
|
|
|
|
|
SDL_EnumerateProperties(
|
|
|
|
|
AKGL_REGISTRY_CHARACTER,
|
|
|
|
|
&akgl_game_save_charactername_iterator,
|
|
|
|
|
(void *)fp);
|
2026-07-31 08:27:15 -04:00
|
|
|
CATCH(e, write_exact((void *)&nullbuf, 1, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp));
|
|
|
|
|
CATCH(e, write_exact((void *)&nullbuf, 1, sizeof(akgl_Character *), fp));
|
2026-05-09 14:45:37 -04:00
|
|
|
} CLEANUP {
|
2026-05-25 21:29:18 -04:00
|
|
|
} PROCESS(e) {
|
|
|
|
|
} FINISH(e, true);
|
|
|
|
|
SUCCEED_RETURN(e);
|
2026-05-09 14:45:37 -04:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 22:01:56 -04:00
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath)
|
|
|
|
|
{
|
|
|
|
|
FILE *fp = NULL;
|
2026-05-25 21:29:18 -04:00
|
|
|
PREPARE_ERROR(e);
|
2026-05-08 22:01:56 -04:00
|
|
|
|
2026-05-09 14:45:37 -04:00
|
|
|
ATTEMPT {
|
2026-05-25 21:29:18 -04:00
|
|
|
FAIL_ZERO_BREAK(e, fpath, AKERR_NULLPOINTER, "NULL file path");
|
|
|
|
|
CATCH(e, aksl_fopen(fpath, "wb", &fp));
|
2026-07-31 08:27:15 -04:00
|
|
|
CATCH(e, write_exact(&game, 1, sizeof(akgl_Game), fp));
|
2026-05-25 21:29:18 -04:00
|
|
|
CATCH(e, akgl_game_save_actors(fp));
|
2026-05-09 14:45:37 -04:00
|
|
|
} CLEANUP {
|
Add physics, heap, json_helpers, game, and actor test suites
Raise line coverage from 39.6 to 61.8 percent with four new suites and an
extension to the actor suite, and register every suite through a single
CMake list so a new test file cannot be left out of the coverage fixture.
Give the test targets a build-tree RPATH and prepend the build tree to
LD_LIBRARY_PATH for CTest, so a developer with a previously installed
libakgl.so exercises the library that was just compiled.
Fix six defects the new tests exposed:
- akgl_physics_simulate read self->gravity_time before its NULL check, so a
NULL backend crashed instead of reporting AKERR_NULLPOINTER.
- akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose
inside the PROCESS switch. An ordinary save never flushed or closed its
stream and produced an empty file.
- akgl_game_save_actors wrote each name table terminator from the address of
a single char, emitting stack contents into the save file and a sentinel
the loader could not recognize.
- akgl_game_load_objectnamemap used CATCH directly inside while(1), where the
break leaves the loop rather than propagating, so a truncated name table
loaded as a successful game.
- akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no
NULL check, unlike their left and right counterparts.
- akgl_actor_logic_movement checked actor twice instead of checking
actor->basechar before dereferencing it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
|
|
|
// CLEANUP must precede PROCESS: with the two transposed, the fclose
|
|
|
|
|
// lands inside the PROCESS switch and only runs when an error context
|
|
|
|
|
// exists and reports success, so an ordinary save never flushed or
|
|
|
|
|
// closed its stream.
|
2026-05-09 14:45:37 -04:00
|
|
|
if ( fp != NULL )
|
|
|
|
|
fclose(fp);
|
Add physics, heap, json_helpers, game, and actor test suites
Raise line coverage from 39.6 to 61.8 percent with four new suites and an
extension to the actor suite, and register every suite through a single
CMake list so a new test file cannot be left out of the coverage fixture.
Give the test targets a build-tree RPATH and prepend the build tree to
LD_LIBRARY_PATH for CTest, so a developer with a previously installed
libakgl.so exercises the library that was just compiled.
Fix six defects the new tests exposed:
- akgl_physics_simulate read self->gravity_time before its NULL check, so a
NULL backend crashed instead of reporting AKERR_NULLPOINTER.
- akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose
inside the PROCESS switch. An ordinary save never flushed or closed its
stream and produced an empty file.
- akgl_game_save_actors wrote each name table terminator from the address of
a single char, emitting stack contents into the save file and a sentinel
the loader could not recognize.
- akgl_game_load_objectnamemap used CATCH directly inside while(1), where the
break leaves the loop rather than propagating, so a truncated name table
loaded as a successful game.
- akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no
NULL check, unlike their left and right counterparts.
- akgl_actor_logic_movement checked actor twice instead of checking
actor->basechar before dereferencing it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
|
|
|
} PROCESS(e) {
|
2026-05-25 21:29:18 -04:00
|
|
|
} FINISH(e, true);
|
|
|
|
|
SUCCEED_RETURN(e); // SUCCEED_NORETURN if in main().
|
2026-05-08 22:01:56 -04:00
|
|
|
}
|
|
|
|
|
|
2026-07-30 01:10:31 -04:00
|
|
|
/**
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:02:20 -04:00
|
|
|
* @brief Read one name table back, building an old-address to current-object map.
|
|
|
|
|
*
|
|
|
|
|
* This is the mechanism that makes a save file portable across runs. Objects are
|
|
|
|
|
* serialized carrying the addresses they had at save time; those addresses mean
|
|
|
|
|
* nothing now, but the table says which *name* lived at each one, and the name
|
|
|
|
|
* still resolves through the registry. So each entry becomes
|
|
|
|
|
* `"0x7f..." -> current object`, and a deserialized pointer can be translated by
|
|
|
|
|
* looking up its printed form.
|
|
|
|
|
*
|
|
|
|
|
* The keys are the old pointers rendered with `%p`, because SDL property sets
|
|
|
|
|
* take string keys only.
|
|
|
|
|
*
|
|
|
|
|
* Reading stops at the terminator -- a zeroed name and a zeroed pointer.
|
|
|
|
|
*
|
|
|
|
|
* @param fp The open save-game stream, positioned at the start of a
|
|
|
|
|
* table. Required.
|
|
|
|
|
* @param map The property set to fill in. Required, and created by the
|
|
|
|
|
* caller.
|
|
|
|
|
* @param namelength Width of the table's name field, which must match what the
|
|
|
|
|
* corresponding save iterator wrote. It also sizes a
|
|
|
|
|
* variable-length array on the stack.
|
|
|
|
|
* @param ptrlength Width of the table's pointer field, i.e. `sizeof` the
|
|
|
|
|
* pointer type that table holds.
|
|
|
|
|
* @param registry The registry to resolve names against -- #AKGL_REGISTRY_ACTOR
|
|
|
|
|
* for the actor table, and so on.
|
2026-07-30 01:10:31 -04:00
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:02:20 -04:00
|
|
|
* @throws AKERR_EOF If the stream ends before a terminator is found, which is
|
|
|
|
|
* what a truncated save file looks like.
|
|
|
|
|
* @throws AKERR_IO On a stream error.
|
|
|
|
|
* @throws AKERR_NULLPOINTER If @p fp is `NULL`, by way of the read wrapper.
|
|
|
|
|
*
|
|
|
|
|
* @note A name in the table that is no longer in @p registry maps to `NULL`
|
|
|
|
|
* rather than being reported: the entry is written with whatever
|
|
|
|
|
* `SDL_GetPointerProperty` returned.
|
2026-07-30 01:10:31 -04:00
|
|
|
*/
|
2026-05-09 14:45:37 -04:00
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_objectnamemap(FILE *fp, SDL_PropertiesID map, int namelength, int ptrlength, SDL_PropertiesID registry)
|
|
|
|
|
{
|
|
|
|
|
void *ptr = NULL;
|
|
|
|
|
char ptrstring[32];
|
|
|
|
|
char objname[namelength];
|
|
|
|
|
int retval = 0;
|
Add physics, heap, json_helpers, game, and actor test suites
Raise line coverage from 39.6 to 61.8 percent with four new suites and an
extension to the actor suite, and register every suite through a single
CMake list so a new test file cannot be left out of the coverage fixture.
Give the test targets a build-tree RPATH and prepend the build tree to
LD_LIBRARY_PATH for CTest, so a developer with a previously installed
libakgl.so exercises the library that was just compiled.
Fix six defects the new tests exposed:
- akgl_physics_simulate read self->gravity_time before its NULL check, so a
NULL backend crashed instead of reporting AKERR_NULLPOINTER.
- akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose
inside the PROCESS switch. An ordinary save never flushed or closed its
stream and produced an empty file.
- akgl_game_save_actors wrote each name table terminator from the address of
a single char, emitting stack contents into the save file and a sentinel
the loader could not recognize.
- akgl_game_load_objectnamemap used CATCH directly inside while(1), where the
break leaves the loop rather than propagating, so a truncated name table
loaded as a successful game.
- akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no
NULL check, unlike their left and right counterparts.
- akgl_actor_logic_movement checked actor twice instead of checking
actor->basechar before dereferencing it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
|
|
|
bool done = false;
|
|
|
|
|
|
2026-05-25 21:29:18 -04:00
|
|
|
PREPARE_ERROR(e);
|
Add physics, heap, json_helpers, game, and actor test suites
Raise line coverage from 39.6 to 61.8 percent with four new suites and an
extension to the actor suite, and register every suite through a single
CMake list so a new test file cannot be left out of the coverage fixture.
Give the test targets a build-tree RPATH and prepend the build tree to
LD_LIBRARY_PATH for CTest, so a developer with a previously installed
libakgl.so exercises the library that was just compiled.
Fix six defects the new tests exposed:
- akgl_physics_simulate read self->gravity_time before its NULL check, so a
NULL backend crashed instead of reporting AKERR_NULLPOINTER.
- akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose
inside the PROCESS switch. An ordinary save never flushed or closed its
stream and produced an empty file.
- akgl_game_save_actors wrote each name table terminator from the address of
a single char, emitting stack contents into the save file and a sentinel
the loader could not recognize.
- akgl_game_load_objectnamemap used CATCH directly inside while(1), where the
break leaves the loop rather than propagating, so a truncated name table
loaded as a successful game.
- akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no
NULL check, unlike their left and right counterparts.
- akgl_actor_logic_movement checked actor twice instead of checking
actor->basechar before dereferencing it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
|
|
|
// The ATTEMPT block sits inside the loop on purpose. CATCH reports a
|
|
|
|
|
// failure by breaking, and a break binds to the innermost enclosing switch
|
|
|
|
|
// or loop, so a CATCH written directly inside `while` would leave the loop
|
|
|
|
|
// and then fall through to SUCCEED_RETURN -- reporting a truncated or
|
|
|
|
|
// corrupt name table as a successful load.
|
|
|
|
|
while ( done == false ) {
|
|
|
|
|
ATTEMPT {
|
2026-07-31 08:27:15 -04:00
|
|
|
CATCH(e, read_exact((void *)&objname, 1, namelength, fp));
|
|
|
|
|
CATCH(e, read_exact((void *)&ptr, 1, ptrlength, fp));
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
// End of the map
|
|
|
|
|
if ( ptr == 0x00 && objname[0] == 0x00 ) {
|
|
|
|
|
done = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// The map allows us to say "Object X has a reference to object Y at
|
|
|
|
|
// address Z. The object they had at address Z was named A. Our current
|
|
|
|
|
// instance of object named A is at address B. So we map address Z to
|
|
|
|
|
// address B, so that we can reconnect function pointers on objects loaded
|
|
|
|
|
// from the save game state."
|
|
|
|
|
|
|
|
|
|
// SDL_Properties objects can only use string keys, so we can't use the
|
|
|
|
|
// old pointer as a key without first converting it to a string.
|
|
|
|
|
CATCH(e, aksl_memset((void *)&ptrstring, 0x00, 32));
|
|
|
|
|
snprintf((char *)&ptrstring, 32, "%p", ptr);
|
|
|
|
|
SDL_SetPointerProperty(
|
|
|
|
|
map,
|
|
|
|
|
ptrstring,
|
|
|
|
|
SDL_GetPointerProperty(registry, objname, NULL));
|
Add physics, heap, json_helpers, game, and actor test suites
Raise line coverage from 39.6 to 61.8 percent with four new suites and an
extension to the actor suite, and register every suite through a single
CMake list so a new test file cannot be left out of the coverage fixture.
Give the test targets a build-tree RPATH and prepend the build tree to
LD_LIBRARY_PATH for CTest, so a developer with a previously installed
libakgl.so exercises the library that was just compiled.
Fix six defects the new tests exposed:
- akgl_physics_simulate read self->gravity_time before its NULL check, so a
NULL backend crashed instead of reporting AKERR_NULLPOINTER.
- akgl_game_save transposed CLEANUP and PROCESS, which placed the fclose
inside the PROCESS switch. An ordinary save never flushed or closed its
stream and produced an empty file.
- akgl_game_save_actors wrote each name table terminator from the address of
a single char, emitting stack contents into the save file and a sentinel
the loader could not recognize.
- akgl_game_load_objectnamemap used CATCH directly inside while(1), where the
break leaves the loop rather than propagating, so a truncated name table
loaded as a successful game.
- akgl_Actor_cmhf_up_on and _down_on dereferenced actor->basechar with no
NULL check, unlike their left and right counterparts.
- akgl_actor_logic_movement checked actor twice instead of checking
actor->basechar before dereferencing it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 02:03:21 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(e) {
|
|
|
|
|
} FINISH(e, true);
|
2026-05-09 14:45:37 -04:00
|
|
|
};
|
2026-05-25 21:29:18 -04:00
|
|
|
SUCCEED_RETURN(e);
|
2026-05-09 14:45:37 -04:00
|
|
|
}
|
|
|
|
|
|
2026-07-30 01:10:31 -04:00
|
|
|
/**
|
Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws
lines across 21 headers read "When the corresponding validation or operation
fails" and told a caller nothing beyond the status name. The @param lines were
the same shape: every output was "Output destination populated by the
function", every instance "Object to initialize, inspect, or modify".
Rewritten against the implementations, following the pattern libakstdlib
already uses:
- @throws names the condition. akgl_sprite_load_json separated AKERR_KEY
(absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS
(filename too long, or array indexed past its end), and gained AKGL_ERR_SDL
and AKGL_ERR_HEAP, which it raises and never declared.
- Parameters say whether they are required, what a NULL means, and what is
written on a failure path. Where an argument is not checked, the doc says so:
akgl_heap_next_actor's dest is a crash on NULL, not an error, and
akgl_render_2d_frame_start dereferences self before testing it.
- The conventions move up to the file blocks so the per-function docs stay
short. json_helpers.h states once that absence is an error here and that
json_t * results are borrowed; heap.h explains the pool model and the
acquire asymmetry; physics.h carries the thrust/environmental/velocity table.
- Struct fields, enum values, macros and exported globals are documented,
including the dead ones - sprite_w/sprite_h, movetimer, p_scale and
timer_gravity are read by nothing, and say so.
Also fixes ten comments in error.h and audio.h that opened with /** rather
than /**<, so Doxygen attached them to the following entity and rendered the
text as part of the macro's value. Verified against the generated HTML.
Comments only - no declaration changed. Doxygen builds clean under
WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 11:02:20 -04:00
|
|
|
* @brief Refuse a save file unless its version matches the running one exactly.
|
|
|
|
|
*
|
|
|
|
|
* Both strings are parsed as semver and compared with `"="` -- exact equality,
|
|
|
|
|
* not compatibility. That is deliberate and strict: a save file is a raw memory
|
|
|
|
|
* image of `akgl_Game` and the object tables, so any change to a struct layout
|
|
|
|
|
* invalidates it, and semver has no way to say "the layout did not move".
|
|
|
|
|
*
|
|
|
|
|
* @param versiontype What is being compared -- `"library"` or `"game"`. Used
|
|
|
|
|
* only to build the message, but required, since a bare
|
|
|
|
|
* "incompatible version" error would not say which.
|
|
|
|
|
* @param newversion The version read out of the save file. Required.
|
|
|
|
|
* @param curversion The version of the running program or library. Required.
|
|
|
|
|
* @return `NULL` when the two match, otherwise an error context owned by the
|
|
|
|
|
* caller.
|
|
|
|
|
* @throws AKERR_NULLPOINTER If any of the three arguments is `NULL`.
|
|
|
|
|
* @throws AKERR_VALUE If either string is not valid semver. The message says
|
|
|
|
|
* which side it came from.
|
|
|
|
|
* @throws AKERR_API If both parse but are not equal. The message quotes both.
|
2026-07-30 01:10:31 -04:00
|
|
|
*/
|
2026-05-09 14:45:37 -04:00
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion)
|
2026-05-08 22:01:56 -04:00
|
|
|
{
|
2026-05-08 23:15:11 -04:00
|
|
|
semver_t current_version = {};
|
|
|
|
|
semver_t compare_version = {};
|
2026-05-25 21:29:18 -04:00
|
|
|
PREPARE_ERROR(e);
|
|
|
|
|
FAIL_ZERO_RETURN(e, versiontype, AKERR_NULLPOINTER, "NULL argument");
|
|
|
|
|
FAIL_ZERO_RETURN(e, curversion, AKERR_NULLPOINTER, "NULL argument");
|
|
|
|
|
FAIL_ZERO_RETURN(e, newversion, AKERR_NULLPOINTER, "NULL argument");
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2026-05-08 23:15:11 -04:00
|
|
|
ATTEMPT {
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
// Check save game library version
|
2026-05-08 23:15:11 -04:00
|
|
|
FAIL_NONZERO_BREAK(
|
2026-05-25 21:29:18 -04:00
|
|
|
e,
|
2026-05-08 23:48:35 -04:00
|
|
|
semver_parse((const char *)curversion, ¤t_version),
|
2026-05-08 23:15:11 -04:00
|
|
|
AKERR_VALUE,
|
2026-05-08 23:48:35 -04:00
|
|
|
"Invalid semantic %s version in current game: %s",
|
|
|
|
|
versiontype,
|
|
|
|
|
(char *)curversion);
|
2026-05-08 23:15:11 -04:00
|
|
|
FAIL_NONZERO_BREAK(
|
2026-05-25 21:29:18 -04:00
|
|
|
e,
|
2026-05-09 14:45:37 -04:00
|
|
|
semver_parse((const char *)newversion, &compare_version),
|
2026-05-08 23:15:11 -04:00
|
|
|
AKERR_VALUE,
|
2026-05-08 23:48:35 -04:00
|
|
|
"Invalid semantic %s version in save game: %s",
|
|
|
|
|
versiontype,
|
2026-05-09 14:45:37 -04:00
|
|
|
(char *)&newversion);
|
2026-05-08 23:15:11 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
2026-05-25 21:29:18 -04:00
|
|
|
e,
|
2026-05-08 23:48:35 -04:00
|
|
|
semver_satisfies(compare_version, current_version, "="),
|
2026-05-08 23:15:11 -04:00
|
|
|
AKERR_API,
|
2026-05-09 14:45:37 -04:00
|
|
|
"Incompatible save game %s version (%s != %s)",
|
2026-05-08 23:48:35 -04:00
|
|
|
versiontype,
|
|
|
|
|
curversion,
|
2026-05-09 14:45:37 -04:00
|
|
|
(char *)&newversion);
|
2026-05-08 23:48:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
semver_free(¤t_version);
|
|
|
|
|
semver_free(&compare_version);
|
2026-05-25 21:29:18 -04:00
|
|
|
} PROCESS(e) {
|
|
|
|
|
} FINISH(e, true);
|
|
|
|
|
SUCCEED_RETURN(e);
|
2026-05-08 23:48:35 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath)
|
|
|
|
|
{
|
2026-05-09 14:45:37 -04:00
|
|
|
akgl_Game savegame;
|
|
|
|
|
SDL_PropertiesID actormap;
|
|
|
|
|
SDL_PropertiesID spritemap;
|
|
|
|
|
SDL_PropertiesID spritesheetmap;
|
|
|
|
|
SDL_PropertiesID charactermap;
|
2026-05-08 23:48:35 -04:00
|
|
|
FILE *fp = NULL;
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2026-05-25 21:29:18 -04:00
|
|
|
PREPARE_ERROR(e);
|
|
|
|
|
FAIL_ZERO_RETURN(e, fpath, AKERR_NULLPOINTER, "NULL file path");
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2026-05-08 23:48:35 -04:00
|
|
|
ATTEMPT {
|
2026-05-25 21:29:18 -04:00
|
|
|
CATCH(e, aksl_fopen(fpath, "rb", &fp));
|
2026-07-31 08:27:15 -04:00
|
|
|
CATCH(e, read_exact((void *)&savegame, 1, sizeof(akgl_Game), fp));
|
2026-05-25 21:29:18 -04:00
|
|
|
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));
|
2026-05-09 14:45:37 -04:00
|
|
|
FAIL_NONZERO_RETURN(
|
2026-05-25 21:29:18 -04:00
|
|
|
e,
|
2026-05-09 14:45:37 -04:00
|
|
|
strncmp((char *)&savegame.name, (char *)&game.name, 256),
|
|
|
|
|
AKERR_API,
|
|
|
|
|
"Savegame is not compatible with this game");
|
|
|
|
|
FAIL_NONZERO_RETURN(
|
2026-05-25 21:29:18 -04:00
|
|
|
e,
|
2026-05-09 14:45:37 -04:00
|
|
|
strncmp((char *)&savegame.uri, (char *)&game.uri, 256),
|
|
|
|
|
AKERR_API,
|
|
|
|
|
"Savegame is not compatible with this game");
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2026-05-09 14:45:37 -04:00
|
|
|
memcpy((void *)&game, (void *)&savegame, sizeof(akgl_Game));
|
|
|
|
|
// Load actor name map
|
|
|
|
|
actormap = SDL_CreateProperties();
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
CATCH(e, akgl_game_load_objectnamemap(fp, actormap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Actor *), AKGL_REGISTRY_ACTOR));
|
2026-05-09 14:45:37 -04:00
|
|
|
// Load sprite name map
|
|
|
|
|
spritemap = SDL_CreateProperties();
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
CATCH(e, akgl_game_load_objectnamemap(fp, spritemap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Sprite *), AKGL_REGISTRY_SPRITE));
|
2026-05-09 14:45:37 -04:00
|
|
|
// Load spritesheet name map
|
|
|
|
|
spritesheetmap = SDL_CreateProperties();
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
CATCH(e, akgl_game_load_objectnamemap(fp, spritesheetmap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_SpriteSheet *), AKGL_REGISTRY_SPRITESHEET));
|
2026-05-09 14:45:37 -04:00
|
|
|
// Load character name map
|
|
|
|
|
charactermap = SDL_CreateProperties();
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
CATCH(e, akgl_game_load_objectnamemap(fp, charactermap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Character *), AKGL_REGISTRY_CHARACTER));
|
|
|
|
|
// Now that we have all of our pointer maps built, we can load the actual binary objects and reset their pointers
|
2026-05-08 23:15:11 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
if ( fp != NULL ) {
|
|
|
|
|
fclose(fp);
|
|
|
|
|
}
|
2026-05-25 21:29:18 -04:00
|
|
|
} PROCESS(e) {
|
|
|
|
|
} FINISH(e, true);
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2026-05-25 21:29:18 -04:00
|
|
|
SUCCEED_RETURN(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(e);
|
|
|
|
|
akgl_Iterator defflags = {
|
|
|
|
|
.flags = (AKGL_ITERATOR_OP_LAYERMASK | AKGL_ITERATOR_OP_LAYERMASK),
|
|
|
|
|
.layerid = 0
|
|
|
|
|
};
|
|
|
|
|
SDL_Time curTime = SDL_GetTicksNS();
|
|
|
|
|
akgl_Actor *actor = NULL;
|
|
|
|
|
|
|
|
|
|
if ( opflags == NULL ) {
|
|
|
|
|
opflags = &defflags;
|
|
|
|
|
}
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
|
2026-05-25 21:29:18 -04:00
|
|
|
PASS(e, akgl_game_state_lock());
|
|
|
|
|
|
|
|
|
|
akgl_game_updateFPS();
|
|
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
if ( actor->refcount == 0 ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_TILEMAPSCALE) ) {
|
2026-06-02 13:15:26 -04:00
|
|
|
PASS(e, akgl_tilemap_scale_actor(gamemap, actor));
|
2026-05-25 21:29:18 -04:00
|
|
|
} else {
|
|
|
|
|
actor->scale = 1.0;
|
|
|
|
|
}
|
Reindent all C sources to the canonical stroustrup style
Mechanical whitespace-only change across src/, include/, tests/, and
util/, applied with scripts/reindent.sh. No behavioral change.
Most files already conformed. The genuine outliers indented at 2 columns
(json_helpers.c, util.c from akgl_rectangle_points onward, assets.c,
staticstring.c, akgl_actor_add_child, util.h, staticstring.h) or used
spaces where the canonical form is a tab (draw.c). cc-mode also re-aligns
line-continuation backslashes in multi-line macros to its default
c-backslash-column, which is required for the tree to be a fixed point of
the indenter.
Verified whitespace-only: `git diff -w` over this change is empty apart
from three trailing blank lines removed at EOF in src/heap.c,
src/registry.c, and tests/tilemap.c. The build produces no new errors and
ctest is unchanged at 13/14, with `character` still the one intentional
failure.
The tree is now a fixed point of `scripts/reindent.sh --check`.
include/akgl/SDL_GameControllerDB.h is generated and excluded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 18:17:37 -04:00
|
|
|
PASS(e, actor->updatefunc(actor));
|
|
|
|
|
}
|
2026-05-25 21:29:18 -04:00
|
|
|
}
|
2026-06-02 13:15:26 -04:00
|
|
|
PASS(e, physics->simulate(physics, NULL));
|
|
|
|
|
PASS(e, renderer->draw_world(renderer, NULL));
|
2026-05-25 21:29:18 -04:00
|
|
|
PASS(e, akgl_game_state_unlock());
|
|
|
|
|
SUCCEED_RETURN(e);
|
2026-05-08 22:01:56 -04:00
|
|
|
}
|