181 lines
4.4 KiB
Odin
181 lines
4.4 KiB
Odin
/*
|
|
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)
|
|
} |