jc/reload/reload_main.jai
2025-05-14 23:01:21 -06:00

229 lines
6 KiB
Text

reload_main :: () {
basic.set_working_directory(strings.path_strip_filename(system.get_path_of_running_executable()));
system_allocator := context.allocator;
// Ensure we're not allocating anywhere unexpected
context.allocator = Crash_Allocator;
lib_api, ok := load_library(0);
basic.assert(ok, "host: failed to load library");
lib_versions: [..]Library;
lib_versions.allocator = system_allocator;
H := lib_api;
next_lib_version := 1;
defer {
for * lib_versions {
unload_library(it);
}
unload_library(*H);
basic.free(lib_versions.data,, allocator = system_allocator);
}
print("host: allocating % bytes memory (% for state)\n", H.max_memory, H.state_size);
lib_memory := basic.alloc(xx H.max_memory,, allocator = system_allocator);
assert(lib_memory != null, "host: failed to allocate memory!");
defer free(lib_memory,, allocator = system_allocator);
lib_arena: Arena;
init_arena(*lib_arena, lib_memory, H.max_memory);
lib_allocator := Allocator.{
proc = arena_allocator_proc,
data = *lib_arena,
};
lib_state := basic.alloc(xx H.state_size,, allocator = lib_allocator);
H.init(lib_state, lib_allocator, true);
H.setup();
while true {
defer reset_temporary_storage();
status := H.frame();
if status == .quit break;
reload := status == .soft_reload || status == .hard_reload;
if !reload {
path := basic.tprint(Default_Lib_Path, Lib_Extension);
mod := futils.file_modtime_and_size(path);
if mod != H.mod_time {
reload = true;
}
}
if !reload {
continue;
}
new_api, ok := load_library(next_lib_version);
if !ok {
basic.print("host: failed to load version % of the library\n", next_lib_version);
continue;
}
old_ssize := H.state_size;
old_msize := H.max_memory;
new_ssize := new_api.state_size;
new_msize := new_api.max_memory;
if old_ssize != new_ssize || old_msize != new_msize {
status = .hard_reload;
}
if status == .hard_reload {
basic.print("host: performing hard reload...\n");
if old_msize <= new_msize {
lib_arena.offset = 0;
}
else {
free(lib_memory,, allocator = system_allocator);
lib_memory = basic.alloc(xx new_msize,, allocator = system_allocator);
basic.assert(lib_memory != null, "host: failed to allocator new memory");
init_arena(*lib_arena, lib_memory, new_msize);
lib_state = basic.alloc(xx new_ssize,, allocator = lib_allocator);
}
}
else {
basic.print("host: performing soft reload...\n");
basic.array_add(*lib_versions, H);
}
H = new_api;
next_lib_version += 1;
H.init(lib_state, lib_allocator, status == .hard_reload);
}
H.teardown();
}
#scope_file;
Library :: struct {
max_memory: u64;
state_size: u64;
init: Init_Proc;
setup: Setup_Proc;
teardown: Teardown_Proc;
frame: Frame_Proc;
version: int;
handle: *void;
mod_time: basic.Apollo_Time;
}
Default_Lib_Path :: "lib.%";
Temp_Lib_Path :: "__temp_lib_%.%";
#if OS == {
case .WINDOWS;
Lib_Extension :: "dll";
case .MACOS;
Lib_Extension :: "dylib";
case .LINUX;
Lib_Extension :: "so";
}
load_library :: (version: int) -> Library, bool {
default_path := basic.tprint(Default_Lib_Path, Lib_Extension);
mod, size, exists := futils.file_modtime_and_size(default_path);
if !exists {
basic.print("host: % did not exist\n", default_path);
return .{}, false;
}
new_path := basic.tprint(Temp_Lib_Path, version, Lib_Extension);
if !futils.copy_file(default_path, new_path,, allocator = basic.temp) {
basic.print("host: could not copy % to %\n", default_path, new_path);
return .{}, false;
}
handle := os_load_library(new_path);
if !handle {
basic.print("host: failed to load library at %\n", new_path);
return .{}, false;
}
return .{
max_memory = os_find_library_symbol(handle, "max_memory").(*u64).*,
state_size = os_find_library_symbol(handle, "state_size").(*u64).*,
init = os_find_library_symbol(handle, "init"),
setup = os_find_library_symbol(handle, "setup"),
teardown = os_find_library_symbol(handle, "teardown"),
frame = os_find_library_symbol(handle, "frame"),
version = version,
handle = handle,
mod_time = mod,
}, true;
}
unload_library :: (library: *Library) {
path := basic.tprint(Temp_Lib_Path, library.version, Lib_Extension);
if library.handle && !os_unload_library(library.handle) {
basic.print("host: failed to unload temporary library: %\n", path);
}
if !file.file_delete(path) {
basic.print("host: failed to delete temporary library: %\n", path);
}
}
#if OS == .WINDOWS {
#import "Windows";
// @note(judah): Haven't tested this on my windows machine,
// but I remember things working like this.
os_load_library :: (path: string) -> *void {
handle := LoadLibraryA(temp_c_string(path));
return handle;
}
os_unload_library :: (handle: *void) -> bool {
return FreeLibrary(handle);
}
os_find_library_symbol :: (handle: *void, symbol: string) -> *void {
return GetProcAddress(handle, temp_c_string(symbol));
}
}
else #if OS == .MACOS || OS == .LINUX {
#import "POSIX";
os_load_library :: (path: string) -> *void {
handle := dlopen(basic.temp_c_string(path), RTLD_NOW);
return handle;
}
os_unload_library :: (handle: *void) -> bool {
return dlclose(handle) == 0;
}
os_find_library_symbol :: (handle: *void, symbol: string) -> *void {
return dlsym(handle, basic.temp_c_string(symbol));
}
}
else {
#assert(false, "only windows, mac, and linux are supported for now");
}
#import "jx";
basic :: #import "Basic";
system :: #import "System";
file :: #import "File";
futils :: #import "File_Utilities";
strings :: #import "String";