lib: *void; editor_main: Editor_Main; thread: *Thread; watcher: File_Watcher; poll_fds: [1]pollfd; do_hot_reload: bool; WATCH_DIR :: "src"; WATCH_FILE :: "main.jai"; FILE_WATCHER_LOG :: false; MAX_RETRY_DEFAULT :: 20; DELAY_MS_DEFAULT :s32: 500; DELAY_MS_MAX :s32: 8000; BLOCK_TILL_EVENT :: -1; MAX_WAIT_SECONDS :: 20_000.0; Editor_Main :: #type (ctx: *#Context); main :: () { log("Hello from Host"); log("FP: %", FP_EDITOR_SO); log("DEF: %", *context); log("Temporary_Storage uses % bytes.", context.temporary_storage.total_bytes_occupied); log("-------------------------------"); if !file_watcher_init(WATCH_DIR) return; max_retry := MAX_RETRY_DEFAULT; delay_ms := DELAY_MS_DEFAULT; needs_wait: bool; wait_seconds: float64; /** Initial start */ rebuild_so(); reload_so(); while true { reset_temporary_storage(); if !needs_wait { event_fds := poll(poll_fds.data, xx poll_fds.count, BLOCK_TILL_EVENT); if event_fds < 0 { action := poll_error(event_fds); if #complete action == { case .EXIT; return; case .RETRY; if exp_backoff() then continue; else return; case .SIGNAL; continue; } } } else { fms := wait_seconds * 1000.0; fms = clamp(fms, 0.0, MAX_WAIT_SECONDS); assert(fms < MAX_WAIT_SECONDS, "Unexpected high timeout"); if fms == MAX_WAIT_SECONDS { log("process_changes() returned a high timeout!", flags=.WARNING); } ms := cast(s32, round(xx fms)); sleep_milliseconds(ms); } _, needs_wait=, wait_seconds= := process_changes(*watcher); if do_hot_reload { do_hot_reload = false; rebuild_so(); reload_so(); } } while !thread_is_done(thread) {} } rebuild_so :: () { log("Rebuilding SO"); CMD :: string.[ "jai", "build.jai", "-", "silent", "hotreload", "rebuild" ]; if run_command(..CMD, print_captured_output=true).exit_code != 0 { exit(1); } } reload_so :: () { log("Reloading SO"); if lib dlclose(lib); lib = dlopen(FP_EDITOR_SO, RTLD_NOW); editor_main = cast(Editor_Main)dlsym(lib, "hotreload_main"); editor_main(*context); log("Temporary_Storage uses % bytes.", context.temporary_storage.total_bytes_occupied); } file_watcher_init :: (dir: string) -> ok: bool = false { if !file_exists(FP_EDITOR_SO) return; if !init( *watcher, file_change_callback, events_to_watch = .ALL_EVENTS, verbose=FILE_WATCHER_LOG) { log_error("Could not initialize watcher"); return; } if !add_directories(*watcher, dir) { log_error("Could not watch directory %", FP_EDITOR_SO); return; } poll_fds[0].fd = watcher.inotify_instance; poll_fds[0].events = POLLIN; log("INotify armed"); return true; } file_change_callback :: (watcher: *File_Watcher(void), change: *File_Change, _: *void) { if !ends_with(change.full_path, WATCH_FILE) return; do_hot_reload = true; } exp_backoff :: () -> should_continue: bool #expand { if `max_retry <= 0 { log_error("POLL: Cannot recover!"); return false; } else { log("POLL: Retry attempt left: % with % ms delay", `max_retry, `delay_ms, flags=.WARNING ); } sleep_milliseconds(`delay_ms); `delay_ms *= 2; `delay_ms = min(`delay_ms, DELAY_MS_MAX); `max_retry -= 1; return true; } poll_error :: (rc: s32) -> enum { EXIT; RETRY; SIGNAL; } { if errno() == { /** The allocation of internal data structures failed but a subsequent request may succeed. */ case EAGAIN; log("POLL: Internal allocation failed.", flags=.WARNING); return .RETRY; /** A signal was caught during poll(). */ case EINTR; return .SIGNAL; /** The nfds argument is greater than {OPEN_MAX}, or one of the fd members refers to a STREAM or multiplexer that is linked (directly or indirectly) downstream from a multiplexer. */ case EINVAL; log_error("POLL: More FDs then supported. Or something else."); return .EXIT; case; log_error("Unknown poll() error. Maybe your ABI version is more recent. My bad."); return .EXIT; } } #import "Basic"; #import "String"; #import "File"; #import "File_Watcher"; #import "File_Utilities"; #import "Process"; #import "POSIX"; #import "Thread"; #import "0x2_Math";