From cc0916cd1fb3a3ce9d91cdbc9a5ef0af38d9297a Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Sat, 9 May 2026 14:45:37 -0400 Subject: [PATCH] Added a (registry object name -> registry object pointer) map to the save method so that registry reference by name can have their actor, sprite, spritesheet, and character references reset --- include/akgl/SDL_GameControllerDB.h | 2 +- include/akgl/character.h | 4 +- include/akgl/registry.h | 4 +- include/akgl/sprite.h | 2 +- src/character.c | 2 +- src/game.c | 223 +++++++++++++++++++++++++--- src/heap.c | 2 +- src/registry.c | 14 +- src/sprite.c | 4 +- tests/sprite.c | 2 +- 10 files changed, 219 insertions(+), 40 deletions(-) diff --git a/include/akgl/SDL_GameControllerDB.h b/include/akgl/SDL_GameControllerDB.h index 57c03d5..277ff13 100644 --- a/include/akgl/SDL_GameControllerDB.h +++ b/include/akgl/SDL_GameControllerDB.h @@ -1,7 +1,7 @@ #ifndef _SDL_GAMECONTROLLERDB_H_ #define _SDL_GAMECONTROLLERDB_H_ -// Taken from https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt on Fri May 8 11:47:47 PM EDT 2026 +// Taken from https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt on Sat May 9 02:26:29 PM EDT 2026 #define AKGL_SDL_GAMECONTROLLER_DB_LEN 2227 diff --git a/include/akgl/character.h b/include/akgl/character.h index 89c0f5b..b80dc6e 100644 --- a/include/akgl/character.h +++ b/include/akgl/character.h @@ -12,11 +12,11 @@ typedef struct akgl_Character { uint8_t refcount; char name[AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH]; SDL_PropertiesID state_sprites; - akerr_ErrorContext AKERR_NOIGNORE *(*sprite_add)(struct akgl_Character *, akgl_Sprite *, int); - akerr_ErrorContext AKERR_NOIGNORE *(*sprite_get)(struct akgl_Character *, int, akgl_Sprite **); uint64_t movementspeed; float32_t vx; float32_t vy; + akerr_ErrorContext AKERR_NOIGNORE *(*sprite_add)(struct akgl_Character *, akgl_Sprite *, int); + akerr_ErrorContext AKERR_NOIGNORE *(*sprite_get)(struct akgl_Character *, int, akgl_Sprite **); } akgl_Character; diff --git a/include/akgl/registry.h b/include/akgl/registry.h index 03c4a7b..08fbc42 100644 --- a/include/akgl/registry.h +++ b/include/akgl/registry.h @@ -5,9 +5,9 @@ extern SDL_PropertiesID AKGL_REGISTRY_ACTOR; extern SDL_PropertiesID AKGL_REGISTRY_SPRITE; -extern SDL_PropertiesID AKGL_AKGL_REGISTRY_SPRITESHEET; +extern SDL_PropertiesID AKGL_REGISTRY_SPRITESHEET; extern SDL_PropertiesID AKGL_REGISTRY_CHARACTER; -extern SDL_PropertiesID AKGL_AKGL_REGISTRY_ACTOR_STATE_STRINGS; +extern SDL_PropertiesID AKGL_REGISTRY_ACTOR_STATE_STRINGS; extern SDL_PropertiesID AKGL_REGISTRY_FONT; extern SDL_PropertiesID AKGL_REGISTRY_MUSIC; extern SDL_PropertiesID AKGL_REGISTRY_PROPERTIES; diff --git a/include/akgl/sprite.h b/include/akgl/sprite.h index bc05e74..e34d1e8 100644 --- a/include/akgl/sprite.h +++ b/include/akgl/sprite.h @@ -24,7 +24,6 @@ typedef struct { typedef struct { uint8_t refcount; - akgl_SpriteSheet *sheet; uint8_t frameids[AKGL_SPRITE_MAX_FRAMES]; // which IDs on the spritesheet belong to our frames uint32_t frames; // how many frames are in this animation uint32_t width; @@ -33,6 +32,7 @@ typedef struct { bool loop; // when this sprite is done playing, it should immediately start again bool loopReverse; // when this sprite is done playing, it should go in reverse order through its frames char name[AKGL_SPRITE_MAX_NAME_LENGTH]; + akgl_SpriteSheet *sheet; } akgl_Sprite; // initializes a new sprite to use the given sheet and otherwise sets to zero diff --git a/src/character.c b/src/character.c index 34d95a2..aa85238 100644 --- a/src/character.c +++ b/src/character.c @@ -98,7 +98,7 @@ static akerr_ErrorContext *akgl_character_load_json_state_int_from_strings(json_ CATCH(errctx, akgl_heap_next_string(&tmpstring)); for ( i = 0; i < json_array_size((json_t *)states) ; i++ ) { CATCH(errctx, akgl_get_json_array_index_string(states, i, &tmpstring)); - newstate = (long)SDL_GetNumberProperty(AKGL_AKGL_REGISTRY_ACTOR_STATE_STRINGS, (char *)&tmpstring->data, 0); + newstate = (long)SDL_GetNumberProperty(AKGL_REGISTRY_ACTOR_STATE_STRINGS, (char *)&tmpstring->data, 0); FAIL_ZERO_BREAK(errctx, newstate, AKERR_KEY, "Unknown actor state %s", (char *)&tmpstring->data); *dest = (*dest | (int)(newstate)); } diff --git a/src/game.c b/src/game.c index e226778..1db1d80 100644 --- a/src/game.c +++ b/src/game.c @@ -123,26 +123,181 @@ void akgl_game_updateFPS() game.lastIterTime = curTime; } +/* + * entity name -> pointer map tables + */ + +void akgl_game_save_actorname_iterator(void *userdata, SDL_PropertiesID props, const char *name) +{ + FILE *fp = (FILE *)userdata; + akgl_Actor *actor = NULL; + PREPARE_ERROR(errctx); + ATTEMPT { + FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); + fwrite((char *)name, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp); + actor = SDL_GetPointerProperty(props, name, NULL); + fwrite(&actor, 1, sizeof(akgl_Actor *), fp); + } CLEANUP { + } PROCESS(errctx) { + } FINISH_NORETURN(errctx); +} + +void akgl_game_save_spritename_iterator(void *userdata, SDL_PropertiesID props, const char *name) +{ + FILE *fp = (FILE *)userdata; + akgl_Sprite *sprite = NULL; + PREPARE_ERROR(errctx); + ATTEMPT { + FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); + sprite = SDL_GetPointerProperty(props, name, NULL); + fwrite((char *)name, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp); + fwrite(&sprite, 1, sizeof(akgl_Sprite *), fp); + } CLEANUP { + } PROCESS(errctx) { + } FINISH_NORETURN(errctx); +} + +void akgl_game_save_spritesheetname_iterator(void *userdata, SDL_PropertiesID props, const char *name) +{ + FILE *fp = (FILE *)userdata; + akgl_SpriteSheet *spritesheet = NULL; + PREPARE_ERROR(errctx); + ATTEMPT { + FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); + spritesheet = SDL_GetPointerProperty(props, name, NULL); + fwrite((char *)name, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp); + fwrite(&spritesheet, 1, sizeof(akgl_SpriteSheet *), fp); + } CLEANUP { + } PROCESS(errctx) { + } FINISH_NORETURN(errctx); +} + +void akgl_game_save_charactername_iterator(void *userdata, SDL_PropertiesID props, const char *name) +{ + FILE *fp = (FILE *)userdata; + PREPARE_ERROR(errctx); + akgl_Character *character = NULL; + ATTEMPT { + FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); + character = SDL_GetPointerProperty(props, name, NULL); + fwrite((char *)name, 1, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp); + fwrite(&character, 1, sizeof(akgl_Character *), fp); + } CLEANUP { + } PROCESS(errctx) { + } FINISH_NORETURN(errctx); +} + +akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp) +{ + PREPARE_ERROR(errctx); + char nullval = 0x00; + + ATTEMPT { + FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); + // write the actor name pointer table + SDL_EnumerateProperties( + AKGL_REGISTRY_ACTOR, + &akgl_game_save_actorname_iterator, + (void *)fp); + fwrite((void *)&nullval, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp); + fwrite((void *)&nullval, 1, sizeof(akgl_Actor *), fp); + // write the sprite name pointer table + SDL_EnumerateProperties( + AKGL_REGISTRY_SPRITE, + &akgl_game_save_spritename_iterator, + (void *)fp); + fwrite((void *)&nullval, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp); + fwrite((void *)&nullval, 1, sizeof(akgl_Sprite *), fp); + // write the spritesheet name pointer table + SDL_EnumerateProperties( + AKGL_REGISTRY_SPRITESHEET, + &akgl_game_save_spritesheetname_iterator, + (void *)fp); + fwrite((void *)&nullval, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp); + fwrite((void *)&nullval, 1, sizeof(akgl_SpriteSheet *), fp); + // write the character name pointer table + SDL_EnumerateProperties( + AKGL_REGISTRY_CHARACTER, + &akgl_game_save_charactername_iterator, + (void *)fp); + fwrite((void *)&nullval, 1, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp); + fwrite((void *)&nullval, 1, sizeof(akgl_Character *), fp); + } CLEANUP { + } PROCESS(errctx) { + } FINISH(errctx, true); + SUCCEED_RETURN(errctx); +} + akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath) { FILE *fp = NULL; PREPARE_ERROR(errctx); - FAIL_ZERO_RETURN(errctx, fpath, AKERR_NULLPOINTER, "NULL file path"); - fp = fopen(fpath, "wb"); - FAIL_ZERO_RETURN(errctx, fp, errno, "%s", fpath); - fwrite(&game, 1, sizeof(akgl_Game), fp); - fclose(fp); + ATTEMPT { + FAIL_ZERO_BREAK(errctx, fpath, AKERR_NULLPOINTER, "NULL file path"); + fp = fopen(fpath, "wb"); + FAIL_ZERO_BREAK(errctx, fp, errno, "%s", fpath); + fwrite(&game, 1, sizeof(akgl_Game), fp); + CATCH(errctx, akgl_game_save_actors(fp)); + } PROCESS(errctx) { + } CLEANUP { + if ( fp != NULL ) + fclose(fp); + } FINISH(errctx, true); SUCCEED_RETURN(errctx); // SUCCEED_NORETURN if in main(). } -akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(FILE *fp, const char *versiontype, char *curversion) +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; + + PREPARE_ERROR(errctx); + while ( 1 ) { + retval = fread((void *)&objname, 1, namelength, fp); + FAIL_NONZERO_RETURN( + errctx, + (retval != namelength), + AKERR_IO, + "Corrupt save file"); + retval = fread((void *)&ptr, 1, ptrlength, fp); + FAIL_NONZERO_RETURN( + errctx, + (retval != ptrlength), + AKERR_IO, + "Corrupt save file"); + // End of the map + if ( ptr == 0x00 && objname[0] == 0x00 ) { + 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. + memset((void *)&ptrstring, 0x00, 32); + snprintf((char *)&ptrstring, 32, "%p", ptr); + SDL_SetPointerProperty( + map, + ptrstring, + SDL_GetPointerProperty(registry, objname, NULL)); + }; + SUCCEED_RETURN(errctx); +} + +akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion) { - char versionstr[32]; semver_t current_version = {}; semver_t compare_version = {}; PREPARE_ERROR(errctx); - FAIL_ZERO_RETURN(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); + FAIL_ZERO_RETURN(errctx, versiontype, AKERR_NULLPOINTER, "NULL argument"); + FAIL_ZERO_RETURN(errctx, curversion, AKERR_NULLPOINTER, "NULL argument"); + FAIL_ZERO_RETURN(errctx, newversion, AKERR_NULLPOINTER, "NULL argument"); ATTEMPT { // Check save game library version @@ -155,24 +310,19 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(FILE *fp, const cha (char *)curversion); FAIL_NONZERO_BREAK( errctx, - (fread((void *)&versionstr, 1, 32, fp) < 32), - AKERR_IO, - "Corrupt save file"); - FAIL_NONZERO_BREAK( - errctx, - semver_parse((const char *)&versionstr, &compare_version), + semver_parse((const char *)newversion, &compare_version), AKERR_VALUE, "Invalid semantic %s version in save game: %s", versiontype, - (char *)&versionstr); + (char *)&newversion); FAIL_ZERO_BREAK( errctx, semver_satisfies(compare_version, current_version, "="), AKERR_API, - "Incompatible save game %s version (%s ^ %s)", + "Incompatible save game %s version (%s != %s)", versiontype, curversion, - (char *)&versionstr); + (char *)&newversion); } CLEANUP { semver_free(¤t_version); semver_free(&compare_version); @@ -183,6 +333,11 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(FILE *fp, const cha akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath) { + akgl_Game savegame; + SDL_PropertiesID actormap; + SDL_PropertiesID spritemap; + SDL_PropertiesID spritesheetmap; + SDL_PropertiesID charactermap; FILE *fp = NULL; PREPARE_ERROR(errctx); @@ -191,14 +346,38 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath) ATTEMPT { fp = fopen(fpath, "rb"); FAIL_ZERO_BREAK(errctx, fp, errno, "%s", fpath); - CATCH(errctx, akgl_game_load_versioncmp(fp, "library", (char *)AKGL_VERSION)); - CATCH(errctx, akgl_game_load_versioncmp(fp, "game", (char *)&game.version)); - rewind(fp); FAIL_NONZERO_BREAK( errctx, - (fread((void *)&game, 1, sizeof(akgl_Game), fp) < sizeof(akgl_Game)), + (fread((void *)&savegame, 1, sizeof(akgl_Game), fp) < sizeof(akgl_Game)), AKERR_IO, - "Corrupt save file"); + "Corrupt save file"); + CATCH(errctx, akgl_game_load_versioncmp("library", (char *)&savegame.libversion, (char *)AKGL_VERSION)); + CATCH(errctx, akgl_game_load_versioncmp("game", (char *)&savegame.version, (char *)&game.version)); + FAIL_NONZERO_RETURN( + errctx, + strncmp((char *)&savegame.name, (char *)&game.name, 256), + AKERR_API, + "Savegame is not compatible with this game"); + FAIL_NONZERO_RETURN( + errctx, + strncmp((char *)&savegame.uri, (char *)&game.uri, 256), + AKERR_API, + "Savegame is not compatible with this game"); + + memcpy((void *)&game, (void *)&savegame, sizeof(akgl_Game)); + // Load actor name map + actormap = SDL_CreateProperties(); + CATCH(errctx, akgl_game_load_objectnamemap(fp, actormap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Actor *), AKGL_REGISTRY_ACTOR)); + // Load sprite name map + spritemap = SDL_CreateProperties(); + CATCH(errctx, akgl_game_load_objectnamemap(fp, spritemap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Sprite *), AKGL_REGISTRY_SPRITE)); + // Load spritesheet name map + spritesheetmap = SDL_CreateProperties(); + CATCH(errctx, akgl_game_load_objectnamemap(fp, spritesheetmap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_SpriteSheet *), AKGL_REGISTRY_SPRITESHEET)); + // Load character name map + charactermap = SDL_CreateProperties(); + CATCH(errctx, 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 } CLEANUP { if ( fp != NULL ) { fclose(fp); diff --git a/src/heap.c b/src/heap.c index c469a8c..d6b5e63 100644 --- a/src/heap.c +++ b/src/heap.c @@ -171,7 +171,7 @@ akerr_ErrorContext *akgl_heap_release_spritesheet(akgl_SpriteSheet *ptr) } if ( ptr->refcount == 0 ) { // TODO : If we go threaded, make sure this is only happening on the main thread - SDL_ClearProperty(AKGL_AKGL_REGISTRY_SPRITESHEET, (char *)&ptr->name); + SDL_ClearProperty(AKGL_REGISTRY_SPRITESHEET, (char *)&ptr->name); if ( ptr-> texture != NULL ) SDL_DestroyTexture(ptr->texture); ptr->texture = NULL; diff --git a/src/registry.c b/src/registry.c index 8a3bbf8..039bd21 100644 --- a/src/registry.c +++ b/src/registry.c @@ -10,9 +10,9 @@ #include SDL_PropertiesID AKGL_REGISTRY_ACTOR; -SDL_PropertiesID AKGL_AKGL_REGISTRY_ACTOR_STATE_STRINGS; +SDL_PropertiesID AKGL_REGISTRY_ACTOR_STATE_STRINGS; SDL_PropertiesID AKGL_REGISTRY_SPRITE; -SDL_PropertiesID AKGL_AKGL_REGISTRY_SPRITESHEET; +SDL_PropertiesID AKGL_REGISTRY_SPRITESHEET; SDL_PropertiesID AKGL_REGISTRY_CHARACTER; SDL_PropertiesID AKGL_REGISTRY_MUSIC; SDL_PropertiesID AKGL_REGISTRY_FONT; @@ -72,12 +72,12 @@ akerr_ErrorContext *akgl_registry_init_actor_state_strings() int i = 0; int flag = 0; PREPARE_ERROR(errctx); - AKGL_AKGL_REGISTRY_ACTOR_STATE_STRINGS = SDL_CreateProperties(); - FAIL_ZERO_RETURN(errctx, AKGL_AKGL_REGISTRY_ACTOR_STATE_STRINGS, AKERR_NULLPOINTER, "Error initializing actor state strings registry"); + AKGL_REGISTRY_ACTOR_STATE_STRINGS = SDL_CreateProperties(); + FAIL_ZERO_RETURN(errctx, AKGL_REGISTRY_ACTOR_STATE_STRINGS, AKERR_NULLPOINTER, "Error initializing actor state strings registry"); for ( i = 0 ; i < AKGL_ACTOR_MAX_STATES; i++ ) { flag = (1 << i); SDL_SetNumberProperty( - AKGL_AKGL_REGISTRY_ACTOR_STATE_STRINGS, + AKGL_REGISTRY_ACTOR_STATE_STRINGS, AKGL_ACTOR_STATE_STRING_NAMES[i], flag); } @@ -95,8 +95,8 @@ akerr_ErrorContext *akgl_registry_init_sprite() akerr_ErrorContext *akgl_registry_init_spritesheet() { PREPARE_ERROR(errctx); - AKGL_AKGL_REGISTRY_SPRITESHEET = SDL_CreateProperties(); - FAIL_ZERO_RETURN(errctx, AKGL_AKGL_REGISTRY_SPRITESHEET, AKERR_NULLPOINTER, "Error initializing spritesheet registry"); + AKGL_REGISTRY_SPRITESHEET = SDL_CreateProperties(); + FAIL_ZERO_RETURN(errctx, AKGL_REGISTRY_SPRITESHEET, AKERR_NULLPOINTER, "Error initializing spritesheet registry"); SUCCEED_RETURN(errctx); } diff --git a/src/sprite.c b/src/sprite.c index c1d75e8..8c7d821 100644 --- a/src/sprite.c +++ b/src/sprite.c @@ -33,7 +33,7 @@ static akerr_ErrorContext *akgl_sprite_load_json_spritesheet(json_t *json, akgl_ SDL_snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s", ss_filename->data); } *sheet = SDL_GetPointerProperty( - AKGL_AKGL_REGISTRY_SPRITESHEET, + AKGL_REGISTRY_SPRITESHEET, (char *)&tmpstr->data, NULL ); @@ -161,7 +161,7 @@ akerr_ErrorContext *akgl_spritesheet_initialize(akgl_SpriteSheet *sheet, int spr FAIL_ZERO_BREAK( errctx, - SDL_SetPointerProperty(AKGL_AKGL_REGISTRY_SPRITESHEET, (char *)sheet->name, (void *)sheet), + SDL_SetPointerProperty(AKGL_REGISTRY_SPRITESHEET, (char *)sheet->name, (void *)sheet), AKERR_KEY, "Unable to add spritesheet to registry: %s", SDL_GetError()); diff --git a/tests/sprite.c b/tests/sprite.c index 68f36e3..a1f52b6 100644 --- a/tests/sprite.c +++ b/tests/sprite.c @@ -51,7 +51,7 @@ akerr_ErrorContext *test_akgl_spritesheet_initialize(void) FAIL_ZERO_BREAK( errctx, - SDL_GetPointerProperty(AKGL_AKGL_REGISTRY_SPRITESHEET, "assets/spritesheet.png", NULL), + SDL_GetPointerProperty(AKGL_REGISTRY_SPRITESHEET, "assets/spritesheet.png", NULL), AKERR_KEY, "Spritesheet was not placed in the registry");