power_play/src/resource.c

280 lines
9.4 KiB
C

#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_thread resource_watch_monitor_thread;
struct sys_thread resource_watch_dispatch_thread;
struct sys_watch watch;
struct atomic_i32 watch_shutdown;
struct sys_mutex watch_dispatcher_mutex;
struct arena watch_dispatcher_info_arena;
struct sys_watch_info_list watch_dispatcher_info_list;
struct sys_condition_variable watch_dispatcher_cv;
struct sys_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_THREAD_ENTRY_POINT_FUNC_DEF(resource_watch_monitor_thread_entry_point, _);
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(resource_watch_dispatcher_thread_entry_point, _);
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(resource_shutdown);
#endif
struct resource_startup_receipt resource_startup(void)
{
G.arena = arena_alloc(GIGABYTE(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_callbacks_mutex = sys_mutex_alloc();
G.watch_dispatcher_mutex = sys_mutex_alloc();
G.watch_dispatcher_info_arena = arena_alloc(GIGABYTE(64));
G.watch_dispatcher_cv = sys_condition_variable_alloc();
app_register_exit_callback(&resource_shutdown);
G.resource_watch_monitor_thread = sys_thread_alloc(resource_watch_monitor_thread_entry_point, NULL, LIT("[P2] Resource watch monitor"));
G.resource_watch_dispatch_thread = sys_thread_alloc(resource_watch_dispatcher_thread_entry_point, NULL, LIT("[P2] Resource watch dispatcher"));
#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 = true;
}
return res;
#else
struct resource res = ZI;
if (name.len < ARRAY_COUNT(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(false);
}
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 APP_EXIT_CALLBACK_FUNC_DEF(resource_shutdown)
{
__prof;
atomic_i32_eval_exchange(&G.watch_shutdown, 1);
sys_condition_variable_broadcast(&G.watch_dispatcher_cv);
sys_watch_wake(&G.watch);
sys_thread_wait_release(&G.resource_watch_dispatch_thread);
sys_thread_wait_release(&G.resource_watch_monitor_thread);
}
void resource_register_watch_callback(resource_watch_callback *callback)
{
struct sys_lock lock = sys_mutex_lock_e(&G.watch_callbacks_mutex);
{
if (G.num_watch_callbacks < ARRAY_COUNT(G.watch_callbacks)) {
G.watch_callbacks[G.num_watch_callbacks++] = callback;
} else {
sys_panic(LIT("Max resource watch callbacks reached"));
}
}
sys_mutex_unlock(&lock);
}
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(resource_watch_monitor_thread_entry_point, _)
{
(UNUSED)_;
struct temp_arena scratch = scratch_begin_no_conflict();
while (!atomic_i32_eval(&G.watch_shutdown)) {
struct temp_arena temp = arena_temp_begin(scratch.arena);
struct sys_watch_info_list res = sys_watch_wait(temp.arena, &G.watch);
if (res.first && !atomic_i32_eval(&G.watch_shutdown)) {
struct sys_lock lock = sys_mutex_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;
}
}
sys_mutex_unlock(&lock);
sys_condition_variable_broadcast(&G.watch_dispatcher_cv);
}
arena_temp_end(temp);
}
scratch_end(scratch);
}
/* NOTE: We separate the responsibilities of monitoring directory changes
* & dispatching watch callbacks into two separate threads so that we can delay
* the dispatch of these callbacks, allowing for deduplication of file
* modification notifications. */
#define WATCH_DISPATCHER_DELAY_SECONDS 0.050
#define WATCH_DISPATCHER_DEDUP_DICT_BINS 128
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(resource_watch_dispatcher_thread_entry_point, _)
{
(UNUSED)_;
struct temp_arena scratch = scratch_begin_no_conflict();
struct sys_lock watch_dispatcher_lock = sys_mutex_lock_e(&G.watch_dispatcher_mutex);
while (!atomic_i32_eval(&G.watch_shutdown)) {
sys_condition_variable_wait(&G.watch_dispatcher_cv, &watch_dispatcher_lock);
if (!atomic_i32_eval(&G.watch_shutdown) && G.watch_dispatcher_info_arena.pos > 0) {
/* Unlock and sleep a bit so duplicate events pile up */
{
sys_mutex_unlock(&watch_dispatcher_lock);
sys_sleep(WATCH_DISPATCHER_DELAY_SECONDS);
watch_dispatcher_lock = sys_mutex_lock_e(&G.watch_dispatcher_mutex);
}
if (!atomic_i32_eval(&G.watch_shutdown)) {
struct temp_arena 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);
/* Unlock and run callbacks */
sys_mutex_unlock(&watch_dispatcher_lock);
{
__profscope(run_resource_watch_callbacks);
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) {
/* Do not run callbacks for the same file more than once */
b32 skip = false;
u64 hash = hash_fnv64(HASH_FNV64_BASIS, info->name);
if (dict_get(&dedup_dict, hash) == 1) {
skip = true;
} else {
dict_set(temp.arena, &dedup_dict, hash, 1);
}
if (!skip) {
struct sys_lock callbacks_lock = sys_mutex_lock_s(&G.watch_callbacks_mutex);
for (u64 i = 0; i < G.num_watch_callbacks; ++i) {
resource_watch_callback *callback = G.watch_callbacks[i];
callback(info->name);
}
sys_mutex_unlock(&callbacks_lock);
}
}
}
watch_dispatcher_lock = sys_mutex_lock_e(&G.watch_dispatcher_mutex);
arena_temp_end(temp);
}
}
}
scratch_end(scratch);
}
#endif