From d87c5d2c20ce4d3c1c0343459f46222305d98faa Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Mon, 25 May 2026 21:29:18 -0400 Subject: [PATCH] More rendering subsystem breakout, added a physics subsystem, everything now fires from akgl_game_update() for the user --- include/akgl/SDL_GameControllerDB.h | 2 +- include/akgl/actor.h | 4 +- include/akgl/error.h | 1 + include/akgl/game.h | 11 +- include/akgl/heap.h | 1 + include/akgl/renderer.h | 4 + src/actor.c | 72 +------- src/controller.c | 9 +- src/game.c | 263 ++++++++++++++++++---------- src/heap.c | 13 +- src/registry.c | 3 + src/renderer.c | 40 ++++- src/tilemap.c | 29 ++- 13 files changed, 276 insertions(+), 176 deletions(-) diff --git a/include/akgl/SDL_GameControllerDB.h b/include/akgl/SDL_GameControllerDB.h index 941ff3d..57b4eaf 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 Sun May 24 09:58:23 PM EDT 2026 +// Taken from https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt on Mon May 25 09:28:01 PM EDT 2026 #define AKGL_SDL_GAMECONTROLLER_DB_LEN 2228 diff --git a/include/akgl/actor.h b/include/akgl/actor.h index 13b8d50..c7dcd07 100644 --- a/include/akgl/actor.h +++ b/include/akgl/actor.h @@ -70,9 +70,11 @@ typedef struct akgl_Actor { bool movement_controls_face; void *actorData; bool visible; - SDL_Time logictimer; + SDL_Time movetimer; + float32_t mass; float32_t x; float32_t y; + float32_t z; float32_t scale; struct akgl_Actor *children[AKGL_ACTOR_MAX_CHILDREN]; struct akgl_Actor *parent; diff --git a/include/akgl/error.h b/include/akgl/error.h index a1f0098..a76608b 100644 --- a/include/akgl/error.h +++ b/include/akgl/error.h @@ -13,5 +13,6 @@ _Pragma("GCC diagnostic pop") #define AKGL_ERR_SDL AKERR_LAST_ERRNO_VALUE + 1 +#define AKGL_ERR_LOGICINTERRUPT AKERR_LAST_ERRNO_VALUE + 2 #endif // _ERROR_H_ diff --git a/include/akgl/game.h b/include/akgl/game.h index 76ea81f..17650ad 100644 --- a/include/akgl/game.h +++ b/include/akgl/game.h @@ -2,10 +2,11 @@ #define _AKGL_GAME_H_ #include -#include "types.h" #include +#include "types.h" #include "tilemap.h" #include "renderer.h" +#include "physics.h" #define AKGL_VERSION "0.1.0" @@ -33,6 +34,7 @@ typedef struct { char name[256]; char uri[256]; akgl_GameState state; + SDL_Mutex *statelock; int16_t fps; SDL_Time gameStartTime; SDL_Time lastIterTime; @@ -49,8 +51,10 @@ extern MIX_Track *akgl_tracks[AKGL_GAME_AUDIO_MAX_TRACKS]; extern SDL_FRect camera; extern akgl_Game game; extern akgl_RenderBackend renderer; +extern akgl_PhysicsBackend physics; #define AKGL_BITMASK_HAS(x, y) (x & y) == y +#define AKGL_BITMASK_HASNOT(x, y) (x & y) != y #define AKGL_BITMASK_ADD(x, y) x |= y #define AKGL_BITMASK_DEL(x, y) x &= ~(y) #define AKGL_BITMASK_CLEAR(x) x = 0; @@ -61,5 +65,8 @@ void akgl_game_updateFPS(); akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath); akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath); void akgl_game_lowfps(void); - +akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_lock(void); +akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void); +akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags); + #endif //_AKGL_GAME_H_ diff --git a/include/akgl/heap.h b/include/akgl/heap.h index 110a467..7b11e24 100644 --- a/include/akgl/heap.h +++ b/include/akgl/heap.h @@ -30,6 +30,7 @@ extern akgl_Character HEAP_CHARACTER[AKGL_MAX_HEAP_CHARACTER]; extern akgl_String HEAP_STRING[AKGL_MAX_HEAP_STRING]; akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_init(); +akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_init_actor(); akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_next_actor(akgl_Actor **dest); akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_next_sprite(akgl_Sprite **dest); akerr_ErrorContext AKERR_NOIGNORE *akgl_heap_next_spritesheet(akgl_SpriteSheet **dest); diff --git a/include/akgl/renderer.h b/include/akgl/renderer.h index 4b7db3c..30cea3b 100644 --- a/include/akgl/renderer.h +++ b/include/akgl/renderer.h @@ -5,6 +5,8 @@ #include +#include + typedef struct akgl_RenderBackend { SDL_Renderer *sdl_renderer; akerr_ErrorContext AKERR_NOIGNORE *(*shutdown)(struct akgl_RenderBackend *self); @@ -12,6 +14,7 @@ typedef struct akgl_RenderBackend { akerr_ErrorContext AKERR_NOIGNORE *(*frame_end)(struct akgl_RenderBackend *self); akerr_ErrorContext AKERR_NOIGNORE *(*draw_texture)(struct akgl_RenderBackend *self, SDL_Texture *texture, SDL_FRect *src, SDL_FRect *dest, double angle, SDL_FPoint *center, SDL_FlipMode flip); akerr_ErrorContext AKERR_NOIGNORE *(*draw_mesh)(struct akgl_RenderBackend *self); + akerr_ErrorContext AKERR_NOIGNORE *(*draw_world)(struct akgl_RenderBackend *self, akgl_Iterator *opflags); } akgl_RenderBackend; akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_shutdown(akgl_RenderBackend *self); @@ -19,6 +22,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_start(akgl_RenderBackend akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_end(akgl_RenderBackend *self); akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_texture(akgl_RenderBackend *self, SDL_Texture *texture, SDL_FRect *src, SDL_FRect *dest, double angle, SDL_FPoint *center, SDL_FlipMode flip); akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_mesh(akgl_RenderBackend *self); +akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend *self, akgl_Iterator *opflags); akerr_ErrorContext AKERR_NOIGNORE *akgl_render_init2d(akgl_RenderBackend *self); diff --git a/src/actor.c b/src/actor.c index 36db925..5177e34 100644 --- a/src/actor.c +++ b/src/actor.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -112,27 +113,13 @@ akerr_ErrorContext *akgl_actor_logic_changeframe(akgl_Actor *obj, akgl_Sprite *c SUCCEED_RETURN(errctx); } +// raises AKGL_ERR_LOGICINTERRUPT if we don't want the physics object to process us akerr_ErrorContext *akgl_actor_logic_movement(akgl_Actor *obj, SDL_Time curtime) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "Null actor reference"); - if ( obj->parent != NULL ) { - // Children don't move independently of their parents, they just have an offset - SUCCEED_RETURN(errctx); - } else { - if ( AKGL_BITMASK_HAS(obj->state, AKGL_ACTOR_STATE_MOVING_LEFT) ) { - obj->x -= obj->basechar->vx; - } - if ( AKGL_BITMASK_HAS(obj->state, AKGL_ACTOR_STATE_MOVING_RIGHT) ) { - obj->x += obj->basechar->vx; - } - if ( AKGL_BITMASK_HAS(obj->state, AKGL_ACTOR_STATE_MOVING_UP) ) { - obj->y -= obj->basechar->vy; - } - if ( AKGL_BITMASK_HAS(obj->state, AKGL_ACTOR_STATE_MOVING_DOWN) ) { - obj->y += obj->basechar->vy; - } - } + // Effectively a NOOP, this is handled by the physics engine now + // These functions are still present in case the library user wants per-actor behavior SUCCEED_RETURN(errctx); } @@ -145,21 +132,11 @@ akerr_ErrorContext *akgl_actor_update(akgl_Actor *obj) FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor reference"); FAIL_ZERO_RETURN(errctx, obj->basechar, AKERR_NULLPOINTER, "Actor has NULL base character reference"); - ATTEMPT { - SDL_GetCurrentTime(&curtime); - CATCH(errctx, obj->facefunc(obj)); - // is it time to apply movement logic? - if ( (curtime - obj->logictimer) >= obj->basechar->movementspeed ) { - CATCH(errctx, obj->movementlogicfunc(obj, curtime)); - obj->logictimer = curtime; - } - } CLEANUP { - } PROCESS(errctx) { - } FINISH(errctx, false); + SDL_GetCurrentTime(&curtime); + PASS(errctx, obj->facefunc(obj)); ATTEMPT { CATCH(errctx, akgl_character_sprite_get(obj->basechar, obj->state, &curSprite)); - // is it time to change frames? if ( ((curtime) - obj->curSpriteFrameTimer) >= curSprite->speed) { CATCH(errctx, obj->changeframefunc(obj, curSprite, curtime)); obj->curSpriteFrameTimer = curtime; @@ -167,6 +144,8 @@ akerr_ErrorContext *akgl_actor_update(akgl_Actor *obj) } CLEANUP { } PROCESS(errctx) { } HANDLE(errctx, AKERR_KEY) { + // TODO : Why are we passing this error? It could only come from akgl_character_sprite_get + // or changeframefunc, both of which should never return AKERR_KEY... SUCCEED_RETURN(errctx); } FINISH(errctx, true); @@ -227,7 +206,7 @@ akerr_ErrorContext *akgl_actor_render(akgl_Actor *obj) if ( ! visible ) { SUCCEED_RETURN(errctx); } - + if ( (obj->curSpriteFrameId > curSprite->frames) ) { // This isn't necessarily an error - this actor's frame index is outside the range of // their current sprite. There are a number of reasons this could happen, and it will @@ -279,39 +258,6 @@ akerr_ErrorContext *akgl_actor_add_child(akgl_Actor *obj, akgl_Actor *child) FAIL_RETURN(errctx, AKERR_OUTOFBOUNDS, "Parent object has no remaining child slots left"); } -// 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 -void akgl_registry_iterate_actor(void *userdata, SDL_PropertiesID registry, const char *name) -{ - PREPARE_ERROR(errctx); - akgl_Iterator *opflags = (akgl_Iterator *)userdata; - - ATTEMPT { - FAIL_ZERO_BREAK(errctx, name, AKERR_NULLPOINTER, "registry_iterate_actor received NULL property name"); - FAIL_ZERO_BREAK(errctx, opflags, AKERR_NULLPOINTER, "received NULL iterator flags"); - akgl_Actor *obj = (akgl_Actor *)SDL_GetPointerProperty(registry, name, NULL); - FAIL_ZERO_BREAK(errctx, obj, AKERR_KEY, "registry_iterate_actor received property name that was not in the registry"); - if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_LAYERMASK) ) { - if ( obj->layer != opflags->layerid ) { - break; - } - } - if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_UPDATE) ) { - CATCH(errctx, obj->updatefunc(obj)); - } - if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_TILEMAPSCALE) ) { - CATCH(errctx, akgl_tilemap_scale_actor(&gamemap, obj)); - } else { - obj->scale = 1.0; - } - if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_RENDER) ) { - CATCH(errctx, obj->renderfunc(obj)); - } - } CLEANUP { - } PROCESS(errctx) { - } FINISH_NORETURN(errctx); -} - akerr_ErrorContext AKERR_NOIGNORE *akgl_Actor_cmhf_left_on(akgl_Actor *obj, SDL_Event *event) { PREPARE_ERROR(errctx); diff --git a/src/controller.c b/src/controller.c index 3d3cb88..dbd1ef3 100644 --- a/src/controller.c +++ b/src/controller.c @@ -59,7 +59,7 @@ akerr_ErrorContext *akgl_controller_handle_event(void *appstate, SDL_Event *even PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate"); FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "NULL event"); - + ATTEMPT { for ( i = 0 ; i < AKGL_MAX_CONTROL_MAPS; i++ ) { curmap = &GAME_ControlMaps[i]; @@ -83,6 +83,13 @@ akerr_ErrorContext *akgl_controller_handle_event(void *appstate, SDL_Event *even event->key.which == curmap->kbid && event->key.key == curcontrol->key) ); + if ( event->type == 768 && event->key.which == 11 && event->key.key == 13 ) { + SDL_Log("Event type=%d, keyboard=%d, key=%d", event->type, event->key.which, event->key.key); + SDL_Log("ControlMap[%d].Controls[%d] keyboard=%d, key=%d", i, j, curmap->kbid, curcontrol->key); + SDL_Log("event %d -> control on %d off %d", event->type, curcontrol->event_on, curcontrol->event_off); + SDL_Log("eventButtonComboMatch for controlmap %d id %d = %d", + i, j, eventButtonComboMatch); + } if ( event->type == curcontrol->event_on && eventButtonComboMatch) { CATCH(errctx, curcontrol->handler_on(curmap->target, event)); goto _akgl_controller_handle_event_success; diff --git a/src/game.c b/src/game.c index 6bf248a..b2b350f 100644 --- a/src/game.c +++ b/src/game.c @@ -15,10 +15,12 @@ #include #include #include +#include #include SDL_Window *window = NULL; akgl_RenderBackend renderer; +akgl_PhysicsBackend physics; akgl_Frame ball; akgl_Frame paddle1; akgl_Frame paddle2; @@ -42,28 +44,32 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init() int screenheight = 0; int i = 0; - PREPARE_ERROR(errctx); + PREPARE_ERROR(e); ATTEMPT { strncpy((char *)&game.libversion, AKGL_VERSION, 32); game.gameStartTime = SDL_GetTicksNS(); game.lastIterTime = game.gameStartTime; game.lastFPSTime = game.gameStartTime; game.lowfpsfunc = &akgl_game_lowfps; - FAIL_ZERO_BREAK(errctx, strlen((char *)&game.name), AKERR_NULLPOINTER, "Must provide game name"); - FAIL_ZERO_BREAK(errctx, strlen((char *)&game.version), AKERR_NULLPOINTER, "Must provide game version"); - FAIL_ZERO_BREAK(errctx, strlen((char *)&game.uri), AKERR_NULLPOINTER, "Must provide game uri"); - CATCH(errctx, akgl_heap_init()); - CATCH(errctx, akgl_registry_init_actor()); - CATCH(errctx, akgl_registry_init_sprite()); - CATCH(errctx, akgl_registry_init_spritesheet()); - CATCH(errctx, akgl_registry_init_character()); - CATCH(errctx, akgl_registry_init_font()); - CATCH(errctx, akgl_registry_init_music()); - CATCH(errctx, akgl_registry_init_properties()); - CATCH(errctx, akgl_registry_init_actor_state_strings()); + game.statelock = SDL_CreateMutex(); + FAIL_ZERO_RETURN(e, game.statelock, AKGL_ERR_SDL, "%s", SDL_GetError()); + CATCH(e, akgl_game_state_lock()); + FAIL_ZERO_BREAK(e, strlen((char *)&game.name), AKERR_NULLPOINTER, "Must provide game name"); + FAIL_ZERO_BREAK(e, strlen((char *)&game.version), AKERR_NULLPOINTER, "Must provide game version"); + FAIL_ZERO_BREAK(e, strlen((char *)&game.uri), AKERR_NULLPOINTER, "Must provide game uri"); + CATCH(e, akgl_heap_init()); + CATCH(e, akgl_registry_init_actor()); + CATCH(e, akgl_registry_init_sprite()); + CATCH(e, akgl_registry_init_spritesheet()); + CATCH(e, akgl_registry_init_character()); + CATCH(e, akgl_registry_init_font()); + CATCH(e, akgl_registry_init_music()); + CATCH(e, akgl_registry_init_properties()); + CATCH(e, akgl_registry_init_actor_state_strings()); } CLEANUP { - } PROCESS(errctx) { - } FINISH(errctx, true) + //IGNORE(akgl_game_state_unlock()); + } PROCESS(e) { + } FINISH(e, true) SDL_SetAppMetadata(game.name, game.version, game.uri); @@ -72,7 +78,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init() } FAIL_ZERO_RETURN( - errctx, + e, SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD | SDL_INIT_AUDIO), AKGL_ERR_SDL, "Couldn't initialize SDL: %s", @@ -82,33 +88,55 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init() for ( i = 0; i < AKGL_SDL_GAMECONTROLLER_DB_LEN ; i++ ) { if ( SDL_AddGamepadMapping(SDL_GAMECONTROLLER_DB[i]) == -1 ) { - FAIL_ZERO_RETURN(errctx, 0, AKGL_ERR_SDL, "%s", SDL_GetError()); + FAIL_ZERO_RETURN(e, 0, AKGL_ERR_SDL, "%s", SDL_GetError()); } } - PASS(errctx, akgl_controller_open_gamepads()); + PASS(e, akgl_controller_open_gamepads()); FAIL_ZERO_RETURN( - errctx, + e, MIX_Init(), AKGL_ERR_SDL, "Couldn't initialize audio: %s", SDL_GetError()); akgl_mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, 0); FAIL_ZERO_RETURN( - errctx, + e, akgl_mixer, AKGL_ERR_SDL, "Unable to create mixer device: %s", SDL_GetError()); FAIL_ZERO_RETURN( - errctx, + e, TTF_Init(), AKGL_ERR_SDL, "Couldn't initialize front engine: %s", SDL_GetError()); + PASS(e, akgl_game_state_unlock()); + SUCCEED_RETURN(e); +} - SUCCEED_RETURN(errctx); +akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_lock(void) +{ + PREPARE_ERROR(e); + SDL_Time totaltime = 0; + + while ( totaltime < AKGL_TIME_ONESEC_MS ) { + if ( SDL_TryLockMutex(game.statelock) == true ) { + SUCCEED_RETURN(e); + } + totaltime += 100; + SDL_Delay(100); + } + FAIL_RETURN(e, AKGL_ERR_SDL, "%s", SDL_GetError()); +} + +akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void) +{ + PREPARE_ERROR(e); + SDL_UnlockMutex(game.statelock); + SUCCEED_RETURN(e); } void akgl_game_updateFPS() @@ -135,119 +163,119 @@ void akgl_game_save_actorname_iterator(void *userdata, SDL_PropertiesID props, c { FILE *fp = (FILE *)userdata; akgl_Actor *actor = NULL; - PREPARE_ERROR(errctx); + PREPARE_ERROR(e); ATTEMPT { - FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); - CATCH(errctx, aksl_fwrite((char *)name, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp)); + FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer"); + CATCH(e, aksl_fwrite((char *)name, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp)); actor = SDL_GetPointerProperty(props, name, NULL); - CATCH(errctx, aksl_fwrite(&actor, 1, sizeof(akgl_Actor *), fp)); + CATCH(e, aksl_fwrite(&actor, 1, sizeof(akgl_Actor *), fp)); } CLEANUP { - } PROCESS(errctx) { - } FINISH_NORETURN(errctx); + } PROCESS(e) { + } FINISH_NORETURN(e); } 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); + PREPARE_ERROR(e); ATTEMPT { - FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); + FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer"); sprite = SDL_GetPointerProperty(props, name, NULL); - CATCH(errctx, aksl_fwrite((char *)name, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp)); - CATCH(errctx, aksl_fwrite(&sprite, 1, sizeof(akgl_Sprite *), fp)); + CATCH(e, aksl_fwrite((char *)name, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp)); + CATCH(e, aksl_fwrite(&sprite, 1, sizeof(akgl_Sprite *), fp)); } CLEANUP { - } PROCESS(errctx) { - } FINISH_NORETURN(errctx); + } PROCESS(e) { + } FINISH_NORETURN(e); } 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); + PREPARE_ERROR(e); ATTEMPT { - FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); + FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer"); spritesheet = SDL_GetPointerProperty(props, name, NULL); - CATCH(errctx, aksl_fwrite((char *)name, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp)); - CATCH(errctx, aksl_fwrite(&spritesheet, 1, sizeof(akgl_SpriteSheet *), fp)); + CATCH(e, aksl_fwrite((char *)name, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp)); + CATCH(e, aksl_fwrite(&spritesheet, 1, sizeof(akgl_SpriteSheet *), fp)); } CLEANUP { - } PROCESS(errctx) { - } FINISH_NORETURN(errctx); + } PROCESS(e) { + } FINISH_NORETURN(e); } void akgl_game_save_charactername_iterator(void *userdata, SDL_PropertiesID props, const char *name) { FILE *fp = (FILE *)userdata; - PREPARE_ERROR(errctx); + PREPARE_ERROR(e); akgl_Character *character = NULL; ATTEMPT { - FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); + FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer"); character = SDL_GetPointerProperty(props, name, NULL); - CATCH(errctx, aksl_fwrite((char *)name, 1, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp)); - CATCH(errctx, aksl_fwrite(&character, 1, sizeof(akgl_Character *), fp)); + CATCH(e, aksl_fwrite((char *)name, 1, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp)); + CATCH(e, aksl_fwrite(&character, 1, sizeof(akgl_Character *), fp)); } CLEANUP { - } PROCESS(errctx) { - } FINISH_NORETURN(errctx); + } PROCESS(e) { + } FINISH_NORETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp) { - PREPARE_ERROR(errctx); + PREPARE_ERROR(e); char nullval = 0x00; ATTEMPT { - FAIL_ZERO_BREAK(errctx, fp, AKERR_NULLPOINTER, "NULL file pointer"); + FAIL_ZERO_BREAK(e, fp, AKERR_NULLPOINTER, "NULL file pointer"); // write the actor name pointer table SDL_EnumerateProperties( AKGL_REGISTRY_ACTOR, &akgl_game_save_actorname_iterator, (void *)fp); - CATCH(errctx, aksl_fwrite((void *)&nullval, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp)); - CATCH(errctx, aksl_fwrite((void *)&nullval, 1, sizeof(akgl_Actor *), fp)); + CATCH(e, aksl_fwrite((void *)&nullval, 1, AKGL_ACTOR_MAX_NAME_LENGTH, fp)); + CATCH(e, aksl_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); - CATCH(errctx, aksl_fwrite((void *)&nullval, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp)); - CATCH(errctx, aksl_fwrite((void *)&nullval, 1, sizeof(akgl_Sprite *), fp)); + CATCH(e, aksl_fwrite((void *)&nullval, 1, AKGL_SPRITE_MAX_NAME_LENGTH, fp)); + CATCH(e, aksl_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); - CATCH(errctx, aksl_fwrite((void *)&nullval, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp)); - CATCH(errctx, aksl_fwrite((void *)&nullval, 1, sizeof(akgl_SpriteSheet *), fp)); + CATCH(e, aksl_fwrite((void *)&nullval, 1, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH, fp)); + CATCH(e, aksl_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); - CATCH(errctx, aksl_fwrite((void *)&nullval, 1, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp)); - CATCH(errctx, aksl_fwrite((void *)&nullval, 1, sizeof(akgl_Character *), fp)); + CATCH(e, aksl_fwrite((void *)&nullval, 1, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH, fp)); + CATCH(e, aksl_fwrite((void *)&nullval, 1, sizeof(akgl_Character *), fp)); } CLEANUP { - } PROCESS(errctx) { - } FINISH(errctx, true); - SUCCEED_RETURN(errctx); + } PROCESS(e) { + } FINISH(e, true); + SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath) { FILE *fp = NULL; - PREPARE_ERROR(errctx); + PREPARE_ERROR(e); ATTEMPT { - FAIL_ZERO_BREAK(errctx, fpath, AKERR_NULLPOINTER, "NULL file path"); - CATCH(errctx, aksl_fopen(fpath, "wb", &fp)); - CATCH(errctx, aksl_fwrite(&game, 1, sizeof(akgl_Game), fp)); - CATCH(errctx, akgl_game_save_actors(fp)); - } PROCESS(errctx) { + FAIL_ZERO_BREAK(e, fpath, AKERR_NULLPOINTER, "NULL file path"); + CATCH(e, aksl_fopen(fpath, "wb", &fp)); + CATCH(e, aksl_fwrite(&game, 1, sizeof(akgl_Game), fp)); + CATCH(e, akgl_game_save_actors(fp)); + } PROCESS(e) { } CLEANUP { if ( fp != NULL ) fclose(fp); - } FINISH(errctx, true); - SUCCEED_RETURN(errctx); // SUCCEED_NORETURN if in main(). + } FINISH(e, true); + SUCCEED_RETURN(e); // SUCCEED_NORETURN if in main(). } akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_objectnamemap(FILE *fp, SDL_PropertiesID map, int namelength, int ptrlength, SDL_PropertiesID registry) @@ -257,10 +285,10 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_objectnamemap(FILE *fp, SDL_Pr char objname[namelength]; int retval = 0; - PREPARE_ERROR(errctx); + PREPARE_ERROR(e); while ( 1 ) { - CATCH(errctx, aksl_fread((void *)&objname, 1, namelength, fp)); - CATCH(errctx, aksl_fread((void *)&ptr, 1, ptrlength, fp)); + CATCH(e, aksl_fread((void *)&objname, 1, namelength, fp)); + CATCH(e, aksl_fread((void *)&ptr, 1, ptrlength, fp)); // End of the map if ( ptr == 0x00 && objname[0] == 0x00 ) { break; @@ -273,43 +301,43 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_objectnamemap(FILE *fp, SDL_Pr // SDL_Properties objects can only use string keys, so we can't use the // old pointer as a key without first converting it to a string. - CATCH(errctx, aksl_memset((void *)&ptrstring, 0x00, 32)); + CATCH(e, aksl_memset((void *)&ptrstring, 0x00, 32)); snprintf((char *)&ptrstring, 32, "%p", ptr); SDL_SetPointerProperty( map, ptrstring, SDL_GetPointerProperty(registry, objname, NULL)); }; - SUCCEED_RETURN(errctx); + SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion) { semver_t current_version = {}; semver_t compare_version = {}; - PREPARE_ERROR(errctx); - 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"); + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, versiontype, AKERR_NULLPOINTER, "NULL argument"); + FAIL_ZERO_RETURN(e, curversion, AKERR_NULLPOINTER, "NULL argument"); + FAIL_ZERO_RETURN(e, newversion, AKERR_NULLPOINTER, "NULL argument"); ATTEMPT { // Check save game library version FAIL_NONZERO_BREAK( - errctx, + e, semver_parse((const char *)curversion, ¤t_version), AKERR_VALUE, "Invalid semantic %s version in current game: %s", versiontype, (char *)curversion); FAIL_NONZERO_BREAK( - errctx, + e, semver_parse((const char *)newversion, &compare_version), AKERR_VALUE, "Invalid semantic %s version in save game: %s", versiontype, (char *)&newversion); FAIL_ZERO_BREAK( - errctx, + e, semver_satisfies(compare_version, current_version, "="), AKERR_API, "Incompatible save game %s version (%s != %s)", @@ -319,9 +347,9 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, } CLEANUP { semver_free(¤t_version); semver_free(&compare_version); - } PROCESS(errctx) { - } FINISH(errctx, true); - SUCCEED_RETURN(errctx); + } PROCESS(e) { + } FINISH(e, true); + SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath) @@ -333,21 +361,21 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath) SDL_PropertiesID charactermap; FILE *fp = NULL; - PREPARE_ERROR(errctx); - FAIL_ZERO_RETURN(errctx, fpath, AKERR_NULLPOINTER, "NULL file path"); + PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, fpath, AKERR_NULLPOINTER, "NULL file path"); ATTEMPT { - CATCH(errctx, aksl_fopen(fpath, "rb", &fp)); - CATCH(errctx, aksl_fread((void *)&savegame, 1, sizeof(akgl_Game), fp)); - 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)); + CATCH(e, aksl_fopen(fpath, "rb", &fp)); + CATCH(e, aksl_fread((void *)&savegame, 1, sizeof(akgl_Game), fp)); + CATCH(e, akgl_game_load_versioncmp("library", (char *)&savegame.libversion, (char *)AKGL_VERSION)); + CATCH(e, akgl_game_load_versioncmp("game", (char *)&savegame.version, (char *)&game.version)); FAIL_NONZERO_RETURN( - errctx, + e, strncmp((char *)&savegame.name, (char *)&game.name, 256), AKERR_API, "Savegame is not compatible with this game"); FAIL_NONZERO_RETURN( - errctx, + e, strncmp((char *)&savegame.uri, (char *)&game.uri, 256), AKERR_API, "Savegame is not compatible with this game"); @@ -355,23 +383,64 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath) 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)); + CATCH(e, 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)); + CATCH(e, 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)); + CATCH(e, 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)); + CATCH(e, akgl_game_load_objectnamemap(fp, charactermap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Character *), AKGL_REGISTRY_CHARACTER)); // Now that we have all of our pointer maps built, we can load the actual binary objects and reset their pointers } CLEANUP { if ( fp != NULL ) { fclose(fp); } - } PROCESS(errctx) { - } FINISH(errctx, true); + } PROCESS(e) { + } FINISH(e, true); - SUCCEED_RETURN(errctx); + SUCCEED_RETURN(e); +} + +akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags) +{ + PREPARE_ERROR(e); + akgl_Iterator defflags = { + .flags = (AKGL_ITERATOR_OP_LAYERMASK | AKGL_ITERATOR_OP_LAYERMASK), + .layerid = 0 + }; + SDL_Time curTime = SDL_GetTicksNS(); + akgl_Actor *actor = NULL; + + if ( opflags == NULL ) { + opflags = &defflags; + } + + PASS(e, akgl_game_state_lock()); + + akgl_game_updateFPS(); + + for ( int i = 0; i < AKGL_TILEMAP_MAX_LAYERS; i++ ) { + if ( opflags == &defflags ) { + opflags->layerid = i; + } + for ( int j = 0; j < AKGL_MAX_HEAP_ACTOR; j++ ) { + actor = &HEAP_ACTOR[j]; + if ( actor->refcount == 0 ) { + continue; + } + if ( AKGL_BITMASK_HAS(opflags->flags, AKGL_ITERATOR_OP_TILEMAPSCALE) ) { + PASS(e, akgl_tilemap_scale_actor(&gamemap, actor)); + } else { + actor->scale = 1.0; + } + PASS(e, actor->updatefunc(actor)); + } + } + PASS(e, physics.simulate(&physics, NULL)); + PASS(e, renderer.draw_world(&renderer, NULL)); + PASS(e, akgl_game_state_unlock()); + SUCCEED_RETURN(e); } diff --git a/src/heap.c b/src/heap.c index 73692a8..e172fa9 100644 --- a/src/heap.c +++ b/src/heap.c @@ -20,9 +20,7 @@ akerr_ErrorContext *akgl_heap_init() PREPARE_ERROR(errctx); int i = 0; akerr_name_for_status(AKGL_ERR_SDL, "SDL Error"); - for ( i = 0; i < AKGL_MAX_HEAP_ACTOR; i++) { - memset(&HEAP_ACTOR[i], 0x00, sizeof(akgl_Actor)); - } + PASS(errctx, akgl_heap_init_actor()); for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++) { memset(&HEAP_SPRITE[i], 0x00, sizeof(akgl_Sprite)); } @@ -38,6 +36,15 @@ akerr_ErrorContext *akgl_heap_init() 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); diff --git a/src/registry.c b/src/registry.c index da86bce..ae7c8cc 100644 --- a/src/registry.c +++ b/src/registry.c @@ -39,6 +39,9 @@ akerr_ErrorContext *akgl_registry_init() akerr_ErrorContext *akgl_registry_init_actor() { PREPARE_ERROR(errctx); + if ( AKGL_REGISTRY_ACTOR != NULL ) { + SDL_DestroyProperties(AKGL_REGISTRY_ACTOR); + } AKGL_REGISTRY_ACTOR = SDL_CreateProperties(); FAIL_ZERO_RETURN(errctx, AKGL_REGISTRY_ACTOR, AKERR_NULLPOINTER, "Error initializing actor registry"); SUCCEED_RETURN(errctx); diff --git a/src/renderer.c b/src/renderer.c index 3b1f8b2..4822b69 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -43,6 +43,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_init2d(akgl_RenderBackend *self) self->frame_end = &akgl_render_2d_frame_end; self->draw_texture = &akgl_render_2d_draw_texture; self->draw_mesh = &akgl_render_2d_draw_mesh; + self->draw_world = &akgl_render_2d_draw_world; SUCCEED_RETURN(e); } @@ -56,14 +57,17 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_shutdown(akgl_RenderBackend *s akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_start(akgl_RenderBackend *self) { PREPARE_ERROR(e); + FAIL_ZERO_RETURN(e, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend"); + SDL_SetRenderDrawColor(self->sdl_renderer, 0, 0, 0, 255); + SDL_RenderClear(self->sdl_renderer); SUCCEED_RETURN(e); } akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_end(akgl_RenderBackend *self) { PREPARE_ERROR(e); - FAIL_ZERO_RETURN(e, renderer.sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend"); - SDL_RenderPresent(renderer.sdl_renderer); + FAIL_ZERO_RETURN(e, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend"); + SDL_RenderPresent(self->sdl_renderer); SUCCEED_RETURN(e); } @@ -98,3 +102,35 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_mesh(akgl_RenderBackend * FAIL_RETURN(e, AKERR_API, "Not implemented"); } +akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend *self, akgl_Iterator *opflags) +{ + PREPARE_ERROR(e); + akgl_Iterator defflags; + SDL_Time curTime = SDL_GetTicksNS(); + akgl_Actor *actor = NULL; + int j = 0; + + FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self"); + + if ( opflags == NULL ) { + opflags = &defflags; + PASS(e, aksl_memset((void *)opflags, 0x00, sizeof(akgl_Iterator))); + } + for ( int i = 0; i < AKGL_TILEMAP_MAX_LAYERS ; i++ ) { + if ( i < gamemap.numlayers ) { + PASS(e, akgl_tilemap_draw((akgl_Tilemap *)&gamemap, &camera, i)); + } + for ( int j = 0; j < AKGL_MAX_HEAP_ACTOR ; j++ ) { + actor = &HEAP_ACTOR[j]; + if ( actor->refcount == 0 ) { + continue; + } + if ( actor->layer != i ) { + continue; + } + PASS(e, actor->renderfunc(actor)); + } + } + + SUCCEED_RETURN(e); +} diff --git a/src/tilemap.c b/src/tilemap.c index 4db41d3..22c4a55 100644 --- a/src/tilemap.c +++ b/src/tilemap.c @@ -24,6 +24,7 @@ akerr_ErrorContext *akgl_get_json_tilemap_property(json_t *obj, char *key, char json_t *properties = NULL; json_t *property = NULL; akgl_String *tmpstr = NULL; + akgl_String *typestr = NULL; int i = 0; // This is not a generic JSON helper. It assumes we are receiving an object with a 'properties' key // inside of it. That key is an array of objects, and each object has a name, type, and value. @@ -38,9 +39,9 @@ akerr_ErrorContext *akgl_get_json_tilemap_property(json_t *obj, char *key, char CATCH(errctx, akgl_heap_release_string(tmpstr)); continue; } - CATCH(errctx, akgl_get_json_string_value(property, "type", &tmpstr)); - if ( strcmp(tmpstr->data, type) != 0 ) { - FAIL_BREAK(errctx, AKERR_TYPE, "Object property is present but is incorrect type"); + CATCH(errctx, akgl_get_json_string_value(property, "type", &typestr)); + if ( strcmp(typestr->data, type) != 0 ) { + FAIL_BREAK(errctx, AKERR_TYPE, "Property %s is present but is incorrect type(expected %s got %s)", key, type, (char *)typestr->data); } *dest = property; SUCCEED_RETURN(errctx); @@ -49,13 +50,15 @@ akerr_ErrorContext *akgl_get_json_tilemap_property(json_t *obj, char *key, char if ( tmpstr != NULL ) { IGNORE(akgl_heap_release_string(tmpstr)); } + if ( typestr != NULL ) { + IGNORE(akgl_heap_release_string(typestr)); + } } PROCESS(errctx) { } FINISH(errctx, true); FAIL_RETURN(errctx, AKERR_KEY, "Property not found in properties map"); } - akerr_ErrorContext *akgl_get_json_properties_string(json_t *obj, char *key, akgl_String **dest) { PREPARE_ERROR(errctx); @@ -101,6 +104,20 @@ akerr_ErrorContext *akgl_get_json_properties_number(json_t *obj, char *key, floa SUCCEED_RETURN(errctx); } +akerr_ErrorContext *akgl_get_json_properties_float(json_t *obj, char *key, float *dest) +{ + PREPARE_ERROR(errctx); + json_t *property = NULL; + ATTEMPT { + CATCH(errctx, akgl_get_json_tilemap_property(obj, key, "float", &property)); + CATCH(errctx, akgl_get_json_number_value(property, "value", dest)); + } CLEANUP { + } PROCESS(errctx) { + } FINISH(errctx, true); + + SUCCEED_RETURN(errctx); +} + akerr_ErrorContext *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilemap *dest, int tsidx, akgl_String *dirname) { PREPARE_ERROR(e); @@ -302,11 +319,11 @@ akerr_ErrorContext *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t * if ( strcmp((char *)curobj->name, "p_foreground") == 0 ) { dest->p_foreground_y = curobj->y; CATCH(errctx, akgl_get_json_integer_value((json_t *)layerdatavalue, "height", &dest->p_foreground_h)); - CATCH(errctx, akgl_get_json_properties_number((json_t *)layerdatavalue, "scale", &dest->p_foreground_scale)); + CATCH(errctx, akgl_get_json_properties_float((json_t *)layerdatavalue, "scale", &dest->p_foreground_scale)); } else if ( strcmp((char *)curobj->name, "p_vanishing") == 0 ) { dest->p_vanishing_y = curobj->y; CATCH(errctx, akgl_get_json_integer_value((json_t *)layerdatavalue, "height", &dest->p_vanishing_h)); - CATCH(errctx, akgl_get_json_properties_number((json_t *)layerdatavalue, "scale", &dest->p_vanishing_scale)); + CATCH(errctx, akgl_get_json_properties_float((json_t *)layerdatavalue, "scale", &dest->p_vanishing_scale)); } }