Build Cradle
odin build main.odin -file -out:build/debug.exe -debug
Build Game (This one is the hot reload one)
odin build game.odin -file -build-mode:dll -out:build/game.dll -debug
Linux/MacOS
remove .exe and swap .dll for .so or .dylib
| package main | |
| import "core:fmt" | |
| // Types and constants from raylib are safe to use - old dynamic libs | |
| // are kept alive until full restart. | |
| // | |
| // Procedures are not safe due to different raylib context between | |
| // Cradle and Game. | |
| import rl "vendor:raylib" | |
| import "raylib_api" | |
| Vec2 :: rl.Vector2 | |
| // We can keep dynamic arrays here as the context (Odin's context) is | |
| // passed from the Cradle to Game via calling convention and thus memory | |
| // is owned by Cradle. | |
| Game_State :: struct { | |
| positions: [dynamic]Vec2, | |
| sizes: [dynamic]Vec2, | |
| colors: [dynamic]rl.Color, | |
| } | |
| @export | |
| game_memory_size :: proc() -> int { | |
| return size_of(Game_State) | |
| } | |
| @export | |
| game_init :: proc(gs: ^Game_State, rl: ^raylib_api.Raylib_API) { | |
| append(&gs.positions, Vec2{10, 10}) | |
| append(&gs.positions, Vec2{50, 50}) | |
| append(&gs.positions, Vec2{150, 20}) | |
| append(&gs.sizes, Vec2{100, 100}) | |
| append(&gs.sizes, Vec2{15, 30}) | |
| append(&gs.sizes, Vec2{30, 800}) | |
| append(&gs.colors, rl.RED) | |
| append(&gs.colors, rl.GREEN) | |
| append(&gs.colors, rl.BLUE) | |
| } | |
| @export | |
| game_update :: proc(gs: ^Game_State, rl: ^raylib_api.Raylib_API) { | |
| // Write changes here, such as (uncomment these then recompile game | |
| // without closing the program): | |
| // gs.sizes[2].y = 80 | |
| // gs.positions[0] = rl.GetMousePosition() | |
| } | |
| @export | |
| game_render :: proc(gs: ^Game_State, rl: ^raylib_api.Raylib_API) { | |
| rl.ClearBackground(rl.BLACK) | |
| for pos, i in gs.positions { | |
| size := gs.sizes[i] | |
| color := gs.colors[i] | |
| rl.DrawRectangleV(pos, size, color) | |
| } | |
| } |
| package main | |
| import "core:fmt" | |
| import "core:os/os2" | |
| import "core:os" | |
| import "core:mem" | |
| import "core:dynlib" | |
| import rl "vendor:raylib" | |
| import "raylib_api" | |
| DLL_PATH :: "build/" | |
| DLL_NAME :: "game" | |
| when ODIN_OS == .Windows { | |
| DLL_EXT :: ".dll" | |
| } else when ODIN_OS == .Darwin { | |
| DLL_EXT :: ".dylib" | |
| } else { | |
| DLL_EXT :: ".so" | |
| } | |
| DLL_PATH_FULL :: DLL_PATH + DLL_NAME + DLL_EXT | |
| Game_API :: struct { | |
| lib: dynlib.Library, | |
| reload_count: int, | |
| last_write_time: os.File_Time, | |
| game_memory_size: proc() -> int, | |
| game_init: proc(game_memory: rawptr, rl: ^raylib_api.Raylib_API), | |
| game_update: proc(game_memory: rawptr, rl: ^raylib_api.Raylib_API), | |
| game_render: proc(game_memory: rawptr, rl: ^raylib_api.Raylib_API), | |
| } | |
| game_api_reload :: proc(api: ^Game_API) -> (ok: bool) { | |
| // Construct the path to copy the DLL | |
| // The DLL must be copied as the OS locks it, so when we compile it | |
| // can't be overwritten | |
| dll_copy_path_full := fmt.tprintf("%s%s_%d%s", DLL_PATH, DLL_NAME, api.reload_count, DLL_EXT) | |
| // NOTE: We don't unload old DLLs because they may contain memory | |
| // being used (string literals, etc) | |
| // | |
| // Alternative: If you don't want to keep old DLLs until a restart, | |
| // allocate every string using the Cradle's allocator (context) | |
| if copy_err := os2.copy_file(dll_copy_path_full, DLL_PATH_FULL); copy_err != nil { | |
| return false | |
| } | |
| // Load the symbols from the DLL | |
| if symbol_count, symbol_init_ok := dynlib.initialize_symbols(api, dll_copy_path_full); !symbol_init_ok { | |
| // Most likely error is 'TOO SHORT' | |
| // as in, the file is still being written | |
| return false | |
| } | |
| // Get the time the DLL was changed | |
| if dll_last_write_time, dll_last_write_time_err := os.last_write_time_by_name(DLL_PATH_FULL); dll_last_write_time_err == nil { | |
| api.last_write_time = dll_last_write_time | |
| } else { | |
| fmt.printfln("Failed to read last write time of `%s`", DLL_PATH_FULL) | |
| return false | |
| } | |
| // Everything is okay | |
| api.reload_count += 1 | |
| return true | |
| } | |
| main :: proc() { | |
| rl_api := raylib_api.init() | |
| game_api: Game_API | |
| assert(game_api_reload(&game_api)) | |
| rl.InitWindow(800, 600, "Hot Reloading Test") | |
| defer rl.CloseWindow() | |
| game_memory_buffer := make([]u8, game_api.game_memory_size()) | |
| game_memory_pointer := raw_data(game_memory_buffer) | |
| game_api.game_init(game_memory_pointer, &rl_api) | |
| for !rl.WindowShouldClose() { | |
| if dll_write_time, dll_write_time_err := os.last_write_time_by_name(DLL_PATH_FULL); dll_write_time_err == nil { | |
| if dll_write_time != game_api.last_write_time { | |
| if !game_api_reload(&game_api) { | |
| // Don't try to call into the DLL until it's loaded | |
| continue | |
| } | |
| } | |
| } | |
| game_api.game_update(game_memory_pointer, &rl_api) | |
| rl.BeginDrawing() | |
| game_api.game_render(game_memory_pointer, &rl_api) | |
| rl.EndDrawing() | |
| } | |
| } |