More progress on builder

This commit is contained in:
sergeypdev 2025-05-27 01:23:46 +04:00
parent 875ff96f4a
commit 8b8ab8c6bc
7 changed files with 137 additions and 66 deletions

3
.vscode/launch.json vendored
View File

@ -50,11 +50,10 @@
{ {
"type": "lldb", "type": "lldb",
"request": "launch", "request": "launch",
"preLaunchTask": "Build Hot Reload",
"name": "Run Hot Reload (Linux / Mac)", "name": "Run Hot Reload (Linux / Mac)",
"args": [], "args": [],
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/game_hot_reload.bin", "program": "${workspaceFolder}/build/hotreload/game.bin",
}, },
{ {
"type": "lldb", "type": "lldb",

View File

@ -2,8 +2,8 @@ package builder
import "core:flags" import "core:flags"
import "core:fmt" import "core:fmt"
import "core:log"
import "core:io" import "core:io"
import "core:log"
import os "core:os/os2" import os "core:os/os2"
import "core:path/filepath" import "core:path/filepath"
import "core:slice" import "core:slice"
@ -30,10 +30,19 @@ temp_concat :: proc(strs: ..[]string) -> []string {
return result 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") log.infof("build_deps")
force := opts.force force := opts.force
shared := opts.variant == .Hot_Reload shared := opts.variant == .Hot_Reload
out_libs := make([dynamic]string, 0, 3, context.temp_allocator)
// Raylib // Raylib
{ {
cwd := "./libs/raylib" cwd := "./libs/raylib"
@ -56,6 +65,10 @@ build_deps :: proc(opts: Options) {
}, },
cwd, cwd,
) )
if shared {
append(&out_libs, "./libs/raylib/zig-out-shared/lib/libraylib.so")
}
} }
// Physfs // Physfs
@ -84,6 +97,10 @@ build_deps :: proc(opts: Options) {
run_cmd(build_cmd, cwd) run_cmd(build_cmd, cwd)
} }
} }
if shared {
append(&out_libs, "./libs/physfs/build/libphysfs.so.1")
}
} }
// Tracy // Tracy
@ -135,9 +152,15 @@ build_deps :: proc(opts: Options) {
run_cmd({"zig", "ar", "rc", TRACY_NAME_STATIC, "tracy.o"}, cwd) 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 { COMMON_FLAGS :: []string {
"-collection:libs=./libs", "-collection:libs=./libs",
"-collection:common=./common", "-collection:common=./common",
@ -176,6 +199,14 @@ 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) {
mkdir_all(path)
for file in files_to_copy {
copy_file(file, temp_path_join(path, filepath.base(file)))
}
}
main :: proc() { main :: proc() {
context.logger = log.create_console_logger() context.logger = log.create_console_logger()
opts := Options { opts := Options {
@ -195,12 +226,11 @@ main :: proc() {
debug_flag: []string = opts.debug ? {"-debug"} : {} debug_flag: []string = opts.debug ? {"-debug"} : {}
optimize_flag: []string = opts.optimize ? {"-o:speed"} : {} optimize_flag: []string = opts.optimize ? {"-o:speed"} : {}
build_deps(opts) shared_dep_paths := build_deps(opts)
switch opts.variant { switch opts.variant {
case .Hot_Reload: case .Hot_Reload:
remove_all("./build/hotreload") setup_bin_dir("./build/hotreload", shared_dep_paths)
mkdir_all("./build/hotreload")
is_main_built := os.is_file("./build/hotreload/game.bin") is_main_built := os.is_file("./build/hotreload/game.bin")
if !is_main_built || opts.force { if !is_main_built || opts.force {
@ -236,19 +266,24 @@ main :: proc() {
COMMON_FLAGS, COMMON_FLAGS,
) )
build_game_lib :: proc(build_cmd: []string) { build_game_lib :: proc(build_cmd: []string) {
run_cmd( run_cmd(build_cmd, "")
build_cmd,
"",
)
rename("./build/hotreload/game_tmp.so", "./build/hotreload/game.so") rename("./build/hotreload/game_tmp.so", "./build/hotreload/game.so")
} }
build_game_lib(build_game_cmd) 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 := context
thread_ctx.user_ptr = &build_game_cmd thread_ctx.user_ptr = &watcher_ctx
thrd := thread.create_and_start(proc() { thrd := thread.create_and_start(proc() {
build_game_cmd := (cast(^[]string)context.user_ptr)^ watcher_ctx := (cast(^Watcher_Context)context.user_ptr)
input := os.to_reader(os.stdin) input := os.to_reader(os.stdin)
buf: [1024]u8 buf: [1024]u8
@ -258,7 +293,7 @@ main :: proc() {
for cmd in buf[0:n] { for cmd in buf[0:n] {
switch cmd { switch cmd {
case 'r', 'R': case 'r', 'R':
build_game_lib(build_game_cmd) build_game_lib(watcher_ctx.build_game_cmd)
case: case:
} }
} }
@ -272,11 +307,10 @@ main :: proc() {
}, thread_ctx) }, thread_ctx)
run_cmd({"./build/hotreload/game.bin"}, "") run_cmd({"./build/hotreload/game.bin"}, "")
thread.join(thrd)
} }
case .Desktop: case .Desktop:
remove_all("./build/desktop") setup_bin_dir("./build/desktop", shared_dep_paths)
mkdir_all("./build/desktop")
cmd := slice.concatenate( cmd := slice.concatenate(
[][]string { [][]string {
[]string{"odin", "build", "main_release", "-out:./build/desktop/game.exe"}, []string{"odin", "build", "main_release", "-out:./build/desktop/game.exe"},
@ -343,4 +377,3 @@ main :: proc() {
) )
} }
} }

View File

@ -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) { mkdir_all :: proc(path: string, expr := #caller_expression, loc := #caller_location) {
log.infof("mkdir -p %s", path) 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( absolute_path :: proc(
@ -199,3 +202,18 @@ set_env :: proc(env, val: string) {
log.infof("set_env(%s, %s)", env, val) log.infof("set_env(%s, %s)", env, val)
handle_error(os.set_env(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
}

View File

@ -440,10 +440,8 @@ get_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_C
skip_whitespase(&ctx) skip_whitespase(&ctx)
switch ctx.bytes[ctx.it] { switch ctx.bytes[ctx.it] {
case 'v': case 'v':
log.debugf("v line: %v", ctx.line)
// vertex // vertex
advance(&ctx) or_break advance(&ctx) or_break
log.debugf("after advance %v", ctx.bytes[ctx.it])
vertex: rl.Vector3 vertex: rl.Vector3
@ -453,10 +451,14 @@ get_convex :: proc(assetman: ^Asset_Manager, path: cstring) -> (result: Loaded_C
s := string(ctx.bytes[ctx.it:]) s := string(ctx.bytes[ctx.it:])
coord_val, nr, ok := strconv.parse_f32_prefix(s) coord_val, nr, ok := strconv.parse_f32_prefix(s)
if !ok { 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 return
} }
log.debugf("parsed float %v %v from %s", coord_idx, coord_val, ctx.bytes[ctx.it:][:nr])
advance(&ctx, nr) or_break advance(&ctx, nr) or_break
vertex[coord_idx] = coord_val vertex[coord_idx] = coord_val

View File

@ -3,6 +3,7 @@ package game
import "base:runtime" import "base:runtime"
import "core:c" import "core:c"
import "core:log" import "core:log"
import "core:path/filepath"
import "core:strings" import "core:strings"
import "libs:physfs" import "libs:physfs"
import rl "libs:raylib" 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.init(strings.clone_to_cstring(args[0], context.temp_allocator))
physfs.setSaneConfig("serega", "gutter-runner", "zip", 0, 1) 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() { init_physifs_raylib_callbacks :: proc() {

View File

@ -1265,9 +1265,7 @@ game_update :: proc() -> bool {
when ODIN_OS != .JS { when ODIN_OS != .JS {
// Never run this proc in browser. It contains a 16 ms sleep on web! // Never run this proc in browser. It contains a 16 ms sleep on web!
if rl.WindowShouldClose() { return !rl.WindowShouldClose()
return true
}
} }
return true return true
} }

View File

@ -23,11 +23,13 @@ when ODIN_OS == .Windows {
TRACY_ENABLE :: #config(TRACY_ENABLE, false) TRACY_ENABLE :: #config(TRACY_ENABLE, false)
// We copy the DLL because using it directly would lock it, which would prevent // 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 { 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 { 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 return false
} }
@ -60,29 +62,18 @@ load_game_api :: proc(api_version: int) -> (api: Game_API, ok: bool) {
return 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. // NOTE: this needs to be a relative path for Linux to work.
game_dll_name := filepath.join( game_dll_name := fmt.tprintf("game_{0}" + DLL_EXT, api_version)
{ bin_dir := filepath.dir(os.args[0], context.temp_allocator)
cwd,
fmt.tprintf( copy_dll(bin_dir, game_dll_name) or_return
"{0}game_{1}" + DLL_EXT,
"./" when ODIN_OS != .Windows else "", game_dll_path := filepath.join({bin_dir, game_dll_name}, context.temp_allocator)
api_version,
),
},
context.temp_allocator,
)
copy_dll(game_dll_name) or_return
// 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.
_, ok = dynlib.initialize_symbols(&api, game_dll_name, "game_", "lib") _, ok = dynlib.initialize_symbols(&api, game_dll_path, "game_", "lib")
if !ok { if !ok {
fmt.printfln("Failed initializing symbols: {0}", dynlib.last_error()) 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) fmt.printfln("Failed to remove game_{0}" + DLL_EXT + " copy", api.api_version)
} }
} }
@ -117,6 +115,10 @@ main :: proc() {
secure = true, secure = true,
) )
} }
bin_dir := filepath.dir(os.args[0])
defer delete(bin_dir)
default_allocator := context.allocator default_allocator := context.allocator
tracking_allocator: mem.Tracking_Allocator tracking_allocator: mem.Tracking_Allocator
mem.tracking_allocator_init(&tracking_allocator, default_allocator) mem.tracking_allocator_init(&tracking_allocator, default_allocator)
@ -154,7 +156,9 @@ main :: proc() {
force_reload := game_api.force_reload() force_reload := game_api.force_reload()
force_restart := game_api.force_restart() force_restart := game_api.force_restart()
reload := force_reload || 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 { if game_dll_mod_err == os.ERROR_NONE && game_api.modification_time != game_dll_mod {
reload = true reload = true
@ -245,4 +249,3 @@ NvOptimusEnablement: u32 = 1
@(export) @(export)
AmdPowerXpressRequestHighPerformance: i32 = 1 AmdPowerXpressRequestHighPerformance: i32 = 1