twn_filewatch.c: file and directory change api, initial support for texture reload

This commit is contained in:
veclavtalica
2025-01-29 07:21:09 +03:00
parent 630c6fb5d4
commit 4b2a22bf3c
7 changed files with 246 additions and 29 deletions

View File

@ -9,6 +9,7 @@
#include "twn_main.c"
#include "twn_textures.c"
#include "twn_util.c"
#include "twn_filewatch.c"
#include "rendering/twn_circles.c"
#include "rendering/twn_draw.c"

171
src/twn_filewatch.c Normal file
View File

@ -0,0 +1,171 @@
#include "twn_filewatch_c.h"
#include "twn_util.h"
#include "twn_engine_context_c.h"
#include <x-watcher.h>
#include <stb_ds.h>
#include <SDL2/SDL.h>
struct FilewatchPathToCallback {
char *path;
FileatchCallback callback;
float tick_last_reported;
enum FilewatchAction action_pending : 3;
bool action_processed : 1;
};
static struct FilewatchPathToCallback *filewatch_directories;
static struct FilewatchPathToCallback *filewatch_files;
/* note: it gets rebuilt on every addition, as api is such */
/* TODO: test whether it's possible to miss on update while watcher is being rebuilt */
/* we might have to enumerate things, just to make sure */
static x_watcher *filewatcher;
static SDL_mutex *filewatcher_lock;
/* TODO: check whether path and p->path match? */
static void filewatch_dispatch(XWATCHER_FILE_EVENT event,
const char *path,
int context,
void *additional_data
) {
(void)additional_data; (void)path;
enum FilewatchAction action;
switch (event) {
case XWATCHER_FILE_CREATED:
action = FILEWATCH_ACTION_FILE_CREATED;
break;
case XWATCHER_FILE_REMOVED:
action = FILEWATCH_ACTION_FILE_DELETED;
break;
case XWATCHER_FILE_MODIFIED:
action = FILEWATCH_ACTION_FILE_MODIFIED;
break;
case XWATCHER_FILE_NONE:
case XWATCHER_FILE_OPENED:
case XWATCHER_FILE_RENAMED:
case XWATCHER_FILE_UNSPECIFIED:
case XWATCHER_FILE_ATTRIBUTES_CHANGED:
default:
return;
}
SDL_LockMutex(filewatcher_lock);
struct FilewatchPathToCallback *p;
if (context < 0)
p = &filewatch_files[-context - 1];
else
p = &filewatch_directories[context];
p->tick_last_reported = ctx.game.frame_number;
p->action_processed = false;
p->action_pending = action;
SDL_UnlockMutex(filewatcher_lock);
}
static bool filewatcher_rebuild(void) {
if (filewatcher)
xWatcher_destroy(filewatcher);
filewatcher = xWatcher_create();
if (!filewatcher) {
log_warn("Error creating xWatcher instance, no file watching is done");
return false;
}
int const filewatch_directories_len = arrlen(filewatch_directories);
for (int i = 0; i < filewatch_directories_len; ++i) {
xWatcher_reference ref = {
.path = filewatch_directories[i].path,
.callback_func = filewatch_dispatch,
.context = i,
};
if (!xWatcher_appendDir(filewatcher, &ref))
log_warn("Error watching dir contents: %s", filewatch_directories[i].path);
}
int const filewatch_files_len = arrlen(filewatch_files);
for (int i = 0; i < filewatch_files_len; ++i) {
xWatcher_reference ref = {
.path = filewatch_files[i].path,
.callback_func = filewatch_dispatch,
.context = -(i + 1), /* in negative range to allow inrefence */
};
if (!xWatcher_appendDir(filewatcher, &ref))
log_warn("Error watching file: %s", filewatch_files[i].path);
}
if (!xWatcher_start(filewatcher)) {
log_warn("Error creating xWatcher instance, no file watching is done");
xWatcher_destroy(filewatcher);
filewatcher = NULL;
return false;
}
return true;
}
bool filewatch_add_directory(char *dir, FileatchCallback callback) {
SDL_assert(dir && callback);
struct FilewatchPathToCallback const w = {
.callback = callback,
.path = SDL_strdup(dir),
.action_processed = true,
.tick_last_reported = ctx.game.frame_number,
};
arrpush(filewatch_directories, w);
return filewatcher_rebuild();
}
bool filewatch_add_file(char *filepath, FileatchCallback callback) {
SDL_assert(filepath && callback);
struct FilewatchPathToCallback const f = {
.callback = callback,
.path = SDL_strdup(filepath),
.action_processed = true,
.tick_last_reported = ctx.game.frame_number,
};
arrpush(filewatch_files, f);
return filewatcher_rebuild();
}
void filewatch_poll(void) {
SDL_LockMutex(filewatcher_lock);
int const filewatch_directories_len = arrlen(filewatch_directories);
for (int i = 0; i < filewatch_directories_len; ++i) {
if (!filewatch_directories[i].action_processed
&& (ctx.game.frame_number - filewatch_directories[i].tick_last_reported > FILEWATCH_MODIFIED_TICKS_MERGED)) {
SDL_assert(filewatch_directories[i].action_pending != FILEWATCH_ACTION_FILE_NONE);
filewatch_directories[i].callback(filewatch_directories[i].path, filewatch_directories[i].action_pending);
filewatch_directories[i].action_pending = FILEWATCH_ACTION_FILE_NONE;
filewatch_directories[i].action_processed = true;
}
}
int const filewatch_files_len = arrlen(filewatch_files);
for (int i = 0; i < filewatch_files_len; ++i) {
if (!filewatch_files[i].action_processed
&& (ctx.game.frame_number - filewatch_files[i].tick_last_reported > FILEWATCH_MODIFIED_TICKS_MERGED)) {
SDL_assert(filewatch_files[i].action_pending != FILEWATCH_ACTION_FILE_NONE);
filewatch_files[i].callback(filewatch_files[i].path, filewatch_files[i].action_pending);
filewatch_files[i].action_pending = FILEWATCH_ACTION_FILE_NONE;
filewatch_files[i].action_processed = true;
}
}
SDL_UnlockMutex(filewatcher_lock);
}

