Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6edcce16c6 | ||
|
|
aea791fe2b |
@@ -36,3 +36,15 @@ vendor
|
|||||||
- microui (temp)
|
- microui (temp)
|
||||||
- box3d?
|
- box3d?
|
||||||
- build.sh -> build.odin
|
- build.sh -> build.odin
|
||||||
|
|
||||||
|
## Hot Reload
|
||||||
|
|
||||||
|
build.sh -> hot reload enabled, debug mode enabled
|
||||||
|
build_release.sh -> hot reload disabled, debug mode disabled
|
||||||
|
build_debug.sh -> hot reload disabled, debug mode enabled
|
||||||
|
|
||||||
|
hot reload builds engine as .exe, then game as dynamic lib.
|
||||||
|
skips building the engine if its already running (pgrep).
|
||||||
|
|
||||||
|
no hot reload does not use game.dll, instead it imports the game source
|
||||||
|
as a normal Odin package during build.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
set -e
|
set -eu
|
||||||
|
|
||||||
PROJECT="sindri"
|
PROJECT="sindri"
|
||||||
VERSION="dev-$(date -u '+%Y-%m-%d')-$(git rev-parse --short HEAD)"
|
VERSION="dev-$(date -u '+%Y-%m-%d')-$(git rev-parse --short HEAD)"
|
||||||
@@ -14,6 +14,12 @@ VERSION="dev-$(date -u '+%Y-%m-%d')-$(git rev-parse --short HEAD)"
|
|||||||
|
|
||||||
mkdir -p build
|
mkdir -p build
|
||||||
|
|
||||||
|
# Game.dll
|
||||||
|
odin build game/ -show-timings \
|
||||||
|
-collection:sindri=src \
|
||||||
|
-build-mode:dynamic \
|
||||||
|
-out:build/game -microarch:native -define:VERSION="$VERSION-debug" -debug "$@"
|
||||||
|
|
||||||
if [ "$1" = "debug" ]; then
|
if [ "$1" = "debug" ]; then
|
||||||
shift
|
shift
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import "core:time"
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
init :: proc() {
|
||||||
|
fmt.println("hello from .dll!")
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.tick_now()
|
||||||
|
|
||||||
|
@(export)
|
||||||
|
should_close :: proc() -> bool {
|
||||||
|
elapsed := time.tick_since(start)
|
||||||
|
seconds := time.duration_seconds(elapsed)
|
||||||
|
if seconds > 4 do return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
+9
-11
@@ -1,6 +1,5 @@
|
|||||||
package sindri
|
package sindri
|
||||||
|
|
||||||
import "core:time"
|
|
||||||
import "vendor:glfw"
|
import "vendor:glfw"
|
||||||
|
|
||||||
import "wgpu:wgpu"
|
import "wgpu:wgpu"
|
||||||
@@ -27,20 +26,19 @@ os_init :: proc() {
|
|||||||
glfw.SetFramebufferSizeCallback(state.os.window, size_callback)
|
glfw.SetFramebufferSizeCallback(state.os.window, size_callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
os_run :: proc() {
|
os_should_close :: proc() -> bool {
|
||||||
dt: f32
|
return bool(glfw.WindowShouldClose(state.os.window))
|
||||||
|
}
|
||||||
|
|
||||||
for !glfw.WindowShouldClose(state.os.window) {
|
os_set_should_close :: proc(val: bool) {
|
||||||
start := time.tick_now()
|
glfw.SetWindowShouldClose(state.os.window, b32(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
os_poll_events :: proc() {
|
||||||
glfw.PollEvents()
|
glfw.PollEvents()
|
||||||
frame(dt)
|
}
|
||||||
|
|
||||||
dt = f32(time.duration_seconds(time.tick_since(start)))
|
|
||||||
}
|
|
||||||
|
|
||||||
instance_destroy()
|
|
||||||
|
|
||||||
|
os_destroy :: proc() {
|
||||||
glfw.DestroyWindow(state.os.window)
|
glfw.DestroyWindow(state.os.window)
|
||||||
glfw.Terminate()
|
glfw.Terminate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package hot_reload
|
||||||
|
|
||||||
|
import "core:dynlib"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
when ODIN_OS == .Windows {
|
||||||
|
LIB_EXT :: ".dll"
|
||||||
|
} else when ODIN_OS == .Darwin {
|
||||||
|
LIB_EXT :: ".dylib"
|
||||||
|
} else {
|
||||||
|
LIB_EXT :: ".so"
|
||||||
|
}
|
||||||
|
|
||||||
|
GAME_LIB_DIR :: "build/"
|
||||||
|
GAME_LIB_PATH :: GAME_LIB_DIR + "game" + LIB_EXT
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
Hot_Reload :: struct {
|
||||||
|
loaded_libs: [dynamic]Game_API,
|
||||||
|
}
|
||||||
|
|
||||||
|
Hot_Reload_Error :: enum u8 {
|
||||||
|
None = 0,
|
||||||
|
Load_Game_Lib_Failed = 1,
|
||||||
|
Unimplemented = 127,
|
||||||
|
Okay = None,
|
||||||
|
}
|
||||||
|
|
||||||
|
Error :: union #shared_nil {
|
||||||
|
Hot_Reload_Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
Game_API :: struct {
|
||||||
|
lib: dynlib.Library,
|
||||||
|
init: proc(),
|
||||||
|
update: proc(),
|
||||||
|
render: proc(),
|
||||||
|
destroy: proc(),
|
||||||
|
should_close: proc() -> bool,
|
||||||
|
api_version: int, // iteration of the game lib
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
hot_reload_init :: proc() -> (state: Hot_Reload, error: Error) {
|
||||||
|
api: Game_API
|
||||||
|
api.api_version = -1
|
||||||
|
|
||||||
|
api_ok := load_game_lib(&api)
|
||||||
|
if !api_ok {
|
||||||
|
fmt.println("Failed to load Game API")
|
||||||
|
return {}, .Load_Game_Lib_Failed
|
||||||
|
}
|
||||||
|
|
||||||
|
state.loaded_libs = make([dynamic]Game_API)
|
||||||
|
append(&state.loaded_libs, api)
|
||||||
|
|
||||||
|
return state, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
hot_reload_destroy :: proc(state: ^Hot_Reload) {
|
||||||
|
for &api in state.loaded_libs {
|
||||||
|
unload_game_lib(&api)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(state.loaded_libs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
active_lib :: proc(hr: ^Hot_Reload) -> ^Game_API {
|
||||||
|
return &hr.loaded_libs[len(hr.loaded_libs) - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
should_close :: proc(hr: ^Hot_Reload) -> bool {
|
||||||
|
api := active_lib(hr)
|
||||||
|
if api.lib == nil do return true
|
||||||
|
return api.should_close()
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_lib :: proc(to: string) -> bool {
|
||||||
|
copy_err := os.copy_file(to, GAME_LIB_PATH)
|
||||||
|
if copy_err != nil {
|
||||||
|
fmt.printfln(
|
||||||
|
"error: Failed to copy " + GAME_LIB_PATH + " to {0}: %v",
|
||||||
|
to,
|
||||||
|
copy_err,
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load game lib symbols
|
||||||
|
load_game_lib :: proc(api: ^Game_API) -> bool {
|
||||||
|
// Load next iteration
|
||||||
|
api.api_version += 1
|
||||||
|
|
||||||
|
lib_name := fmt.tprintf(
|
||||||
|
GAME_LIB_DIR + "game_{0}" + LIB_EXT,
|
||||||
|
api.api_version,
|
||||||
|
)
|
||||||
|
copy_lib(lib_name) or_return
|
||||||
|
|
||||||
|
// Match the names of the fields in Game_API to symbols in the game DLL
|
||||||
|
_, ok := dynlib.initialize_symbols(api, GAME_LIB_PATH, "", "lib")
|
||||||
|
if !ok {
|
||||||
|
fmt.printfln("Failed initializing symbols: {0}", dynlib.last_error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload game lib, used during full restarts
|
||||||
|
unload_game_lib :: proc(api: ^Game_API) {
|
||||||
|
if api.lib != nil {
|
||||||
|
if !dynlib.unload_library(api.lib) {
|
||||||
|
fmt.eprintfln(
|
||||||
|
"error: Failed unloading lib: {0}",
|
||||||
|
dynlib.last_error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.remove(
|
||||||
|
fmt.tprintf(GAME_LIB_DIR + "game_{0}" + LIB_EXT, api.api_version),
|
||||||
|
) !=
|
||||||
|
nil {
|
||||||
|
fmt.printfln(
|
||||||
|
"error: Failed to remove {0}game_{1}" + LIB_EXT + " copy",
|
||||||
|
GAME_LIB_DIR,
|
||||||
|
api.api_version,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package sindri
|
package sindri
|
||||||
|
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
|
import "core:time"
|
||||||
|
|
||||||
|
import "sindri:hot_reload"
|
||||||
|
|
||||||
VERSION :: #config(VERSION, "dev")
|
VERSION :: #config(VERSION, "dev")
|
||||||
|
|
||||||
@@ -9,5 +12,29 @@ VERSION :: #config(VERSION, "dev")
|
|||||||
main :: proc() {
|
main :: proc() {
|
||||||
fmt.println("hello world!")
|
fmt.println("hello world!")
|
||||||
|
|
||||||
|
// Hot reload development functionality
|
||||||
|
hr, err := hot_reload.hot_reload_init()
|
||||||
|
defer hot_reload.hot_reload_destroy(&hr)
|
||||||
|
|
||||||
|
api := hot_reload.active_lib(&hr)
|
||||||
|
api.init()
|
||||||
|
|
||||||
|
// Initialize Window
|
||||||
|
os_init()
|
||||||
|
defer os_destroy()
|
||||||
|
|
||||||
|
// Initialize GPU resources
|
||||||
instance_init()
|
instance_init()
|
||||||
|
defer instance_destroy()
|
||||||
|
|
||||||
|
gt: f32
|
||||||
|
|
||||||
|
for !os_should_close() && !hot_reload.should_close(&hr) {
|
||||||
|
start := time.tick_now()
|
||||||
|
|
||||||
|
os_poll_events()
|
||||||
|
frame(gt)
|
||||||
|
|
||||||
|
gt = f32(time.duration_seconds(time.tick_since(start)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ state: struct {
|
|||||||
instance_init :: proc() {
|
instance_init :: proc() {
|
||||||
state.ctx = context
|
state.ctx = context
|
||||||
|
|
||||||
os_init()
|
|
||||||
|
|
||||||
state.instance = wgpu.CreateInstance(nil)
|
state.instance = wgpu.CreateInstance(nil)
|
||||||
if state.instance == nil {
|
if state.instance == nil {
|
||||||
panic("WebGPU is not supported")
|
panic("WebGPU is not supported")
|
||||||
@@ -130,8 +128,6 @@ instance_init :: proc() {
|
|||||||
multisample = {count = 1, mask = 0xFFFFFFFF},
|
multisample = {count = 1, mask = 0xFFFFFFFF},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
os_run()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user