diff --git a/src/game.c b/src/game.c index 17afcf90..e020f9c2 100644 --- a/src/game.c +++ b/src/game.c @@ -420,7 +420,7 @@ INTERNAL void game_update(void) f64 time_in_frame = ent->animation_time_in_frame + G.world.dt; u64 span_frame_offset = ent->animation_frame; - struct sheet *sheet = sheet_from_tag_await(sheet_frame_scope, ent->sprite_sheet_tag); + struct sheet *sheet = sheet_from_tag_async(sheet_frame_scope, ent->sprite_sheet_tag); struct sheet_span span = sheet_get_span(sheet, ent->sprite_span_name); u64 frame_index = span.start + span_frame_offset; diff --git a/src/sheet.c b/src/sheet.c index 001d96cb..7fd9ca91 100644 --- a/src/sheet.c +++ b/src/sheet.c @@ -10,6 +10,7 @@ #include "work.h" #include "atomic.h" #include "thread_local.h" +#include "app.h" #define SHEET_ARENA_RESERVE MEGABYTE(64) #define SHEET_LOOKUP_TABLE_BUCKET_RATIO 2.0 @@ -18,26 +19,21 @@ #define CACHE_BUCKETS_COUNT 256 #define SCOPE_BUCKETS_COUNT 256 +#define MAX_LOADER_THREADS 4 + /* ========================== * * Load cmd structs * ========================== */ struct load_cmd { - struct cache_node *node; + struct load_cmd *next; + struct load_cmd *next_free; + + struct cache_node *cache_node; struct sheet_tag tag; u8 tag_path_buff[4096]; }; -struct load_cmd_store { - struct arena arena; - struct sys_mutex mutex; -}; - -struct load_cmd_array { - u64 count; - struct load_cmd *cmds; -}; - /* ========================== * * Cache structs * ========================== */ @@ -83,11 +79,27 @@ struct sheet_scope_reference { * ========================== */ GLOBAL struct { - struct load_cmd_store load_cmd_store; + /* Cache */ struct cache cache; + + /* Load cmds */ + b32 shutdown; + struct arena load_cmd_arena; + struct sys_mutex load_cmd_mutex; + struct sys_condition_variable load_cmd_or_shutdown_cv; + struct load_cmd *first_free_load_cmd; + struct load_cmd *first_load_cmd; + struct load_cmd *last_load_cmd; + + /* Threads */ + u64 loader_threads_count; + struct sys_thread loader_threads[MAX_LOADER_THREADS]; + struct sys_thread evictor_thread; } G = { 0 }, DEBUG_ALIAS(G, G_sheet); -GLOBAL READONLY struct sheet g_sheet_nil = { 0 }; +GLOBAL READONLY struct sheet g_sheet_loading = { + .loading = true +}; /* ========================== * * Thread local state @@ -117,6 +129,9 @@ GLOBAL THREAD_LOCAL_VAR_DEF(tl_sheet_tctx, struct sheet_tctx, sheet_tctx_alloc, * Startup * ========================== */ +INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(sheet_shutdown); +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_loader_thread_entry_point, arg); + struct sheet_startup_receipt sheet_startup(struct work_startup_receipt *work_sr, struct asset_cache_startup_receipt *asset_cache_sr, struct resource_startup_receipt *resource_sr) @@ -125,16 +140,52 @@ struct sheet_startup_receipt sheet_startup(struct work_startup_receipt *work_sr, (UNUSED)asset_cache_sr; (UNUSED)resource_sr; - G.load_cmd_store.arena = arena_alloc(GIGABYTE(64)); - G.load_cmd_store.mutex = sys_mutex_alloc(); - G.cache.rw_mutex = sys_rw_mutex_alloc(); G.cache.arena = arena_alloc(GIGABYTE(64)); G.cache.buckets = arena_push_array_zero(&G.cache.arena, struct cache_node *, CACHE_BUCKETS_COUNT); + G.load_cmd_arena = arena_alloc(GIGABYTE(64)); + G.load_cmd_mutex = sys_mutex_alloc(); + G.load_cmd_or_shutdown_cv = sys_condition_variable_alloc(); + + /* Startup threads */ + { + struct temp_arena scratch = scratch_begin_no_conflict(); + G.loader_threads_count = clamp_i64(1, MAX_LOADER_THREADS, sys_num_logical_processors() - 1); + for (u64 i = 0; i < G.loader_threads_count; ++i) { + struct string thread_name = string_format(scratch.arena, + STR("[P0] Sheet loader thread %F"), + FMT_UINT(i)); + G.loader_threads[i] = sys_thread_alloc(sheet_loader_thread_entry_point, NULL, thread_name); + } + scratch_end(scratch); + } + //G.evictor_thread = sys_thread_alloc(sheet_evictor_thread_entry_point, NULL, STR("[P0] Sheet evictor thread")); + + app_register_exit_callback(&sheet_shutdown); + return (struct sheet_startup_receipt) { 0 }; } +INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(sheet_shutdown) +{ + __prof; + + /* Signal shutdown */ + sys_mutex_lock(&G.load_cmd_mutex); + { + G.shutdown = true; + sys_condition_variable_broadcast(&G.load_cmd_or_shutdown_cv); + } + sys_mutex_unlock(&G.load_cmd_mutex); + + /* Wait on threads */ + for (u64 i = 0; i < G.loader_threads_count; ++i) { + sys_thread_wait_release(&G.loader_threads[i]); + } + //sys_thread_wait_release(&G.evictor_thread); +} + /* ========================== * * Tag * ========================== */ @@ -163,7 +214,7 @@ struct sheet_tag sheet_tag_from_path(struct string path) { struct sheet_tag res = { 0 }; res.hash = HASH_FNV64_SEED; - res.hash = hash_fnv64(&res.hash, BUFFER_FROM_STRING(path)); + res.hash = hash_fnv64(res.hash, BUFFER_FROM_STRING(path)); res.path = path; return res; } @@ -175,44 +226,6 @@ INTERNAL struct sheet_tag sheet_tag_copy(struct arena *arena, struct sheet_tag s return res; } -/* ========================== * - * Load cmd - * ========================== */ - -#if 0 -INTERNAL void push_load_cmd(struct cache_node *n, struct sheet_tag tag) -{ - sys_mutex_lock(&G.load_cmd_store.mutex); - { - struct load_cmd *cmd = arena_push(&G.load_cmd_store.arena, struct load_cmd); - cmd->node = n; - cmd->tag = tag; - { - u64 copy_len = min_u64(tag.path.len, ARRAY_COUNT(cmd->tag_path_text)); - cmd->tag.path.text = cmd->path_text_dest; - MEMCPY(cmd->tag.path.text, tag.path.text, copy_len); - } - } - sys_mutex_unlock(&G.load_cmd_store.mutex); -} - -INTERNAL struct load_cmd_array pop_load_cmds(struct arena *arena) -{ - struct load_cmd_array array = { 0 }; - sys_mutex_lock(&G.load_cmd_store.mutex); - { - struct buffer cmds_buff = arena_to_buffer(&G.load_cmd_store.arena); - arena_align(arena, alignof(struct load_cmd)); - array.cmds = (struct load_cmd *)arena_push_array(arena, u8, cmds_buff.size); - array.count = cmds_buff.size / sizeof(struct load_cmd); - MEMCPY(array.cmds, cmds_buff.data, cmds_buff.size); - arena_reset(&G.load_cmd_store.arena); - } - sys_mutex_unlock(&G.load_cmd_store.mutex); - return array; -} -#endif - /* ========================== * * Load * ========================== */ @@ -369,8 +382,6 @@ void sheet_scope_end(struct sheet_scope *scope) * Cache interface * ========================== */ -/* FIXME: locking */ - INTERNAL struct cache_node *node_lookup_touch(struct sheet_scope *scope, struct sheet_tag tag) { struct cache_node **n_next = NULL; @@ -418,7 +429,7 @@ INTERNAL struct cache_node *node_lookup_touch(struct sheet_scope *scope, struct INTERNAL struct sheet *sheet_from_tag_internal(struct sheet_scope *scope, struct sheet_tag tag, b32 await) { - struct sheet *res = &g_sheet_nil; + struct sheet *res = &g_sheet_loading; struct cache_node *n = node_lookup_touch(scope, tag); @@ -427,13 +438,39 @@ INTERNAL struct sheet *sheet_from_tag_internal(struct sheet_scope *scope, struct res = &n->sheet; } else if (state == CACHE_NODE_STATE_NONE) { if (atomic_u32_eval_compare_exchange(&n->state, CACHE_NODE_STATE_NONE, CACHE_NODE_STATE_QUEUED) == CACHE_NODE_STATE_NONE) { + /* Node is new, load sheet */ if (await) { sheet_load(n, tag); res = &n->sheet; } else { - /* TODO */ - /* Node is new, push load command. */ - //push_load_cmd(n, tag); + sys_mutex_lock(&G.load_cmd_mutex); + { + /* Allocate cmd */ + struct load_cmd *cmd = NULL; + if (G.first_free_load_cmd) { + cmd = G.first_free_load_cmd; + G.first_free_load_cmd = cmd->next_free; + } else { + cmd = arena_push(&G.load_cmd_arena, struct load_cmd); + } + + /* Initialize cmd */ + cmd->cache_node = n; + cmd->tag = tag; + { + u64 copy_len = min_u64(tag.path.len, ARRAY_COUNT(cmd->tag_path_buff)); + cmd->tag.path.text = cmd->tag_path_buff; + MEMCPY(cmd->tag.path.text, tag.path.text, copy_len); + } + + /* Add cmd to queue */ + *(G.last_load_cmd ? &G.last_load_cmd->next : &G.first_load_cmd) = cmd; + G.last_load_cmd = cmd; + + /* Signal work ready */ + sys_condition_variable_signal(&G.load_cmd_or_shutdown_cv); + } + sys_mutex_unlock(&G.load_cmd_mutex); } } @@ -470,6 +507,55 @@ struct sheet_span sheet_get_span(struct sheet *sheet, struct string name) struct sheet_frame sheet_get_frame(struct sheet *sheet, u32 index) { - index = min_u32(sheet->frames_count - 1, index); - return sheet->frames[index]; + if (sheet->frames_count > 0) { + index = min_u32(sheet->frames_count - 1, index); + return sheet->frames[index]; + } else { + return (struct sheet_frame) { + .index = 0, + .duration = 0.1, + .clip = CLIP_ALL + }; + } +} + +/* ========================== * + * Loader thread + * ========================== */ + +INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_loader_thread_entry_point, arg) +{ + (UNUSED)arg; + + while (true) { + sys_mutex_lock(&G.load_cmd_mutex); + + if (G.shutdown) { + /* Thread shutdown */ + sys_mutex_unlock(&G.load_cmd_mutex); + break; + } else if (!G.first_load_cmd) { + /* Wait for work */ + sys_condition_variable_wait(&G.load_cmd_or_shutdown_cv, &G.load_cmd_mutex); + } + + if (G.first_load_cmd) { + /* Pull cmd from queue */ + struct load_cmd *cmd = G.first_load_cmd; + G.first_load_cmd = cmd->next; + + /* Do work (temporarily unlock) */ + sys_mutex_unlock(&G.load_cmd_mutex); + { + sheet_load(cmd->cache_node, cmd->tag); + } + sys_mutex_lock(&G.load_cmd_mutex); + + /* Free cmd */ + cmd->next_free = G.first_free_load_cmd; + G.first_free_load_cmd = cmd; + } + + sys_mutex_unlock(&G.load_cmd_mutex); + } } diff --git a/src/sheet.h b/src/sheet.h index 89d496b3..6398b19c 100644 --- a/src/sheet.h +++ b/src/sheet.h @@ -15,6 +15,7 @@ struct sheet_tag { }; struct sheet { + b32 loading; struct v2 image_size; struct v2 frame_size; u32 frames_count; diff --git a/src/string.c b/src/string.c index fe01266d..462b1c6a 100644 --- a/src/string.c +++ b/src/string.c @@ -429,7 +429,7 @@ struct string string_formatv(struct arena *arena, struct string fmt, va_list arg } break; case FMT_TYPE_FLOAT: { - parsed_str = string_from_float(arena, arg.value.f, arg.precision); + parsed_str = string_from_float(arena, arg.value.f.val, arg.value.f.precision); } break; case FMT_TYPE_END: { diff --git a/src/string.h b/src/string.h index 1c09a035..4a91923f 100644 --- a/src/string.h +++ b/src/string.h @@ -60,9 +60,11 @@ struct fmt_arg { u64 uint; i64 sint; void *ptr; - f64 f; + struct { + f64 val; + u32 precision; + } f; } value; - u32 precision; }; #define FMT_END (struct fmt_arg) {.type = FMT_TYPE_END} @@ -74,7 +76,7 @@ struct fmt_arg { #define FMT_HEX(v) (struct fmt_arg) {.type = FMT_TYPE_HEX, .value.uint = (v)} #define FMT_PTR(v) (struct fmt_arg) {.type = FMT_TYPE_PTR, .value.ptr = (v)} #define FMT_FLOAT(v) FMT_FLOAT_P(v, DEFAULT_FMT_PRECISION) -#define FMT_FLOAT_P(v, p) (struct fmt_arg) {.type = FMT_TYPE_FLOAT, .value.f = (v), .precision = p} +#define FMT_FLOAT_P(v, p) (struct fmt_arg) {.type = FMT_TYPE_FLOAT, .value.f.val = (v), .value.f.precision = p} #define string_format(arena, fmt, ...) _string_format(arena, fmt, __VA_ARGS__, FMT_END) struct string _string_format(struct arena *arena, struct string fmt, ...); diff --git a/src/user.c b/src/user.c index ad0c1014..334af61f 100644 --- a/src/user.c +++ b/src/user.c @@ -771,7 +771,7 @@ INTERNAL void user_update(void) { struct sheet_tag sheet_tag = ent->sprite_sheet_tag; - struct sheet *sheet = sheet_from_tag_await(sheet_frame_scope, sheet_tag); + struct sheet *sheet = sheet_from_tag_async(sheet_frame_scope, sheet_tag); struct sheet_span span = sheet_get_span(sheet, ent->sprite_span_name); u64 frame_index = span.start + ent->animation_frame; diff --git a/src/work.c b/src/work.c index 77e149cd..b490eac8 100644 --- a/src/work.c +++ b/src/work.c @@ -400,11 +400,13 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(worker_thread_entry_point, thread_data) break; } - /* Wait */ - sys_condition_variable_wait(&G.cv, &G.mutex); + if (!G.scheduled_work_head) { + /* Wait for work */ + sys_condition_variable_wait(&G.cv, &G.mutex); + } /* Do work from top */ - while (G.scheduled_work_head) { + if (G.scheduled_work_head) { struct work *work = G.scheduled_work_head; if (work) { __profscope(work_pool_task); @@ -414,7 +416,6 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(worker_thread_entry_point, thread_data) } } } - sys_mutex_unlock(&G.mutex); } }