#include "resource.h" #include "app.h" #include "arena.h" #include "tar.h" #include "incbin.h" #include "util.h" /* ========================== * * Global data * ========================== */ #if RESOURCES_EMBEDDED # include "inc.h" #endif /* Add resource data to binary */ GLOBAL struct { struct arena *arena; #if RESOURCES_EMBEDDED struct tar_archive archive; #endif #if RESOURCE_RELOADING struct sys_watch *watch; struct atomic32 watch_shutdown; struct snc_counter watch_jobs_counter; struct snc_mutex watch_dispatcher_mutex; struct arena *watch_dispatcher_info_arena; struct sys_watch_info_list watch_dispatcher_info_list; struct snc_cv watch_dispatcher_cv; struct snc_mutex watch_callbacks_mutex; resource_watch_callback *watch_callbacks[64]; u64 num_watch_callbacks; #endif } G = ZI, DEBUG_ALIAS(G, G_resource); /* ========================== * * Startup * ========================== */ #if RESOURCE_RELOADING INTERNAL SYS_JOB_DEF(resource_watch_monitor_job, _); INTERNAL SYS_JOB_DEF(resource_watch_dispatcher_job, _); INTERNAL SYS_EXIT_FUNC(resource_shutdown); #endif struct resource_startup_receipt resource_startup(void) { __prof; G.arena = arena_alloc(GIBI(64)); #if RESOURCES_EMBEDDED struct string embedded_data = inc_res_tar(); if (embedded_data.len <= 0) { sys_panic(LIT("No embedded resources found")); } G.archive = tar_parse(G.arena, embedded_data, LIT("")); #else /* Ensure we have the right working directory */ if (!sys_is_dir(LIT("res"))) { sys_panic(LIT("Resource directory \"res\" not found. Make sure the executable is being launched from the correct working directory.")); } #endif #if RESOURCE_RELOADING G.watch = sys_watch_alloc(LIT("res")); G.watch_dispatcher_info_arena = arena_alloc(GIBI(64)); sys_run(1, resource_watch_monitor_job, 0, SYS_POOL_FLOATING, SYS_PRIORITY_LOW, &G.watch_jobs_counter); sys_run(1, resource_watch_dispatcher_job, 0, SYS_POOL_BACKGROUND, SYS_PRIORITY_LOW, &G.watch_jobs_counter); sys_on_exit(&resource_shutdown); #endif return (struct resource_startup_receipt) { 0 }; } /* ========================== * * Open / close * ========================== */ struct resource resource_open(struct string name) { __prof; #if RESOURCES_EMBEDDED struct resource res = ZI; struct tar_entry *entry = tar_get(&G.archive, name); if (entry) { res._data = entry->data; res._name = entry->file_name; res._exists = 1; } return res; #else struct resource res = ZI; if (name.len < countof(res._name_text)) { u8 path_text[RESOURCE_NAME_LEN_MAX + (sizeof("res/") - 1)]; struct string path = ZI; { path_text[0] = 'r'; path_text[1] = 'e'; path_text[2] = 's'; path_text[3] = '/'; u64 path_text_len = 4; MEMCPY(path_text + path_text_len, name.text, name.len); path_text_len += name.len; path = STRING(path_text_len, path_text); } struct sys_file file = sys_file_open_read_wait(path); struct sys_file_map file_map = ZI; struct string data = ZI; if (file.valid) { file_map = sys_file_map_open_read(file); if (file_map.valid) { data = sys_file_map_data(file_map); } else { sys_file_map_close(file_map); } } else { sys_file_close(file); } res._exists = file.valid && file_map.valid; res._data = data; res._file = file; res._file_map = file_map; res._name_len = name.len; MEMCPY(res._name_text, name.text, name.len); } else { ASSERT(0); } return res; #endif } #if !RESOURCES_EMBEDDED void resource_close(struct resource *res_ptr) { sys_file_map_close(res_ptr->_file_map); sys_file_close(res_ptr->_file); } #endif /* ========================== * * Watch * ========================== */ #if RESOURCE_RELOADING INTERNAL SYS_EXIT_FUNC(resource_shutdown) { __prof; atomic32_fetch_set(&G.watch_shutdown, 1); { struct snc_lock lock = snc_lock_e(&G.watch_dispatcher_mutex); snc_cv_signal(&G.watch_dispatcher_cv, I32_MAX); sys_watch_wake(G.watch); snc_unlock(&lock); } snc_counter_wait(&G.watch_jobs_counter); } void resource_register_watch_callback(resource_watch_callback *callback) { struct snc_lock lock = snc_lock_e(&G.watch_callbacks_mutex); { if (G.num_watch_callbacks < countof(G.watch_callbacks)) { G.watch_callbacks[G.num_watch_callbacks++] = callback; } else { sys_panic(LIT("Max resource watch callbacks reached")); } } snc_unlock(&lock); } INTERNAL SYS_JOB_DEF(resource_watch_monitor_job, _) { (UNUSED)_; struct arena_temp scratch = scratch_begin_no_conflict(); while (!atomic32_fetch(&G.watch_shutdown)) { struct arena_temp temp = arena_temp_begin(scratch.arena); struct sys_watch_info_list res = sys_watch_read_wait(temp.arena, G.watch); if (res.first && !atomic32_fetch(&G.watch_shutdown)) { struct snc_lock lock = snc_lock_e(&G.watch_dispatcher_mutex); { struct sys_watch_info_list list_part = sys_watch_info_copy(G.watch_dispatcher_info_arena, res); if (G.watch_dispatcher_info_list.last) { G.watch_dispatcher_info_list.last->next = list_part.first; list_part.first->prev = G.watch_dispatcher_info_list.last; G.watch_dispatcher_info_list.last = list_part.last; } else { G.watch_dispatcher_info_list = list_part; } } snc_cv_signal(&G.watch_dispatcher_cv, I32_MAX); snc_unlock(&lock); } arena_temp_end(temp); } scratch_end(scratch); } /* NOTE: We separate the responsibilities of monitoring directory changes * & dispatching watch callbacks into two separate jobs so that we can delay * the dispatch, allowing for deduplication of file modification notifications. */ #define WATCH_DISPATCHER_DELAY_SECONDS 0.050 #define WATCH_DISPATCHER_DEDUP_DICT_BINS 128 struct resource_watch_callback_job_sig { struct string name; resource_watch_callback **callbacks; }; INTERNAL SYS_JOB_DEF(resource_watch_callback_job, job) { __prof; struct resource_watch_callback_job_sig *sig = job.sig; struct string name = sig->name; resource_watch_callback *callback = sig->callbacks[job.id]; callback(name); } INTERNAL SYS_JOB_DEF(resource_watch_dispatcher_job, _) { (UNUSED)_; struct arena_temp scratch = scratch_begin_no_conflict(); struct snc_lock watch_dispatcher_lock = snc_lock_e(&G.watch_dispatcher_mutex); while (!atomic32_fetch(&G.watch_shutdown)) { snc_cv_wait(&G.watch_dispatcher_cv, &watch_dispatcher_lock); if (!atomic32_fetch(&G.watch_shutdown) && G.watch_dispatcher_info_arena->pos > 0) { __profn("Dispatch resource watch callbacks"); /* Unlock and sleep a bit so duplicate events pile up */ { __profn("Delay"); snc_unlock(&watch_dispatcher_lock); sys_wait(0, 0, 0, NS_FROM_SECONDS(WATCH_DISPATCHER_DELAY_SECONDS)); watch_dispatcher_lock = snc_lock_e(&G.watch_dispatcher_mutex); } if (!atomic32_fetch(&G.watch_shutdown)) { struct arena_temp temp = arena_temp_begin(scratch.arena); /* Pull watch info from queue */ struct sys_watch_info_list watch_info_list = sys_watch_info_copy(temp.arena, G.watch_dispatcher_info_list); MEMZERO_STRUCT(&G.watch_dispatcher_info_list); arena_reset(G.watch_dispatcher_info_arena); /* Build callbacks array */ u64 num_callbacks = 0; resource_watch_callback **callbacks = 0; struct snc_lock callbacks_lock = snc_lock_s(&G.watch_callbacks_mutex); { num_callbacks = G.num_watch_callbacks; callbacks = arena_push_array_no_zero(temp.arena, resource_watch_callback *, num_callbacks); for (u64 i = 0; i < num_callbacks; ++i) { callbacks[i] = G.watch_callbacks[i]; } } snc_unlock(&callbacks_lock); /* Unlock and run callbacks */ snc_unlock(&watch_dispatcher_lock); { struct dict *dedup_dict = dict_init(temp.arena, WATCH_DISPATCHER_DEDUP_DICT_BINS); for (struct sys_watch_info *info = watch_info_list.first; info; info = info->next) { __profn("Dispatch"); /* Do not run callbacks for the same file more than once */ b32 skip = 0; u64 hash = hash_fnv64(HASH_FNV64_BASIS, info->name); if (dict_get(dedup_dict, hash) == 1) { skip = 1; } else { dict_set(temp.arena, dedup_dict, hash, 1); } if (!skip) { struct resource_watch_callback_job_sig sig = ZI; sig.name = info->name; sig.callbacks = callbacks; struct snc_counter counter = ZI; sys_run(num_callbacks, resource_watch_callback_job, &sig, SYS_POOL_BACKGROUND, SYS_PRIORITY_LOW, &counter); snc_counter_wait(&counter); } } } watch_dispatcher_lock = snc_lock_e(&G.watch_dispatcher_mutex); arena_temp_end(temp); } } } scratch_end(scratch); } #endif