resource reloading via file change notifications
This commit is contained in:
parent
abac85d324
commit
86a696a70a
@ -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
|
||||
*/
|
||||
|
||||
30
src/log.h
30
src/log.h
@ -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
|
||||
|
||||
/* ========================== *
|
||||
|
||||
@ -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 string error_msg = ZI;
|
||||
if (resource_exists(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 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,
|
||||
error_msg = string_format(scratch.arena,
|
||||
LIT("Failed to compile shader \"%F\":\n\n%F"),
|
||||
FMT_STR(name),
|
||||
FMT_STR(error));
|
||||
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);
|
||||
}
|
||||
shader_release(&new_shader);
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
|
||||
119
src/resource.c
119
src/resource.c
@ -1,25 +1,50 @@
|
||||
#include "resource.h"
|
||||
#include "app.h"
|
||||
#include "arena.h"
|
||||
#include "tar.h"
|
||||
#include "incbin.h"
|
||||
|
||||
#if RESOURCES_EMBEDDED
|
||||
/* ========================== *
|
||||
* 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_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
|
||||
|
||||
@ -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
|
||||
|
||||
26
src/sys.h
26
src/sys.h
@ -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
|
||||
* ========================== */
|
||||
|
||||
184
src/sys_win32.c
184
src/sys_win32.c
@ -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));
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user