From 8b8ab8c6bc733fd4e267ef9ee5ec3661ceccc857 Mon Sep 17 00:00:00 2001 From: sergeypdev Date: Tue, 27 May 2025 01:23:46 +0400 Subject: [PATCH] More progress on builder --- .vscode/launch.json | 3 +- builder/builder.odin | 97 +++++++++++++++++++--------- builder/helpers.odin | 20 +++++- game/assets/assets.odin | 10 +-- game/fs.odin | 18 ++++++ game/game.odin | 4 +- main_hot_reload/main_hot_reload.odin | 51 ++++++++------- 7 files changed, 137 insertions(+), 66 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index ca37f2e..686d371 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -50,11 +50,10 @@ { "type": "lldb", "request": "launch", - "preLaunchTask": "Build Hot Reload", "name": "Run Hot Reload (Linux / Mac)", "args": [], "cwd": "${workspaceFolder}", - "program": "${workspaceFolder}/game_hot_reload.bin", + "program": "${workspaceFolder}/build/hotreload/game.bin", }, { "type": "lldb", diff --git a/builder/builder.odin b/builder/builder.odin index 8e0f9cc..d139c29 100644 --- a/builder/builder.odin +++ b/builder/builder.odin @@ -2,8 +2,8 @@ package builder import "core:flags" import "core:fmt" -import "core:log" import "core:io" +import "core:log" import os "core:os/os2" import "core:path/filepath" import "core:slice" @@ -30,10 +30,19 @@ temp_concat :: proc(strs: ..[]string) -> []string { return result } -build_deps :: proc(opts: Options) { +temp_path_join :: proc(paths: ..string) -> string { + result, _ := filepath.join(paths, context.temp_allocator) + return result +} + +// Returns a list of shared dependencies that have to be copied to out dir +build_deps :: proc(opts: Options) -> []string { log.infof("build_deps") force := opts.force shared := opts.variant == .Hot_Reload + + out_libs := make([dynamic]string, 0, 3, context.temp_allocator) + // Raylib { cwd := "./libs/raylib" @@ -56,6 +65,10 @@ build_deps :: proc(opts: Options) { }, cwd, ) + + if shared { + append(&out_libs, "./libs/raylib/zig-out-shared/lib/libraylib.so") + } } // Physfs @@ -84,6 +97,10 @@ build_deps :: proc(opts: Options) { run_cmd(build_cmd, cwd) } } + + if shared { + append(&out_libs, "./libs/physfs/build/libphysfs.so.1") + } } // Tracy @@ -135,7 +152,13 @@ build_deps :: proc(opts: Options) { run_cmd({"zig", "ar", "rc", TRACY_NAME_STATIC, "tracy.o"}, cwd) } } + + if shared { + append(&out_libs, "./libs/tracy/tracy.so") + } } + + return out_libs[:] } COMMON_FLAGS :: []string { @@ -176,6 +199,14 @@ setup_emsdk_env :: proc() { set_env("EMSDK", absolute_path("./libs/emsdk")) } +setup_bin_dir :: proc(path: string, files_to_copy: []string) { + mkdir_all(path) + + for file in files_to_copy { + copy_file(file, temp_path_join(path, filepath.base(file))) + } +} + main :: proc() { context.logger = log.create_console_logger() opts := Options { @@ -195,12 +226,11 @@ main :: proc() { debug_flag: []string = opts.debug ? {"-debug"} : {} optimize_flag: []string = opts.optimize ? {"-o:speed"} : {} - build_deps(opts) + shared_dep_paths := build_deps(opts) switch opts.variant { case .Hot_Reload: - remove_all("./build/hotreload") - mkdir_all("./build/hotreload") + setup_bin_dir("./build/hotreload", shared_dep_paths) is_main_built := os.is_file("./build/hotreload/game.bin") if !is_main_built || opts.force { @@ -236,47 +266,51 @@ main :: proc() { COMMON_FLAGS, ) build_game_lib :: proc(build_cmd: []string) { - run_cmd( - build_cmd, - "", - ) + run_cmd(build_cmd, "") rename("./build/hotreload/game_tmp.so", "./build/hotreload/game.so") } build_game_lib(build_game_cmd) - if opts.run { + 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 = &build_game_cmd + thread_ctx.user_ptr = &watcher_ctx thrd := thread.create_and_start(proc() { - build_game_cmd := (cast(^[]string)context.user_ptr)^ - input := os.to_reader(os.stdin) + watcher_ctx := (cast(^Watcher_Context)context.user_ptr) + input := os.to_reader(os.stdin) - buf: [1024]u8 - for { - n, err := io.read(input, buf[:]) + buf: [1024]u8 + for { + n, err := io.read(input, buf[:]) - for cmd in buf[0:n] { - switch cmd { - case 'r', 'R': - build_game_lib(build_game_cmd) - case: + for cmd in buf[0:n] { + switch cmd { + case 'r', 'R': + build_game_lib(watcher_ctx.build_game_cmd) + case: + } } - } - if err == .EOF { - break - } + if err == .EOF { + break + } - handle_error(err) - } - }, thread_ctx) + handle_error(err) + } + }, thread_ctx) run_cmd({"./build/hotreload/game.bin"}, "") - thread.join(thrd) } case .Desktop: - remove_all("./build/desktop") - mkdir_all("./build/desktop") + setup_bin_dir("./build/desktop", shared_dep_paths) + cmd := slice.concatenate( [][]string { []string{"odin", "build", "main_release", "-out:./build/desktop/game.exe"}, @@ -343,4 +377,3 @@ main :: proc() { ) } } - diff --git a/builder/helpers.odin b/builder/helpers.odin index ca50ccb..b387d08 100644 --- a/builder/helpers.odin +++ b/builder/helpers.odin @@ -180,7 +180,10 @@ rename :: proc(src, dst: string, expr := #caller_expression, loc := #caller_loca mkdir_all :: proc(path: string, expr := #caller_expression, loc := #caller_location) { log.infof("mkdir -p %s", path) - handle_error(os.mkdir_all(path), "", expr, loc) + err := os.mkdir_all(path) + if err != .Exist { + handle_error(err, "", expr, loc) + } } absolute_path :: proc( @@ -199,3 +202,18 @@ set_env :: proc(env, val: string) { log.infof("set_env(%s, %s)", env, val) handle_error(os.set_env(env, val)) } + +process_exists :: proc(name: string) -> bool { + pids := handle_error1(os.process_list(context.temp_allocator)) + + for pid in pids { + pinfo, err := os.process_info_by_pid(pid, {.Executable_Path}, context.temp_allocator) + if err == nil { + if filepath.base(pinfo.executable_path) == name { + return true + } + } + } + + return false +} diff --git a/game/assets/assets.odin b/game/assets/assets.odin index 0f94230..65913d7 100644 --- a/game/assets/assets.odin +++ b/game/assets/assets.odin @@ -440,10 +440,8 @@ get_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_C skip_whitespase(&ctx) switch ctx.bytes[ctx.it] { case 'v': - log.debugf("v line: %v", ctx.line) // vertex advance(&ctx) or_break - log.debugf("after advance %v", ctx.bytes[ctx.it]) vertex: rl.Vector3 @@ -453,10 +451,14 @@ get_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_C s := string(ctx.bytes[ctx.it:]) coord_val, nr, ok := strconv.parse_f32_prefix(s) if !ok { - log.errorf("failed to parse float %v %s at line %d", coord_idx, ctx.bytes[ctx.it:][:12], ctx.line) + log.errorf( + "failed to parse float %v %s at line %d", + coord_idx, + ctx.bytes[ctx.it:][:12], + ctx.line, + ) return } - log.debugf("parsed float %v %v from %s", coord_idx, coord_val, ctx.bytes[ctx.it:][:nr]) advance(&ctx, nr) or_break vertex[coord_idx] = coord_val diff --git a/game/fs.odin b/game/fs.odin index c6011b8..d401170 100644 --- a/game/fs.odin +++ b/game/fs.odin @@ -3,6 +3,7 @@ package game import "base:runtime" import "core:c" import "core:log" +import "core:path/filepath" import "core:strings" import "libs:physfs" import rl "libs:raylib" @@ -15,6 +16,23 @@ init_physfs :: proc(args: []string) { physfs.init(strings.clone_to_cstring(args[0], context.temp_allocator)) physfs.setSaneConfig("serega", "gutter-runner", "zip", 0, 1) + + // For non web builds, binary will live 2 subdirs below main content dir, so add it to search path + if args[0] != "." { + base_dir := filepath.dir( + filepath.dir(filepath.dir(args[0], context.temp_allocator), context.temp_allocator), + context.temp_allocator, + ) + abs_base_dir, ok := filepath.abs(base_dir, context.temp_allocator) + assert(ok) + err := physfs.mount(strings.clone_to_cstring(abs_base_dir, context.temp_allocator), "/", 1) + + if err == 0 { + log.panicf("physfs: failed to set base dir {0}", physfs.getLastError()) + } + + log.infof("physfs: added {0} to search path", abs_base_dir) + } } init_physifs_raylib_callbacks :: proc() { diff --git a/game/game.odin b/game/game.odin index 6d9c52b..9e4ef6a 100644 --- a/game/game.odin +++ b/game/game.odin @@ -1265,9 +1265,7 @@ game_update :: proc() -> bool { when ODIN_OS != .JS { // Never run this proc in browser. It contains a 16 ms sleep on web! - if rl.WindowShouldClose() { - return true - } + return !rl.WindowShouldClose() } return true } diff --git a/main_hot_reload/main_hot_reload.odin b/main_hot_reload/main_hot_reload.odin index 87b9cbb..4d8350c 100644 --- a/main_hot_reload/main_hot_reload.odin +++ b/main_hot_reload/main_hot_reload.odin @@ -23,11 +23,13 @@ when ODIN_OS == .Windows { TRACY_ENABLE :: #config(TRACY_ENABLE, false) // We copy the DLL because using it directly would lock it, which would prevent -// the compiler from writing to it. +// the compiler from writing to it (on windows). copy_dll :: proc(bin_dir, to: string) -> bool { - err := os2.copy_file(filepath.join({bin_dir, to}, context.temp_allocator), filepath.join({bin_dir, "game" + DLL_EXT}, context.temp_allocator), ) + src := filepath.join({bin_dir, "game" + DLL_EXT}, context.temp_allocator) + dst := filepath.join({bin_dir, to}, context.temp_allocator) + err := os2.copy_file(dst, src) if err != nil { - fmt.printfln("Failed to copy game" + DLL_EXT + " to {0}", to) + fmt.printfln("Error {0}: Failed to copy {1} to {2}", err, src, dst) return false } @@ -60,29 +62,18 @@ load_game_api :: proc(api_version: int) -> (api: Game_API, ok: bool) { return } - cwd, err := os2.getwd(context.temp_allocator) - if err != nil { - log.panicf("getcwd: %v", err) - } - // NOTE: this needs to be a relative path for Linux to work. - game_dll_name := filepath.join( - { - cwd, - fmt.tprintf( - "{0}game_{1}" + DLL_EXT, - "./" when ODIN_OS != .Windows else "", - api_version, - ), - }, - context.temp_allocator, - ) - copy_dll(game_dll_name) or_return + 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 + + 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 // game DLL. It actually looks for symbols starting with `game_`, which is // why the argument `"game_"` is there. - _, ok = dynlib.initialize_symbols(&api, game_dll_name, "game_", "lib") + _, ok = dynlib.initialize_symbols(&api, game_dll_path, "game_", "lib") if !ok { fmt.printfln("Failed initializing symbols: {0}", dynlib.last_error()) } @@ -101,7 +92,14 @@ unload_game_api :: proc(api: ^Game_API) { } } - if os.remove(fmt.tprintf("game_{0}" + DLL_EXT, api.api_version)) != nil { + bin_dir := filepath.dir(os.args[0], context.temp_allocator) + + game_dll_path := filepath.join( + {bin_dir, fmt.tprintf("game_{0}" + DLL_EXT, api.api_version)}, + context.temp_allocator, + ) + + if os2.remove(game_dll_path) != nil { fmt.printfln("Failed to remove game_{0}" + DLL_EXT + " copy", api.api_version) } } @@ -117,6 +115,10 @@ main :: proc() { secure = true, ) } + + bin_dir := filepath.dir(os.args[0]) + defer delete(bin_dir) + default_allocator := context.allocator tracking_allocator: mem.Tracking_Allocator mem.tracking_allocator_init(&tracking_allocator, default_allocator) @@ -154,7 +156,9 @@ main :: proc() { force_reload := game_api.force_reload() force_restart := game_api.force_restart() reload := force_reload || force_restart - game_dll_mod, game_dll_mod_err := os.last_write_time_by_name("game" + DLL_EXT) + game_dll_mod, game_dll_mod_err := os.last_write_time_by_name( + filepath.join({bin_dir, "game" + DLL_EXT}, context.temp_allocator), + ) if game_dll_mod_err == os.ERROR_NONE && game_api.modification_time != game_dll_mod { reload = true @@ -245,4 +249,3 @@ NvOptimusEnablement: u32 = 1 @(export) AmdPowerXpressRequestHighPerformance: i32 = 1 -