resource reloading via file change notifications

This commit is contained in:
jacob 2025-05-13 00:41:44 -05:00
parent abac85d324
commit 86a696a70a
8 changed files with 439 additions and 89 deletions

View File

@ -18,8 +18,8 @@
#endif
/* If we are not compiling in developer mode, assume resources are embedded as
* a tar archive in the executable. Otherwise, look for resources in the file
* system. */
* a tar archive in the executable. Otherwise, assume resources are files on
* disk. */
#define RESOURCES_EMBEDDED (!DEVELOPER)
#define RESOURCE_RELOADING (DEVELOPER && !RESOURCES_EMBEDDED)
@ -28,7 +28,7 @@
#define IMAGE_PIXELS_PER_UNIT 128.0
/* How many ticks back in time should the user blend between?
/* How many ticks back in time should the user thread blend between?
* <Delay> = <USER_INTERP_RATIO> * <Tick interval>
* E.g: At 1.5, the user thread will render 75ms back in time if the sim runs at 50tps
*/

View File

@ -59,67 +59,67 @@ void log_register_callback(log_event_callback_func *func);
#if LOG_LEVEL(LOG_LEVEL_CRITICAL)
# if LOG_INCLUDE_SOURCE_LOCATION
# define log_critical(msg) _log(LOG_LEVEL_CRITICAL, LIT(__FILE__), __LINE__, msg)
# define logf_critical(fmt, ...) _logf(LOG_LEVEL_CRITICAL, LIT(__FILE__), __LINE__, LIT(fmt) , ## __VA_ARGS__, FMT_END)
# define logf_critical(fmt_lit, ...) _logf(LOG_LEVEL_CRITICAL, LIT(__FILE__), __LINE__, LIT(fmt_lit) , ## __VA_ARGS__, FMT_END)
# else
# define log_critical(msg) _log(LOG_LEVEL_CRITICAL, msg)
# define logf_critical(fmt, ...) _logf(LOG_LEVEL_CRITICAL, LIT(fmt) , ## __VA_ARGS__, FMT_END)
# define logf_critical(fmt_lit, ...) _logf(LOG_LEVEL_CRITICAL, LIT(fmt_lit) , ## __VA_ARGS__, FMT_END)
# endif
#else
# define log_critical(msg)
# define logf_critical(fmt, ...)
# define logf_critical(fmt_lit, ...)
#endif
#if LOG_LEVEL(LOG_LEVEL_ERROR)
# if LOG_INCLUDE_SOURCE_LOCATION
# define log_error(msg) _log(LOG_LEVEL_ERROR, LIT(__FILE__), __LINE__, msg)
# define logf_error(fmt, ...) _logf(LOG_LEVEL_ERROR, LIT(__FILE__), __LINE__, LIT(fmt) , ## __VA_ARGS__, FMT_END)
# define logf_error(fmt_lit, ...) _logf(LOG_LEVEL_ERROR, LIT(__FILE__), __LINE__, LIT(fmt_lit) , ## __VA_ARGS__, FMT_END)
# else
# define log_error(msg) _log(LOG_LEVEL_ERROR, msg)
# define logf_error(fmt, ...) _logf(LOG_LEVEL_ERROR, LIT(fmt) , ## __VA_ARGS__, FMT_END)
# define logf_error(fmt_lit, ...) _logf(LOG_LEVEL_ERROR, LIT(fmt_lit) , ## __VA_ARGS__, FMT_END)
# endif
#else
# define log_error(msg)
# define logf_error(fmt, ...)
# define logf_error(fmt_lit, ...)
#endif
#if LOG_LEVEL(LOG_LEVEL_WARNING)
# if LOG_INCLUDE_SOURCE_LOCATION
# define log_warning(msg) _log(LOG_LEVEL_WARNING, LIT(__FILE__), __LINE__, msg)
# define logf_warning(fmt, ...) _logf(LOG_LEVEL_WARNING, LIT(__FILE__), __LINE__, LIT(fmt) , ## __VA_ARGS__, FMT_END)
# define logf_warning(fmt_lit, ...) _logf(LOG_LEVEL_WARNING, LIT(__FILE__), __LINE__, LIT(fmt_lit) , ## __VA_ARGS__, FMT_END)
# else
# define log_warning(msg) _log(LOG_LEVEL_WARNING, msg)
# define logf_warning(fmt, ...) _logf(LOG_LEVEL_WARNING, LIT(fmt) , ## __VA_ARGS__, FMT_END)
# define logf_warning(fmt_lit, ...) _logf(LOG_LEVEL_WARNING, LIT(fmt_lit) , ## __VA_ARGS__, FMT_END)
# endif
#else
# define log_warning(msg)
# define logf_warning(fmt, ...)
# define logf_warning(fmt_lit, ...)
#endif
#if LOG_LEVEL(LOG_LEVEL_DEBUG)
# if LOG_INCLUDE_SOURCE_LOCATION
# define log_debug(msg) _log(LOG_LEVEL_DEBUG, LIT(__FILE__), __LINE__, msg)
# define logf_debug(fmt, ...) _logf(LOG_LEVEL_DEBUG, LIT(__FILE__), __LINE__, LIT(fmt) , ## __VA_ARGS__, FMT_END)
# define logf_debug(fmt_lit, ...) _logf(LOG_LEVEL_DEBUG, LIT(__FILE__), __LINE__, LIT(fmt_lit) , ## __VA_ARGS__, FMT_END)
# else
# define log_debug(msg) _log(LOG_LEVEL_DEBUG, msg)
# define logf_debug(fmt, ...) _logf(LOG_LEVEL_DEBUG, LIT(fmt) , ## __VA_ARGS__, FMT_END)
# define logf_debug(fmt_lit, ...) _logf(LOG_LEVEL_DEBUG, LIT(fmt_lit) , ## __VA_ARGS__, FMT_END)
# endif
#else
# define log_debug(msg)
# define logf_debug(fmt, ...)
# define logf_debug(fmt_lit, ...)
#endif
#if LOG_LEVEL(LOG_LEVEL_INFO)
# if LOG_INCLUDE_SOURCE_LOCATION
# define log_info(msg) _log(LOG_LEVEL_INFO, LIT(__FILE__), __LINE__, msg)
# define logf_info(fmt, ...) _logf(LOG_LEVEL_INFO, LIT(__FILE__), __LINE__, LIT(fmt) , ## __VA_ARGS__, FMT_END)
# define logf_info(fmt_lit, ...) _logf(LOG_LEVEL_INFO, LIT(__FILE__), __LINE__, LIT(fmt_lit) , ## __VA_ARGS__, FMT_END)
# else
# define log_info(msg) _log(LOG_LEVEL_INFO, msg)
# define logf_info(fmt, ...) _logf(LOG_LEVEL_INFO, LIT(fmt) , ## __VA_ARGS__, FMT_END)
# define logf_info(fmt_lit, ...) _logf(LOG_LEVEL_INFO, LIT(fmt_lit) , ## __VA_ARGS__, FMT_END)
# endif
#else
# define log_info(msg)
# define logf_info(fmt, ...)
# define logf_info(fmt_lit, ...)
#endif
/* ========================== *

View File

@ -26,6 +26,7 @@
#pragma comment(lib, "d3dcompiler")
#define MAX_CMD_BUFFERS 1024
#define SHADER_INFO_LOOKUP_BINS 64
/* FIXME: Enable this and resolve unreleased references */
//#define D3D11_DEBUG RTC
@ -40,10 +41,6 @@ struct dx11_shader {
ID3D11InputLayout *input_layout;
ID3D11VertexShader *vs;
ID3D11PixelShader *ps;
#if RESOURCE_RELOADING
struct sys_datetime last_modified_load_attempt; /* The last modified time of the shader src file that a load has been attempted on */
#endif
};
struct dx11_constant_buffer_data {
@ -136,6 +133,11 @@ struct dx11_shader_desc {
char *name_cstr;
u32 vertex_size;
D3D11_INPUT_ELEMENT_DESC input_layout_desc[64]; /* NULL terminated array */
/* Internal */
#if RESOURCE_RELOADING
struct atomic_i32 should_reload;
#endif
};
GLOBAL struct {
@ -166,6 +168,7 @@ GLOBAL struct {
struct dx11_shader shaders[NUM_SHADERS];
struct dx11_shader_desc shader_info[NUM_SHADERS];
struct fixed_dict shader_info_lookup;
} G = ZI, DEBUG_ALIAS(G, G_renderer_d3d11);
@ -210,7 +213,8 @@ INTERNAL void init_shader_table(void)
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }
}
},
{ 0 }
};
/* Grid shader layout */
@ -224,8 +228,16 @@ INTERNAL void init_shader_table(void)
{ "THICKNESS", 0, DXGI_FORMAT_R32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "SPACING", 0, DXGI_FORMAT_R32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "OFFSET", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }
}
},
{ 0 }
};
G.shader_info_lookup = fixed_dict_init(&G.arena, SHADER_INFO_LOOKUP_BINS);
for (u64 i = SHADER_NONE + 1; i < ARRAY_COUNT(G.shader_info); ++i) {
struct dx11_shader_desc *desc = &G.shader_info[i];
struct string name = string_from_cstr_no_limit(desc->name_cstr);
fixed_dict_set(&G.arena, &G.shader_info_lookup, name, desc);
}
}
/* If shader compilation fails, then error string is returned allocated on `arena` */
@ -317,58 +329,59 @@ INTERNAL void shader_release(struct dx11_shader *shader)
}
}
INTERNAL void reload_shaders(void)
INTERNAL void reload_shader(struct dx11_shader *old_shader, struct dx11_shader_desc *desc)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
for (u32 i = SHADER_NONE + 1; i < NUM_SHADERS; ++i) {
struct dx11_shader_desc *desc = &G.shader_info[i];
{
struct string name = string_from_cstr_no_limit(desc->name_cstr);
if (!resource_exists(name)) {
sys_panic(string_format(scratch.arena, LIT("Could not find shader \"%F\""), FMT_STR(name)));
}
struct resource src_res = resource_open(name);
{
struct dx11_shader *old_shader = &G.shaders[i];
b32 should_load = !old_shader->valid;
#if RESOURCE_RELOADING
struct sys_datetime last_modified = resource_get_time(&src_res).modified;
if (!MEMEQ_STRUCT(&last_modified, &old_shader->last_modified_load_attempt)) {
should_load = true;
}
old_shader->last_modified_load_attempt = last_modified;
#endif
if (should_load) {
struct string error_msg = ZI;
if (resource_exists(name)) {
struct resource src_res = resource_open(name);
{
struct dx11_shader new_shader = ZI;
struct string error = shader_alloc(scratch.arena, &new_shader, desc, &src_res);
#if RESOURCE_RELOADING
new_shader.last_modified_load_attempt = last_modified;
#endif
if (error.len == 0) {
struct string comp_error = shader_alloc(scratch.arena, &new_shader, desc, &src_res);
if (comp_error.len == 0) {
if (old_shader->valid) {
shader_release(old_shader);
}
*old_shader = new_shader;
} else {
struct string error_msg = string_format(scratch.arena,
LIT("Failed to compile shader \"%F\":\n\n%F"),
FMT_STR(name),
FMT_STR(error));
if (old_shader->valid) {
log_error(error_msg);
} else {
sys_panic(error_msg);
}
error_msg = string_format(scratch.arena,
LIT("Failed to compile shader \"%F\":\n\n%F"),
FMT_STR(name),
FMT_STR(comp_error));
shader_release(&new_shader);
}
}
resource_close(&src_res);
} else {
error_msg = string_format(scratch.arena, LIT("Could not find shader \"%F\""), FMT_STR(name));
}
if (error_msg.len != 0) {
if (old_shader->valid) {
/* If shader failed to load but a working shader already exists, just error rather than panicking */
log_error(error_msg);
} else {
sys_panic(error_msg);
}
}
resource_close(&src_res);
}
scratch_end(scratch);
}
#if RESOURCE_RELOADING
INTERNAL RESOURCE_WATCH_CALLBACK_DEF(shader_resource_watch_callback, info)
{
struct string name = info->name;
struct dx11_shader_desc *desc = (struct dx11_shader_desc *)fixed_dict_get(&G.shader_info_lookup, name);
if (desc) {
logf_info("Shader source file \"%F\" has changed", FMT_STR(name));
atomic_i32_eval_exchange(&desc->should_reload, 1);
}
}
#endif
/* ========================== *
* Startup
* ========================== */
@ -580,9 +593,18 @@ struct renderer_startup_receipt renderer_startup(struct sys_window *window)
/* Init shaders */
logf_info("Compiling shaders");
reload_shaders();
for (u32 i = SHADER_NONE + 1; i < NUM_SHADERS; ++i) {
struct dx11_shader *shader = &G.shaders[i];
struct dx11_shader_desc *desc = &G.shader_info[i];
reload_shader(shader, desc);
}
logf_info("Finished compiling shaders");
/* Setup file change callbacks */
#if RESOURCE_RELOADING
resource_register_watch_callback(shader_resource_watch_callback);
#endif
return (struct renderer_startup_receipt) { 0 };
}
@ -902,24 +924,21 @@ struct renderer_texture renderer_backbuffer_recreate(struct v2i32 size)
void renderer_backbuffer_present(i32 vsync)
{
__prof;
#if RESOURCE_RELOADING
for (u64 i = SHADER_NONE + 1; i < NUM_SHADERS; ++i) {
struct dx11_shader_desc *desc = &G.shader_info[i];
if (atomic_i32_eval_compare_exchange(&desc->should_reload, 1, 0) == 1) {
reload_shader(&G.shaders[i], desc);
}
}
#endif
renderer_capture_image_for_profiler();
{
__profscope(IDXGISwapchain_Present);
IDXGISwapChain1_Present(G.swapchain, vsync, 0);
__profframe(0);
}
#if RESOURCE_RELOADING
{
i64 now_ns = sys_time_ns();
const i64 reload_interval_ns = NS_FROM_SECONDS(0.1);
static f32 last_reload_ns = 0;
if (now_ns >= (last_reload_ns + reload_interval_ns)) {
reload_shaders();
last_reload_ns = now_ns;
}
}
#endif
}
/* ========================== *

View File

@ -1,25 +1,50 @@
#include "resource.h"
#include "app.h"
#include "arena.h"
#include "tar.h"
#include "incbin.h"
#if RESOURCES_EMBEDDED
/* ========================== *
* Global data
* ========================== */
#include "inc.h"
#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_thread;
struct sys_mutex watch_callbacks_mutex;
b32 watch_stop;
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_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();
G.arena = arena_alloc(GIGABYTE(64));
if (embedded_data.len <= 0) {
sys_panic(LIT("No embedded resources found"));
}
@ -31,9 +56,20 @@ struct resource_startup_receipt resource_startup(void)
}
#endif
#if RESOURCE_RELOADING
G.watch_callbacks_mutex = sys_mutex_alloc();
app_register_exit_callback(&resource_shutdown);
G.resource_watch_thread = sys_thread_alloc(resource_watch_thread_entry_point, NULL, LIT("[P2] Resource watcher"));
#endif
return (struct resource_startup_receipt) { 0 };
}
/* ========================== *
* Open / close
* ========================== */
struct resource resource_open(struct string path)
{
__prof;
@ -70,13 +106,12 @@ void resource_close(struct resource *res_ptr)
sys_file_map_close(res_ptr->_file_map);
sys_file_close(res_ptr->_file);
}
struct sys_file_time resource_get_time(struct resource *res_ptr)
{
return sys_file_get_time(res_ptr->_file);
}
#endif
/* ========================== *
* Util
* ========================== */
b32 resource_exists(struct string path)
{
__prof;
@ -87,3 +122,73 @@ b32 resource_exists(struct string path)
return sys_is_file(path);
#endif
}
#if !RESOURCES_EMBEDDED
struct sys_file_time resource_get_time(struct resource *res_ptr)
{
return sys_file_get_time(res_ptr->_file);
}
#endif
/* ========================== *
* Watch
* ========================== */
#if RESOURCE_RELOADING
INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(resource_shutdown)
{
__prof;
/* Wait until any watch callbacks finish before shutting down */
struct sys_lock lock = sys_mutex_lock_e(&G.watch_callbacks_mutex);
{
G.watch_stop = true;
}
sys_mutex_unlock(&lock);
}
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_thread_entry_point, _)
{
(UNUSED)_;
struct temp_arena scratch = scratch_begin_no_conflict();
struct sys_watch dir_watch = sys_watch_alloc(LIT("res"));
/* NOTE: This thread is force-shutdown at the moment, however shutdown is guaranteed not to occur while the watch mutex is locked by the thread */
volatile i32 run = true;
while (run) {
struct temp_arena temp = arena_temp_begin(scratch.arena);
struct sys_watch_info *info = sys_watch_wait(temp.arena, &dir_watch);
{
struct sys_lock lock = sys_mutex_lock_s(&G.watch_callbacks_mutex);
if (!G.watch_stop) {
while (info) {
for (u64 i = 0; i < G.num_watch_callbacks; ++i) {
resource_watch_callback *callback = G.watch_callbacks[i];
callback(info);
}
info = info->next;
}
}
sys_mutex_unlock(&lock);
}
arena_temp_end(temp);
}
sys_watch_release(&dir_watch);
scratch_end(scratch);
}
#endif

