#include "asset_cache.h" #include "sys.h" #include "string.h" #include "memory.h" #include "arena.h" #include "scratch.h" #include "util.h" #include "work.h" #include "log.h" /* ========================== * * Global state * ========================== */ #define MAX_ASSETS 1024 #define ASSET_LOOKUP_TABLE_CAPACITY (MAX_ASSETS * 4) GLOBAL struct { struct sys_rw_mutex lookup_rw_mutex; struct asset lookup[ASSET_LOOKUP_TABLE_CAPACITY]; u64 num_assets; struct sys_rw_mutex store_rw_mutex; struct arena store_arena; #if RTC /* Array of len `num_assets` pointing into populated entries of `lookup`. */ struct asset *dbg_table[ASSET_LOOKUP_TABLE_CAPACITY]; u64 dbg_table_count; struct sys_mutex dbg_table_mutex; #endif } G = { 0 }, DEBUG_ALIAS(G, G_asset_cache); /* ========================== * * Startup * ========================== */ struct asset_cache_startup_receipt asset_cache_startup(struct work_startup_receipt *work_sr) { (UNUSED)work_sr; /* Init lookup */ G.lookup_rw_mutex = sys_rw_mutex_alloc(); /* Init store */ G.store_rw_mutex = sys_rw_mutex_alloc(); G.store_arena = arena_alloc(GIGABYTE(64)); #if RTC /* Init debug */ G.dbg_table_mutex = sys_mutex_alloc(); #endif return (struct asset_cache_startup_receipt) { 0 }; } /* ========================== * * Lookup * ========================== */ INTERNAL void refresh_dbg_table(void) { #if RTC sys_mutex_lock(&G.dbg_table_mutex); { MEMZERO_ARRAY(G.dbg_table); G.dbg_table_count = 0; for (u64 i = 0; i < ARRAY_COUNT(G.lookup); ++i) { struct asset *asset = &G.lookup[i]; if (asset->hash != 0) { G.dbg_table[G.dbg_table_count++] = asset; } } } sys_mutex_unlock(&G.dbg_table_mutex); #endif } /* Returns first matching slot or first empty slot if not found. * Check returned slot->hash != 0 for presence. */ INTERNAL struct asset *asset_cache_get_slot_assume_locked(struct string key, u64 hash) { u64 index = hash % ARRAY_COUNT(G.lookup); while (true) { struct asset *slot = &G.lookup[index]; if (slot->hash) { /* Occupied */ if (hash == slot->hash && string_eq(key, slot->key)) { /* Matched slot */ return slot; } else { ++index; if (index >= ARRAY_COUNT(G.lookup)) { index = 0; } } } else { /* Empty slot */ return slot; } } } u64 asset_cache_hash(struct string key) { /* TODO: Better hash */ return hash_fnv64(HASH_FNV64_BASIS, BUFFER_FROM_STRING(key)); } /* `key` text is copied by this function * * Returns existing asset entry or inserts a new one. * * If is_first_touch (out parameter) is set to true, then the caller has * inserted the asset into the cache. * * */ struct asset *asset_cache_touch(struct string key, u64 hash, b32 *is_first_touch) { struct asset *asset = NULL; if (is_first_touch) { *is_first_touch = false; } /* Lookup */ { sys_rw_mutex_lock_shared(&G.lookup_rw_mutex); asset = asset_cache_get_slot_assume_locked(key, hash); sys_rw_mutex_unlock_shared(&G.lookup_rw_mutex); } /* Insert if not found */ if (!asset->hash) { sys_rw_mutex_lock_exclusive(&G.lookup_rw_mutex); /* Re-check asset presence in case it was inserted since lock */ asset = asset_cache_get_slot_assume_locked(key, hash); if (!asset->hash) { if (G.num_assets >= MAX_ASSETS) { sys_panic(STR("Max assets reached")); } struct string key_stored = { 0 }; { /* Copy key to store */ struct asset_cache_store store = asset_cache_store_open(); key_stored = string_copy(store.arena, key); asset_cache_store_close(&store); } /* Initialize asset data */ logf_info("Inserting asset cache entry for \"%F\"", FMT_STR(key)); *asset = (struct asset) { .status = ASSET_STATUS_UNINITIALIZED, .hash = hash, .key = key_stored, .work_ready_sf = sync_flag_alloc(), .asset_ready_sf = sync_flag_alloc() }; if (is_first_touch) { *is_first_touch = true; } ++G.num_assets; refresh_dbg_table(); } sys_rw_mutex_unlock_exclusive(&G.lookup_rw_mutex); } return asset; } /* ========================== * * Marking * ========================== */ /* Call this once asset work has been created */ void asset_cache_mark_loading(struct asset *asset) { asset->status = ASSET_STATUS_LOADING; } /* Call this once asset work has finished */ void asset_cache_mark_ready(struct asset *asset, void *store_data) { asset->store_data = store_data; asset->status = ASSET_STATUS_READY; WRITE_BARRIER(); sync_flag_set(&asset->asset_ready_sf); } /* ========================== * * Work * ========================== */ /* NOTE: If an asset doesn't have any load work then call this function with `NULL` */ void asset_cache_set_work(struct asset *asset, struct work_handle *handle) { asset->work = handle ? *handle : (struct work_handle) { 0 }; sync_flag_set(&asset->work_ready_sf); } void asset_cache_wait(struct asset *asset) { if (asset->status != ASSET_STATUS_READY) { /* Wait for work to be set */ sync_flag_wait(&asset->work_ready_sf); /* Help with work */ if (asset->work.gen != 0) { work_help(asset->work); } /* Wait for asset to be ready */ sync_flag_wait(&asset->asset_ready_sf); } } /* ========================== * * Store * ========================== */ /* NOTE: At the moment only one global asset store exists, however in the * future there could be more based on asset lifetime. */ void *asset_cache_get_store_data(struct asset *asset) { if (asset->status == ASSET_STATUS_READY) { return asset->store_data; } else { return NULL; } } /* Asset store should be opened to allocate memory to the store arena */ struct asset_cache_store asset_cache_store_open(void) { struct asset_cache_store store = { .rw_mutex = &G.store_rw_mutex, .arena = &G.store_arena }; sys_rw_mutex_lock_exclusive(store.rw_mutex); return store; } void asset_cache_store_close(struct asset_cache_store *store) { sys_rw_mutex_unlock_exclusive(store->rw_mutex); }