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";