#include "AssetManager.h" #include "Renderer.h" #include "SDL_error.h" #include "SDL_image.h" #include "SDL_mixer.h" #include "SDL_render.h" #include "SDL_ttf.h" #include "aliases.h" #include "constants.h" #include "reusable_ids.h" #include #include #include #include typedef struct asset { AssetType type; char *path; union { SDL_Texture *texture; Mix_Chunk *chunk; Mix_Music *music; TTF_Font *font; } data; } Asset; typedef struct id_node IDNode; struct id_node { AssetID id; IDNode *next; }; typedef struct id_node_list { IDNode *start_node; u64 node_count; } IDNodeList; struct asset_manager { u64 count; u64 deleted_count; AssetID reusable_ids[MAX_ASSET_COUNT]; Asset assets[MAX_ASSET_COUNT]; u64 user_counter[MAX_ASSET_COUNT]; IDNodeList hashed_path_ids[MAX_ASSET_COUNT]; }; u64 hash_path(const char *path) { /** * Converts a string into a numerical index using a simple hashing algorithm */ u64 i = 0; for (u64 j = 0; path[j]; ++j) { i += (u64)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 (u64 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 (u64 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) { /** * Checks to see if an asset with the same path exists and returns the ID of * that asset if it exists, or an INVALID_ID otherwise */ if (node_list->node_count == 0) { return INVALID_ID; } IDNode *id_node = node_list->start_node; for (u64 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) { return NULL; } am->count = 0; 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; } AssetID load_asset_to_manager(AssetManager *am, const char *path, AssetType type, Renderer *renderer) { if (!am || type == ASSET_TYPE_COUNT) { return INVALID_ID; } u64 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) { if (am->assets[existing_id].type != type) { // If the user tries to load an asset that is already loaded, but they // try to load it as a type that doesn't match the asset's actual type, // we return an INVALID_ID to ensure that the user doesn't try using the // asset with functions that work on the wrong type return INVALID_ID; } ++(am->user_counter[existing_id]); return existing_id; } if (am->count + 1 == MAX_ASSET_COUNT && am->deleted_count == 0) { return INVALID_ID; } Asset asset = {}; bool loaded = true; switch (type) { case ASSET_TYPE_IMAGE: if (!renderer) { loaded = false; break; } asset.data.texture = load_texture(renderer, path); if (!(asset.data.texture)) { loaded = false; } break; case ASSET_TYPE_AUDIO_FX: asset.data.chunk = Mix_LoadWAV(path); if (!(asset.data.chunk)) { loaded = false; } break; case ASSET_TYPE_MUSIC: asset.data.music = Mix_LoadMUS(path); if (!(asset.data.music)) { loaded = false; } break; case ASSET_TYPE_FONT: asset.data.font = TTF_OpenFont(path, 32); if (!(asset.data.font)) { loaded = false; } break; default: loaded = false; break; } if (!loaded) { printf("Error loading %s: %s\n", path, SDL_GetError()); return INVALID_ID; } AssetID id; if (am->deleted_count > 0) { id = (AssetID)reuse_id((i32 *)am->reusable_ids, am->deleted_count); am->deleted_count -= 1; am->count += 1; } else { 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); strcpy(am->assets[id].path, path); am->assets[id].data = asset.data; switch (type) { case ASSET_TYPE_IMAGE: asset.data.texture = NULL; break; case ASSET_TYPE_AUDIO_FX: asset.data.chunk = NULL; break; case ASSET_TYPE_MUSIC: asset.data.music = NULL; break; case ASSET_TYPE_FONT: asset.data.font = NULL; break; default: break; } return id; } 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; } u64 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 = NULL; switch (am->assets[id].type) { case ASSET_TYPE_IMAGE: SDL_DestroyTexture(am->assets[id].data.texture); am->assets[id].data.texture = NULL; break; case ASSET_TYPE_AUDIO_FX: Mix_FreeChunk(am->assets[id].data.chunk); am->assets[id].data.chunk = NULL; break; case ASSET_TYPE_MUSIC: Mix_FreeMusic(am->assets[id].data.music); am->assets[id].data.music = NULL; break; case ASSET_TYPE_FONT: TTF_CloseFont(am->assets[id].data.font); am->assets[id].data.font = NULL; break; default: break; } am->reusable_ids[(am->deleted_count)++] = id; am->count -= 1; } SDL_Texture *get_texture(AssetManager *am, AssetID id) { if (id < 0 || am->assets[id].type != ASSET_TYPE_IMAGE) { return NULL; } return am->assets[id].data.texture; } TTF_Font *get_font(AssetManager *am, AssetID id) { if (id < 0 || am->assets[id].type != ASSET_TYPE_FONT) { return NULL; } return am->assets[id].data.font; } Mix_Chunk *get_audio_chunk(AssetManager *am, AssetID id) { if (id < 0 || am->assets[id].type != ASSET_TYPE_AUDIO_FX) { return NULL; } return am->assets[id].data.chunk; } Mix_Music *get_music(AssetManager *am, AssetID id) { if (id < 0 || am->assets[id].type != ASSET_TYPE_MUSIC) { return NULL; } return am->assets[id].data.music; } void destroy_asset_manager(AssetManager **am) { if (!(*am)) { return; } // TODO (Abdelrahman): Figure out if there is a better way to remove all of // the existing assets without having to loop for the maximum number of // assets allowed. for (u64 i = 0; i < MAX_ASSET_COUNT; ++i) { if (!(*am)->assets[i].path) { continue; } (*am)->user_counter[i] = 0; u64 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; switch ((*am)->assets[i].type) { case ASSET_TYPE_IMAGE: SDL_DestroyTexture((*am)->assets[i].data.texture); (*am)->assets[i].data.texture = NULL; break; case ASSET_TYPE_AUDIO_FX: Mix_FreeChunk((*am)->assets[i].data.chunk); (*am)->assets[i].data.chunk = NULL; break; case ASSET_TYPE_MUSIC: Mix_FreeMusic((*am)->assets[i].data.music); (*am)->assets[i].data.music = NULL; break; case ASSET_TYPE_FONT: TTF_CloseFont((*am)->assets[i].data.font); (*am)->assets[i].data.font = NULL; break; default: break; } } (*am)->count = 0; (*am)->deleted_count = 0; free(*am); *am = NULL; }