2025-08-03 10:07:35 -04:00
|
|
|
#include <SDL3/SDL.h>
|
2026-01-05 08:58:06 -05:00
|
|
|
#include <akerror.h>
|
2025-08-03 10:41:09 -04:00
|
|
|
|
2026-05-07 22:20:10 -04:00
|
|
|
#include <akgl/character.h>
|
|
|
|
|
#include <akgl/actor.h>
|
|
|
|
|
#include <akgl/error.h>
|
|
|
|
|
#include <akgl/heap.h>
|
|
|
|
|
#include <akgl/registry.h>
|
|
|
|
|
#include <akgl/iterator.h>
|
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>
2026-07-31 23:32:21 -04:00
|
|
|
#include <akgl/game.h>
|
|
|
|
|
#include <akgl/renderer.h>
|
|
|
|
|
|
|
|
|
|
#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.
|
|
|
|
|
*/
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *test_akgl_character_initialize()
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_Character *testchar = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_next_character(&testchar));
|
|
|
|
|
CATCH(errctx, akgl_character_initialize(testchar, "testchar"));
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
2026-05-06 23:18:42 -04:00
|
|
|
SDL_GetPointerProperty(AKGL_REGISTRY_CHARACTER, "testchar", NULL),
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_KEY,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Character was not placed in the registry");
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
strcmp((char *)&testchar->name, "testchar"),
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_VALUE,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Character was not named properly ('testchar' vs '%s')",
|
|
|
|
|
(char *)&testchar->name);
|
|
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
testchar->state_sprites,
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_NULLPOINTER,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Character state sprites map was not initialized");
|
|
|
|
|
} CLEANUP {
|
2026-05-06 23:18:42 -04:00
|
|
|
IGNORE(akgl_heap_release_character(testchar));
|
2025-08-03 10:07:35 -04:00
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 23:57:55 -04:00
|
|
|
akerr_ErrorContext *test_character_sprite_mgmt()
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_Character *testchar = NULL;
|
|
|
|
|
akgl_Sprite *testsprite = NULL;
|
|
|
|
|
akgl_Sprite *testsprite2 = NULL;
|
|
|
|
|
akgl_Sprite *comparesprite = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_next_character(&testchar));
|
2026-07-29 18:01:05 -04:00
|
|
|
CATCH(errctx, akgl_character_initialize(testchar, "testchar"));
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_sprite_load_json("assets/testsprite.json"));
|
|
|
|
|
testsprite = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite", NULL);
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
testsprite,
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_KEY,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Sprite loaded from json but not in registry");
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_sprite_load_json("assets/testsprite2.json"));
|
|
|
|
|
testsprite2 = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite2", NULL);
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
testsprite2,
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_KEY,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Sprite 2 loaded from json but not in registry");
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, testchar->sprite_add(testchar, testsprite, AKGL_ACTOR_STATE_ALIVE));
|
|
|
|
|
CATCH(errctx, testchar->sprite_add(testchar, testsprite2, AKGL_ACTOR_STATE_DEAD));
|
|
|
|
|
CATCH(errctx, testchar->sprite_get(testchar, (AKGL_ACTOR_STATE_ALIVE), &comparesprite));
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, (comparesprite == testsprite), AKERR_VALUE, "Wrong sprite for state AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_LEFT");
|
|
|
|
|
CATCH(errctx, testchar->sprite_get(testchar, AKGL_ACTOR_STATE_DEAD, &comparesprite));
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, (comparesprite == testsprite2), AKERR_VALUE, "Wrong sprite for state AKGL_ACTOR_STATE_DEAD");
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
2026-05-06 23:18:42 -04:00
|
|
|
IGNORE(akgl_heap_release_sprite(testsprite));
|
|
|
|
|
IGNORE(akgl_heap_release_sprite(testsprite2));
|
|
|
|
|
IGNORE(akgl_heap_release_character(testchar));
|
2025-08-03 10:07:35 -04:00
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 23:57:55 -04:00
|
|
|
akerr_ErrorContext *test_character_iterate_state_sprites()
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_Character *testchar = NULL;
|
|
|
|
|
akgl_Sprite *testsprite = NULL;
|
|
|
|
|
akgl_Sprite *testsprite2 = NULL;
|
|
|
|
|
akgl_Iterator opflags = {.flags = AKGL_ITERATOR_OP_RELEASE, .layerid = 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 10:07:35 -04:00
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_next_character(&testchar));
|
2026-07-29 18:01:05 -04:00
|
|
|
CATCH(errctx, akgl_character_initialize(testchar, "testchar"));
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_sprite_load_json("assets/testsprite.json"));
|
|
|
|
|
testsprite = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite", NULL);
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
testsprite,
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_KEY,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Sprite loaded from json but not in registry");
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_sprite_load_json("assets/testsprite2.json"));
|
|
|
|
|
testsprite2 = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite2", NULL);
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
testsprite2,
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_KEY,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Sprite 2 loaded from json but not in registry");
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, testchar->sprite_add(testchar, testsprite, AKGL_ACTOR_STATE_ALIVE));
|
|
|
|
|
CATCH(errctx, testchar->sprite_add(testchar, testsprite2, AKGL_ACTOR_STATE_DEAD));
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
SDL_EnumerateProperties(testchar->state_sprites, &akgl_character_state_sprites_iterate, &opflags);
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
// This is called by akgl_heap_release_character so we should assume that our property map is being
|
2025-08-03 10:07:35 -04:00
|
|
|
// deleted soon after this. So we don't care if the sprites have been deleted from the sprite_states.
|
|
|
|
|
// We just want to know they've been released.
|
|
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
(testsprite->refcount > 1),
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_VALUE,
|
2026-05-06 23:18:42 -04:00
|
|
|
"akgl_heap_release_sprite not called for testsprite from iterator");
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_NONZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
(testsprite2->refcount > 1),
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_VALUE,
|
2026-05-06 23:18:42 -04:00
|
|
|
"akgl_heap_release_sprite not called for testsprite from iterator");
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
2026-05-06 23:18:42 -04:00
|
|
|
IGNORE(akgl_heap_release_sprite(testsprite));
|
|
|
|
|
IGNORE(akgl_heap_release_sprite(testsprite2));
|
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
|
|
|
IGNORE(akgl_heap_release_character(testchar));
|
2025-08-03 10:07:35 -04:00
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *test_akgl_character_load_json()
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_Character *testcharacter = NULL;
|
|
|
|
|
akgl_Sprite *testsprite = NULL;
|
|
|
|
|
akgl_Sprite *testsprite2 = NULL;
|
|
|
|
|
akgl_Sprite *comparesprite = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
int tsrc = 0;
|
|
|
|
|
int tsrc2 = 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 10:07:35 -04:00
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_next_character(&testcharacter));
|
|
|
|
|
CATCH(errctx, akgl_sprite_load_json("assets/testsprite.json"));
|
|
|
|
|
testsprite = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite", NULL);
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
testsprite,
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_KEY,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Sprite loaded from json but not in registry");
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_sprite_load_json("assets/testsprite2.json"));
|
|
|
|
|
testsprite2 = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite2", NULL);
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
testsprite2,
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_KEY,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Sprite 2 loaded from json but not in registry");
|
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-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_character_load_json("assets/testcharacter.json"));
|
|
|
|
|
testcharacter = SDL_GetPointerProperty(AKGL_REGISTRY_CHARACTER, "testcharacter", NULL);
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
testcharacter,
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_KEY,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Character loaded from json but not in registry");
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, testcharacter->sprite_get(testcharacter, (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_LEFT), &comparesprite));
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, (comparesprite == testsprite), AKERR_VALUE, "Wrong sprite for state AKGL_ACTOR_STATE_ALIVE");
|
|
|
|
|
CATCH(errctx, testcharacter->sprite_get(testcharacter, AKGL_ACTOR_STATE_DEAD, &comparesprite));
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, (comparesprite == testsprite2), AKERR_VALUE, "Wrong sprite for state AKGL_ACTOR_STATE_DEAD");
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-07-29 18:01:05 -04:00
|
|
|
FAIL_ZERO_BREAK(errctx, (testcharacter->ax != 0.200000003), AKERR_VALUE, "Wrong X acceleration for test character");
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, (testcharacter->ay != 0.200000003), AKERR_VALUE, "Wrong Y acceleration for test character");
|
2025-08-03 10:07:35 -04:00
|
|
|
|
|
|
|
|
// Release our handles on the sprites so the character's heap_release can reduce them to 0
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_release_sprite(testsprite));
|
|
|
|
|
CATCH(errctx, akgl_heap_release_sprite(testsprite2));
|
2025-08-03 10:07:35 -04:00
|
|
|
tsrc = testsprite->refcount;
|
|
|
|
|
tsrc2 = testsprite2->refcount;
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_release_character(testcharacter));
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
((testsprite->refcount < tsrc) || (testsprite2->refcount < tsrc2)),
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_VALUE,
|
2025-08-03 10:07:35 -04:00
|
|
|
"character did not reduce reference count of its child sprites when released");
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
Give back every pooled string and sprite reference the loaders take
Closes Defects items 20, 22, 23 and 29, and the residual of item 38.
The tilemap load leak was five pooled strings per load; the property-lookup
fix in an earlier commit took it to two, and the last two were each a claim
with no matching release -- the string every layer's `type` was read into, and
the dirname the map's relative paths resolve against. tests/tilemap.c asserts
the pool is exactly where it started after one load/release cycle and after 64.
Finding those two was a matter of dumping the contents of every still-claimed
slot after a cycle rather than reading the code again; 'tilelayer' and an
assets directory named themselves immediately.
Same file, same class, fixed with it: akgl_tilemap_load_layer_objects released
its scratch string after reading each object's name and then kept using the
slot, because akgl_get_json_string_value reuses a non-NULL destination without
taking another reference. The slot was free while still live, so any other
claim could have been handed it.
akgl_character_sprite_add wrote over an existing binding without releasing the
sprite it displaced, so a character that rebinds a state while alive leaked a
sprite slot per rebind -- teardown only gives back what the map holds at the
end. The new reference is taken before the write and given back if the write
fails, so there is no window where a sprite is bound with nothing behind it.
The write was unchecked too.
Three failure-path leaks moved into CLEANUP blocks: akgl_render_2d_init's two
pooled strings, akgl_controller_open_gamepads' enumeration array, and
akgl_text_rendertextat's surface and texture -- the last being a leak per frame
on a HUD line.
akgl_text_unloadallfonts() closes every font in the registry and destroys it,
which is what item 38 left open. Deliberately not a whole akgl_game_shutdown:
tearing down the mixer, SDL_ttf and SDL in the right order is a design
question, and this is the part that was simply missing. It is a new public
symbol, which 0.5.0 already covers -- this release has not shipped.
Every fix has a test that fails against the old code.
25/25 pass, memcheck clean, reindent --check, check_api_surface and
check_error_protocol all clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 00:44:50 -04:00
|
|
|
/**
|
|
|
|
|
* @brief Rebinding a state must release the sprite it displaces.
|
|
|
|
|
*
|
|
|
|
|
* akgl_character_sprite_add took a reference for every binding and wrote over
|
|
|
|
|
* any existing entry without releasing the one it replaced, so a character that
|
|
|
|
|
* rebinds a state while alive leaked a sprite slot per rebind. Releasing at
|
|
|
|
|
* teardown does not cover it: the map only holds the last binding by then, so
|
|
|
|
|
* the earlier ones are unreachable.
|
|
|
|
|
*
|
|
|
|
|
* The write itself was also unchecked, so a failure to record the binding was
|
|
|
|
|
* reported as success -- with the reference already taken.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext *test_character_sprite_rebind_releases_displaced(void)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
akgl_Character *testchar = NULL;
|
|
|
|
|
akgl_Sprite *first = NULL;
|
|
|
|
|
akgl_Sprite *second = NULL;
|
|
|
|
|
akgl_Sprite *found = NULL;
|
|
|
|
|
int firstbefore = 0;
|
|
|
|
|
int secondbefore = 0;
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
ATTEMPT {
|
|
|
|
|
CATCH(errctx, akgl_heap_next_character(&testchar));
|
|
|
|
|
CATCH(errctx, akgl_character_initialize(testchar, "rebindchar"));
|
|
|
|
|
// Real sprites out of the fixtures: akgl_sprite_initialize refuses a
|
|
|
|
|
// NULL spritesheet, and a hand-built sprite would not be a sprite.
|
|
|
|
|
CATCH(errctx, akgl_sprite_load_json("assets/testsprite.json"));
|
|
|
|
|
CATCH(errctx, akgl_sprite_load_json("assets/testsprite2.json"));
|
|
|
|
|
first = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite", NULL);
|
|
|
|
|
second = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite2", NULL);
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, first, AKERR_KEY, "testsprite is not in the registry");
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, second, AKERR_KEY, "testsprite2 is not in the registry");
|
|
|
|
|
|
|
|
|
|
firstbefore = first->refcount;
|
|
|
|
|
secondbefore = second->refcount;
|
|
|
|
|
|
|
|
|
|
CATCH(errctx, akgl_character_sprite_add(testchar, first, AKGL_ACTOR_STATE_ALIVE));
|
|
|
|
|
TEST_ASSERT(errctx, first->refcount == (firstbefore + 1),
|
|
|
|
|
"binding a sprite did not take a reference (%d, expected %d)",
|
|
|
|
|
first->refcount, firstbefore + 1);
|
|
|
|
|
|
|
|
|
|
// Rebind the same state to a different sprite.
|
|
|
|
|
CATCH(errctx, akgl_character_sprite_add(testchar, second, AKGL_ACTOR_STATE_ALIVE));
|
|
|
|
|
TEST_ASSERT(errctx, second->refcount == (secondbefore + 1),
|
|
|
|
|
"rebinding did not take a reference on the new sprite (%d, expected %d)",
|
|
|
|
|
second->refcount, secondbefore + 1);
|
|
|
|
|
TEST_ASSERT(errctx, first->refcount == firstbefore,
|
|
|
|
|
"rebinding did not release the displaced sprite (%d, expected %d)",
|
|
|
|
|
first->refcount, firstbefore);
|
|
|
|
|
|
|
|
|
|
CATCH(errctx, testchar->sprite_get(testchar, AKGL_ACTOR_STATE_ALIVE, &found));
|
|
|
|
|
TEST_ASSERT(errctx, found == second, "the state is not bound to the new sprite");
|
|
|
|
|
|
|
|
|
|
// Rebinding a state to the sprite already there must be a no-op on the
|
|
|
|
|
// count rather than a release followed by a claim of the same slot.
|
|
|
|
|
CATCH(errctx, akgl_character_sprite_add(testchar, second, AKGL_ACTOR_STATE_ALIVE));
|
|
|
|
|
TEST_ASSERT(errctx, second->refcount == (secondbefore + 2),
|
|
|
|
|
"rebinding a state to the sprite it already held mishandled the count (%d)",
|
|
|
|
|
second->refcount);
|
|
|
|
|
|
|
|
|
|
// And a long run of rebinds must not drift.
|
|
|
|
|
for ( i = 0; i < 200; i++ ) {
|
|
|
|
|
CATCH(errctx, akgl_character_sprite_add(testchar, first, AKGL_ACTOR_STATE_DEAD));
|
|
|
|
|
CATCH(errctx, akgl_character_sprite_add(testchar, second, AKGL_ACTOR_STATE_DEAD));
|
|
|
|
|
}
|
|
|
|
|
TEST_ASSERT(errctx, first->refcount <= (firstbefore + 1),
|
|
|
|
|
"200 rebinds left the first sprite at refcount %d", first->refcount);
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( testchar != NULL ) {
|
|
|
|
|
IGNORE(akgl_heap_release_character(testchar));
|
|
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
int main(void)
|
|
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
ATTEMPT {
|
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
|
|
|
CATCH(errctx, akgl_error_init());
|
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>
2026-07-31 23:32:21 -04:00
|
|
|
TEST_TRAP_UNHANDLED_ERRORS();
|
|
|
|
|
akgl_renderer = &akgl_default_renderer;
|
|
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
|
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 10:07:35 -04:00
|
|
|
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) {
|
2026-05-06 23:18:42 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
|
2025-08-03 10:07:35 -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
|
|
|
|
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>
2026-07-31 23:32:21 -04:00
|
|
|
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_character", 640, 480, SDL_WINDOW_HIDDEN, &akgl_window, &akgl_renderer->sdl_renderer)) {
|
2026-05-06 23:18:42 -04:00
|
|
|
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
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>
2026-07-31 23:32:21 -04:00
|
|
|
CATCH(errctx, akgl_render_2d_bind(akgl_renderer));
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_init());
|
|
|
|
|
CATCH(errctx, akgl_registry_init());
|
|
|
|
|
CATCH(errctx, test_akgl_character_initialize());
|
2025-08-03 10:07:35 -04:00
|
|
|
CATCH(errctx, test_character_sprite_mgmt());
|
|
|
|
|
CATCH(errctx, test_character_iterate_state_sprites());
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, test_akgl_character_load_json());
|
Give back every pooled string and sprite reference the loaders take
Closes Defects items 20, 22, 23 and 29, and the residual of item 38.
The tilemap load leak was five pooled strings per load; the property-lookup
fix in an earlier commit took it to two, and the last two were each a claim
with no matching release -- the string every layer's `type` was read into, and
the dirname the map's relative paths resolve against. tests/tilemap.c asserts
the pool is exactly where it started after one load/release cycle and after 64.
Finding those two was a matter of dumping the contents of every still-claimed
slot after a cycle rather than reading the code again; 'tilelayer' and an
assets directory named themselves immediately.
Same file, same class, fixed with it: akgl_tilemap_load_layer_objects released
its scratch string after reading each object's name and then kept using the
slot, because akgl_get_json_string_value reuses a non-NULL destination without
taking another reference. The slot was free while still live, so any other
claim could have been handed it.
akgl_character_sprite_add wrote over an existing binding without releasing the
sprite it displaced, so a character that rebinds a state while alive leaked a
sprite slot per rebind -- teardown only gives back what the map holds at the
end. The new reference is taken before the write and given back if the write
fails, so there is no window where a sprite is bound with nothing behind it.
The write was unchecked too.
Three failure-path leaks moved into CLEANUP blocks: akgl_render_2d_init's two
pooled strings, akgl_controller_open_gamepads' enumeration array, and
akgl_text_rendertextat's surface and texture -- the last being a leak per frame
on a HUD line.
akgl_text_unloadallfonts() closes every font in the registry and destroys it,
which is what item 38 left open. Deliberately not a whole akgl_game_shutdown:
tearing down the mixer, SDL_ttf and SDL in the right order is a design
question, and this is the part that was simply missing. It is a new public
symbol, which 0.5.0 already covers -- this release has not shipped.
Every fix has a test that fails against the old code.
25/25 pass, memcheck clean, reindent --check, check_api_surface and
check_error_protocol all clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 00:44:50 -04:00
|
|
|
CATCH(errctx, test_character_sprite_rebind_releases_displaced());
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH_NORETURN(errctx);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|