View File

@ -23,18 +23,35 @@ struct resource_startup_receipt resource_startup(void);
struct resource resource_open(struct string path);
#define resource_get_data(res_ptr) (res_ptr)->_data
#if RESOURCES_EMBEDDED
#define resource_close(res_ptr) (UNUSED)res_ptr
#define resource_get_time(res_ptr) ((UNUSED)res_ptr, (struct sys_file_time) ZI)
#define resource_get_name(res_ptr) (res_ptr)->_file_name
#define resource_close(res_ptr)
#else
void resource_close(struct resource *res_ptr);
struct sys_file_time resource_get_time(struct resource *res_ptr);
#define resource_get_name(res_ptr) STRING((res_ptr)->_file_name_len, (res_ptr)->_file_name_text)
#endif
b32 resource_exists(struct string path);
#define resource_get_data(res_ptr) (res_ptr)->_data
#if RESOURCES_EMBEDDED
#define resource_get_time(res_ptr) (struct sys_file_time) ZI
#else
struct sys_file_time resource_get_time(struct resource *res_ptr);
#endif
#if RESOURCES_EMBEDDED
#define resource_get_name(res_ptr) (res_ptr)->_file_name
#else
#define resource_get_name(res_ptr) STRING((res_ptr)->_file_name_len, (res_ptr)->_file_name_text)
#endif
#define RESOURCE_WATCH_CALLBACK_DEF(func_name, arg_info) void func_name(struct sys_watch_info *arg_info)
typedef RESOURCE_WATCH_CALLBACK_DEF(resource_watch_callback, info);
#if RESOURCE_RELOADING
void resource_register_watch_callback(resource_watch_callback *callback);
#else
#define resource_register_watch_callback(callback)
#endif
#endif

