asset_cache refactor

This commit is contained in:
jacob 2025-07-30 16:57:33 -05:00
parent c1c0ca5464
commit 9731a5742a
5 changed files with 179 additions and 132 deletions

View File

@ -239,7 +239,7 @@ void P_AppStartup(String args_str)
/* Subsystems */ /* Subsystems */
N_StartupReceipt host_sr = host_startup(); N_StartupReceipt host_sr = host_startup();
AC_StartupReceipt asset_cache_sr = asset_cache_startup(); AC_StartupReceipt asset_cache_sr = AC_Startup();
TTF_StartupReceipt ttf_sr = ttf_startup(); TTF_StartupReceipt ttf_sr = ttf_startup();
F_StartupReceipt font_sr = font_startup(&asset_cache_sr, &ttf_sr); F_StartupReceipt font_sr = font_startup(&asset_cache_sr, &ttf_sr);
S_StartupReceipt sprite_sr = sprite_startup(); S_StartupReceipt sprite_sr = sprite_startup();

View File

@ -1,52 +1,43 @@
/* ========================== * /* TODO: Remove this entire layer */
* Global state
* ========================== */
#define MAX_ASSETS 1024 AC_SharedState AC_shared_state = ZI;
#define ASSET_LOOKUP_TABLE_CAPACITY (MAX_ASSETS * 4)
Global struct { ////////////////////////////////
P_Mutex lookup_mutex; //~ Startup
AC_Asset lookup[ASSET_LOOKUP_TABLE_CAPACITY];
u64 num_assets;
P_Mutex store_mutex; AC_StartupReceipt AC_Startup(void)
Arena *store_arena;
#if RtcIsEnabled
/* Array of len `num_assets` pointing into populated entries of `lookup`. */
AC_Asset *dbg_table[ASSET_LOOKUP_TABLE_CAPACITY];
u64 dbg_table_count;
P_Mutex dbg_table_mutex;
#endif
} G = ZI, DebugAlias(G, G_asset_cache);
/* ========================== *
* Startup
* ========================== */
AC_StartupReceipt asset_cache_startup(void)
{ {
__prof; __prof;
/* Init store */ AC_SharedState *g = &AC_shared_state;
G.store_arena = AllocArena(Gibi(64)); g->store_arena = AllocArena(Gibi(64));
return (AC_StartupReceipt) { 0 }; return (AC_StartupReceipt) { 0 };
} }
/* ========================== * ////////////////////////////////
* Lookup //~ Hash
* ========================== */
internal void refresh_dbg_table(void) u64 AC_HashFromKey(String key)
{
/* TODO: Better hash */
return HashFnv64(Fnv64Basis, key);
}
////////////////////////////////
//~ Cache
void AC_RefreshDebugTable(void)
{ {
#if RtcIsEnabled #if RtcIsEnabled
P_Lock lock = P_LockE(&G.dbg_table_mutex); AC_SharedState *g = &AC_shared_state;
ZeroArray(G.dbg_table); P_Lock lock = P_LockE(&g->dbg_table_mutex);
G.dbg_table_count = 0; ZeroArray(g->dbg_table);
for (u64 i = 0; i < countof(G.lookup); ++i) { g->dbg_table_count = 0;
AC_Asset *asset = &G.lookup[i]; for (u64 i = 0; i < countof(g->lookup); ++i)
if (asset->hash != 0) { {
G.dbg_table[G.dbg_table_count++] = asset; AC_Asset *asset = &g->lookup[i];
if (asset->hash != 0)
{
g->dbg_table[g->dbg_table_count++] = asset;
} }
} }
P_Unlock(&lock); P_Unlock(&lock);
@ -55,38 +46,41 @@ internal void refresh_dbg_table(void)
/* Returns first matching slot or first empty slot if not found. /* Returns first matching slot or first empty slot if not found.
* Check returned slot->hash != 0 for presence. */ * Check returned slot->hash != 0 for presence. */
internal AC_Asset *asset_cache_get_slot_locked(P_Lock *lock, String key, u64 hash) AC_Asset *AC_GetAssetCacheSlotLocked(P_Lock *lock, String key, u64 hash)
{ {
P_AssertLockedES(lock, &G.lookup_mutex); AC_SharedState *g = &AC_shared_state;
P_AssertLockedES(lock, &g->lookup_mutex);
(UNUSED)lock; (UNUSED)lock;
u64 index = hash % countof(G.lookup); u64 index = hash % countof(g->lookup);
for (;;) { for (;;)
AC_Asset *slot = &G.lookup[index]; {
if (slot->hash) { AC_Asset *slot = &g->lookup[index];
if (slot->hash)
{
/* Occupied */ /* Occupied */
if (hash == slot->hash && EqString(key, slot->key)) { if (hash == slot->hash && EqString(key, slot->key))
{
/* Matched slot */ /* Matched slot */
return slot; return slot;
} else { }
else
{
++index; ++index;
if (index >= countof(G.lookup)) { if (index >= countof(g->lookup))
{
index = 0; index = 0;
} }
} }
} else { }
else
{
/* Empty slot */ /* Empty slot */
return slot; return slot;
} }
} }
} }
u64 asset_cache_hash(String key)
{
/* TODO: Better hash */
return HashFnv64(Fnv64Basis, key);
}
/* `key` text is copied by this function /* `key` text is copied by this function
* *
* Returns existing asset entry or inserts a new one. * Returns existing asset entry or inserts a new one.
@ -95,38 +89,45 @@ u64 asset_cache_hash(String key)
* inserted the asset into the cache. * inserted the asset into the cache.
* *
* */ * */
AC_Asset *asset_cache_touch(String key, u64 hash, b32 *is_first_touch) AC_Asset *AC_TouchCache(String key, u64 hash, b32 *is_first_touch)
{ {
AC_Asset *asset = 0; AC_Asset *asset = 0;
AC_SharedState *g = &AC_shared_state;
/* Lookup */ /* Lookup */
{ {
P_Lock lock = P_LockS(&G.lookup_mutex); P_Lock lock = P_LockS(&g->lookup_mutex);
asset = asset_cache_get_slot_locked(&lock, key, hash); asset = AC_GetAssetCacheSlotLocked(&lock, key, hash);
P_Unlock(&lock); P_Unlock(&lock);
} }
/* Insert if not found */ /* Insert if not found */
if (asset->hash) { if (asset->hash)
if (is_first_touch) { {
if (is_first_touch)
{
*is_first_touch = 0; *is_first_touch = 0;
} }
} else { }
P_Lock lock = P_LockE(&G.lookup_mutex); else
{
P_Lock lock = P_LockE(&g->lookup_mutex);
/* Re-check asset presence in case it was inserted since lock */ /* Re-check asset presence in case it was inserted since lock */
asset = asset_cache_get_slot_locked(&lock, key, hash); asset = AC_GetAssetCacheSlotLocked(&lock, key, hash);
if (!asset->hash) { if (!asset->hash)
if (G.num_assets >= MAX_ASSETS) { {
if (g->num_assets >= AC_MaxAssets)
{
P_Panic(Lit("Max assets reached")); P_Panic(Lit("Max assets reached"));
} }
String key_stored = ZI; String key_stored = ZI;
{ {
/* CopyStruct key to store */ /* CopyStruct key to store */
AC_Store store = asset_cache_store_open(); AC_Store store = AC_OpenStore();
key_stored = CopyString(store.arena, key); key_stored = CopyString(store.arena, key);
asset_cache_store_close(&store); AC_CloseStore(&store);
} }
/* Initialize asset data */ /* Initialize asset data */
P_LogInfoF("Inserting asset cache entry for \"%F\"", FmtString(key)); P_LogInfoF("Inserting asset cache entry for \"%F\"", FmtString(key));
@ -136,12 +137,13 @@ AC_Asset *asset_cache_touch(String key, u64 hash, b32 *is_first_touch)
.key = key_stored .key = key_stored
}; };
P_CounterAdd(&asset->counter, 1); P_CounterAdd(&asset->counter, 1);
if (is_first_touch) { if (is_first_touch)
{
*is_first_touch = 1; *is_first_touch = 1;
} }
++G.num_assets; ++g->num_assets;
refresh_dbg_table(); AC_RefreshDebugTable();
} }
P_Unlock(&lock); P_Unlock(&lock);
@ -150,61 +152,56 @@ AC_Asset *asset_cache_touch(String key, u64 hash, b32 *is_first_touch)
return asset; return asset;
} }
/* ========================== * ////////////////////////////////
* Marking //~ Status
* ========================== */
/* Call this once asset job has been created */ /* Call this once asset job has been created */
void asset_cache_mark_loading(AC_Asset *asset) void AC_MarkLoading(AC_Asset *asset)
{ {
asset->status = ASSET_STATUS_LOADING; asset->status = ASSET_STATUS_LOADING;
} }
/* Call this once asset job has finished */ /* Call this once asset job has finished */
void asset_cache_mark_ready(AC_Asset *asset, void *store_data) void AC_MarkReady(AC_Asset *asset, void *store_data)
{ {
asset->store_data = store_data; asset->store_data = store_data;
asset->status = ASSET_STATUS_READY; asset->status = ASSET_STATUS_READY;
P_CounterAdd(&asset->counter, -1); P_CounterAdd(&asset->counter, -1);
} }
/* ========================== * void AC_WaitOnAssetReady(AC_Asset *asset)
* Job
* ========================== */
void asset_cache_wait(AC_Asset *asset)
{ {
P_WaitOnCounter(&asset->counter); P_WaitOnCounter(&asset->counter);
} }
/* ========================== * ////////////////////////////////
* Store //~ Store
* ========================== */
/* NOTE: At the moment only one global asset store exists, however in the void *AC_DataFromStore(AC_Asset *asset)
* future there could be more based on asset lifetime. */
void *asset_cache_get_store_data(AC_Asset *asset)
{ {
if (asset->status == ASSET_STATUS_READY) { if (asset->status == ASSET_STATUS_READY)
{
return asset->store_data; return asset->store_data;
} else { }
else
{
return 0; return 0;
} }
} }
/* Asset store should be opened to allocate memory to the store arena */ /* Asset store should be opened to allocate memory to the store arena */
AC_Store asset_cache_store_open(void) AC_Store AC_OpenStore(void)
{ {
P_Lock lock = P_LockE(&G.store_mutex); AC_SharedState *g = &AC_shared_state;
P_Lock lock = P_LockE(&g->store_mutex);
AC_Store store = { AC_Store store = {
.lock = lock, .lock = lock,
.arena = G.store_arena .arena = g->store_arena
}; };
return store; return store;
} }
void asset_cache_store_close(AC_Store *store) void AC_CloseStore(AC_Store *store)
{ {
P_Unlock(&store->lock); P_Unlock(&store->lock);
} }

View File

@ -1,14 +1,19 @@
typedef i32 AC_Status; enum { /* TODO: Remove this entire layer */
ASSET_STATUS_NONE,
////////////////////////////////
//~ Asset types
typedef i32 AC_Status; enum
{
ASSET_STATUS_NONE,
ASSET_STATUS_UNINITIALIZED, ASSET_STATUS_UNINITIALIZED,
/* TODO: ASSET_STATUS_QUEUED? */
ASSET_STATUS_LOADING, ASSET_STATUS_LOADING,
ASSET_STATUS_READY ASSET_STATUS_READY
}; };
Struct(AC_Asset) { Struct(AC_Asset)
/* Managed via asset_cache_touch */ {
/* Managed via AC_TouchCache */
u64 hash; u64 hash;
String key; String key;
@ -21,25 +26,70 @@ Struct(AC_Asset) {
void *store_data; void *store_data;
}; };
Struct(AC_Store) { ////////////////////////////////
//~ Asset store types
Struct(AC_Store)
{
Arena *arena; Arena *arena;
/* internal */ /* internal */
P_Lock lock; P_Lock lock;
}; };
////////////////////////////////
//~ Shared state
#define AC_MaxAssets 1024
#define AC_AssetLookupTableCapacity (AC_MaxAssets * 4)
Struct(AC_SharedState)
{
P_Mutex lookup_mutex;
AC_Asset lookup[AC_AssetLookupTableCapacity];
u64 num_assets;
P_Mutex store_mutex;
Arena *store_arena;
#if RtcIsEnabled
/* Array of len `num_assets` pointing into populated entries of `lookup`. */
AC_Asset *dbg_table[AC_AssetLookupTableCapacity];
u64 dbg_table_count;
P_Mutex dbg_table_mutex;
#endif
};
extern AC_SharedState AC_shared_state;
////////////////////////////////
//~ Startup
Struct(AC_StartupReceipt) { i32 _; }; Struct(AC_StartupReceipt) { i32 _; };
AC_StartupReceipt asset_cache_startup(void); AC_StartupReceipt AC_Startup(void);
AC_Asset *asset_cache_touch(String key, u64 hash, b32 *is_first_touch); ////////////////////////////////
//~ Hash
void asset_cache_mark_loading(AC_Asset *asset); u64 AC_HashFromKey(String key);
void asset_cache_mark_ready(AC_Asset *asset, void *store_data);
void asset_cache_wait(AC_Asset *asset); ////////////////////////////////
//~ Cache operations
void *asset_cache_get_store_data(AC_Asset *asset); void AC_RefreshDebugTable(void);
AC_Store asset_cache_store_open(void); AC_Asset *AC_GetAssetCacheSlotLocked(P_Lock *lock, String key, u64 hash);
void asset_cache_store_close(AC_Store *store); AC_Asset *AC_TouchCache(String key, u64 hash, b32 *is_first_touch);
u64 asset_cache_hash(String key); ////////////////////////////////
//~ Status operations
void AC_MarkLoading(AC_Asset *asset);
void AC_MarkReady(AC_Asset *asset, void *store_data);
void AC_WaitOnAssetReady(AC_Asset *asset);
////////////////////////////////
//~ Store operations
void *AC_DataFromStore(AC_Asset *asset);
AC_Store AC_OpenStore(void);
void AC_CloseStore(AC_Store *store);

View File

@ -105,11 +105,11 @@ internal P_JobDef(font_load_asset_job, job)
/* Allocate store memory */ /* Allocate store memory */
F_Font *font = 0; F_Font *font = 0;
{ {
AC_Store store = asset_cache_store_open(); AC_Store store = AC_OpenStore();
font = PushStruct(store.arena, F_Font); font = PushStruct(store.arena, F_Font);
font->glyphs = PushStructsNoZero(store.arena, F_Glyph, result.glyphs_count); font->glyphs = PushStructsNoZero(store.arena, F_Glyph, result.glyphs_count);
font->lookup = PushStructs(store.arena, u16, LOOKUP_TABLE_SIZE); font->lookup = PushStructs(store.arena, u16, LOOKUP_TABLE_SIZE);
asset_cache_store_close(&store); AC_CloseStore(&store);
} }
/* Set font data */ /* Set font data */
@ -139,7 +139,7 @@ internal P_JobDef(font_load_asset_job, job)
font_task_params_release(params); font_task_params_release(params);
P_LogSuccessF("Loaded font \"%F\" (point size %F) in %F seconds", FmtString(path), FmtFloat((f64)point_size), FmtFloat(SecondsFromNs(P_TimeNs() - start_ns))); P_LogSuccessF("Loaded font \"%F\" (point size %F) in %F seconds", FmtString(path), FmtFloat((f64)point_size), FmtFloat(SecondsFromNs(P_TimeNs() - start_ns)));
asset_cache_mark_ready(asset, font); AC_MarkReady(asset, font);
EndScratch(scratch); EndScratch(scratch);
} }
@ -155,9 +155,9 @@ AC_Asset *font_load_asset(String path, f32 point_size, b32 wait)
Lit("%F%F_font"), Lit("%F%F_font"),
FmtString(path), FmtString(path),
FmtFloatP((f64)point_size, 1)); FmtFloatP((f64)point_size, 1));
u64 hash = asset_cache_hash(key); u64 hash = AC_HashFromKey(key);
b32 is_first_touch; b32 is_first_touch;
AC_Asset *asset = asset_cache_touch(key, hash, &is_first_touch); AC_Asset *asset = AC_TouchCache(key, hash, &is_first_touch);
if (is_first_touch) { if (is_first_touch) {
/* Assemble task params */ /* Assemble task params */
@ -173,10 +173,10 @@ AC_Asset *font_load_asset(String path, f32 point_size, b32 wait)
params->point_size = point_size; params->point_size = point_size;
/* PushStruct task */ /* PushStruct task */
asset_cache_mark_loading(asset); AC_MarkLoading(asset);
P_Run(1, font_load_asset_job, params, P_Pool_Background, P_Priority_Low, 0); P_Run(1, font_load_asset_job, params, P_Pool_Background, P_Priority_Low, 0);
if (wait) { if (wait) {
asset_cache_wait(asset); AC_WaitOnAssetReady(asset);
} }
} }
@ -188,7 +188,7 @@ F_Font *font_load_async(String path, f32 point_size)
{ {
__prof; __prof;
AC_Asset *asset = font_load_asset(path, point_size, 0); AC_Asset *asset = font_load_asset(path, point_size, 0);
F_Font *f = (F_Font *)asset_cache_get_store_data(asset); F_Font *f = (F_Font *)AC_DataFromStore(asset);
return f; return f;
} }
@ -196,8 +196,8 @@ F_Font *font_load(String path, f32 point_size)
{ {
__prof; __prof;
AC_Asset *asset = font_load_asset(path, point_size, 1); AC_Asset *asset = font_load_asset(path, point_size, 1);
asset_cache_wait(asset); AC_WaitOnAssetReady(asset);
F_Font *f = (F_Font *)asset_cache_get_store_data(asset); F_Font *f = (F_Font *)AC_DataFromStore(asset);
return f; return f;
} }

