Powervessel/reloader/reloader.odin

181 lines
4.4 KiB
Odin
Raw Permalink Normal View History

2024-08-14 11:53:51 +00:00
/*
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"
2024-08-14 11:53:51 +00:00
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,
2024-08-14 11:53:51 +00:00
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
}
2024-08-14 11:53:51 +00:00
for {
if api.update() == false || window.update() == false {
2024-08-14 11:53:51 +00:00
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()
2024-08-14 11:53:51 +00:00
api = new_api
win_config = api.init()
win_ok = window.init(win_config)
if !win_ok {
log.fatalf("Failed to init Window")
return
}
2024-08-14 11:53:51 +00:00
} 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()
2024-08-14 11:53:51 +00:00
api_unload(api)
mem.tracking_allocator_destroy(&tracking_allocator)
}