Finally working hot reload again

This commit is contained in:
sergeypdev 2025-05-27 11:52:12 +04:00
parent 8b8ab8c6bc
commit 33cffe95a2
6 changed files with 56 additions and 88 deletions

View File

@ -1,2 +1,2 @@
vim.opt_global.makeprg = "./build_hot_reload.sh" vim.opt_global.makeprg = "./build.sh"
vim.opt.errorformat = "%f(%l:%c)\\ %m" vim.opt.errorformat = "%f(%l:%c)\\ %m"

View File

@ -199,20 +199,24 @@ setup_emsdk_env :: proc() {
set_env("EMSDK", absolute_path("./libs/emsdk")) set_env("EMSDK", absolute_path("./libs/emsdk"))
} }
setup_bin_dir :: proc(path: string, files_to_copy: []string) { setup_bin_dir :: proc(opts: Options, path: string, files_to_copy: []string) {
mkdir_all(path) mkdir_all(path)
for file in files_to_copy { for file in files_to_copy {
copy_file(file, temp_path_join(path, filepath.base(file))) dst := temp_path_join(path, filepath.base(file))
if !os.is_file(dst) || opts.force {
copy_file(file, dst)
}
} }
} }
main :: proc() { main :: proc() {
context.logger = log.create_console_logger() context.logger = log.create_console_logger()
opts := Options { opts := Options {
tracy = true, tracy = true,
debug = true, debug = true,
run = true, optimize = true,
} }
flags.parse_or_exit(&opts, os.args, .Unix, context.temp_allocator) flags.parse_or_exit(&opts, os.args, .Unix, context.temp_allocator)
if opts.variant == .Web { if opts.variant == .Web {
@ -230,10 +234,10 @@ main :: proc() {
switch opts.variant { switch opts.variant {
case .Hot_Reload: case .Hot_Reload:
setup_bin_dir("./build/hotreload", shared_dep_paths) setup_bin_dir(opts, "./build/hotreload", shared_dep_paths)
is_main_built := os.is_file("./build/hotreload/game.bin") is_running := process_exists("game.bin")
if !is_main_built || opts.force { if !is_running {
run_cmd( run_cmd(
temp_concat( temp_concat(
[]string { []string {
@ -244,72 +248,32 @@ main :: proc() {
}, },
debug_flag, debug_flag,
tracy_flag, tracy_flag,
optimize_flag,
COMMON_FLAGS, COMMON_FLAGS,
), ),
"", "",
) )
} }
build_game_cmd := temp_concat( build_game_cmd := run_cmd(
[]string { temp_concat(
"odin", []string {
"build", "odin",
"game", "build",
"-define:RAYLIB_SHARED=true", "game",
"-define:PHYSFS_SHARED=true", "-define:RAYLIB_SHARED=true",
"-build-mode:dll", "-define:PHYSFS_SHARED=true",
"-out:./build/hotreload/game_tmp.so", "-build-mode:dll",
}, "-out:./build/hotreload/game_tmp.so",
tracy_flag, },
debug_flag, tracy_flag,
optimize_flag, debug_flag,
COMMON_FLAGS, optimize_flag,
COMMON_FLAGS,
),
"",
) )
build_game_lib :: proc(build_cmd: []string) { rename("./build/hotreload/game_tmp.so", "./build/hotreload/game.so")
run_cmd(build_cmd, "")
rename("./build/hotreload/game_tmp.so", "./build/hotreload/game.so")
}
build_game_lib(build_game_cmd)
if opts.run && !process_exists("game.bin") {
Watcher_Context :: struct {
run: bool,
build_game_cmd: []string,
}
watcher_ctx := Watcher_Context {
run = true,
build_game_cmd = build_game_cmd,
}
thread_ctx := context
thread_ctx.user_ptr = &watcher_ctx
thrd := thread.create_and_start(proc() {
watcher_ctx := (cast(^Watcher_Context)context.user_ptr)
input := os.to_reader(os.stdin)
buf: [1024]u8
for {
n, err := io.read(input, buf[:])
for cmd in buf[0:n] {
switch cmd {
case 'r', 'R':
build_game_lib(watcher_ctx.build_game_cmd)
case:
}
}
if err == .EOF {
break
}
handle_error(err)
}
}, thread_ctx)
run_cmd({"./build/hotreload/game.bin"}, "")
}
case .Desktop: case .Desktop:
setup_bin_dir("./build/desktop", shared_dep_paths) setup_bin_dir(opts, "./build/desktop", shared_dep_paths)
cmd := slice.concatenate( cmd := slice.concatenate(
[][]string { [][]string {

View File

@ -20,9 +20,11 @@ Run_Error :: union #shared_nil {
os.Error, os.Error,
} }
@(private = "file")
run_cmd_internal :: proc( run_cmd_internal :: proc(
cmd: []string, cmd: []string,
cwd: string, cwd: string,
capture: bool,
loc := #caller_location, loc := #caller_location,
) -> ( ) -> (
result: string, result: string,
@ -70,7 +72,9 @@ run_cmd_internal :: proc(
buf: [1024]u8 buf: [1024]u8
if has_data { if has_data {
n = handle_error1(os.read(r, buf[:])) n = handle_error1(os.read(r, buf[:]))
strings.write_bytes(&b, buf[0:n]) if capture {
strings.write_bytes(&b, buf[0:n])
}
handle_error1(os.write(os.stderr, buf[0:n])) handle_error1(os.write(os.stderr, buf[0:n]))
} }
@ -106,10 +110,11 @@ run_cmd_internal :: proc(
run_cmd :: proc( run_cmd :: proc(
cmd: []string, cmd: []string,
cwd: string, cwd: string,
capture := true,
expr := #caller_expression, expr := #caller_expression,
loc := #caller_location, loc := #caller_location,
) -> string { ) -> string {
result, err := run_cmd_internal(cmd, cwd, loc) result, err := run_cmd_internal(cmd, cwd, capture, loc)
handle_error(err, "", expr, loc) handle_error(err, "", expr, loc)
return result return result
} }

View File

@ -577,8 +577,6 @@ get_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_C
} }
} }
log.debugf("num verts: %v", len(vertices))
center := (max_pos + min_pos) * 0.5 center := (max_pos + min_pos) * 0.5
extent := (max_pos - min_pos) * 0.5 extent := (max_pos - min_pos) * 0.5

View File

@ -1302,6 +1302,7 @@ game_init :: proc() {
g_mem.ui_context.default_style.font_size = 20 g_mem.ui_context.default_style.font_size = 20
g_mem.ui_context.text_size = ui.rl_measure_text_2d g_mem.ui_context.text_size = ui.rl_measure_text_2d
log.debugf("game_init")
game_hot_reloaded(g_mem) game_hot_reloaded(g_mem)
} }
@ -1340,6 +1341,7 @@ game_hot_reloaded :: proc(mem: rawptr) {
ui.rl_init() ui.rl_init()
g_mem.runtime_world.orbit_camera.distance = 4 g_mem.runtime_world.orbit_camera.distance = 4
log.debugf("hot reloaded")
} }
@(export) @(export)

View File

@ -52,8 +52,14 @@ Game_API :: struct {
api_version: int, api_version: int,
} }
load_game_api :: proc(api_version: int) -> (api: Game_API, ok: bool) { load_game_api :: proc(bin_dir: string, api_version: int) -> (api: Game_API, ok: bool) {
mod_time, mod_time_error := os.last_write_time_by_name("game" + DLL_EXT) // NOTE: this needs to be a relative path for Linux to work.
game_dll_name := fmt.tprintf("game_{0}" + DLL_EXT, api_version)
game_dll_path := filepath.join({bin_dir, game_dll_name}, context.temp_allocator)
mod_time, mod_time_error := os.last_write_time_by_name(
filepath.join({bin_dir, "game" + DLL_EXT}, context.temp_allocator),
)
if mod_time_error != os.ERROR_NONE { if mod_time_error != os.ERROR_NONE {
fmt.printfln( fmt.printfln(
"Failed getting last write time of game" + DLL_EXT + ", error code: {1}", "Failed getting last write time of game" + DLL_EXT + ", error code: {1}",
@ -62,14 +68,9 @@ load_game_api :: proc(api_version: int) -> (api: Game_API, ok: bool) {
return return
} }
// NOTE: this needs to be a relative path for Linux to work.
game_dll_name := fmt.tprintf("game_{0}" + DLL_EXT, api_version)
bin_dir := filepath.dir(os.args[0], context.temp_allocator)
copy_dll(bin_dir, game_dll_name) or_return copy_dll(bin_dir, game_dll_name) or_return
game_dll_path := filepath.join({bin_dir, game_dll_name}, context.temp_allocator)
// This proc matches the names of the fields in Game_API to symbols in the // This proc matches the names of the fields in Game_API to symbols in the
// game DLL. It actually looks for symbols starting with `game_`, which is // game DLL. It actually looks for symbols starting with `game_`, which is
// why the argument `"game_"` is there. // why the argument `"game_"` is there.
@ -85,15 +86,13 @@ load_game_api :: proc(api_version: int) -> (api: Game_API, ok: bool) {
return return
} }
unload_game_api :: proc(api: ^Game_API) { unload_game_api :: proc(bin_dir: string, api: ^Game_API) {
if api.lib != nil { if api.lib != nil {
if !dynlib.unload_library(api.lib) { if !dynlib.unload_library(api.lib) {
fmt.printfln("Failed unloading lib: {0}", dynlib.last_error()) fmt.printfln("Failed unloading lib: {0}", dynlib.last_error())
} }
} }
bin_dir := filepath.dir(os.args[0], context.temp_allocator)
game_dll_path := filepath.join( game_dll_path := filepath.join(
{bin_dir, fmt.tprintf("game_{0}" + DLL_EXT, api.api_version)}, {bin_dir, fmt.tprintf("game_{0}" + DLL_EXT, api.api_version)},
context.temp_allocator, context.temp_allocator,
@ -137,7 +136,7 @@ main :: proc() {
} }
game_api_version := 0 game_api_version := 0
game_api, game_api_ok := load_game_api(game_api_version) game_api, game_api_ok := load_game_api(bin_dir, game_api_version)
if !game_api_ok { if !game_api_ok {
fmt.println("Failed to load Game API") fmt.println("Failed to load Game API")
@ -165,7 +164,7 @@ main :: proc() {
} }
if reload { if reload {
new_game_api, new_game_api_ok := load_game_api(game_api_version) new_game_api, new_game_api_ok := load_game_api(bin_dir, game_api_version)
if new_game_api_ok { if new_game_api_ok {
force_restart = force_restart =
@ -194,11 +193,11 @@ main :: proc() {
reset_tracking_allocator(&tracking_allocator) reset_tracking_allocator(&tracking_allocator)
for &g in old_game_apis { for &g in old_game_apis {
unload_game_api(&g) unload_game_api(bin_dir, &g)
} }
clear(&old_game_apis) clear(&old_game_apis)
unload_game_api(&game_api) unload_game_api(bin_dir, &game_api)
game_api = new_game_api game_api = new_game_api
game_api.init() game_api.init()
} }
@ -232,13 +231,13 @@ main :: proc() {
} }
for &g in old_game_apis { for &g in old_game_apis {
unload_game_api(&g) unload_game_api(bin_dir, &g)
} }
delete(old_game_apis) delete(old_game_apis)
game_api.shutdown_window() game_api.shutdown_window()
unload_game_api(&game_api) unload_game_api(bin_dir, &game_api)
mem.tracking_allocator_destroy(&tracking_allocator) mem.tracking_allocator_destroy(&tracking_allocator)
} }