/* A simple hot reloading app that loads a dll of the application as it is compiled https://zylinski.se/posts/hot-reload-gameplay-code */ package reloader import "core:dynlib" import "core:os" import "core:fmt" import "core:log" import "core:c/libc" import "core:mem" import "../window" when ODIN_OS == .Windows { DLL_EXT :: ".dll" COPY_CMD :: "copy" } else when ODIN_OS == .Darwin { DLL_EXT :: ".dylib" COPY_CMD :: "cp" } else { DLL_EXT :: ".so" COPY_CMD :: "cp" } Api :: struct { init: proc() -> window.Config, update: proc() -> bool, shutdown: proc(), memory: proc() -> (rawptr, int), memory_set: proc(rawptr), should_reload: proc() -> bool, should_restart: proc() -> bool, lib: dynlib.Library, dll_time: os.File_Time, version: int } api_load :: proc(version: int) -> (Api, bool) { dll_time, dll_time_err := os.last_write_time_by_name("app" + DLL_EXT) if dll_time_err != os.ERROR_NONE { log.errorf("Could not fetch last write date of game" + DLL_EXT) return {}, false } dll_name := fmt.tprintf("app_{0}" + DLL_EXT, version) copy_cmd := fmt.ctprintf("{0} app{1} {2}", COPY_CMD, DLL_EXT, dll_name) if libc.system(copy_cmd) != 0 { log.errorf("Failed to copy app{0} to {1}", DLL_EXT, dll_name) return {}, false } api: Api symbols_loaded, ok := dynlib.initialize_symbols(&api, dll_name, "app_", "lib") if !ok { log.errorf("Failed to load symbols from {0}", dll_name) return {}, false } api.dll_time = dll_time api.version = version return api, true } api_unload :: proc(api: Api) { if api.lib != nil { dynlib.unload_library(api.lib) } del_cmd := fmt.ctprintf("del app_{0}" + DLL_EXT, api.version) if libc.system(del_cmd) != 0 { fmt.println("Failed to remove app_{0}.dll copy", api.version) } } main :: proc() { context.logger = log.create_console_logger() default_allocator := context.allocator tracking_allocator: mem.Tracking_Allocator mem.tracking_allocator_init(&tracking_allocator, default_allocator) context.allocator = mem.tracking_allocator(&tracking_allocator) reset_tracking_allocator :: proc(a: ^mem.Tracking_Allocator) -> bool { err := false for _, value in a.allocation_map { fmt.printf("%v: Leaked %v bytes\n", value.location, value.size) err = true } mem.tracking_allocator_clear(a) return err } api_version := 0 api, api_ok := api_load(api_version) if !api_ok { log.fatalf("Failed to load Application API") return } api_version += 1 win_config := api.init() win_ok := window.init(win_config) if !win_ok { log.fatalf("Failed to init Window") return } for { if api.update() == false || window.update() == false { break } dll_time, dll_time_err := os.last_write_time_by_name("app" + DLL_EXT) restart := api.should_restart() reload := api.should_reload() || restart if dll_time_err == os.ERROR_NONE && api.dll_time != dll_time { reload = true } if reload { new_api, new_api_ok := api_load(api_version) if new_api_ok { memory, size := api.memory() _, new_size := new_api.memory() if size != new_size || restart { api.shutdown() reset_tracking_allocator(&tracking_allocator) api_unload(api) window.shutdown() api = new_api win_config = api.init() win_ok = window.init(win_config) if !win_ok { log.fatalf("Failed to init Window") return } } else { api_unload(api) api = new_api api.memory_set(memory) } api_version += 1 } } if len(tracking_allocator.bad_free_array) > 0 { for b in tracking_allocator.bad_free_array { log.errorf("Bad free at: %v", b.location) } libc.getchar() panic("Bad free detected") } free_all(context.temp_allocator) } free_all(context.temp_allocator) api.shutdown() if reset_tracking_allocator(&tracking_allocator) { libc.getchar() } window.shutdown() api_unload(api) mem.tracking_allocator_destroy(&tracking_allocator) }