diff --git a/extern/SDL2/CMakeLists.txt b/extern/SDL2/CMakeLists.txt index accddf4..116bada 100644 --- a/extern/SDL2/CMakeLists.txt +++ b/extern/SDL2/CMakeLists.txt @@ -3,8 +3,10 @@ cmake_minimum_required(VERSION 3.10) set(SDL_TEST_ENABLED_BY_DEFAULT OFF CACHE BOOL "" FORCE) set(SDL2_DISABLE_INSTALL OFF CACHE BOOL "" FORCE) set(SDL2_DISABLE_UNINSTALL OFF CACHE BOOL "" FORCE) +set(SDL2IMAGE_VENDORED ON CACHE BOOL "" FORCE) set(SDL2MIXER_VENDORED ON CACHE BOOL "" FORCE) set(SDL2TTF_VENDORED ON CACHE BOOL "" FORCE) +set(SDL2IMAGE_TIF ON CACHE BOOL "" FORCE) add_subdirectory(SDL_image) add_subdirectory(SDL_ttf) diff --git a/include/AssetManager.h b/include/AssetManager.h index 26412e1..c06bebb 100644 --- a/include/AssetManager.h +++ b/include/AssetManager.h @@ -5,7 +5,11 @@ #include "SDL_mixer.h" #include "SDL_render.h" #include "reusable_ids.h" -#include +#include + +#ifdef __cplusplus +extern "C" { +#endif // !__cplusplus typedef enum { ASSET_TYPE_IMAGE, @@ -19,10 +23,14 @@ typedef struct asset_manager AssetManager; AssetManager *create_asset_manager(); AssetID load_asset_to_manager(AssetManager *am, const char *path, AssetType type, Renderer *renderer); -void remove_asset_from_manager(AssetManager *am, AssetID id); +void remove_asset_from_manager(AssetManager *am, AssetID *id_ptr); SDL_Texture *get_texture(AssetManager *am, AssetID id); Mix_Chunk *get_audio_chunk(AssetManager *am, AssetID id); Mix_Music *get_music(AssetManager *am, AssetID id); void destroy_asset_manager(AssetManager *am); +#ifdef __cplusplus +} +#endif // !__cplusplus + #endif // !ASSET_MANAGER_H diff --git a/include/GameEngine.h b/include/GameEngine.h index d7b5043..5c3b697 100644 --- a/include/GameEngine.h +++ b/include/GameEngine.h @@ -7,18 +7,26 @@ #include "reusable_ids.h" #include +#ifdef __cplusplus +extern "C" { +#endif // !__cplusplus + bool init_game(const char *game_name, int window_width, int window_height, SDL_Color window_background_color, bool full_screen); bool game_running(); ObjectID add_object(void *object, EventHandlerPtr eh, UpdateFuncPtr uf, RenderFuncPtr rf); -void remove_object(ObjectID id); +void remove_object(ObjectID *id); AssetID load_asset(const char *path, AssetType type); void render_texture_asset(AssetID id, float x, float y, float w, float h); -void close_asset(AssetID id); +void close_asset(AssetID *id); void handle_events(); void update(); void render(); void close_game(); +#ifdef __cplusplus +} +#endif // !__cplusplus + #endif // !GAME_H diff --git a/include/ObjectStore.h b/include/ObjectStore.h index b7d2c2a..c9cec83 100644 --- a/include/ObjectStore.h +++ b/include/ObjectStore.h @@ -6,6 +6,10 @@ #include "engine_build_config.h" #include "reusable_ids.h" +#ifdef __cplusplus +extern "C" { +#endif // !__cplusplus + typedef void (*EventHandlerPtr)(const SDL_Event *event, void *object); typedef void (*UpdateFuncPtr)(float delta, void *object); typedef void (*RenderFuncPtr)(void *object); @@ -15,7 +19,7 @@ typedef struct object_store ObjectStore; ObjectStore *create_object_store(); ObjectID add_object_to_store(ObjectStore *os, void *object, EventHandlerPtr eh, UpdateFuncPtr uf, RenderFuncPtr rf); -void remove_object_from_store(ObjectStore *os, ObjectID id); +void remove_object_from_store(ObjectStore *os, ObjectID *id_ptr); void handle_all_objects_events(ObjectStore *os, const SDL_Event *event); void update_all_objects(ObjectStore *os, float delta); void render_all_objects(ObjectStore *os); @@ -25,4 +29,8 @@ void destroy_object_store(ObjectStore *os); void print_objects(ObjectStore *os); #endif // !DEBUG_ENGINE +#ifdef __cplusplus +} +#endif // !__cplusplus + #endif // !OBJECT_STORE_H diff --git a/include/Renderer.h b/include/Renderer.h index 8fc8d88..8768ac7 100644 --- a/include/Renderer.h +++ b/include/Renderer.h @@ -6,6 +6,10 @@ #include "SDL_video.h" #include +#ifdef __cplusplus +extern "C" { +#endif // !__cplusplus + typedef struct renderer Renderer; Renderer *create_renderer(); @@ -17,4 +21,8 @@ void render_texture(const Renderer *renderer, SDL_Texture *texture, float x, void swap_buffers(const Renderer *renderer); void destroy_renderer(Renderer *renderer); +#ifdef __cplusplus +} +#endif // !__cplusplus + #endif // !RENDERER_H diff --git a/include/Window.h b/include/Window.h index e672de3..fa9e50f 100644 --- a/include/Window.h +++ b/include/Window.h @@ -6,6 +6,10 @@ #include "SDL_pixels.h" #include +#ifdef __cplusplus +extern "C" { +#endif // !__cplusplus + typedef struct window { int width; int height; @@ -20,4 +24,8 @@ void clear_window(const Window *wnd); void swap_render_buffers(const Window *wnd); void destroy_window(Window *wnd); +#ifdef __cplusplus +} +#endif // !__cplusplus + #endif // !WINDOW_H diff --git a/include/constants.h b/include/constants.h index 7d1cd70..40085b7 100644 --- a/include/constants.h +++ b/include/constants.h @@ -1,6 +1,10 @@ #ifndef CONSTANTS_H #define CONSTANTS_H +#ifdef __cplusplus +extern "C" { +#endif // !__cplusplus + #define INTERNAL static #define MAX_OBJ_COUNT 10000 @@ -8,4 +12,8 @@ #define HASH_PRIME_01 1987 #define HASH_PRIME_02 7919 +#ifdef __cplusplus +} +#endif // !__cplusplus + #endif // !CONSTANTS_H diff --git a/include/reusable_ids.h b/include/reusable_ids.h index 4ecc6b6..9116ff2 100644 --- a/include/reusable_ids.h +++ b/include/reusable_ids.h @@ -4,9 +4,19 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif // !__cplusplus + +#define INVALID_ID -1 + typedef int32_t ObjectID; typedef int32_t AssetID; int32_t reuse_id(int32_t *ids, size_t count); +#ifdef __cplusplus +} +#endif // !__cplusplus + #endif // !REUSABLE_IDS_H diff --git a/src/AssetManager.c b/src/AssetManager.c index a2b3d4b..3068ef6 100644 --- a/src/AssetManager.c +++ b/src/AssetManager.c @@ -1,5 +1,6 @@ #include "AssetManager.h" #include "Renderer.h" +#include "SDL_error.h" #include "SDL_image.h" #include "SDL_mixer.h" #include "SDL_render.h" @@ -8,6 +9,7 @@ #include #include #include +#include #include #include @@ -21,13 +23,128 @@ typedef struct asset { } data; } Asset; +typedef struct id_node IDNode; +struct id_node { + AssetID id; + IDNode *next; +}; + +typedef struct id_node_list { + IDNode *start_node; + size_t node_count; +} IDNodeList; + struct asset_manager { size_t count; size_t deleted_count; AssetID reusable_ids[MAX_ASSET_COUNT]; Asset assets[MAX_ASSET_COUNT]; + size_t user_counter[MAX_ASSET_COUNT]; + IDNodeList hashed_path_ids[MAX_ASSET_COUNT]; }; +size_t hash_path(const char *path) { + size_t i = 0; + + for (size_t j = 0; path[j]; ++j) { + i += (size_t)path[j] * HASH_PRIME_01 % MAX_ASSET_COUNT; + } + + i = (i * HASH_PRIME_02) % MAX_ASSET_COUNT; + + return i; +} + +void insert_id_node(IDNodeList *node_list, AssetID id) { + if (node_list->node_count == 0) { + node_list->start_node = malloc(sizeof(IDNode)); + node_list->start_node->id = id; + node_list->start_node->next = NULL; + + node_list->node_count = 1; + + return; + } + + IDNode *last_node = node_list->start_node; + for (size_t i = 1; i < node_list->node_count; ++i) { + last_node = last_node->next; + } + + last_node->next = malloc(sizeof(IDNode)); + last_node->next->id = id; + last_node->next->next = NULL; + + ++(node_list->node_count); +} + +void remove_id_node(IDNodeList *node_list, AssetID id) { + if (node_list->node_count == 0) { + return; + } else if (node_list->node_count == 1) { + free(node_list->start_node); + node_list->start_node = NULL; + + node_list->node_count = 0; + + return; + } + + IDNode *node = NULL; + IDNode *prev = NULL; + IDNode *current = node_list->start_node; + for (size_t i = 0; i < node_list->node_count; ++i) { + if (current->id == id) { + node = current; + break; + } + + prev = current; + current = current->next; + } + + if (node) { + IDNode *next = node->next; + if (!prev) { + // Handle the case when there is no previous node, which means that node + // is actually the start node in the list. + free(node_list->start_node); + + node_list->start_node = next; + } else { + free(prev->next); + + prev->next = next; + } + + --(node_list->node_count); + } +} + +AssetID get_asset_id_from_id_node(AssetManager *am, IDNodeList *node_list, + const char *path) { + if (node_list->node_count == 0) { + return INVALID_ID; + } + + IDNode *id_node = node_list->start_node; + for (size_t i = 0; i < node_list->node_count; ++i) { + if (!id_node) { + break; + } + + Asset asset = am->assets[id_node->id]; + + if (strcmp(asset.path, path) == 0) { + return id_node->id; + } + + id_node = id_node->next; + } + + return INVALID_ID; +} + AssetManager *create_asset_manager() { AssetManager *am = (AssetManager *)malloc(sizeof(AssetManager)); if (!am) { @@ -38,6 +155,8 @@ AssetManager *create_asset_manager() { am->deleted_count = 0; memset(am->reusable_ids, 0, sizeof(am->reusable_ids)); memset(am->assets, 0, sizeof(am->assets)); + memset(am->user_counter, 0, sizeof(am->user_counter)); + memset(am->hashed_path_ids, 0, sizeof(am->hashed_path_ids)); return am; } @@ -45,16 +164,21 @@ AssetManager *create_asset_manager() { AssetID load_asset_to_manager(AssetManager *am, const char *path, AssetType type, Renderer *renderer) { if (!am || type == ASSET_TYPE_COUNT) { - return -1; + return INVALID_ID; + } + + size_t hash_index = hash_path(path); + + AssetID existing_id = + get_asset_id_from_id_node(am, &(am->hashed_path_ids[hash_index]), path); + if (existing_id != INVALID_ID) { + ++(am->user_counter[existing_id]); + return existing_id; } Asset asset = {}; bool loaded = true; - // TODO (Abdelrahman): Implement a way to make sure that an asset with the - // same path is not already loaded. If the user attempts to load an asset - // that already exists, the function should return -1 to indicate an error - switch (type) { case ASSET_TYPE_IMAGE: if (!renderer) { @@ -65,6 +189,7 @@ AssetID load_asset_to_manager(AssetManager *am, const char *path, asset.data.texture = load_texture(renderer, path); if (!(asset.data.texture)) { + printf("Error loading %s: %s\n", path, SDL_GetError()); loaded = false; } @@ -81,7 +206,7 @@ AssetID load_asset_to_manager(AssetManager *am, const char *path, } if (!loaded) { - return -1; + return INVALID_ID; } AssetID id; @@ -94,6 +219,10 @@ AssetID load_asset_to_manager(AssetManager *am, const char *path, id = (am->count)++; } + insert_id_node(&(am->hashed_path_ids[hash_index]), id); + + am->user_counter[id] = 1; + am->assets[id].type = type; am->assets[id].path = (char *)malloc(strlen(path) + 1); @@ -118,11 +247,24 @@ AssetID load_asset_to_manager(AssetManager *am, const char *path, return id; } -void remove_asset_from_manager(AssetManager *am, AssetID id) { - if (!am || am->count == 0) { +void remove_asset_from_manager(AssetManager *am, AssetID *id_ptr) { + AssetID id = *id_ptr; + + if (!am || am->count == 0 || id == INVALID_ID || !(am->assets[id].path)) { return; } + --(am->user_counter[id]); + *id_ptr = INVALID_ID; + + // If there are still other objects using the asset, don't delete it + if (am->user_counter[id] > 0) { + return; + } + + size_t hash_index = hash_path(am->assets[id].path); + remove_id_node(&(am->hashed_path_ids[hash_index]), id); + free(am->assets[id].path); am->assets[id].path = 0; @@ -176,6 +318,11 @@ void destroy_asset_manager(AssetManager *am) { } for (size_t i = 0; i < am->count; ++i) { + am->user_counter[i] = 0; + + size_t hash_index = hash_path(am->assets[i].path); + remove_id_node(&(am->hashed_path_ids[hash_index]), i); + free(am->assets[i].path); am->assets[i].path = NULL; diff --git a/src/GameEngine.c b/src/GameEngine.c index fd6bfd4..e130d47 100644 --- a/src/GameEngine.c +++ b/src/GameEngine.c @@ -8,25 +8,29 @@ #include "SDL_image.h" #include "SDL_pixels.h" #include "SDL_render.h" +#include "SDL_timer.h" #include "Window.h" #include "constants.h" #include "engine_build_config.h" #include "reusable_ids.h" +#include #include +#include #include #ifdef DEBUG_ENGINE #include #endif /* DEBUG_ENGINE */ -INTERNAL struct game { +INTERNAL struct game_engine { bool running; float delta; + uint64_t last_time; Window window; SDL_Event event; ObjectStore *object_store; AssetManager *asset_manager; -} Game; +} GameEngine; bool init_game(const char *game_name, int window_width, int window_height, SDL_Color window_background_color, bool full_screen) { @@ -40,11 +44,15 @@ bool init_game(const char *game_name, int window_width, int window_height, return false; } - if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG) { + uint32_t img_flags = IMG_INIT_PNG | IMG_INIT_TIF; + if ((IMG_Init(img_flags) & img_flags) != img_flags) { +#ifdef DEBUG_ENGINE + printf("Failed to initialise SDL. Error: %s\n", IMG_GetError()); +#endif /* DEBUG_ENGINE */ initialisation_complete = false; } - if (!init_window(&(Game.window), game_name, window_width, window_height, + if (!init_window(&(GameEngine.window), game_name, window_width, window_height, window_background_color, full_screen)) { #ifdef DEBUG_ENGINE printf("Failed to initialise window\n"); @@ -53,15 +61,15 @@ bool init_game(const char *game_name, int window_width, int window_height, initialisation_complete = false; } - Game.object_store = create_object_store(); + GameEngine.object_store = create_object_store(); - if (!(Game.object_store)) { + if (!(GameEngine.object_store)) { initialisation_complete = false; } - Game.asset_manager = create_asset_manager(); + GameEngine.asset_manager = create_asset_manager(); - if (!(Game.asset_manager)) { + if (!(GameEngine.asset_manager)) { initialisation_complete = false; } @@ -71,9 +79,9 @@ bool init_game(const char *game_name, int window_width, int window_height, return false; } - Game.running = true; - // Game.delta = 0.0f; - Game.delta = 1.0f / 60.0f; + GameEngine.running = true; + GameEngine.delta = 0.0f; + GameEngine.last_time = SDL_GetTicks64(); #ifdef DEBUG_ENGINE printf("Game initialised successfully\n"); @@ -82,31 +90,32 @@ bool init_game(const char *game_name, int window_width, int window_height, return true; } -bool game_running() { return Game.running; } +bool game_running() { return GameEngine.running; } ObjectID add_object(void *object, EventHandlerPtr event_handler, UpdateFuncPtr update_func, RenderFuncPtr render_func) { - if (!(Game.running) || !(Game.object_store)) { + if (!(GameEngine.running) || !(GameEngine.object_store)) { return -1; } - return add_object_to_store(Game.object_store, object, event_handler, + return add_object_to_store(GameEngine.object_store, object, event_handler, update_func, render_func); } -void remove_object(ObjectID id) { - if (!(Game.running) || !(Game.object_store)) { +void remove_object(ObjectID *id) { + if (!(GameEngine.running) || !(GameEngine.object_store)) { return; } - remove_object_from_store(Game.object_store, id); + remove_object_from_store(GameEngine.object_store, id); } AssetID load_asset(const char *path, AssetType type) { return type == ASSET_TYPE_IMAGE - ? load_asset_to_manager(Game.asset_manager, path, type, - Game.window.renderer) - : load_asset_to_manager(Game.asset_manager, path, type, NULL); + ? load_asset_to_manager(GameEngine.asset_manager, path, type, + GameEngine.window.renderer) + : load_asset_to_manager(GameEngine.asset_manager, path, type, + NULL); } void render_texture_asset(AssetID id, float x, float y, float w, float h) { @@ -114,69 +123,79 @@ void render_texture_asset(AssetID id, float x, float y, float w, float h) { return; } - SDL_Texture *texture = get_texture(Game.asset_manager, id); + SDL_Texture *texture = get_texture(GameEngine.asset_manager, id); - render_texture(Game.window.renderer, texture, x, y, w, h); + render_texture(GameEngine.window.renderer, texture, x, y, w, h); } -void close_asset(AssetID id) { - remove_asset_from_manager(Game.asset_manager, id); +void close_asset(AssetID *id) { + remove_asset_from_manager(GameEngine.asset_manager, id); } void handle_events() { - if (!(Game.running)) { + if (!(GameEngine.running)) { return; } - while (SDL_PollEvent(&(Game.event))) { - switch (Game.event.type) { + while (SDL_PollEvent(&(GameEngine.event))) { + switch (GameEngine.event.type) { case SDL_QUIT: - Game.running = false; + GameEngine.running = false; break; } - if (!Game.object_store) { + if (!GameEngine.object_store) { break; } - handle_all_objects_events(Game.object_store, &(Game.event)); + handle_all_objects_events(GameEngine.object_store, &(GameEngine.event)); } } void update() { - if (!(Game.running) || !(Game.object_store)) { + if (!(GameEngine.running) || !(GameEngine.object_store)) { return; } - update_all_objects(Game.object_store, Game.delta); + uint64_t current_time = SDL_GetTicks64(); + + // Get the delta time in seconds since the SDL_GetTicks64() returns the + // time in milliseconds + GameEngine.delta = (current_time - GameEngine.last_time) / 1000.0f; + + update_all_objects(GameEngine.object_store, GameEngine.delta); + + GameEngine.last_time = current_time; } void render() { - if (!(Game.running)) { + if (!(GameEngine.running)) { return; } - clear_window(&(Game.window)); + clear_window(&(GameEngine.window)); - if (Game.object_store) { - render_all_objects(Game.object_store); + if (GameEngine.object_store) { + render_all_objects(GameEngine.object_store); } - swap_render_buffers(&(Game.window)); + swap_render_buffers(&(GameEngine.window)); } void close_game() { - destroy_asset_manager(Game.asset_manager); - Game.asset_manager = NULL; + destroy_asset_manager(GameEngine.asset_manager); + GameEngine.asset_manager = NULL; - destroy_object_store(Game.object_store); - Game.object_store = NULL; + destroy_object_store(GameEngine.object_store); + GameEngine.object_store = NULL; - destroy_window(&(Game.window)); + destroy_window(&(GameEngine.window)); - Game.delta = 0.0f; + GameEngine.last_time = 0.0f; - Game.running = false; + GameEngine.delta = 0.0f; + + GameEngine.running = false; IMG_Quit(); diff --git a/src/ObjectStore.c b/src/ObjectStore.c index 7b509ff..a424b6e 100644 --- a/src/ObjectStore.c +++ b/src/ObjectStore.c @@ -49,7 +49,7 @@ ObjectID add_object_to_store(ObjectStore *os, void *object, EventHandlerPtr eh, printf("Failed to add object\n"); #endif /* DEBUG_ENGINE */ - return -1; + return INVALID_ID; } // TODO (Abdelrahman): Need to figure out a way to check that the object @@ -78,11 +78,15 @@ ObjectID add_object_to_store(ObjectStore *os, void *object, EventHandlerPtr eh, return id; } -void remove_object_from_store(ObjectStore *os, ObjectID id) { - if (!os || os->count == 0 || !(os->object_ptrs[id])) { +void remove_object_from_store(ObjectStore *os, ObjectID *id_ptr) { + ObjectID id = *id_ptr; + + if (!os || os->count == 0 || id == INVALID_ID || !(os->object_ptrs[id])) { return; } + *id_ptr = INVALID_ID; + os->object_ptrs[id] = NULL; os->event_handlers[id] = NULL; os->update_funcs[id] = NULL; diff --git a/src/reusable_ids.c b/src/reusable_ids.c index b18b7c0..0bb6b4a 100644 --- a/src/reusable_ids.c +++ b/src/reusable_ids.c @@ -8,5 +8,7 @@ int32_t reuse_id(int32_t *ids, size_t count) { ids[i] = ids[i + 1]; } + ids[count - 1] = INVALID_ID; + return id; }