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>
199 lines
5.5 KiB
C
199 lines
5.5 KiB
C
/**
|
|
* @file heap.c
|
|
* @brief Implements the heap subsystem.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <akerror.h>
|
|
|
|
#include <akgl/game.h>
|
|
#include <akgl/sprite.h>
|
|
#include <akgl/heap.h>
|
|
#include <akgl/registry.h>
|
|
#include <akgl/staticstring.h>
|
|
#include <akgl/iterator.h>
|
|
#include <akgl/error.h>
|
|
|
|
akgl_Actor HEAP_ACTOR[AKGL_MAX_HEAP_ACTOR];
|
|
akgl_Sprite HEAP_SPRITE[AKGL_MAX_HEAP_SPRITE];
|
|
akgl_SpriteSheet HEAP_SPRITESHEET[AKGL_MAX_HEAP_SPRITESHEET];
|
|
akgl_Character HEAP_CHARACTER[AKGL_MAX_HEAP_CHARACTER];
|
|
akgl_String HEAP_STRING[AKGL_MAX_HEAP_STRING];
|
|
|
|
akerr_ErrorContext *akgl_heap_init()
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
int i = 0;
|
|
PASS(errctx, akgl_heap_init_actor());
|
|
for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++) {
|
|
memset(&HEAP_SPRITE[i], 0x00, sizeof(akgl_Sprite));
|
|
}
|
|
for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++) {
|
|
memset(&HEAP_SPRITESHEET[i], 0x00, sizeof(akgl_SpriteSheet));
|
|
}
|
|
for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++) {
|
|
memset(&HEAP_CHARACTER[i], 0x00, sizeof(akgl_Character));
|
|
}
|
|
for ( i = 0; i < AKGL_MAX_HEAP_STRING; i++) {
|
|
memset(&HEAP_STRING[i], 0x00, sizeof(akgl_String));
|
|
}
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akgl_heap_init_actor(void)
|
|
{
|
|
PREPARE_ERROR(e);
|
|
for ( int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++) {
|
|
memset(&HEAP_ACTOR[i], 0x00, sizeof(akgl_Actor));
|
|
}
|
|
SUCCEED_RETURN(e);
|
|
}
|
|
|
|
akerr_ErrorContext *akgl_heap_next_actor(akgl_Actor **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
for (int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
|
|
if ( HEAP_ACTOR[i].refcount != 0 ) {
|
|
continue;
|
|
}
|
|
*dest = &HEAP_ACTOR[i];
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused actor on the heap");
|
|
}
|
|
|
|
akerr_ErrorContext *akgl_heap_next_sprite(akgl_Sprite **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
for (int i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) {
|
|
if ( HEAP_SPRITE[i].refcount != 0 ) {
|
|
continue;
|
|
}
|
|
*dest = &HEAP_SPRITE[i];
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused sprite on the heap");
|
|
}
|
|
|
|
akerr_ErrorContext *akgl_heap_next_spritesheet(akgl_SpriteSheet **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
for (int i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) {
|
|
if ( HEAP_SPRITESHEET[i].refcount != 0 ) {
|
|
continue;
|
|
}
|
|
*dest = &HEAP_SPRITESHEET[i];
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused spritesheet on the heap");
|
|
}
|
|
|
|
akerr_ErrorContext *akgl_heap_next_character(akgl_Character **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
for (int i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) {
|
|
if ( HEAP_CHARACTER[i].refcount != 0 ) {
|
|
continue;
|
|
}
|
|
*dest = &HEAP_CHARACTER[i];
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused character on the heap");
|
|
}
|
|
|
|
akerr_ErrorContext *akgl_heap_next_string(akgl_String **dest)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
for (int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
|
|
if ( HEAP_STRING[i].refcount != 0 ) {
|
|
continue;
|
|
}
|
|
*dest = &HEAP_STRING[i];
|
|
HEAP_STRING[i].refcount += 1;
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused string on the heap");
|
|
}
|
|
|
|
akerr_ErrorContext *akgl_heap_release_actor(akgl_Actor *ptr)
|
|
{
|
|
int i = 0;
|
|
PREPARE_ERROR(errctx);
|
|
FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "NULL actor reference");
|
|
if ( ptr->refcount > 0 ) {
|
|
ptr->refcount -= 1;
|
|
}
|
|
if ( ptr->refcount == 0 ) {
|
|
for ( i = 0; i < AKGL_ACTOR_MAX_CHILDREN; i++ ) {
|
|
if ( ptr->children[i] != NULL ) {
|
|
PASS(errctx, akgl_heap_release_actor(ptr->children[i]));
|
|
}
|
|
}
|
|
SDL_ClearProperty(AKGL_REGISTRY_ACTOR, (char *)&ptr->name);
|
|
memset(ptr, 0x00, sizeof(akgl_Actor));
|
|
}
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akgl_heap_release_character(akgl_Character *basechar)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
akgl_Iterator opflags;
|
|
FAIL_ZERO_RETURN(errctx, basechar, AKERR_NULLPOINTER, "NULL character reference");
|
|
AKGL_BITMASK_CLEAR(opflags.flags);
|
|
|
|
if ( basechar->refcount > 0 ) {
|
|
basechar->refcount -= 1;
|
|
}
|
|
if ( basechar->refcount == 0 ) {
|
|
SDL_ClearProperty(AKGL_REGISTRY_CHARACTER, (char *)&basechar->name);
|
|
memset(basechar, 0x00, sizeof(akgl_Character));
|
|
}
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akgl_heap_release_sprite(akgl_Sprite *ptr)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "Received NULL sprite reference");
|
|
if ( ptr->refcount > 0 ) {
|
|
ptr->refcount -= 1;
|
|
}
|
|
if ( ptr->refcount == 0 ) {
|
|
SDL_ClearProperty(AKGL_REGISTRY_SPRITE, (char *)&ptr->name);
|
|
memset(ptr, 0x00, sizeof(akgl_Sprite));
|
|
}
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akgl_heap_release_spritesheet(akgl_SpriteSheet *ptr)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "Received NULL spritesheet reference");
|
|
if ( ptr->refcount > 0 ) {
|
|
ptr->refcount -= 1;
|
|
}
|
|
if ( ptr->refcount == 0 ) {
|
|
// TODO : If we go threaded, make sure this is only happening on the main thread
|
|
SDL_ClearProperty(AKGL_REGISTRY_SPRITESHEET, (char *)&ptr->name);
|
|
if ( ptr-> texture != NULL )
|
|
SDL_DestroyTexture(ptr->texture);
|
|
ptr->texture = NULL;
|
|
memset(ptr, 0x00, sizeof(akgl_SpriteSheet));
|
|
}
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akgl_heap_release_string(akgl_String *ptr)
|
|
{
|
|
PREPARE_ERROR(errctx);
|
|
FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "Received NULL string reference");
|
|
if ( ptr->refcount > 0 ) {
|
|
ptr->refcount -= 1;
|
|
}
|
|
if ( ptr->refcount == 0 ) {
|
|
memset(&ptr->data, 0x00, AKGL_MAX_STRING_LENGTH);
|
|
}
|
|
SUCCEED_RETURN(errctx);
|
|
}
|