View File

@ -256,6 +256,32 @@ struct sys_file_filter sys_file_filter_begin(struct arena *arena, struct string
b32 sys_file_filter_next(struct arena *arena, struct sys_file_filter *iter);
void sys_file_filter_end(struct sys_file_filter *iter);
/* ========================== *
* Watch
* ========================== */
enum sys_watch_info_kind {
SYS_WATCH_INFO_KIND_UNKNOWN,
SYS_WATCH_INFO_KIND_ADDED,
SYS_WATCH_INFO_KIND_REMOVED,
SYS_WATCH_INFO_KIND_MODIFIED
};
struct sys_watch {
u64 handle;
};
struct sys_watch_info {
enum sys_watch_info_kind kind;
struct string name;
struct sys_watch_info *next;
};
struct sys_watch sys_watch_alloc(struct string path);
void sys_watch_release(struct sys_watch *dw);
struct sys_watch_info *sys_watch_wait(struct arena *arena, struct sys_watch *dw);
/* ========================== *
* Window
* ========================== */

View File

@ -128,6 +128,11 @@ GLOBAL struct {
struct win32_thread *threads_last;
struct win32_thread *threads_first_free;
/* Watches */
struct sys_mutex watches_mutex;
struct arena watches_arena;
struct win32_watch *watches_first_free;
/* Windows */
WNDCLASSEXW window_class;
struct sys_mutex windows_mutex;
@ -663,6 +668,181 @@ void sys_file_filter_end(struct sys_file_filter *filter)
}
}
/* ========================== *
* Watch
* ========================== */
struct win32_watch {
HANDLE dir_handle;
struct win32_watch *next_free;
u64 dir_path_len;
u8 dir_path_text[1024];
u8 results_buff[KILOBYTE(64)];
};
struct sys_watch sys_watch_alloc(struct string dir_path)
{
struct temp_arena scratch = scratch_begin_no_conflict();
struct win32_watch *w32_watch = NULL;
{
struct sys_lock lock = sys_mutex_lock_e(&G.watches_mutex);
{
if (G.watches_first_free) {
w32_watch = G.watches_first_free;
G.watches_first_free = w32_watch->next_free;
} else {
w32_watch = arena_push(&G.watches_arena, struct win32_watch);
}
}
sys_mutex_unlock(&lock);
}
MEMZERO_STRUCT(w32_watch);
if (dir_path.len > 0 && dir_path.len < (ARRAY_COUNT(w32_watch->dir_path_text) - 1)) {
u64 dir_path_len = dir_path.len;
MEMCPY(w32_watch->dir_path_text, dir_path.text, dir_path_len);
for (u64 i = 0; i < dir_path_len; ++i) {
if (w32_watch->dir_path_text[i] == '\\') {
w32_watch->dir_path_text[i] = '/';
}
}
if (w32_watch->dir_path_text[dir_path_len - 1] != '/') {
w32_watch->dir_path_text[dir_path_len] = '/';
++dir_path_len;
}
w32_watch->dir_path_len = dir_path_len;
} else {
sys_panic(string_format(scratch.arena, LIT("Directory path too \"%F\" has invalid length"), FMT_STR(dir_path)));
}
wchar_t *dir_path_wstr = wstr_from_string(scratch.arena, dir_path);
w32_watch->dir_handle = CreateFileW(
dir_path_wstr,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
scratch_end(scratch);
struct sys_watch watch = ZI;
watch.handle = (u64)w32_watch;
return watch;
}
void sys_watch_release(struct sys_watch *dw)
{
struct win32_watch *w32_watch = (struct win32_watch *)dw->handle;
CloseHandle(w32_watch->dir_handle);
struct sys_lock lock = sys_mutex_lock_e(&G.watches_mutex);
{
w32_watch->next_free = G.watches_first_free;
G.watches_first_free = w32_watch;
}
sys_mutex_unlock(&lock);
}
struct sys_watch_info *sys_watch_wait(struct arena *arena, struct sys_watch *dw)
{
__prof;
struct win32_watch *w32_watch = (struct win32_watch *)dw->handle;
struct sys_watch_info *first_info = NULL;
struct sys_watch_info *last_info = NULL;
DWORD filter = FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_CREATION |
FILE_NOTIFY_CHANGE_SECURITY;
b32 done = false;
while (!done) {
DWORD num_bytes_returned = 0;
BOOL success = ReadDirectoryChangesW(w32_watch->dir_handle,
w32_watch->results_buff,
ARRAY_COUNT(w32_watch->results_buff),
true,
filter,
&num_bytes_returned,
NULL,
NULL);
if (success && num_bytes_returned > 0) {
i64 offset = 0;
while (!done) {
FILE_NOTIFY_INFORMATION *res = (FILE_NOTIFY_INFORMATION *)(w32_watch->results_buff + offset);
struct sys_watch_info *info = arena_push_zero(arena, struct sys_watch_info);
if (last_info) {
last_info->next = info;
} else {
first_info = info;
}
struct string16 name16 = ZI;
name16.text = res->FileName;
name16.len = res->FileNameLength / sizeof(wchar_t);
info->name = string_copy(arena, STRING(w32_watch->dir_path_len, w32_watch->dir_path_text));
info->name.len += string_from_string16(arena, name16).len;
for (u64 i = 0; i < info->name.len; ++i) {
if (info->name.text[i] == '\\') {
info->name.text[i] = '/';
}
}
switch (res->Action) {
case FILE_ACTION_ADDED:
{
info->kind = SYS_WATCH_INFO_KIND_ADDED;
} break;
case FILE_ACTION_REMOVED:
{
info->kind = SYS_WATCH_INFO_KIND_REMOVED;
} break;
case FILE_ACTION_MODIFIED:
{
info->kind = SYS_WATCH_INFO_KIND_MODIFIED;
} break;
case FILE_ACTION_RENAMED_OLD_NAME:
{
info->kind = SYS_WATCH_INFO_KIND_REMOVED;
} break;
case FILE_ACTION_RENAMED_NEW_NAME:
{
info->kind = SYS_WATCH_INFO_KIND_ADDED;
} break;
default:
{
info->kind = SYS_WATCH_INFO_KIND_UNKNOWN;
} break;
}
if (res->NextEntryOffset == 0) {
done = true;
} else {
offset += res->NextEntryOffset;
}
}
}
}
return first_info;
}
/* ========================== *
* Window
* ========================== */
@ -2100,6 +2280,10 @@ int CALLBACK wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev_instance,
G.threads_mutex = sys_mutex_alloc();
G.threads_arena = arena_alloc(GIGABYTE(64));
/* Set up watches */
G.watches_mutex = sys_mutex_alloc();
G.watches_arena = arena_alloc(GIGABYTE(64));
/* Set up windows */
G.windows_mutex = sys_mutex_alloc();
G.windows_arena = arena_alloc(GIGABYTE(64));

View File

@ -61,9 +61,8 @@ INTERNAL u64 str_oct_to_u64(struct string str)
/* `prefix` will be prepended to all file names in the archive
*
* NOTE: The resulting archive merely points into the supplied tar data. No
* copying is done. Accessing the archive assumes that the data it's derived
* from is valid (AKA open if from a file / memory map).
* NOTE: The resulting archive merely points into the supplied tar data, no
* copying is done. Accessing the archive assumes that the data string is still valid.
*/
struct tar_archive tar_parse(struct arena *arena, struct string data, struct string prefix)
{