2026-07-30 01:10:31 -04:00
|
|
|
/**
|
|
|
|
|
* @file character.c
|
|
|
|
|
* @brief Implements the character subsystem.
|
|
|
|
|
*/
|
|
|
|
|
|
2025-08-03 10:07:35 -04:00
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
|
#include <SDL3_image/SDL_image.h>
|
2026-01-05 08:58:06 -05:00
|
|
|
#include <akerror.h>
|
2025-08-03 10:07:35 -04:00
|
|
|
#include <string.h>
|
|
|
|
|
#include <jansson.h>
|
|
|
|
|
|
2026-05-07 22:20:10 -04:00
|
|
|
#include <akgl/game.h>
|
|
|
|
|
#include <akgl/sprite.h>
|
|
|
|
|
#include <akgl/json_helpers.h>
|
|
|
|
|
#include <akgl/heap.h>
|
|
|
|
|
#include <akgl/registry.h>
|
|
|
|
|
#include <akgl/staticstring.h>
|
|
|
|
|
#include <akgl/iterator.h>
|
|
|
|
|
#include <akgl/util.h>
|
2025-08-03 10:07:35 -04:00
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_character_initialize(akgl_Character *obj, char *name)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
2026-05-06 23:18:42 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL akgl_Character reference");
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, name, AKERR_NULLPOINTER, "NULL name string pointer");
|
2026-05-06 23:18:42 -04:00
|
|
|
memset(obj, 0x00, sizeof(akgl_Character));
|
|
|
|
|
strncpy(obj->name, name, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH);
|
2025-08-03 10:07:35 -04:00
|
|
|
obj->state_sprites = SDL_CreateProperties();
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, obj->state_sprites, AKERR_NULLPOINTER, "Unable to initialize SDL_PropertiesID for character state map");
|
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
|
|
|
obj->sprite_add = &akgl_character_sprite_add;
|
|
|
|
|
obj->sprite_get = &akgl_character_sprite_get;
|
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
|
|
|
FAIL_ZERO_RETURN(
|
|
|
|
|
errctx,
|
2026-05-06 23:18:42 -04:00
|
|
|
SDL_SetPointerProperty(AKGL_REGISTRY_CHARACTER, name, (void *)obj),
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_KEY,
|
2025-08-03 10:07:35 -04:00
|
|
|
"Unable to add character to registry");
|
|
|
|
|
obj->refcount += 1;
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_character_sprite_add(akgl_Character *basechar, akgl_Sprite *ref, int state)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
char stateval[32];
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, basechar, AKERR_NULLPOINTER, "NULL character reference");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, ref, AKERR_NULLPOINTER, "NULL sprite reference");
|
2025-08-03 10:07:35 -04:00
|
|
|
memset(&stateval, 0x00, 32);
|
|
|
|
|
SDL_itoa(state, (char *)&stateval, 10);
|
|
|
|
|
SDL_SetPointerProperty(basechar->state_sprites, (char *)&stateval, ref);
|
2025-08-08 22:39:48 -04:00
|
|
|
SDL_Log("Added sprite %s to character %s for state %b", (char *)&ref->name, (char *)&basechar->name, state);
|
2025-08-03 10:07:35 -04:00
|
|
|
ref->refcount += 1;
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 23:18:42 -04:00
|
|
|
akerr_ErrorContext *akgl_character_sprite_get(akgl_Character *basechar, int state, akgl_Sprite **dest)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_Sprite *target = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
char stateval[32];
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL pointer to sprite pointer (**dest)");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, basechar, AKERR_NULLPOINTER, "NULL character reference");
|
2025-08-03 10:07:35 -04:00
|
|
|
memset(&stateval, 0x00, 32);
|
|
|
|
|
SDL_itoa(state, (char *)&stateval, 10);
|
2026-05-06 23:18:42 -04:00
|
|
|
*dest = (akgl_Sprite *)SDL_GetPointerProperty(basechar->state_sprites, (char *)&stateval, NULL);
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, *dest, AKERR_KEY, "Sprite for state %d (%b) not found in the character's registry", state, state);
|
2025-08-04 21:37:36 -04:00
|
|
|
target = *dest;
|
2025-08-08 22:39:48 -04:00
|
|
|
//SDL_Log("Sprite state %d (%s) has character %s", state, (char *)&stateval, target->name);
|
2025-08-03 10:07:35 -04:00
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SDL iterator so we can't return error information here, void only
|
|
|
|
|
// this means we don't have anywhere to send exceptions up to, so if we hit an error, we log and exit(1) here
|
2026-05-06 23:18:42 -04:00
|
|
|
void akgl_character_state_sprites_iterate(void *userdata, SDL_PropertiesID registry, const char *name)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_Sprite *spriteptr;
|
|
|
|
|
akgl_Iterator *opflags = (akgl_Iterator *)userdata;
|
2025-08-03 10:07:35 -04:00
|
|
|
ATTEMPT {
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_BREAK(errctx, opflags, AKERR_NULLPOINTER, "Character state sprite iterator received null iterator op pointer");
|
|
|
|
|
FAIL_ZERO_BREAK(errctx, name, AKERR_NULLPOINTER, "Character state sprite iterator received null sprite name");
|
2026-05-06 23:18:42 -04:00
|
|
|
spriteptr = (akgl_Sprite *)SDL_GetPointerProperty(registry, name, NULL);
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_BREAK(errctx, spriteptr, AKERR_NULLPOINTER, "Character state sprite for %s not found", name);
|
2026-05-06 23:18:42 -04:00
|
|
|
if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_RELEASE) ) {
|
|
|
|
|
CATCH(errctx, akgl_heap_release_sprite(spriteptr));
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH_NORETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
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 OR a JSON array of actor-state names together into one bitmask.
|
|
|
|
|
*
|
|
|
|
|
* This is what lets a character JSON say `["FACE_LEFT", "MOVING_LEFT"]` rather
|
|
|
|
|
* than a number: each name is looked up in #AKGL_REGISTRY_ACTOR_STATE_STRINGS
|
|
|
|
|
* and OR-ed in. A name that is not a known state is an error rather than being
|
|
|
|
|
* skipped -- a typo in a state name would otherwise bind a sprite to the wrong
|
|
|
|
|
* combination and show up as art that never appears.
|
|
|
|
|
*
|
|
|
|
|
* @param states JSON array of state-name strings. Required. An empty array is
|
|
|
|
|
* legal and leaves @p dest as it was.
|
|
|
|
|
* @param dest Receives the OR of every named bit. Required in practice, and
|
|
|
|
|
* **not** checked -- the guard that should test it tests @p states
|
|
|
|
|
* a second time instead, so a `NULL` here is a crash. It is OR-ed
|
|
|
|
|
* into rather than assigned, so the caller must zero it first.
|
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 states is `NULL`.
|
|
|
|
|
* @throws AKERR_KEY If a name is not in the actor-state registry. The message
|
|
|
|
|
* quotes it. Note that bits 11 and 12 are unreachable by name; see
|
|
|
|
|
* akgl_registry_init_actor_state_strings.
|
|
|
|
|
* @throws AKERR_TYPE If an array element is not a string.
|
|
|
|
|
* @throws AKERR_OUTOFBOUNDS If the array is indexed past its end.
|
|
|
|
|
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
2026-07-30 01:10:31 -04:00
|
|
|
*/
|
2026-05-06 23:18:42 -04:00
|
|
|
static akerr_ErrorContext *akgl_character_load_json_state_int_from_strings(json_t *states, int *dest)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
int i = 0;
|
|
|
|
|
long newstate = 0;
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_String *tmpstring = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
PREPARE_ERROR(errctx);
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, states, AKERR_NULLPOINTER, "NULL states array");
|
|
|
|
|
FAIL_ZERO_RETURN(errctx, states, AKERR_NULLPOINTER, "NULL destination integer");
|
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
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_next_string(&tmpstring));
|
2025-08-03 10:07:35 -04:00
|
|
|
for ( i = 0; i < json_array_size((json_t *)states) ; i++ ) {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_array_index_string(states, i, &tmpstring));
|
2026-05-09 14:45:37 -04:00
|
|
|
newstate = (long)SDL_GetNumberProperty(AKGL_REGISTRY_ACTOR_STATE_STRINGS, (char *)&tmpstring->data, 0);
|
2026-05-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_BREAK(errctx, newstate, AKERR_KEY, "Unknown actor state %s", (char *)&tmpstring->data);
|
2025-08-03 10:07:35 -04:00
|
|
|
*dest = (*dest | (int)(newstate));
|
|
|
|
|
}
|
|
|
|
|
} CLEANUP {
|
2026-05-06 23:18:42 -04:00
|
|
|
IGNORE(akgl_heap_release_string(tmpstring));
|
2025-08-03 10:07:35 -04:00
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
|
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|
|
|
|
|
|
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 Name a character and build its state-to-sprite map from a JSON document.
|
|
|
|
|
*
|
|
|
|
|
* The half of akgl_character_load_json that deals with identity and sprites; the
|
|
|
|
|
* caller handles the numeric fields afterwards. Reads `name`, initializes the
|
|
|
|
|
* character with it, then walks `sprite_mappings` binding each named sprite to
|
|
|
|
|
* the bitmask its `state` array adds up to.
|
|
|
|
|
*
|
|
|
|
|
* Every sprite named must already be in #AKGL_REGISTRY_SPRITE.
|
|
|
|
|
*
|
|
|
|
|
* @param json The parsed character document. Required in practice; not checked,
|
|
|
|
|
* and passed straight to accessors that report it.
|
|
|
|
|
* @param obj The pooled character to fill in. Required, unchecked -- it is
|
|
|
|
|
* handed to akgl_character_initialize, which does check it.
|
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 a mapping names a sprite that is not registered.
|
|
|
|
|
* The message names the character, the state, and the sprite.
|
|
|
|
|
* @throws AKERR_KEY If `name`, `sprite_mappings`, or a mapping's `sprite` or
|
|
|
|
|
* `state` is absent, or if a state name is unknown.
|
|
|
|
|
* @throws AKERR_TYPE If one of those keys has the wrong JSON type.
|
|
|
|
|
* @throws AKERR_OUTOFBOUNDS If an array is indexed past its end.
|
|
|
|
|
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
2026-07-30 01:10:31 -04:00
|
|
|
*/
|
2026-05-06 23:18:42 -04:00
|
|
|
static akerr_ErrorContext *akgl_character_load_json_inner(json_t *json, akgl_Character *obj)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
|
|
|
|
json_t *mappings = NULL;
|
|
|
|
|
json_t *curmapping = NULL;
|
|
|
|
|
json_t *statearray = NULL;
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_Sprite *spriteptr = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
int i = 0;
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_String *tmpstr = NULL;
|
|
|
|
|
akgl_String *tmpstr2 = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
int stateval = 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
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_string_value((json_t *)json, "name", &tmpstr));
|
|
|
|
|
CATCH(errctx, akgl_character_initialize((akgl_Character *)obj, tmpstr->data));
|
|
|
|
|
CATCH(errctx, akgl_get_json_array_value((json_t *)json, "sprite_mappings", &mappings));
|
2025-08-03 10:07:35 -04:00
|
|
|
for ( i = 0; i < json_array_size((json_t *)mappings) ; i++ ) {
|
|
|
|
|
stateval = 0;
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_array_index_object((json_t *)mappings, i, &curmapping));
|
|
|
|
|
CATCH(errctx, akgl_get_json_string_value((json_t *)curmapping, "sprite", &tmpstr));
|
|
|
|
|
spriteptr = (akgl_Sprite *)SDL_GetPointerProperty(
|
|
|
|
|
AKGL_REGISTRY_SPRITE,
|
2025-08-03 10:07:35 -04:00
|
|
|
tmpstr->data,
|
|
|
|
|
NULL
|
|
|
|
|
);
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_get_json_string_value((json_t *)json, "name", &tmpstr2));
|
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_get_json_array_value((json_t *)curmapping, "state", &statearray));
|
|
|
|
|
CATCH(errctx, akgl_character_load_json_state_int_from_strings(statearray, &stateval));
|
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_get_json_string_value((json_t *)curmapping, "sprite", &tmpstr));
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
spriteptr,
|
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
|
|
|
AKERR_NULLPOINTER,
|
2025-08-08 22:39:48 -04:00
|
|
|
"Character %s for state %b references sprite %s but not found in the registry",
|
2025-08-03 10:07:35 -04:00
|
|
|
tmpstr2->data,
|
|
|
|
|
stateval,
|
|
|
|
|
tmpstr->data
|
|
|
|
|
);
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_character_sprite_add((akgl_Character *)obj, (akgl_Sprite *)spriteptr, stateval));
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
} CLEANUP {
|
|
|
|
|
if ( tmpstr != NULL ) {
|
2026-05-06 23:18:42 -04:00
|
|
|
IGNORE(akgl_heap_release_string(tmpstr));
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
if ( tmpstr2 != NULL ) {
|
2026-05-06 23:18:42 -04:00
|
|
|
IGNORE(akgl_heap_release_string(tmpstr2));
|
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 *akgl_character_load_json(char *filename)
|
2025-08-03 10:07:35 -04:00
|
|
|
{
|
|
|
|
|
PREPARE_ERROR(errctx);
|
Fix every memory defect the checker found, and bump to 0.4.0
Six findings, all of them libakgl's, all closed. The memcheck run is clean.
Not one of the four json_load_file calls in src/ was ever matched by a
json_decref, so every asset load abandoned its parsed document: 1.5 KB per
sprite, 2.1 KB per character, 9 KB per load of the 2x2 fixture map, and on
the order of a megabyte for a real one. Each loader now releases it in the
CLEANUP block it already had, on the success path as well as the failure
one. akgl_registry_load_properties needed its loop moved inside the ATTEMPT
block first: props is a borrowed reference into the document and is read
after the block ends, so every exit from that loop leaked the whole tree.
akgl_get_property copied a fixed AKGL_MAX_STRING_LENGTH bytes out of what
SDL handed back, which is SDL's strdup of the value -- four bytes for
"0.0". That read up to 4 KiB past the end of somebody else's allocation on
every property read, returned whatever was there past the terminator, and
would have faulted on a value that landed at the end of a page. It copies
the value and its terminator now, and refuses a value too long for an
akgl_String rather than truncating it into an unterminated buffer. The
header note that described the overread as a quirk describes correct
behaviour instead.
The four savegame name tables wrote a fixed-width field starting at the
registry key, and SDL sizes that allocation to the name. They read past it
on every entry and put what they found into the save file: up to half a
kilobyte of this process's heap per registered object, in a file a player
might send to somebody. They stage through a zeroed buffer now, and a
negative-array-size typedef fails the build if a table's width ever outgrows
it.
akgl_controller_list_keyboards never freed the array SDL_GetKeyboards
allocated for it.
A font could be opened and published and never handed back -- there was no
way to close one, so a game that changed fonts between scenes leaked ten
kilobytes each time, and loading over a live name leaked the font it
displaced. akgl_text_unloadfont is that missing half, and akgl_text_loadfont
calls it when it replaces a name, after the new font has opened so a failed
reload leaves the caller with the font they had. A new public symbol takes
the version to 0.4.0 and the soname with it: an 0.3 consumer cannot be
handed this library and told it is the same ABI.
tests/registry.c fills a destination with a sentinel and asserts the bytes
past the terminator survive a read, which fails against the old copy.
tests/text.c covers unload, double unload, unloading a name that was never
registered, and replacement closing the displaced font. The JSON releases
have no test of their own and cannot sensibly have one -- nothing in the
public API can observe a jansson refcount -- so the memcheck run is their
test, which is an argument for gating it rather than against.
The two remaining findings are in deps/semver's own unit test, which is
vendored. They are suppressed by function name, so a rewrite of those cases
comes back as a finding.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:49:11 -04:00
|
|
|
json_t *json = NULL;
|
2025-08-03 10:07:35 -04:00
|
|
|
json_error_t error;
|
2026-05-06 23:18:42 -04:00
|
|
|
akgl_Character *obj = NULL;
|
|
|
|
|
//akgl_String *tmpstr = 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-03 23:57:55 -04:00
|
|
|
FAIL_ZERO_RETURN(errctx, filename, AKERR_NULLPOINTER, "Received null filename");
|
2025-08-03 10:07:35 -04:00
|
|
|
ATTEMPT {
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_heap_next_character(&obj));
|
|
|
|
|
//CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
|
|
|
|
//CATCH(errctx, akgl_string_initialize(tmpstr, NULL));
|
|
|
|
|
//SDL_snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), filename);
|
2025-08-04 22:34:25 -04:00
|
|
|
json = (json_t *)json_load_file(filename, 0, &error);
|
2025-08-03 10:07:35 -04:00
|
|
|
FAIL_ZERO_BREAK(
|
|
|
|
|
errctx,
|
|
|
|
|
json,
|
2026-05-03 23:57:55 -04:00
|
|
|
AKERR_NULLPOINTER,
|
2025-08-03 15:14:36 -04:00
|
|
|
"Error while loading character from %s on line %d: %s", filename, error.line, error.text
|
2025-08-03 10:07:35 -04:00
|
|
|
);
|
2026-05-06 23:18:42 -04:00
|
|
|
CATCH(errctx, akgl_character_load_json_inner(json, obj));
|
2026-05-26 10:36:31 -04:00
|
|
|
CATCH(errctx, akgl_get_json_integer_value(json, "speedtime", (int *)&obj->speedtime));
|
|
|
|
|
obj->speedtime = obj->speedtime * AKGL_TIME_ONESEC_MS;
|
|
|
|
|
CATCH(errctx, akgl_get_json_number_value(json, "speed_x", &obj->sx));
|
|
|
|
|
CATCH(errctx, akgl_get_json_number_value(json, "speed_y", &obj->sy));
|
|
|
|
|
CATCH(errctx, akgl_get_json_number_value(json, "acceleration_x", &obj->ax));
|
|
|
|
|
CATCH(errctx, akgl_get_json_number_value(json, "acceleration_y", &obj->ay));
|
2025-08-03 10:07:35 -04:00
|
|
|
} CLEANUP {
|
2026-05-06 23:18:42 -04:00
|
|
|
//IGNORE(akgl_heap_release_string(tmpstr));
|
Fix every memory defect the checker found, and bump to 0.4.0
Six findings, all of them libakgl's, all closed. The memcheck run is clean.
Not one of the four json_load_file calls in src/ was ever matched by a
json_decref, so every asset load abandoned its parsed document: 1.5 KB per
sprite, 2.1 KB per character, 9 KB per load of the 2x2 fixture map, and on
the order of a megabyte for a real one. Each loader now releases it in the
CLEANUP block it already had, on the success path as well as the failure
one. akgl_registry_load_properties needed its loop moved inside the ATTEMPT
block first: props is a borrowed reference into the document and is read
after the block ends, so every exit from that loop leaked the whole tree.
akgl_get_property copied a fixed AKGL_MAX_STRING_LENGTH bytes out of what
SDL handed back, which is SDL's strdup of the value -- four bytes for
"0.0". That read up to 4 KiB past the end of somebody else's allocation on
every property read, returned whatever was there past the terminator, and
would have faulted on a value that landed at the end of a page. It copies
the value and its terminator now, and refuses a value too long for an
akgl_String rather than truncating it into an unterminated buffer. The
header note that described the overread as a quirk describes correct
behaviour instead.
The four savegame name tables wrote a fixed-width field starting at the
registry key, and SDL sizes that allocation to the name. They read past it
on every entry and put what they found into the save file: up to half a
kilobyte of this process's heap per registered object, in a file a player
might send to somebody. They stage through a zeroed buffer now, and a
negative-array-size typedef fails the build if a table's width ever outgrows
it.
akgl_controller_list_keyboards never freed the array SDL_GetKeyboards
allocated for it.
A font could be opened and published and never handed back -- there was no
way to close one, so a game that changed fonts between scenes leaked ten
kilobytes each time, and loading over a live name leaked the font it
displaced. akgl_text_unloadfont is that missing half, and akgl_text_loadfont
calls it when it replaces a name, after the new font has opened so a failed
reload leaves the caller with the font they had. A new public symbol takes
the version to 0.4.0 and the soname with it: an 0.3 consumer cannot be
handed this library and told it is the same ABI.
tests/registry.c fills a destination with a sentinel and asserts the bytes
past the terminator survive a read, which fails against the old copy.
tests/text.c covers unload, double unload, unloading a name that was never
registered, and replacement closing the displaced font. The JSON releases
have no test of their own and cannot sensibly have one -- nothing in the
public API can observe a jansson refcount -- so the memcheck run is their
test, which is an argument for gating it rather than against.
The two remaining findings are in deps/semver's own unit test, which is
vendored. They are suppressed by function name, so a rewrite of those cases
comes back as a finding.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 15:49:11 -04:00
|
|
|
// The character keeps nothing that points into the document -- every
|
|
|
|
|
// field above is copied out of it -- so the whole parsed tree goes back
|
|
|
|
|
// here, on the success path as well as the failure one.
|
|
|
|
|
if ( json != NULL ) {
|
|
|
|
|
json_decref(json);
|
|
|
|
|
json = NULL;
|
|
|
|
|
}
|
2025-08-03 10:07:35 -04:00
|
|
|
if ( errctx != NULL ) {
|
|
|
|
|
if ( errctx->status != 0 ) {
|
2026-05-06 23:18:42 -04:00
|
|
|
IGNORE(akgl_heap_release_character(obj));
|
2025-08-03 10:07:35 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} PROCESS(errctx) {
|
|
|
|
|
} FINISH(errctx, true);
|
2026-05-13 16:56:24 -04:00
|
|
|
SDL_Log("Character %s loaded from %s", obj->name, filename);
|
2025-08-03 10:07:35 -04:00
|
|
|
SUCCEED_RETURN(errctx);
|
|
|
|
|
}
|