23
src/twn_filewatch_c.h Normal file
View File

@ -0,0 +1,23 @@
#ifndef TWN_FILEWATCH_C_H
#define TWN_FILEWATCH_C_H
#include <stdbool.h>
#define FILEWATCH_MODIFIED_TICKS_MERGED 10
enum FilewatchAction {
FILEWATCH_ACTION_FILE_NONE,
FILEWATCH_ACTION_FILE_CREATED,
FILEWATCH_ACTION_FILE_DELETED,
FILEWATCH_ACTION_FILE_MODIFIED,
};
typedef void (*FileatchCallback)(char const *path, enum FilewatchAction action);
bool filewatch_add_directory(char *dir, FileatchCallback callback);
bool filewatch_add_file(char *file, FileatchCallback callback);
void filewatch_poll(void);
#endif

View File

@ -1,5 +1,6 @@
#include "twn_loop.h"
#include "twn_engine_context_c.h"
#include "twn_filewatch_c.h"
#include "twn_input_c.h"
#include "twn_util.h"
#include "twn_util_c.h"
@ -733,8 +734,6 @@ static void clean_up(void) {
text_cache_deinit(&ctx.text_cache);
textures_cache_deinit(&ctx.texture_cache);
textures_reset_state();
arrfree(ctx.render_queue_2d);
toml_free(ctx.config_table);
@ -754,6 +753,25 @@ static void reset_state(void) {
}
static void pack_contents_modified(char const *path, enum FilewatchAction action) {
log_info("Pack contents invalidated: %s, action: %i", path, action);
reset_state();
}
/* TODO: handle .btw packs */
static bool try_mounting_root_pack(char *path) {
log_info("Mounting %s", path);
if (!PHYSFS_mount(path, NULL, true))
return false;
filewatch_add_directory(path, &pack_contents_modified);
return true;
}
int enter_loop(int argc, char **argv) {
profile_start("startup");
@ -795,10 +813,8 @@ int enter_loop(int argc, char **argv) {
return EXIT_FAILURE;
}
if (!PHYSFS_mount(argv[i+1], NULL, true)) {
CRY_PHYSFS("Data dir mount override failed.");
if (!try_mounting_root_pack(argv[i+1]))
return EXIT_FAILURE;
}
data_dir_mounted = true;
@ -823,10 +839,10 @@ int enter_loop(int argc, char **argv) {
/* try mouning data folder first, relative to executable root */
char *full_path;
SDL_asprintf(&full_path, "%sdata", ctx.base_dir);
if (!PHYSFS_mount(full_path, NULL, true)) {
if (!try_mounting_root_pack(full_path)) {
SDL_free(full_path);
SDL_asprintf(&full_path, "%sdata.btw", ctx.base_dir);
if (!PHYSFS_mount(full_path, NULL, true)) {
if (!try_mounting_root_pack(full_path)) {
SDL_free(full_path);
CRY_PHYSFS("Cannot find data.btw or data directory in root. Please create them or specify with --data-dir parameter.");
return EXIT_FAILURE;
@ -857,6 +873,7 @@ int enter_loop(int argc, char **argv) {
reset_state();
}
filewatch_poll();
main_loop();
}

View File

@ -308,6 +308,8 @@ static void update_texture_rects_in_atlas(TextureCache *cache, stbrp_rect *rects
void textures_cache_init(TextureCache *cache, SDL_Window *window) {
SDL_zero(*cache);
cache->window = window;
sh_new_arena(cache->hash);
@ -581,4 +583,6 @@ size_t textures_get_num_atlases(const TextureCache *cache) {
}
void textures_reset_state(void) {
textures_cache_deinit(&ctx.texture_cache);
textures_cache_init(&ctx.texture_cache, ctx.window);
}