Logo

index : gist

Random things

  • summary
  • about
  • tree
  • log
  • branches
<< path: root/public/gist.git/html/jai/hot_reload/src/host.jai blob: 809a675e1318f1ac88f2f72cb60f7725a5aaa3b8 [raw] [clear marker]

        
0
1
2
3
4lib: *void;
5editor_main: Editor_Main;
6
7thread: *Thread;
8watcher: File_Watcher;
9poll_fds: [1]pollfd;
10do_hot_reload: bool;
11
12WATCH_DIR :: "src";
13WATCH_FILE :: "main.jai";
14
15FILE_WATCHER_LOG :: false;
16
17MAX_RETRY_DEFAULT :: 20;
18DELAY_MS_DEFAULT :s32: 500;
19DELAY_MS_MAX :s32: 8000;
20
21BLOCK_TILL_EVENT :: -1;
22MAX_WAIT_SECONDS :: 20_000.0;
23
24
25Editor_Main :: #type (ctx: *#Context);
26
27
28main :: () {
29 log("Hello from Host");
30 log("FP: %", FP_EDITOR_SO);
31 log("DEF: %", *context);
32
33 log("Temporary_Storage uses % bytes.", context.temporary_storage.total_bytes_occupied);
34 log("-------------------------------");
35
36 if !file_watcher_init(WATCH_DIR) return;
37
38
39 max_retry := MAX_RETRY_DEFAULT;
40 delay_ms := DELAY_MS_DEFAULT;
41
42 needs_wait: bool;
43 wait_seconds: float64;
44
45
46 /** Initial start */
47 rebuild_so();
48 reload_so();
49
50
51 while true {
52 reset_temporary_storage();
53
54 if !needs_wait {
55 event_fds := poll(poll_fds.data, xx poll_fds.count, BLOCK_TILL_EVENT);
56
57 if event_fds < 0 {
58 action := poll_error(event_fds);
59
60 if #complete action == {
61 case .EXIT; return;
62 case .RETRY; if exp_backoff() then continue; else return;
63 case .SIGNAL; continue;
64 }
65 }
66 } else {
67 fms := wait_seconds * 1000.0;
68 fms = clamp(fms, 0.0, MAX_WAIT_SECONDS);
69
70 assert(fms < MAX_WAIT_SECONDS, "Unexpected high timeout");
71 if fms == MAX_WAIT_SECONDS {
72 log("process_changes() returned a high timeout!", flags=.WARNING);
73 }
74
75 ms := cast(s32, round(xx fms));
76 sleep_milliseconds(ms);
77 }
78
79 _, needs_wait=, wait_seconds= := process_changes(*watcher);
80
81 if do_hot_reload {
82 do_hot_reload = false;
83 rebuild_so();
84 reload_so();
85 }
86 }
87
88 while !thread_is_done(thread) {}
89}
90
91rebuild_so :: () {
92 log("Rebuilding SO");
93 CMD :: string.[ "jai", "build.jai", "-", "silent", "hotreload", "rebuild" ];
94 if run_command(..CMD, print_captured_output=true).exit_code != 0 {
95 exit(1);
96 }
97}
98
99reload_so :: () {
100 log("Reloading SO");
101 if lib dlclose(lib);
102 lib = dlopen(FP_EDITOR_SO, RTLD_NOW);
103 editor_main = cast(Editor_Main)dlsym(lib, "hotreload_main");
104 editor_main(*context);
105 log("Temporary_Storage uses % bytes.", context.temporary_storage.total_bytes_occupied);
106}
107
108file_watcher_init :: (dir: string) -> ok: bool = false {
109 if !file_exists(FP_EDITOR_SO) return;
110
111 if !init(
112 *watcher,
113 file_change_callback,
114 events_to_watch = .ALL_EVENTS,
115 verbose=FILE_WATCHER_LOG)
116 {
117 log_error("Could not initialize watcher");
118 return;
119 }
120
121 if !add_directories(*watcher, dir) {
122 log_error("Could not watch directory %", FP_EDITOR_SO);
123 return;
124 }
125
126 poll_fds[0].fd = watcher.inotify_instance;
127 poll_fds[0].events = POLLIN;
128
129 log("INotify armed");
130 return true;
131}
132
133file_change_callback :: (watcher: *File_Watcher(void), change: *File_Change, _: *void) {
134 if !ends_with(change.full_path, WATCH_FILE) return;
135 do_hot_reload = true;
136}
137
138exp_backoff :: () -> should_continue: bool #expand {
139 if `max_retry <= 0 {
140 log_error("POLL: Cannot recover!");
141 return false;
142 } else {
143 log("POLL: Retry attempt left: % with % ms delay",
144 `max_retry,
145 `delay_ms,
146 flags=.WARNING
147 );
148 }
149
150 sleep_milliseconds(`delay_ms);
151 `delay_ms *= 2;
152 `delay_ms = min(`delay_ms, DELAY_MS_MAX);
153 `max_retry -= 1;
154 return true;
155}
156
157poll_error :: (rc: s32) -> enum { EXIT; RETRY; SIGNAL; } {
158 if errno() == {
159 /** The allocation of internal data structures failed but a subsequent request may succeed. */
160 case EAGAIN;
161 log("POLL: Internal allocation failed.", flags=.WARNING);
162 return .RETRY;
163
164 /** A signal was caught during poll(). */
165 case EINTR;
166 return .SIGNAL;
167
168 /** The nfds argument is greater than {OPEN_MAX}, or one of the fd members refers to a
169 STREAM or multiplexer that is linked (directly or indirectly) downstream from
170 a multiplexer.
171 */
172 case EINVAL;
173 log_error("POLL: More FDs then supported. Or something else.");
174 return .EXIT;
175 case;
176 log_error("Unknown poll() error. Maybe your ABI version is more recent. My bad.");
177 return .EXIT;
178 }
179}
180
181
182#import "Basic";
183#import "String";
184#import "File";
185#import "File_Watcher";
186#import "File_Utilities";
187#import "Process";
188#import "POSIX";
189#import "Thread";
190
191#import "0x2_Math";
192
193
Copyright 2026  E766CB298A6D1E64 | Git-Thing heavily inspired by cgit