View File

@ -108,10 +108,10 @@ internal P_JobDef(sound_load_asset_job, job)
u64 samples_count = decoded.samples_count; u64 samples_count = decoded.samples_count;
i16 *samples = 0; i16 *samples = 0;
{ {
AC_Store store = asset_cache_store_open(); AC_Store store = AC_OpenStore();
sound = PushStructNoZero(store.arena, SND_Sound); sound = PushStructNoZero(store.arena, SND_Sound);
samples = PushStructsNoZero(store.arena, i16, samples_count); samples = PushStructsNoZero(store.arena, i16, samples_count);
asset_cache_store_close(&store); AC_CloseStore(&store);
} }
/* Initialize */ /* Initialize */
@ -122,19 +122,19 @@ internal P_JobDef(sound_load_asset_job, job)
CopyBytes(sound->samples, decoded.samples, decoded.samples_count * sizeof(*decoded.samples)); CopyBytes(sound->samples, decoded.samples, decoded.samples_count * sizeof(*decoded.samples));
P_LogSuccessF("Loaded sound \"%F\" in %F seconds", FmtString(path), FmtFloat(SecondsFromNs(P_TimeNs() - start_ns))); P_LogSuccessF("Loaded sound \"%F\" in %F seconds", FmtString(path), FmtFloat(SecondsFromNs(P_TimeNs() - start_ns)));
asset_cache_mark_ready(asset, sound); AC_MarkReady(asset, sound);
} else { } else {
P_LogErrorF("Error loading sound \"%F\": %F", FmtString(path), FmtString(error_msg)); P_LogErrorF("Error loading sound \"%F\": %F", FmtString(path), FmtString(error_msg));
/* Store */ /* Store */
SND_Sound *sound = 0; SND_Sound *sound = 0;
{ {
AC_Store store = asset_cache_store_open(); AC_Store store = AC_OpenStore();
sound = PushStructNoZero(store.arena, SND_Sound); sound = PushStructNoZero(store.arena, SND_Sound);
asset_cache_store_close(&store); AC_CloseStore(&store);
} }
*sound = (SND_Sound) { 0 }; *sound = (SND_Sound) { 0 };
asset_cache_mark_ready(asset, sound); AC_MarkReady(asset, sound);
} }
sound_task_params_release(params); sound_task_params_release(params);
@ -152,9 +152,9 @@ AC_Asset *sound_load_asset(String path, u32 flags, b32 wait)
Lit("%F%F_sound"), Lit("%F%F_sound"),
FmtString(path), FmtString(path),
FmtUint((u64)flags)); FmtUint((u64)flags));
u64 hash = asset_cache_hash(key); u64 hash = AC_HashFromKey(key);
b32 is_first_touch; b32 is_first_touch;
AC_Asset *asset = asset_cache_touch(key, hash, &is_first_touch); AC_Asset *asset = AC_TouchCache(key, hash, &is_first_touch);
if (is_first_touch) { if (is_first_touch) {
/* Assemble task params */ /* Assemble task params */
@ -170,10 +170,10 @@ AC_Asset *sound_load_asset(String path, u32 flags, b32 wait)
params->flags = flags; params->flags = flags;
/* PushStruct task */ /* PushStruct task */
asset_cache_mark_loading(asset); AC_MarkLoading(asset);
P_Run(1, sound_load_asset_job, params, P_Pool_Background, P_Priority_Low, &asset->counter); P_Run(1, sound_load_asset_job, params, P_Pool_Background, P_Priority_Low, &asset->counter);
if (wait) { if (wait) {
asset_cache_wait(asset); AC_WaitOnAssetReady(asset);
} }
} }
@ -185,7 +185,7 @@ SND_Sound *sound_load_async(String path, u32 flags)
{ {
__prof; __prof;
AC_Asset *asset = sound_load_asset(path, flags, 0); AC_Asset *asset = sound_load_asset(path, flags, 0);
SND_Sound *sound = (SND_Sound *)asset_cache_get_store_data(asset); SND_Sound *sound = (SND_Sound *)AC_DataFromStore(asset);
return sound; return sound;
} }
@ -194,7 +194,7 @@ SND_Sound *sound_load(String path, u32 flags)
{ {
__prof; __prof;
AC_Asset *asset = sound_load_asset(path, flags, 1); AC_Asset *asset = sound_load_asset(path, flags, 1);
asset_cache_wait(asset); AC_WaitOnAssetReady(asset);
SND_Sound *sound = (SND_Sound *)asset_cache_get_store_data(asset); SND_Sound *sound = (SND_Sound *)AC_DataFromStore(asset);
return sound; return sound;
} }