#include "sheet.h" #include "arena.h" #include "log.h" #include "sys.h" #include "scratch.h" #include "resource.h" #include "asset_cache.h" #include "ase.h" #include "util.h" struct sheet_task_params { struct sheet_task_params *next_free; struct asset *asset; u64 path_len; char path_cstr[1024]; }; struct sheet_task_params_store { struct sheet_task_params *head_free; struct arena arena; struct sys_mutex mutex; }; /* ========================== * * Global state * ========================== */ GLOBAL struct { struct sheet_task_params_store params; } L = { 0 }, DEBUG_LVAR(L_sheet); /* ========================== * * Startup * ========================== */ void sheet_startup(void) { L.params.arena = arena_alloc(GIGABYTE(64)); L.params.mutex = sys_mutex_alloc(); } /* ========================== * * Load task param store * ========================== */ INTERNAL struct sheet_task_params *sheet_task_params_alloc(void) { struct sheet_task_params *p = NULL; sys_mutex_lock(&L.params.mutex); { if (L.params.head_free) { p = L.params.head_free; L.params.head_free = p->next_free; } else { p = arena_push_zero(&L.params.arena, struct sheet_task_params); } } sys_mutex_unlock(&L.params.mutex); return p; } INTERNAL void sheet_task_params_release(struct sheet_task_params *p) { sys_mutex_lock(&L.params.mutex); { p->next_free = L.params.head_free; L.params.head_free = p; } sys_mutex_unlock(&L.params.mutex); } /* ========================== * * Init * ========================== */ #define SHEET_LOOKUP_TABLE_CAPACITY_FACTOR 2.0 INTERNAL struct sheet sheet_from_ase(struct arena *arena, struct ase_decode_sheet_result ase) { struct sheet sheet = { 0 }; ASSERT(ase.num_frames >= 1); /* Init frames */ sheet.image_size = ase.image_size; sheet.frame_size = ase.frame_size; sheet.frames = arena_push_array_zero(arena, struct sheet_frame, ase.num_frames); sheet.frames_count = ase.num_frames; for (struct ase_frame *ase_frame = ase.frame_head; ase_frame; ase_frame = ase_frame->next) { u32 index = ase_frame->index; sheet.frames[index] = (struct sheet_frame) { .index = index, .duration = ase_frame->duration, .clip = ase_frame->clip }; } /* Init tags */ sheet.tags_count = ase.num_tags; if (ase.num_tags > 0) { sheet.tags_dict = fixed_dict_init(arena, (u64)(ase.num_tags * SHEET_LOOKUP_TABLE_CAPACITY_FACTOR)); for (struct ase_tag *ase_tag = ase.tag_head; ase_tag; ase_tag = ase_tag->next) { struct string name = string_copy(arena, ase_tag->name); struct sheet_tag *tag = arena_push(arena, struct sheet_tag); *tag = (struct sheet_tag) { .name = name, .start = ase_tag->start, .end = ase_tag->end }; fixed_dict_set(arena, &sheet.tags_dict, name, tag); } } return sheet; } INTERNAL struct sheet sheet_default(struct arena *arena) { struct sheet sheet = { 0 }; sheet.frames_count = 1; sheet.frames = arena_push(arena, struct sheet_frame); sheet.frames[0] = (struct sheet_frame) { .index = 0, .duration = 0, .clip = CLIP_ALL }; return sheet; } /* ========================== * * Load * ========================== */ INTERNAL WORK_TASK_FUNC_DEF(sheet_load_asset_task, vparams) { __prof; struct sheet_task_params *params = (struct sheet_task_params *)vparams; struct temp_arena scratch = scratch_begin_no_conflict(); struct string path = string_from_cstr_len(params->path_cstr, params->path_len); struct asset *asset = params->asset; logf_info("Loading sheet \"%F\"", FMT_STR(path)); sys_timestamp_t start_ts = sys_timestamp(); b32 success = false; struct string error_msg = STR("Unknown error"); ASSERT(string_ends_with(path, STR(".ase"))); if (resource_exists(path)) { /* Decode */ struct resource sheet_rs = resource_open(path); struct ase_decode_sheet_result decoded = ase_decode_sheet(scratch.arena, sheet_rs.bytes); resource_close(sheet_rs); /* Failure paths */ if (!decoded.valid) { if (decoded.error_msg.len > 0) { error_msg = decoded.error_msg; } goto abort; } else { success = true; } /* Initialize sheet & its data into store */ struct sheet *sheet = NULL; { struct asset_cache_store store = asset_cache_store_open(); sheet = arena_push(store.arena, struct sheet); *sheet = sheet_from_ase(store.arena, decoded); asset_cache_store_close(&store); } logf_info("Finished loading sheet \"%F\" in %F seconds", FMT_STR(path), FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts))); asset_cache_mark_ready(asset, sheet); } else { success = false; error_msg = STR("Resource not found"); goto abort; } abort: if (!success) { logf_error("Error loading sheet \"%F\": %F", FMT_STR(path), FMT_STR(error_msg)); /* Store */ struct sheet *sheet = NULL; { struct asset_cache_store store = asset_cache_store_open(); sheet = arena_push(store.arena, struct sheet); *sheet = sheet_default(store.arena); asset_cache_store_close(&store); } asset_cache_mark_ready(asset, sheet); } sheet_task_params_release(params); /* Decommit decoded sheet data */ scratch_end_and_decommit(scratch); } struct asset *sheet_load_asset(struct string path, b32 help) { __prof; struct temp_arena scratch = scratch_begin_no_conflict(); struct string key = string_cat(scratch.arena, path, STR("_sheet")); u64 hash = asset_cache_hash(key); b32 is_first_touch; struct asset *asset = asset_cache_touch(key, hash, &is_first_touch); if (is_first_touch) { /* Assemble task params */ struct sheet_task_params *params = sheet_task_params_alloc(); if (path.len > (sizeof(params->path_cstr) - 1)) { sys_panic(string_format(scratch.arena, STR("Sheet path \"%F\" too long!"), FMT_STR(path))); } string_to_cstr_buff(path, BUFFER_FROM_ARRAY(params->path_cstr)); params->path_len = path.len; params->asset = asset; /* Push task */ asset_cache_mark_loading(asset); struct work_handle wh = { 0 }; if (help) { wh = work_push_task_and_help(&sheet_load_asset_task, params, WORK_PRIORITY_NORMAL); } else { wh = work_push_task(&sheet_load_asset_task, params, WORK_PRIORITY_NORMAL); } asset_cache_set_work(asset, &wh); } scratch_end(scratch); return asset; } struct sheet *sheet_load_async(struct string path) { __prof; struct asset *asset = sheet_load_asset(path, false); struct sheet *sheet = (struct sheet *)asset_cache_get_store_data(asset); return sheet; } struct sheet *sheet_load(struct string path) { __prof; struct asset *asset = sheet_load_asset(path, true); asset_cache_wait(asset); struct sheet *sheet = (struct sheet *)asset_cache_get_store_data(asset); return sheet; } /* ========================== * * Sheet data * ========================== */ GLOBAL READONLY struct sheet_tag g_default_tag = { 0 }; struct sheet_tag sheet_get_tag(struct sheet *sheet, struct string name) { struct sheet_tag res = g_default_tag; if (sheet->tags_count > 0) { struct sheet_tag *entry = fixed_dict_get(&sheet->tags_dict, name); if (entry) { res = *entry; } } return res; } struct sheet_frame sheet_get_frame(struct sheet *sheet, u32 index) { index = min_u32(sheet->frames_count - 1, index); return sheet->frames[index]; }