423 lines
9.1 KiB
C
423 lines
9.1 KiB
C
#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 <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
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;
|
|
}
|