Commit: 2cfaa5b99e70e408789879ec12b2c0b9383d218b
Parent: ff7eb7ae337b13b9bd1a3a3dd803d64cc85d557a
Author: Randy Palamar
Date: Sun, 10 Nov 2024 14:37:22 -0700
introduce platform file watch and use it for debug lib
Diffstat:
3 files changed, 213 insertions(+), 124 deletions(-)
diff --git a/os_unix.c b/os_unix.c
@@ -304,22 +304,6 @@ os_fork_child(char *cmd)
}
static b32
-os_child_data_available(iptr c)
-{
- b32 result = 0;
-
- struct timespec timeout = {0};
- fd_set rfd;
- FD_ZERO(&rfd);
- FD_SET(c, &rfd);
-
- pselect(c + 1, &rfd, NULL, NULL, &timeout, NULL);
-
- result = FD_ISSET(c, &rfd) != 0;
- return result;
-}
-
-static b32
os_child_exited(iptr pid)
{
i32 r, status;
diff --git a/platform_linux_x11.c b/platform_linux_x11.c
@@ -19,6 +19,39 @@ typedef void *Window;
#define VERSION "unknown"
#endif
+typedef struct {
+ platform_file_watch_callback_fn *fn;
+ u8 *path;
+ void *user_ctx;
+ i32 inode;
+ i32 handle;
+} linux_file_watch;
+
+typedef struct {
+ Arena platform_memory;
+ void *window;
+
+ TerminalMemory memory;
+ TerminalInput input;
+
+ posix_platform_process child;
+ i32 inotify_fd;
+
+ linux_file_watch file_watches[32];
+ i32 file_watch_count;
+
+ Stream error_stream;
+
+#ifdef _DEBUG
+ void *library_handle;
+#endif
+
+ /* TODO: remove */
+ b32 terminal_initialized;
+} PlatformCtx;
+
+static PlatformCtx linux_ctx;
+
#ifndef _DEBUG
#define do_debug(...)
#include "vtgl.c"
@@ -26,8 +59,7 @@ typedef void *Window;
#include <dlfcn.h>
#include <time.h>
-static char *libname = "./vtgl.so";
-static void *libhandle;
+#define DEBUG_LIB_NAME "./vtgl.so"
/* TODO: cleanup */
typedef void reset_terminal_fn(TerminalMemory *);
@@ -44,51 +76,40 @@ typedef void reset_terminal_fn(TerminalMemory *);
LIB_FNS
#undef X
-static void
-load_library(const char *lib, Stream *err)
+static PLATFORM_FILE_WATCH_CALLBACK_FN(debug_reload_library)
{
+ PlatformCtx *ctx = user_ctx;
+
+ if (ctx->input.executable_reloaded)
+ return;
+ ctx->input.executable_reloaded = 1;
s8 nl = s8("\n");
/* NOTE: glibc sucks and will crash if this is NULL */
- if (libhandle)
- dlclose(libhandle);
- libhandle = dlopen(lib, RTLD_NOW|RTLD_LOCAL);
- if (!libhandle)
- stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlopen: "), c_str_to_s8(dlerror()), nl});
+ if (ctx->library_handle)
+ dlclose(ctx->library_handle);
+ ctx->library_handle = dlopen((c8 *)path, RTLD_NOW|RTLD_LOCAL);
+ if (!ctx->library_handle)
+ stream_push_s8s(&ctx->error_stream, 3,
+ (s8 []){s8("dlopen: "), c_str_to_s8(dlerror()), nl});
+
#define X(name) \
- name = dlsym(libhandle, #name); \
- if (!name) stream_push_s8s(err, 3, (s8 []){s8("do_debug: dlsym: "), \
- c_str_to_s8(dlerror()), nl});
+ name = dlsym(ctx->library_handle, #name); \
+ if (!name) stream_push_s8s(&ctx->error_stream, 3, (s8 []){s8("dlsym: "), \
+ c_str_to_s8(dlerror()), nl});
LIB_FNS
#undef X
-}
-static void
-do_debug(TerminalMemory *t, Stream *err)
-{
- static FileStats updated;
- FileStats test = posix_get_file_stats((u8 *)libname);
-
- if (test.timestamp > updated.timestamp) {
- updated = test;
- /* NOTE: sucks but seems to be easiest reliable way to make sure lib is written */
- struct timespec sleep_time = { .tv_nsec = 100e6 };
- nanosleep(&sleep_time, &sleep_time);
- load_library(libname, err);
- if (t) reset_terminal(t);
- stream_push_s8(err, s8("Reloaded Main Program\n"));
- }
-
- /* TODO: clear debug memory? */
+ if (ctx->terminal_initialized) reset_terminal(&ctx->memory);
+ stream_push_s8(&ctx->error_stream, s8("Reloaded Main Program\n"));
- if (err->widx) {
- os_write_err_msg(stream_to_s8(err));
- err->widx = 0;
- }
+ os_write_err_msg(stream_to_s8(&ctx->error_stream));
+ ctx->error_stream.widx = 0;
}
-
#endif /* _DEBUG */
-static GLFWwindow *g_window;
+#include <sys/inotify.h>
+
+#define LINUX_INOTIFY_MASK (IN_CLOSE|IN_MODIFY)
static void
button_action(ButtonState *button, b32 pressed)
@@ -199,13 +220,54 @@ init_window(tmp_user_ctx *ctx, iv2 window_size)
}
static void
-update_input(GLFWwindow *win, TerminalInput *input, posix_platform_process child)
+dispatch_file_watch_events(PlatformCtx *ctx)
{
+ u8 *mem = alloc_(&ctx->platform_memory, 4096, 64, 1);
+ s8 buf = {.len = 4096, .data = mem};
+ for (;;) {
+ size rlen = read(ctx->inotify_fd, buf.data, buf.len);
+ if (rlen <= 0)
+ break;
+ struct inotify_event *ie;
+ for (u8 *data = buf.data; data < buf.data + rlen; data += sizeof(*ie) + ie->len) {
+ ie = (struct inotify_event *)data;
+ for (i32 i = 0; i < ctx->file_watch_count; i++) {
+ linux_file_watch *fw = ctx->file_watches + i;
+ if (fw->handle == ie->wd) {
+ b32 file_changed = (ie->mask & IN_CLOSE_WRITE) != 0;
+ file_changed |= (ie->mask & IN_MODIFY) != 0;
+ /* NOTE: some editors and the compiler will rewrite a file
+ * completely and thus the inode will change; here we
+ * detect that and restart the watch */
+ struct stat sb;
+ stat((c8 *)fw->path, &sb);
+ if (fw->inode != sb.st_ino) {
+ inotify_rm_watch(ctx->inotify_fd, fw->handle);
+ fw->inode = sb.st_ino;
+ fw->handle = inotify_add_watch(ctx->inotify_fd,
+ (c8 *)fw->path,
+ LINUX_INOTIFY_MASK);
+ file_changed = 1;
+ }
+ if (file_changed)
+ fw->fn(fw->path, fw->user_ctx);
+ }
+ }
+ }
+ }
+}
+
+static void
+update_input(PlatformCtx *ctx)
+{
+ TerminalInput *input = &ctx->input;
+ ctx->input.executable_reloaded = 0;
+
/* NOTE: mouse */
input->mouse_scroll = (v2){0};
f64 mouse_x, mouse_y;
- glfwGetCursorPos(win, &mouse_x, &mouse_y);
+ glfwGetCursorPos(ctx->window, &mouse_x, &mouse_y);
input->mouse.x = mouse_x;
input->mouse.y = input->window_size.h - mouse_y;
@@ -216,8 +278,25 @@ update_input(GLFWwindow *win, TerminalInput *input, posix_platform_process child
glfwPollEvents();
+ /* TODO: pselect should include x11 fd after removing glfw */
+ /* TODO: for now this is a poll but we could roll some wait time into it as well */
+ struct timespec timeout = {0};
+ fd_set rfd;
+ FD_ZERO(&rfd);
+ FD_SET(ctx->child.handle, &rfd);
+ FD_SET(ctx->inotify_fd, &rfd);
+
+ i32 max_fd = MAX(ctx->inotify_fd, ctx->child.handle);
+ pselect(max_fd + 1, &rfd, NULL, NULL, &timeout, NULL);
+
+ input->data_available = FD_ISSET(ctx->child.handle, &rfd) != 0;
+
+ if (FD_ISSET(ctx->inotify_fd, &rfd)) {
+ dispatch_file_watch_events(ctx);
+ }
+
+ /* NOTE: char_stream was filled in char callback */
input->character_input = stream_to_s8(&input->char_stream);
- input->data_available = os_child_data_available(child.handle);
}
static PLATFORM_CLIPBOARD_FN(x11_get_clipboard)
@@ -256,7 +335,7 @@ static PLATFORM_CLIPBOARD_FN(x11_set_clipboard)
static PLATFORM_WINDOW_TITLE_FN(x11_get_window_title)
{
ASSERT(buffer);
- char *title = (c8 *)glfwGetWindowTitle(g_window);
+ char *title = (c8 *)glfwGetWindowTitle(linux_ctx.window);
if (title) stream_push_s8(buffer, c_str_to_s8(title));
}
@@ -265,7 +344,23 @@ static PLATFORM_WINDOW_TITLE_FN(x11_set_window_title)
ASSERT(buffer);
stream_push_byte(buffer, 0);
if (!buffer->errors)
- glfwSetWindowTitle(g_window, (c8 *)buffer->buf);
+ glfwSetWindowTitle(linux_ctx.window, (c8 *)buffer->buf);
+}
+
+static PLATFORM_ADD_FILE_WATCH_FN(linux_add_file_watch)
+{
+ struct stat sb;
+ stat((c8 *)path, &sb);
+
+ i32 wd = inotify_add_watch(linux_ctx.inotify_fd, (c8 *)path, LINUX_INOTIFY_MASK);
+ i32 idx = linux_ctx.file_watch_count++;
+ ASSERT(idx < ARRAY_COUNT(linux_ctx.file_watches));
+
+ linux_ctx.file_watches[idx].fn = fn;
+ linux_ctx.file_watches[idx].path = path;
+ linux_ctx.file_watches[idx].handle = wd;
+ linux_ctx.file_watches[idx].inode = sb.st_ino;
+ linux_ctx.file_watches[idx].user_ctx = user_ctx;
}
static void
@@ -280,34 +375,34 @@ usage(char *argv0, Stream *err)
i32
main(i32 argc, char *argv[], char *envp[])
{
- TerminalMemory term_memory = {0};
{
/* TODO: remove this step */
Arena tmp = os_new_arena(16 * MEGABYTE);
- term_memory.memory = tmp.beg;
- term_memory.memory_size = tmp.end - tmp.beg;
+ linux_ctx.memory.memory = tmp.beg;
+ linux_ctx.memory.memory_size = tmp.end - tmp.beg;
}
#ifdef _DEBUG
{
- Arena debug_memory = os_new_arena(128 * MEGABYTE);
- term_memory.debug_memory = debug_memory.beg;
- term_memory.debug_memory_size = debug_memory.end - debug_memory.beg;
+ Arena debug_memory = os_new_arena(128 * MEGABYTE);
+ linux_ctx.memory.debug_memory = debug_memory.beg;
+ linux_ctx.memory.debug_memory_size = debug_memory.end - debug_memory.beg;
}
#endif
- term_memory.platform_api.read = posix_read;
- term_memory.platform_api.write = posix_write;
- term_memory.platform_api.allocate_ring_buffer = posix_allocate_ring_buffer;
- term_memory.platform_api.get_file_stats = posix_get_file_stats;
- term_memory.platform_api.read_file = posix_read_file;
- term_memory.platform_api.get_clipboard = x11_get_clipboard;
- term_memory.platform_api.set_clipboard = x11_set_clipboard;
- term_memory.platform_api.get_window_title = x11_get_window_title;
- term_memory.platform_api.set_window_title = x11_set_window_title;
- term_memory.platform_api.path_separator = '/';
-
- Arena platform_arena = os_new_arena(2 * MEGABYTE);
- Stream error_stream = stream_alloc(&platform_arena, MEGABYTE / 4);
+ linux_ctx.memory.platform_api.read = posix_read;
+ linux_ctx.memory.platform_api.write = posix_write;
+ linux_ctx.memory.platform_api.allocate_ring_buffer = posix_allocate_ring_buffer;
+ linux_ctx.memory.platform_api.get_file_stats = posix_get_file_stats;
+ linux_ctx.memory.platform_api.read_file = posix_read_file;
+ linux_ctx.memory.platform_api.get_clipboard = x11_get_clipboard;
+ linux_ctx.memory.platform_api.set_clipboard = x11_set_clipboard;
+ linux_ctx.memory.platform_api.get_window_title = x11_get_window_title;
+ linux_ctx.memory.platform_api.set_window_title = x11_set_window_title;
+ linux_ctx.memory.platform_api.add_file_watch = linux_add_file_watch;
+ linux_ctx.memory.platform_api.path_separator = '/';
+
+ linux_ctx.platform_memory = os_new_arena(2 * MEGABYTE);
+ linux_ctx.error_stream = stream_alloc(&linux_ctx.platform_memory, MEGABYTE / 4);
iv2 cells = {.x = -1, .y = -1};
@@ -317,14 +412,14 @@ main(i32 argc, char *argv[], char *envp[])
for (i32 i = 0; i < argc; i++) {
char *arg = argv[i];
if (!arg || !arg[0])
- usage(argv0, &error_stream);
+ usage(argv0, &linux_ctx.error_stream);
if (arg[0] != '-')
break;
arg++;
switch (arg[0]) {
case 'g':
if (!argv[i + 1])
- usage(argv0, &error_stream);
+ usage(argv0, &linux_ctx.error_stream);
cres = i32_from_cstr(argv[i + 1], 'x');
if (cres.status == CR_SUCCESS)
cells.w = cres.i;
@@ -332,27 +427,32 @@ main(i32 argc, char *argv[], char *envp[])
if (cres.status == CR_SUCCESS)
cells.h = cres.i;
if (cells.w <= 0 || cells.h <= 0) {
- stream_push_s8(&error_stream, s8("ignoring malformed geometry: "));
- stream_push_s8(&error_stream, c_str_to_s8(argv[i + 1]));
- stream_push_byte(&error_stream, '\n');
+ stream_push_s8(&linux_ctx.error_stream, s8("ignoring malformed geometry: "));
+ stream_push_s8(&linux_ctx.error_stream, c_str_to_s8(argv[i + 1]));
+ stream_push_byte(&linux_ctx.error_stream, '\n');
}
argv++;
argc--;
break;
case 'v':
- stream_push_s8s(&error_stream, 2,
+ stream_push_s8s(&linux_ctx.error_stream, 2,
(s8 []){c_str_to_s8(argv0), s8(" " VERSION "\n")});
- os_fatal(stream_to_s8(&error_stream));
+ os_fatal(stream_to_s8(&linux_ctx.error_stream));
default:
- usage(argv0, &error_stream);
+ usage(argv0, &linux_ctx.error_stream);
}
}
- if (error_stream.widx) {
- os_write_err_msg(stream_to_s8(&error_stream));
- error_stream.widx = 0;
+ if (linux_ctx.error_stream.widx) {
+ os_write_err_msg(stream_to_s8(&linux_ctx.error_stream));
+ linux_ctx.error_stream.widx = 0;
}
- do_debug(NULL, &error_stream);
+ linux_ctx.inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+
+#ifdef _DEBUG
+ debug_reload_library((u8 *)DEBUG_LIB_NAME, &linux_ctx);
+ linux_add_file_watch((u8 *)DEBUG_LIB_NAME, debug_reload_library, &linux_ctx);
+#endif
if (!glfwInit())
os_fatal(s8("Failed to init GLFW\n"));
@@ -367,84 +467,80 @@ main(i32 argc, char *argv[], char *envp[])
iv2 monitor_size;
glfwGetMonitorWorkarea(mon, NULL, NULL, &monitor_size.w, &monitor_size.h);
- TerminalInput input = {0};
- input.char_stream = arena_stream(platform_arena);
+ linux_ctx.input.char_stream = arena_stream(linux_ctx.platform_memory);
/* TODO: delete !!!! */
tmp_user_ctx ctx;
- ctx.memory = &term_memory;
- ctx.input = &input;
+ ctx.memory = &linux_ctx.memory;
+ ctx.input = &linux_ctx.input;
- iv2 window_size = {.w = 1280, .h = 720};
- GLFWwindow *window = init_window(&ctx, window_size);
- g_window = window;
+ iv2 window_size = {.w = 1280, .h = 720};
+ linux_ctx.window = init_window(&ctx, window_size);
+ linux_ctx.child = os_fork_child("/bin/sh");
- posix_platform_process child = os_fork_child("/bin/sh");
-
- iv2 requested_size = vtgl_initialize(&term_memory, child.handle, cells, monitor_size);
+ iv2 requested_size = vtgl_initialize(&linux_ctx.memory, linux_ctx.child.handle, cells, monitor_size);
if (requested_size.w > 0 && requested_size.h > 0 &&
(requested_size.w != window_size.w || requested_size.h != window_size.h))
{
- glfwSetWindowAttrib(window, GLFW_FLOATING, GLFW_TRUE);
+ glfwSetWindowAttrib(linux_ctx.window, GLFW_FLOATING, GLFW_TRUE);
i32 x = ABS(window_size.w - requested_size.w) / 2;
i32 y = ABS(window_size.h - requested_size.h) / 2;
window_size = requested_size;
- glfwSetWindowMonitor(window, 0, x, y, window_size.w, window_size.h, GLFW_DONT_CARE);
+ glfwSetWindowMonitor(linux_ctx.window, 0, x, y, window_size.w, window_size.h, GLFW_DONT_CARE);
/* NOTE: resizable must be set after the window is shown; otherwise tiling window
* managers will forcibly resize us even if we are supposed to be floating */
- glfwShowWindow(window);
- glfwSetWindowAttrib(window, GLFW_RESIZABLE, GLFW_TRUE);
+ glfwShowWindow(linux_ctx.window);
+ glfwSetWindowAttrib(linux_ctx.window, GLFW_RESIZABLE, GLFW_TRUE);
} else {
/* NOTE: on the other hand we should let the window be resized if no size was
* explicitly requested */
- glfwSetWindowAttrib(window, GLFW_RESIZABLE, GLFW_TRUE);
- glfwSetWindowPos(window,
+ glfwSetWindowAttrib(linux_ctx.window, GLFW_RESIZABLE, GLFW_TRUE);
+ glfwSetWindowPos(linux_ctx.window,
ABS(monitor_size.w - window_size.w) / 2,
ABS(monitor_size.h - window_size.h) / 2);
- glfwShowWindow(window);
+ glfwShowWindow(linux_ctx.window);
}
- input.window_size = window_size;
+ linux_ctx.input.window_size = window_size;
/* TODO: remove */
{
- Term *t = term_memory.memory;
- t->gl.window = window;
- reset_terminal(&term_memory);
+ Term *t = linux_ctx.memory.memory;
+ t->gl.window = linux_ctx.window;
+ reset_terminal(&linux_ctx.memory);
+ linux_ctx.terminal_initialized = 1;
}
Range last_sel = {0};
f64 last_time = os_get_time();
- while (!glfwWindowShouldClose(window)) {
- if (os_child_exited(child.process_id))
+ while (!glfwWindowShouldClose(linux_ctx.window)) {
+ if (os_child_exited(linux_ctx.child.process_id))
break;
- update_input(window, &input, child);
-
- do_debug(&term_memory, &error_stream);
+ update_input(&linux_ctx);
/* TODO: cpu time excluding waiting for the vblank */
- f64 current_time = os_get_time();
- input.dt = current_time - last_time;
- last_time = current_time;
+ f64 current_time = os_get_time();
+ linux_ctx.input.dt = current_time - last_time;
+ last_time = current_time;
- debug_begin_frame(&term_memory, input.dt);
+ debug_begin_frame(&linux_ctx.memory, linux_ctx.input.dt);
- vtgl_frame_step(&term_memory, &input);
+ vtgl_frame_step(&linux_ctx.memory, &linux_ctx.input);
- Range current_sel = vtgl_active_selection(&term_memory, 0);
+ Range current_sel = vtgl_active_selection(&linux_ctx.memory, 0);
if (is_valid_range(current_sel) && !equal_range(current_sel, last_sel)) {
- Stream buf = arena_stream(platform_arena);
- vtgl_active_selection(&term_memory, &buf);
+ Stream buf = arena_stream(linux_ctx.platform_memory);
+ vtgl_active_selection(&linux_ctx.memory, &buf);
stream_push_byte(&buf, 0);
if (!buf.errors)
glfwSetX11SelectionString((c8 *)buf.buf);
last_sel = current_sel;
}
- debug_end_frame(&term_memory);
+ debug_end_frame(&linux_ctx.memory);
- glfwSwapBuffers(window);
+ glfwSwapBuffers(linux_ctx.window);
}
return 0;
diff --git a/vtgl.h b/vtgl.h
@@ -24,6 +24,13 @@ typedef PLATFORM_CLIPBOARD_FN(platform_clipboard_fn);
#define PLATFORM_WINDOW_TITLE_FN(name) void name(Stream *buffer)
typedef PLATFORM_WINDOW_TITLE_FN(platform_window_title_fn);
+/* NOTE: for now we will do the callback route but this will change if we do multithreading */
+#define PLATFORM_FILE_WATCH_CALLBACK_FN(name) void name(u8 *path, void *user_ctx)
+typedef PLATFORM_FILE_WATCH_CALLBACK_FN(platform_file_watch_callback_fn);
+
+#define PLATFORM_ADD_FILE_WATCH_FN(name) void name(u8 *path, platform_file_watch_callback_fn *fn, void *user_ctx)
+typedef PLATFORM_ADD_FILE_WATCH_FN(platform_add_file_watch_fn);
+
typedef struct {
platform_allocate_ring_buffer_fn *allocate_ring_buffer;
platform_read_fn *read;
@@ -38,6 +45,8 @@ typedef struct {
platform_window_title_fn *get_window_title;
platform_window_title_fn *set_window_title;
+ platform_add_file_watch_fn *add_file_watch;
+
u8 path_separator;
} PlatformAPI;