309 lines
10 KiB
C
309 lines
10 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 atomic32 watch_shutdown;
|
|
|
|
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_THREAD_DEF(resource_watch_monitor_thread_entry_point, _);
|
|
INTERNAL SYS_THREAD_DEF(resource_watch_dispatcher_thread_entry_point, _);
|
|
INTERNAL SYS_EXIT_FUNC(resource_shutdown);
|
|
#endif
|
|
|
|
struct resource_startup_receipt resource_startup(void)
|
|
{
|
|
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_on_exit(&resource_shutdown);
|
|
G.resource_watch_monitor_thread = sys_thread_alloc(resource_watch_monitor_thread_entry_point, 0, LIT("Resource watch monitor"), PROF_THREAD_GROUP_IO);
|
|
G.resource_watch_dispatch_thread = sys_thread_alloc(resource_watch_dispatcher_thread_entry_point, 0, LIT("Resource watch dispatcher"), PROF_THREAD_GROUP_IO);
|
|
#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);
|
|
}
|
|
|
|
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 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_THREAD_DEF(resource_watch_monitor_thread_entry_point, _)
|
|
{
|
|
(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_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 threads 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_THREAD_DEF(resource_watch_dispatcher_thread_entry_point, _)
|
|
{
|
|
(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
|