Compare commits

..
3 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
7 changed files with 28 additions and 172 deletions
+3 -8
View File
@@ -7,11 +7,6 @@ $VERSION = "dev-$(Get-Date -Format 'yyyy-MM-dd')-$COMMIT"
$OPTION = $args[0] $OPTION = $args[0]
if ($args) { $args = $args[1..($args.Length - 1)] } else { $args = @() } if ($args) { $args = $args[1..($args.Length - 1)] } else { $args = @() }
# ------------------------------------------
# Setup compiled wgpu binary
& (Join-Path $PSScriptRoot "scripts\wgpu-init.ps1")
# ------------------------------------------ # ------------------------------------------
# Game compile # Game compile
@@ -21,7 +16,7 @@ New-Item -ItemType Directory -Force -Path build | Out-Null
odin build game/ -show-timings ` odin build game/ -show-timings `
-collection:sindri=src ` -collection:sindri=src `
-build-mode:dynamic ` -build-mode:dynamic `
-out:build/game_tmp.dll -microarch:native "-define:VERSION=$VERSION-debug" -debug @args -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, # 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. # which the engine will load before it is actually fully written.
@@ -46,7 +41,7 @@ if ($OPTION -eq "debug") {
-collection:sindri=src ` -collection:sindri=src `
-collection:gram=vendor/gram/src ` -collection:gram=vendor/gram/src `
-collection:wgpu=vendor ` -collection:wgpu=vendor `
-out:build/$PROJECT.exe -microarch:native -use-separate-modules "-define:VERSION=$VERSION-debug" -debug @args -out:build/$PROJECT -microarch:native -use-separate-modules "-define:VERSION=$VERSION-debug" -debug @args
exit 0 exit 0
} }
@@ -54,4 +49,4 @@ odin build src/ -show-timings `
-collection:sindri=src ` -collection:sindri=src `
-collection:gram=vendor/gram/src ` -collection:gram=vendor/gram/src `
-collection:wgpu=vendor ` -collection:wgpu=vendor `
-out:build/$PROJECT.exe -microarch:native -o:speed "-define:VERSION=$VERSION" @args -out:build/$PROJECT -microarch:native -o:speed "-define:VERSION=$VERSION" @args
+6 -48
View File
@@ -9,56 +9,14 @@ tested and validated findings, for continued reference by me.
When a program loads in a dynamic library, what happens with package globals? When a program loads in a dynamic library, what happens with package globals?
Findings (macOS): Findings (macOS):
- Package globals are shared between modules, but only indirectly - The package globals are shared between the host and the lib
- This is due to the dynamic linker merging symbols into a (single) flat namespace
- Practically this means the lib's unset package globals get shadowed by the hosts'
- After hot reloading the lib, the memory of the packge is not refreshed, - After hot reloading the lib, the memory of the packge is not refreshed,
it keeps pointing to the package global of the host it keeps pointing to the package global of the host
Findings (Windows): The clanker states this is due to "flat-namespace symbol interposition".
- Package globals are not shared between modules, they have their own copy
Findings (Linux): Linux "ELF has symbol preemption: when resolving references, ld.so searches
- Package globals are not shared between modules, they have their own copy globals scope in order — executable first, then loaded shared objects".
Conclusion: avoid package globals, or pass pointers to the dynamic library so Windows PE has no interposition at all, every module (exe and each DLL) has its
it can match its package globals with the host module. own symbol table.
## Dependency Patterns
1. Strict layering, only call downwards. If two modules need each other, one of
the modules is in a wrong layer. Move it, or extract the shared part into a
lower layer.
2. Dependency inversion: if required, define an interface in the lower layer,
upper layer registers itself.
3. Dependency Injection: Prevent dependencies via data, over includes. Pass what
a function needs as arguments instead of reaching another module's state.
4. Events instead of callbacks both ways If A and B would call each other,
have A push events into a queue and B consume them. Neither module includes
the other; they only share the event type (which lives in a lower layer).
5. Merge or split as a last resort, if two modules genuinely can't be untangled.
Sindri Engine:
```
+---------------------+ Game:
| Game | - Game
+---------------------+
|
v
+---------------------+ +---------------------+ Core:
| Core |<----+----| main | - Input
+---------------------+ | +---------------------+
| |
v |
+---------------------+ | Platform:
| Platform |<----+ - GLFW
+---------------------+ | - wgpu
| |
v |
+---------------------+ | Base:
| Base |<----+ - Hot Reload
+---------------------+ - Settings
- Event
- Keycodes
```
+10 -1
View File
@@ -5,6 +5,7 @@ import "core:time"
import "core:fmt" import "core:fmt"
import "sindri:core" import "sindri:core"
import "sindri:test"
// ----------------------------------------- // -----------------------------------------
@@ -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)
@@ -62,6 +65,8 @@ init :: proc() {
g = new(Game_Memory) g = new(Game_Memory)
memory_set(g) memory_set(g)
test.test_proc = proc() { asd := 2 }
} }
@(export) @(export)
@@ -71,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)
@@ -84,7 +93,7 @@ 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
} }
-100
View File
@@ -1,100 +0,0 @@
$ErrorActionPreference = "Stop"
$VENDOR_DIR = Join-Path $PSScriptRoot "..\vendor" | Resolve-Path
# ------------------------------------------
# Platform detection (target dir name, only used for the up-to-date check)
# Windows PowerShell 5.1 doesn't define $IsWindows/$IsMacOS/$IsLinux
if (-not (Test-Path variable:IsWindows)) { $IsWindows = $env:OS -eq "Windows_NT" }
if (-not (Test-Path variable:IsMacOS)) { $IsMacOS = $false }
if (-not (Test-Path variable:IsLinux)) { $IsLinux = $false }
if ($IsWindows) {
$os = "windows"
$extra = "-msvc"
} elseif ($IsMacOS) {
$os = "macos"
$extra = ""
} elseif ($IsLinux) {
$os = "linux"
$extra = ""
} else {
Write-Error "wgpu setup: unsupported OS"
exit 1
}
$procArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
$arch = if ($procArch -eq [System.Runtime.InteropServices.Architecture]::Arm64) { "aarch64" } else { "x86_64" }
$odinRoot = (odin root).Trim()
$name = "wgpu-$os-$arch$extra-release"
$target = Join-Path $VENDOR_DIR "wgpu\lib\$name"
# ------------------------------------------
# WGPU version detection
if ($args.Count -gt 0 -and $args[0]) {
$WGPU_VERSION = $args[0]
} else {
$odinTypes = Join-Path $odinRoot "vendor\wgpu\wgpu_native_types.odin"
$typesContent = Get-Content $odinTypes -Raw
if ($typesContent -match 'BINDINGS_VERSION_STRING :: "([^"]*)"') {
$WGPU_VERSION = "v" + $Matches[1]
}
}
if (-not $WGPU_VERSION -or $WGPU_VERSION -eq "v") {
Write-Error "wgpu setup: could not detect the wgpu-native version"
exit 1
}
# Detect the version of the vendored wgpu bindings
$currentTypes = Join-Path $VENDOR_DIR "wgpu\wgpu_native_types.odin"
$current = $null
if (Test-Path $currentTypes) {
$cur = Get-Content $currentTypes -Raw
if ($cur -match 'BINDINGS_VERSION_STRING :: "([^"]*)"') {
$current = "v" + $Matches[1]
}
}
# Check if the vendored wgpu is already up to date
if ($current -eq $WGPU_VERSION -and (Test-Path $target)) {
exit 0
}
# ------------------------------------------
# Copy vendor/wgpu
# Remove any stale copy
if (Test-Path (Join-Path $VENDOR_DIR "wgpu")) {
Remove-Item -Recurse -Force (Join-Path $VENDOR_DIR "wgpu")
}
# Copy wgpu
$wgpuPath = Join-Path $odinRoot "vendor\wgpu"
Copy-Item -Recurse $wgpuPath $VENDOR_DIR
# Point imports at the copied vendor wgpu package
Get-ChildItem -Path (Join-Path $VENDOR_DIR "wgpu") -Recurse -Filter *.odin | ForEach-Object {
(Get-Content $_.FullName -Raw) -replace '"vendor:wgpu"', '"wgpu:wgpu"' | Set-Content $_.FullName -NoNewline
}
# ------------------------------------------
# Download
$assetBase = "https://github.com/gfx-rs/wgpu-native/releases/download/$WGPU_VERSION"
if (-not (Test-Path $target)) {
$url = "$assetBase/$name.zip"
Write-Output "wgpu setup: downloading $url"
$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString())
New-Item -ItemType Directory -Path $tmp | Out-Null
try {
Invoke-WebRequest -MaximumRedirection 5 -Uri $url -OutFile (Join-Path $tmp "wgpu.zip")
Expand-Archive -Force -Path (Join-Path $tmp "wgpu.zip") -DestinationPath $target
} finally {
Remove-Item -Recurse -Force $tmp
}
}
-15
View File
@@ -1,6 +1,5 @@
package hot_reload package hot_reload
import "core:c"
import "core:dynlib" import "core:dynlib"
import "core:fmt" import "core:fmt"
import "core:os" import "core:os"
@@ -133,20 +132,6 @@ load_game_lib :: proc(api: ^Game_API) -> bool {
"error: failed initializing symbols: {0}", "error: failed initializing symbols: {0}",
dynlib.last_error(), dynlib.last_error(),
) )
} else {
// Odin's shared libraries don't register a constructor, so the lib's
// runtime init (@init procs like os.stdout setup) never runs on load.
// Call it explicitly.
// Windows doesn't need this: its dynamic build exports DllMain, which
// the OS calls on load and which runs _startup_runtime itself.
when ODIN_OS != .Windows {
Entry :: #type proc "c" () -> c.int
entry_ptr, entry_ok := dynlib.symbol_address(
api.lib,
"_odin_entry_point",
)
if entry_ok do (transmute(Entry)entry_ptr)()
}
} }
return ok return ok
+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)))
+5
View File
@@ -0,0 +1,5 @@
package test
test: int = 0
test_proc: proc()