Compare commits

...
5 Commits
Author SHA1 Message Date
Riyyi be516d39a3 Add Windows build script 2026-09-15 18:41:04 +02:00
Riyyi e7cc935035 lol 2026-09-14 23:00:24 +02:00
Riyyi 416b42c27e . 2026-09-14 19:25:33 +02:00
Riyyi 2b8488a1e2 Add mouse position input function 2026-09-13 00:43:18 +02:00
Riyyi 7740632541 Free wgpu capabilities memory 2026-09-13 00:23:56 +02:00
9 changed files with 140 additions and 18 deletions
+52
View File
@@ -0,0 +1,52 @@
$ErrorActionPreference = "Stop"
$PROJECT = "sindri"
$COMMIT = git rev-parse --short HEAD
$VERSION = "dev-$(Get-Date -Format 'yyyy-MM-dd')-$COMMIT"
$OPTION = $args[0]
if ($args) { $args = $args[1..($args.Length - 1)] } else { $args = @() }
# ------------------------------------------
# Game compile
New-Item -ItemType Directory -Force -Path build | Out-Null
# Game.dll
odin build game/ -show-timings `
-collection:sindri=src `
-build-mode:dynamic `
-out:build/game_tmp -microarch:native "-define:VERSION=$VERSION-debug" -debug @args
# Need to use a temp file on Windows because it first writes an empty file,
# which the engine will load before it is actually fully written.
if (-not (Test-Path "build/game_tmp.dll")) {
Write-Error "error: build produced no game lib.dll"
exit 1
}
Move-Item -Force "build/game_tmp.dll" "build/game.dll"
# ------------------------------------------
# Engine compile
# If the executable is already running, then don't try to build and start it.
$proc = Get-Process -Name $PROJECT -ErrorAction SilentlyContinue
if ($proc) {
Write-Output "Hot reloading..."
exit 0
}
if ($OPTION -eq "debug") {
odin build src/ -show-timings `
-collection:sindri=src `
-collection:gram=vendor/gram/src `
-collection:wgpu=vendor `
-out:build/$PROJECT -microarch:native -use-separate-modules "-define:VERSION=$VERSION-debug" -debug @args
exit 0
}
odin build src/ -show-timings `
-collection:sindri=src `
-collection:gram=vendor/gram/src `
-collection:wgpu=vendor `
-out:build/$PROJECT -microarch:native -o:speed "-define:VERSION=$VERSION" @args
+22
View File
@@ -0,0 +1,22 @@
# Odin
This document contains specifics about the workings of the Odin programming
language, dynamic linking and state management design patterns. These are
tested and validated findings, for continued reference by me.
## Dynamic Library Memory
When a program loads in a dynamic library, what happens with package globals?
Findings (macOS):
- The package globals are shared between the host and the lib
- After hot reloading the lib, the memory of the packge is not refreshed,
it keeps pointing to the package global of the host
The clanker states this is due to "flat-namespace symbol interposition".
Linux "ELF has symbol preemption: when resolving references, ld.so searches
globals scope in order — executable first, then loaded shared objects".
Windows PE has no interposition at all, every module (exe and each DLL) has its
own symbol table.
+14 -6
View File
@@ -5,11 +5,12 @@ import "core:time"
import "core:fmt" import "core:fmt"
import "sindri:core" import "sindri:core"
import "sindri:test"
// ----------------------------------------- // -----------------------------------------
Game_Memory :: struct { Game_Memory :: struct {
should_close: bool should_close: bool,
} }
g: ^Game_Memory g: ^Game_Memory
@@ -54,6 +55,8 @@ settings :: proc() -> core.Settings {
@(export) @(export)
init_once :: proc() { init_once :: proc() {
fmt.println("init once") fmt.println("init once")
test.test_proc = proc() {}
} }
@(export) @(export)
@@ -61,6 +64,9 @@ init :: proc() {
fmt.println("hello from .dll!") fmt.println("hello from .dll!")
g = new(Game_Memory) g = new(Game_Memory)
memory_set(g)
test.test_proc = proc() { asd := 2 }
} }
@(export) @(export)
@@ -70,6 +76,10 @@ update :: proc(dt: f32) {
if input.key_state(.Key_Escape) == .Press { if input.key_state(.Key_Escape) == .Press {
g.should_close = true g.should_close = true
} }
fmt.println("GAME:", test.test)
fmt.printf("pointer: %p | %p\n", &test.test, &test.test_proc)
test.test += 1
} }
@(export) @(export)
@@ -83,19 +93,17 @@ start := time.tick_now()
should_close :: proc() -> bool { should_close :: proc() -> bool {
elapsed := time.tick_since(start) elapsed := time.tick_since(start)
seconds := time.duration_seconds(elapsed) seconds := time.duration_seconds(elapsed)
if seconds > 4 do return true // if seconds > 4 do return true
return g.should_close return g.should_close
} }
@(export) @(export)
force_reload :: proc() -> bool { force_reload :: proc() -> bool {
// TODO: read keypress return input.key_state(.Key_F5) == .Press
return false
} }
@(export) @(export)
force_restart :: proc() -> bool { force_restart :: proc() -> bool {
// TODO: read keypress return input.key_state(.Key_F6) == .Press
return false
} }
+20
View File
@@ -5,9 +5,11 @@ import "core:fmt"
import "sindri:input" import "sindri:input"
// ----------------------------------------- // -----------------------------------------
// Types
// event category, bitfield (?) // event category, bitfield (?)
// Event_Data :: union {
Event :: union { Event :: union {
Window_Close_Event, Window_Close_Event,
Window_Resize_Event, Window_Resize_Event,
@@ -22,6 +24,11 @@ Event :: union {
Mouse_Scroll_Event, Mouse_Scroll_Event,
} }
// Event :: struct {
// data: Event_Data,
// handled: bool,
// }
Window_Close_Event :: struct { Window_Close_Event :: struct {
handled: bool, handled: bool,
} }
@@ -85,6 +92,13 @@ Mouse_Scroll_Event :: struct {
} }
// ----------------------------------------- // -----------------------------------------
// Variables
// I want to have a list of Listeners per Event type, but this is state
// listeners: map[typeid][dynamic]Listener
// -----------------------------------------
// Public functions
// Dispatcher(Event) -> forwards to the right function // Dispatcher(Event) -> forwards to the right function
on_event :: proc(event: Event) { on_event :: proc(event: Event) {
@@ -116,3 +130,9 @@ on_event :: proc(event: Event) {
fmt.println("Joy dis:", e.id) fmt.println("Joy dis:", e.id)
} }
} }
/*
Notes:
Have the events queued up into a frame buffer that gets drained at a specific time.
*/
+4
View File
@@ -11,5 +11,9 @@ key_state: proc(key: Key) -> Action
// implementation in platform package, to prevent cyclic dependency // implementation in platform package, to prevent cyclic dependency
mouse_button_state: proc(button: Mouse_Button) -> Action mouse_button_state: proc(button: Mouse_Button) -> Action
// Returns the position of the mouse cursor
// implementation in platform package, to prevent cyclic dependency
mouse_position: proc() -> (x_pos: f32, y_pos: f32)
// ----------------------------------------- // -----------------------------------------
// Private functions // Private functions
+4
View File
@@ -2,6 +2,7 @@ package sindri
import "core:fmt" import "core:fmt"
import "core:time" import "core:time"
import "sindri:test"
import "sindri:hot_reload" import "sindri:hot_reload"
import "sindri:platform" import "sindri:platform"
@@ -41,6 +42,9 @@ main :: proc() {
platform.os_poll_events() platform.os_poll_events()
api.update(dt) api.update(dt)
fmt.println("MAIN:", test.test)
fmt.printf("pointer: %p | %p\n", &test.test, &test.test_proc)
platform.frame(dt) platform.frame(dt)
dt = f32(time.duration_seconds(time.tick_since(start))) dt = f32(time.duration_seconds(time.tick_since(start)))
+7 -5
View File
@@ -52,11 +52,7 @@ os_init :: proc(settings: core.Settings) {
// Register input functions // Register input functions
input.key_state = key_state input.key_state = key_state
input.mouse_button_state = mouse_button_state input.mouse_button_state = mouse_button_state
input.mouse_position = mouse_position
// TODO: Figure out proper vsync, found 3 spots so far
// - glfw.SwapInterval(0) this is only for OpenGL it seems?
// - glfw.SetWindowMonitor(refresh)
// - wgpu presentMode = .Fifo
} }
os_destroy :: proc() { os_destroy :: proc() {
@@ -157,6 +153,12 @@ mouse_button_state :: proc(button: input.Mouse_Button) -> input.Action {
return input_action(glfw.GetMouseButton(state.os.window, i32(button))) return input_action(glfw.GetMouseButton(state.os.window, i32(button)))
} }
@(private = "file")
mouse_position :: proc() -> (x_pos: f32, y_pos: f32) {
x_pos_f64, y_pos_64 := glfw.GetCursorPos(state.os.window)
return f32(x_pos_f64), f32(y_pos_64)
}
@(private = "file") @(private = "file")
input_action :: proc(action: i32) -> input.Action { input_action :: proc(action: i32) -> input.Action {
if action == glfw.RELEASE do return input.Action.Release if action == glfw.RELEASE do return input.Action.Release
+12 -7
View File
@@ -26,7 +26,7 @@ state: struct {
pipeline_layout: wgpu.PipelineLayout, pipeline_layout: wgpu.PipelineLayout,
pipeline: wgpu.RenderPipeline, pipeline: wgpu.RenderPipeline,
// ---------------------------------------- // ----------------------------------------
present_modes: []wgpu.PresentMode, capabilities: wgpu.SurfaceCapabilities,
vsync: bool, vsync: bool,
} }
@@ -69,6 +69,7 @@ instance_destroy :: proc() {
wgpu.ShaderModuleRelease(state.module) wgpu.ShaderModuleRelease(state.module)
wgpu.QueueRelease(state.queue) wgpu.QueueRelease(state.queue)
wgpu.DeviceRelease(state.device) wgpu.DeviceRelease(state.device)
wgpu.SurfaceCapabilitiesFreeMembers(state.capabilities)
wgpu.AdapterRelease(state.adapter) wgpu.AdapterRelease(state.adapter)
wgpu.SurfaceRelease(state.surface) wgpu.SurfaceRelease(state.surface)
wgpu.InstanceRelease(state.instance) wgpu.InstanceRelease(state.instance)
@@ -260,25 +261,29 @@ get_present_modes :: proc() {
if status != .Success { if status != .Success {
fmt.panicf("[wgpu] surface capabilities failure: [%v]", status) fmt.panicf("[wgpu] surface capabilities failure: [%v]", status)
} }
state.present_modes = capabilities.presentModes[:capabilities.presentModeCount] state.capabilities = capabilities
fmt.println("[wgpu] supported present modes", state.present_modes)
present_modes := capabilities.presentModes[:capabilities.presentModeCount]
fmt.println("[wgpu] supported present modes", present_modes)
} }
@(private = "file") @(private = "file")
pick_present_mode :: proc(vsync: bool) -> wgpu.PresentMode { pick_present_mode :: proc(vsync: bool) -> wgpu.PresentMode {
if len(state.present_modes) == 0 do return .Fifo present_modes := state.capabilities.presentModes[:state.capabilities.presentModeCount]
if len(present_modes) == 0 do return .Fifo
// Mimmick wgpu auto Vsync behavior // Mimmick wgpu auto Vsync behavior
// https://docs.rs/wgpu/latest/wgpu/enum.PresentMode.html // https://docs.rs/wgpu/latest/wgpu/enum.PresentMode.html
if vsync { if vsync {
if slice.contains(state.present_modes, wgpu.PresentMode.FifoRelaxed) { if slice.contains(present_modes, wgpu.PresentMode.FifoRelaxed) {
return .FifoRelaxed // Adaptive Vsync return .FifoRelaxed // Adaptive Vsync
} }
} else { } else {
if slice.contains(state.present_modes, wgpu.PresentMode.Immediate) { if slice.contains(present_modes, wgpu.PresentMode.Immediate) {
return .Immediate // Vsync Off return .Immediate // Vsync Off
} }
if slice.contains(state.present_modes, wgpu.PresentMode.Mailbox) { if slice.contains(present_modes, wgpu.PresentMode.Mailbox) {
return .Mailbox // Fast Vsync return .Mailbox // Fast Vsync
} }
} }
+5
View File
@@ -0,0 +1,5 @@
package test
test: int = 0
test_proc: proc()