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
16 changed files with 261 additions and 548 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
+2 -4
View File
@@ -21,10 +21,8 @@ mkdir -p build
# Game.dll # Game.dll
odin build game/ -show-timings \ odin build game/ -show-timings \
-collection:sindri=src \ -collection:sindri=src \
-collection:gram=vendor/gram/src \
-collection:wgpu=vendor \
-build-mode:dynamic \ -build-mode:dynamic \
-out:build/game_tmp -microarch:native -o:minimal -use-separate-modules -define:VERSION="$VERSION-debug" -debug "$@" -out:build/game_tmp -microarch:native -define:VERSION="$VERSION-debug" -debug "$@"
# Need to use a temp file on Linux/macOS because it first writes an empty file, # Need to use a temp file on Linux/macOS 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.
@@ -49,7 +47,7 @@ if [ "$OPTION" = "debug" ]; then
-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 -microarch:native -o:minimal -use-separate-modules -define:VERSION="$VERSION-debug" -debug "$@" -out:build/$PROJECT -microarch:native -use-separate-modules -define:VERSION="$VERSION-debug" -debug "$@"
exit 0 exit 0
fi fi
+6 -53
View File
@@ -4,66 +4,19 @@ This document contains specifics about the workings of the Odin programming
language, dynamic linking and state management design patterns. These are language, dynamic linking and state management design patterns. These are
tested and validated findings, for continued reference by me. tested and validated findings, for continued reference by me.
## Compiler
Odin's compiler is demand-driver: a procedure's body is only semantically
analyzed when the proc is actually referenced.
## Dynamic Library Memory ## Dynamic Library Memory
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
```
+17 -8
View File
@@ -1,10 +1,11 @@
package game package game
import "sindri:input"
import "core:time" import "core:time"
import "core:fmt" import "core:fmt"
import "sindri:base"
import "sindri:core" import "sindri:core"
import "sindri:test"
// ----------------------------------------- // -----------------------------------------
@@ -40,12 +41,12 @@ memory_set :: proc(mem: rawptr) {
} }
@(export) @(export)
settings :: proc() -> base.Settings { settings :: proc() -> core.Settings {
return base.Settings { return core.Settings {
width = 960, width = 960,
height = 540, height = 540,
title = "WGPU Native Triangle", title = "WGPU Native Triangle",
mode = base.WindowMode.Windowed, mode = core.WindowMode.Windowed,
refresh = 60, refresh = 60,
vsync = true, vsync = true,
} }
@@ -54,6 +55,8 @@ settings :: proc() -> base.Settings {
@(export) @(export)
init_once :: proc() { init_once :: proc() {
fmt.println("init once") fmt.println("init once")
test.test_proc = proc() {}
} }
@(export) @(export)
@@ -62,15 +65,21 @@ init :: proc() {
g = new(Game_Memory) g = new(Game_Memory)
memory_set(g) memory_set(g)
test.test_proc = proc() { asd := 2 }
} }
@(export) @(export)
update :: proc(dt: f32) { update :: proc(dt: f32) {
fmt.println("dt:", dt) fmt.println("dt:", dt)
if core.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,17 +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 {
return core.key_state(.Key_F5) == .Press return input.key_state(.Key_F5) == .Press
} }
@(export) @(export)
force_restart :: proc() -> bool { force_restart :: proc() -> bool {
return core.key_state(.Key_F6) == .Press return input.key_state(.Key_F6) == .Press
} }
-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
}
}
-268
View File
@@ -1,268 +0,0 @@
package base
import "base:intrinsics"
import "base:runtime"
import "core:fmt"
// -----------------------------------------
// Types
// event category, bitfield (?)
Event_Data :: union {
Window_Close_Event,
Window_Resize_Event,
Joystick_Connect_Event,
Joystick_Disconnect_Event,
Key_Press_Event,
Key_Release_Event,
Key_Repeat_Event,
Mouse_Button_Press_Event,
Mouse_Button_Release_Event,
Mouse_Position_Event,
Mouse_Scroll_Event,
}
Event :: struct {
data: Event_Data,
handled: bool,
}
Window_Close_Event :: struct {}
Window_Resize_Event :: struct {
width: i32,
height: i32,
}
Key_Press_Event :: struct {
key: Key,
mods: Mod_Set,
}
Key_Release_Event :: struct {
key: Key,
mods: Mod_Set,
}
Key_Repeat_Event :: struct {
key: Key,
mods: Mod_Set,
}
Mouse_Button_Press_Event :: struct {
button: Mouse_Button,
mods: Mod_Set,
}
Mouse_Button_Release_Event :: struct {
button: Mouse_Button,
mods: Mod_Set,
}
Mouse_Position_Event :: struct {
x_pos: f32,
y_pos: f32,
}
Mouse_Scroll_Event :: struct {
x_offset: f32,
y_offset: f32,
}
Joystick_Connect_Event :: struct {
id: i32,
}
Joystick_Disconnect_Event :: struct {
id: i32,
}
// Message Receiver
@(private)
Listener :: struct {
callback: proc(event: ^Event_Data, user_ptr: rawptr) -> bool, // return true = don't propagate
user_ptr: rawptr,
// TODO: test if hot_reload keeps the same function pointers and the listeners remain working.
// if they dont add a flag "engine_listener: bool", which engine calls should set true,
// then clear all game listeners
}
// Transfers messages between Sender and Receiver
Event_Bus :: struct {
queue: [dynamic]Event,
listeners: map[typeid][dynamic]Listener,
allocator: runtime.Allocator,
}
// -----------------------------------------
// Variables
event_bus: Event_Bus
// -----------------------------------------
// Constructor/destructor
event_bus_init :: proc(allocator := context.allocator) {
// make() doesnt allocate until there's capacity, so set it > 0
queue := make([dynamic]Event, 0, 4, allocator)
listeners := make(map[typeid][dynamic]Listener, 4, allocator)
event_bus = {
queue = queue,
listeners = listeners,
allocator = allocator,
}
}
event_bus_destroy :: proc() {
assert(event_bus.queue != nil)
assert(event_bus.listeners != nil)
for _, ls in event_bus.listeners do delete(ls)
delete(event_bus.queue)
delete(event_bus.listeners)
event_bus.queue = nil
event_bus.listeners = nil
}
// -----------------------------------------
// Public functions
event_subscribe :: proc(
$T: typeid,
callback: proc(event: ^T, user_ptr: rawptr) -> bool,
user_ptr: rawptr = nil,
) where intrinsics.type_is_variant_of(Event_Data, T) {
cb := cast(proc(_: ^Event_Data, _: rawptr) -> bool)callback // upcast
event_subscribe_raw(T, cb, user_ptr)
}
event_unsubscribe :: proc(
$T: typeid,
callback: proc(event: ^T, user_ptr: rawptr) -> bool,
user_ptr: rawptr = nil,
) where intrinsics.type_is_variant_of(Event_Data, T) {
cb := cast(proc(_: ^Event_Data, _: rawptr) -> bool)callback // upcast
event_unsubscribe_raw(T, cb, user_ptr)
}
on_event :: proc(data: Event_Data) {
assert(event_bus.queue != nil)
append(&event_bus.queue, Event{data = data})
}
// Drain all the events that have been queued up, in enqueue order
drain_event_queue :: proc() {
assert(event_bus.queue != nil)
fmt.println("toby!")
for &event in event_bus.queue {
#partial switch e in event.data {
case Window_Close_Event:
event_dispatch(Window_Close_Event, &event)
fmt.println("Close")
case Window_Resize_Event:
event_dispatch(Window_Resize_Event, &event)
fmt.println("Resize:", e.width, e.height)
case Key_Press_Event:
event_dispatch(Key_Press_Event, &event)
fmt.println("Key press", e.key, e.mods)
case Key_Release_Event:
event_dispatch(Key_Release_Event, &event)
fmt.println("Key release", e.key, e.mods)
case Key_Repeat_Event:
event_dispatch(Key_Repeat_Event, &event)
fmt.println("Key repeat", e.key, e.mods)
case Mouse_Button_Press_Event:
event_dispatch(Mouse_Button_Press_Event, &event)
fmt.println("Mouse press", e.button, e.mods)
case Mouse_Button_Release_Event:
event_dispatch(Mouse_Button_Release_Event, &event)
fmt.println("Mouse release", e.button, e.mods)
case Mouse_Position_Event:
event_dispatch(Mouse_Position_Event, &event)
fmt.printfln("Mouse pos: {}x{}", e.x_pos, e.y_pos)
case Mouse_Scroll_Event:
event_dispatch(Mouse_Scroll_Event, &event)
fmt.printfln("Mouse scroll: {}x{}", e.x_offset, e.y_offset)
case Joystick_Connect_Event:
event_dispatch(Joystick_Connect_Event, &event)
fmt.println("Joy con:", e.id)
case Joystick_Disconnect_Event:
event_dispatch(Joystick_Disconnect_Event, &event)
fmt.println("Joy dis:", e.id)
case:
panic("[event] unimplemented event")
}
}
clear(&event_bus.queue)
}
// Dispatch to the listeners registered for T, in reverse subscription order.
// First listener to return true claims the event and stops propagation
event_dispatch :: proc(
$T: typeid,
event: ^Event,
) -> bool where intrinsics.type_is_variant_of(Event_Data, T) {
assert(event_bus.listeners != nil)
ls, ok := event_bus.listeners[T]
if !ok do return false
#reverse for &l in ls {
callback := cast(proc(_: ^T, _: rawptr) -> bool)l.callback // downcast
if callback(&event.data.(T), l.user_ptr) {
event.handled = true
return true
}
}
return false
}
// -----------------------------------------
// Private functions
@(private)
event_subscribe_raw :: proc(
$T: typeid,
callback: proc(event: ^Event_Data, user_ptr: rawptr) -> bool,
user_ptr: rawptr = nil,
) where intrinsics.type_is_variant_of(Event_Data, T) {
assert(event_bus.listeners != nil)
if _, ok := event_bus.listeners[T]; !ok {
event_bus.listeners[T] = make([dynamic]Listener, event_bus.allocator)
}
append(&event_bus.listeners[T], Listener{callback, user_ptr})
}
@(private)
event_unsubscribe_raw :: proc(
$T: typeid,
callback: proc(event: ^Event_Data, user_ptr: rawptr) -> bool,
user_ptr: rawptr = nil,
) where intrinsics.type_is_variant_of(Event_Data, T) {
assert(event_bus.listeners != nil)
ls, ok := event_bus.listeners[T]
if !ok do return
for i, l in ls {
if l.callback == callback && l.user_ptr == user_ptr {
ordered_remove(&event_bus.listeners[T], i) // preserve subscription order
return
}
}
}
-25
View File
@@ -1,25 +0,0 @@
package core
import "sindri:base"
import "sindri:platform"
// -----------------------------------------
// Public functions
// Returns the state of the keyboard key
key_state :: proc(key: base.Key) -> base.Action {
return platform.os_key_state(key)
}
// Returns the state of the mouse button
mouse_button_state :: proc(button: base.Mouse_Button) -> base.Action {
return platform.os_mouse_button_state(button)
}
// Returns the position of the mouse cursor
mouse_position :: proc() -> (x_pos: f32, y_pos: f32) {
return platform.os_mouse_position()
}
// -----------------------------------------
// Private functions
@@ -1,4 +1,4 @@
package base package core
// ----------------------------------------- // -----------------------------------------
+138
View File
@@ -0,0 +1,138 @@
package event
import "core:fmt"
import "sindri:input"
// -----------------------------------------
// Types
// event category, bitfield (?)
// Event_Data :: union {
Event :: union {
Window_Close_Event,
Window_Resize_Event,
Joystick_Connect_Event,
Joystick_Disconnect_Event,
Key_Press_Event,
Key_Release_Event,
Key_Repeat_Event,
Mouse_Button_Press_Event,
Mouse_Button_Release_Event,
Mouse_Position_Event,
Mouse_Scroll_Event,
}
// Event :: struct {
// data: Event_Data,
// handled: bool,
// }
Window_Close_Event :: struct {
handled: bool,
}
Window_Resize_Event :: struct {
handled: bool,
width: i32,
height: i32,
}
Joystick_Connect_Event :: struct {
handled: bool,
id: i32,
}
Joystick_Disconnect_Event :: struct {
handled: bool,
id: i32,
}
Key_Press_Event :: struct {
handled: bool,
key: input.Key,
mods: input.Mod_Set,
}
Key_Release_Event :: struct {
handled: bool,
key: input.Key,
mods: input.Mod_Set,
}
Key_Repeat_Event :: struct {
handled: bool,
key: input.Key,
mods: input.Mod_Set,
}
Mouse_Button_Press_Event :: struct {
handled: bool,
button: input.Mouse_Button,
mods: input.Mod_Set,
}
Mouse_Button_Release_Event :: struct {
handled: bool,
button: input.Mouse_Button,
mods: input.Mod_Set,
}
Mouse_Position_Event :: struct {
handled: bool,
x_pos: f32,
y_pos: f32,
}
Mouse_Scroll_Event :: struct {
handled: bool,
x_offset: f32,
y_offset: f32,
}
// -----------------------------------------
// 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
on_event :: proc(event: Event) {
fmt.println("toby!")
#partial switch e in event {
case Window_Close_Event:
fmt.println("Close")
case Window_Resize_Event:
fmt.println("Resize:", e.width, e.height)
case Key_Press_Event:
fmt.println("Key press", e.key, e.mods)
case Key_Release_Event:
fmt.println("Key release", e.key, e.mods)
case Key_Repeat_Event:
fmt.println("Key repeat", e.key, e.mods)
case Mouse_Button_Press_Event:
fmt.println("Mouse press", e.button, e.mods)
case Mouse_Button_Release_Event:
fmt.println("Mouse release", e.button, e.mods)
case Mouse_Position_Event:
fmt.printfln("Mouse pos: {}x{}", e.x_pos, e.y_pos)
case Joystick_Connect_Event:
fmt.println("Joy con:", e.id)
case Joystick_Disconnect_Event:
fmt.println("Joy dis:", e.id)
}
}
/*
Notes:
Have the events queued up into a frame buffer that gets drained at a specific time.
*/
@@ -1,11 +1,12 @@
package base package hot_reload
import "core:c"
import "core:dynlib" import "core:dynlib"
import "core:fmt" import "core:fmt"
import "core:os" import "core:os"
import "core:time" import "core:time"
import "sindri:core"
when ODIN_OS == .Windows { when ODIN_OS == .Windows {
LIB_EXT :: ".dll" LIB_EXT :: ".dll"
} else when ODIN_OS == .Darwin { } else when ODIN_OS == .Darwin {
@@ -40,7 +41,7 @@ Game_API :: struct {
memory_free: proc(), memory_free: proc(),
memory_size: proc() -> int, memory_size: proc() -> int,
memory_set: proc(mem: rawptr), memory_set: proc(mem: rawptr),
settings: proc() -> Settings, settings: proc() -> core.Settings,
init_once: proc(), init_once: proc(),
init: proc(), init: proc(),
update: proc(_: f32), update: proc(_: f32),
@@ -131,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
+19
View File
@@ -0,0 +1,19 @@
package input
// -----------------------------------------
// Public functions
// Returns the state of the keyboard key
// implementation in platform package, to prevent cyclic dependency
key_state: proc(key: Key) -> Action
// Returns the state of the mouse button
// implementation in platform package, to prevent cyclic dependency
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
@@ -1,4 +1,4 @@
package base package input
// ----------------------------------------- // -----------------------------------------
+10 -18
View File
@@ -2,8 +2,9 @@ package sindri
import "core:fmt" import "core:fmt"
import "core:time" import "core:time"
import "sindri:test"
import "sindri:base" import "sindri:hot_reload"
import "sindri:platform" import "sindri:platform"
VERSION :: #config(VERSION, "dev") VERSION :: #config(VERSION, "dev")
@@ -14,16 +15,10 @@ main :: proc() {
fmt.println("hello world!") fmt.println("hello world!")
// Hot reload development functionality // Hot reload development functionality
hr, err := base.hot_reload_init() hr, err := hot_reload.hot_reload_init()
defer base.hot_reload_destroy(&hr) defer hot_reload.hot_reload_destroy(&hr)
// Event system api := hot_reload.active_lib(&hr)
base.event_bus_init()
defer base.event_bus_destroy()
base.event_subscribe(base.Key_Press_Event, test)
api := base.active_lib(&hr)
settings := api.settings() settings := api.settings()
// Initialize Window // Initialize Window
@@ -41,27 +36,24 @@ main :: proc() {
gt: f64 = 0 gt: f64 = 0
dt: f32 dt: f32
for !platform.os_should_close() && !base.should_close(&hr) { for !platform.os_should_close() && !hot_reload.should_close(&hr) {
start := time.tick_now() start := time.tick_now()
platform.os_poll_events() platform.os_poll_events()
base.drain_event_queue()
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)))
gt += f64(dt) gt += f64(dt)
// Hot reload // Hot reload
api, err = base.reload_game_lib(&hr) api, err = hot_reload.reload_game_lib(&hr)
if err != nil do break if err != nil do break
} }
api.destroy() api.destroy()
} }
test :: proc(e: ^base.Key_Press_Event, _: rawptr) -> bool {
fmt.println("main: @@@@@@@@@@@", e)
return true
}
+53 -43
View File
@@ -7,7 +7,9 @@ import "vendor:glfw"
import "wgpu:wgpu" import "wgpu:wgpu"
import "wgpu:wgpu/glfwglue" import "wgpu:wgpu/glfwglue"
import "sindri:base" import "sindri:core"
import "sindri:event"
import "sindri:input"
// ----------------------------------------- // -----------------------------------------
// Types // Types
@@ -19,7 +21,7 @@ OS :: struct {
// ----------------------------------------- // -----------------------------------------
// Constructor / destructor // Constructor / destructor
os_init :: proc(settings: base.Settings) { os_init :: proc(settings: core.Settings) {
if !glfw.Init() { if !glfw.Init() {
panic("[glfw] init failure") panic("[glfw] init failure")
} }
@@ -46,6 +48,11 @@ os_init :: proc(settings: base.Settings) {
glfw.SetCursorPosCallback(state.os.window, cursor_pos_callback) glfw.SetCursorPosCallback(state.os.window, cursor_pos_callback)
glfw.SetScrollCallback(state.os.window, scroll_callback) glfw.SetScrollCallback(state.os.window, scroll_callback)
glfw.SetJoystickCallback(joystick_callback) glfw.SetJoystickCallback(joystick_callback)
// Register input functions
input.key_state = key_state
input.mouse_button_state = mouse_button_state
input.mouse_position = mouse_position
} }
os_destroy :: proc() { os_destroy :: proc() {
@@ -56,7 +63,7 @@ os_destroy :: proc() {
// ----------------------------------------- // -----------------------------------------
// Public functions // Public functions
os_set_monitor :: proc(settings: base.Settings) { os_set_monitor :: proc(settings: core.Settings) {
monitor := glfw.GetPrimaryMonitor() monitor := glfw.GetPrimaryMonitor()
x_pos: i32 x_pos: i32
y_pos: i32 y_pos: i32
@@ -133,44 +140,47 @@ os_get_surface :: proc(instance: wgpu.Instance) -> wgpu.Surface {
return glfwglue.GetSurface(instance, state.os.window) return glfwglue.GetSurface(instance, state.os.window)
} }
os_key_state :: proc(key: base.Key) -> base.Action {
return input_action(glfw.GetKey(state.os.window, i32(key)))
}
os_mouse_button_state :: proc(button: base.Mouse_Button) -> base.Action {
return input_action(glfw.GetMouseButton(state.os.window, i32(button)))
}
os_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 functions // Private functions
@(private = "file") @(private = "file")
input_action :: proc(action: i32) -> base.Action { key_state :: proc(key: input.Key) -> input.Action {
if action == glfw.RELEASE do return base.Action.Release return input_action(glfw.GetKey(state.os.window, i32(key)))
else if action == glfw.PRESS do return base.Action.Press }
else if action == glfw.REPEAT do return base.Action.Repeat
@(private = "file")
mouse_button_state :: proc(button: input.Mouse_Button) -> input.Action {
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")
input_action :: proc(action: i32) -> input.Action {
if action == glfw.RELEASE do return input.Action.Release
else if action == glfw.PRESS do return input.Action.Press
else if action == glfw.REPEAT do return input.Action.Repeat
when ODIN_DEBUG do panic("[glfw] unknown action") when ODIN_DEBUG do panic("[glfw] unknown action")
return .None return .None
} }
@(private = "file") @(private = "file")
input_key :: proc(key: i32) -> base.Key { input_key :: proc(key: i32) -> input.Key {
return base.Key(key) // values match return input.Key(key) // values match
} }
@(private = "file") @(private = "file")
input_mod_set :: proc(mods: i32) -> (set: base.Mod_Set) { input_mod_set :: proc(mods: i32) -> (set: input.Mod_Set) {
return transmute(base.Mod_Set)i8(mods & 0x3f) // values match bit position return transmute(input.Mod_Set)i8(mods & 0x3f) // values match bit position
} }
@(private = "file") @(private = "file")
input_mouse_button :: proc(button: i32) -> base.Mouse_Button { input_mouse_button :: proc(button: i32) -> input.Mouse_Button {
return base.Mouse_Button(button) // values match return input.Mouse_Button(button) // values match
} }
// Error callback // Error callback
@@ -184,7 +194,7 @@ error_callback :: proc "c" (error: i32, description: cstring) {
@(private = "file") @(private = "file")
window_close_callback :: proc "c" (window: glfw.WindowHandle) { window_close_callback :: proc "c" (window: glfw.WindowHandle) {
context = state.ctx context = state.ctx
base.on_event(base.Window_Close_Event{}) event.on_event(event.Window_Close_Event{})
} // GLFWwindowclosefun } // GLFWwindowclosefun
// Window resize callback // Window resize callback
@@ -201,24 +211,24 @@ key_callback :: proc "c" (
) { ) {
context = state.ctx context = state.ctx
if action == glfw.PRESS { if action == glfw.PRESS {
base.on_event( event.on_event(
base.Key_Press_Event { event.Key_Press_Event {
key = input_key(key), key = input_key(key),
mods = input_mod_set(mods), mods = input_mod_set(mods),
}, },
) )
} }
if action == glfw.RELEASE { if action == glfw.RELEASE {
base.on_event( event.on_event(
base.Key_Release_Event { event.Key_Release_Event {
key = input_key(key), key = input_key(key),
mods = input_mod_set(mods), mods = input_mod_set(mods),
}, },
) )
} }
if action == glfw.REPEAT { if action == glfw.REPEAT {
base.on_event( event.on_event(
base.Key_Repeat_Event { event.Key_Repeat_Event {
key = input_key(key), key = input_key(key),
mods = input_mod_set(mods), mods = input_mod_set(mods),
}, },
@@ -234,16 +244,16 @@ mouse_button_callback :: proc "c" (
) { ) {
context = state.ctx context = state.ctx
if action == glfw.PRESS { if action == glfw.PRESS {
base.on_event( event.on_event(
base.Mouse_Button_Press_Event { event.Mouse_Button_Press_Event {
button = input_mouse_button(button), button = input_mouse_button(button),
mods = input_mod_set(mods), mods = input_mod_set(mods),
}, },
) )
} }
if action == glfw.RELEASE { if action == glfw.RELEASE {
base.on_event( event.on_event(
base.Mouse_Button_Release_Event { event.Mouse_Button_Release_Event {
button = input_mouse_button(button), button = input_mouse_button(button),
mods = input_mod_set(mods), mods = input_mod_set(mods),
}, },
@@ -258,8 +268,8 @@ cursor_pos_callback :: proc "c" (
x_pos, y_pos: f64, x_pos, y_pos: f64,
) { ) {
context = state.ctx context = state.ctx
base.on_event( event.on_event(
base.Mouse_Position_Event{x_pos = f32(x_pos), y_pos = f32(y_pos)}, event.Mouse_Position_Event{x_pos = f32(x_pos), y_pos = f32(y_pos)},
) )
} // GLFWcursorposfun } // GLFWcursorposfun
@@ -270,8 +280,8 @@ scroll_callback :: proc "c" (
x_offset, y_offset: f64, x_offset, y_offset: f64,
) { ) {
context = state.ctx context = state.ctx
base.on_event( event.on_event(
base.Mouse_Scroll_Event { event.Mouse_Scroll_Event {
x_offset = f32(x_offset), x_offset = f32(x_offset),
y_offset = f32(y_offset), y_offset = f32(y_offset),
}, },
@@ -283,9 +293,9 @@ scroll_callback :: proc "c" (
joystick_callback :: proc "c" (id, connected: i32) { joystick_callback :: proc "c" (id, connected: i32) {
context = state.ctx context = state.ctx
if connected == glfw.CONNECTED { if connected == glfw.CONNECTED {
base.on_event(base.Joystick_Connect_Event{id = id}) event.on_event(event.Joystick_Connect_Event{id = id})
} else { } else {
base.on_event(base.Joystick_Disconnect_Event{id = id}) event.on_event(event.Joystick_Disconnect_Event{id = id})
} }
} // GLFWjoystickfun } // GLFWjoystickfun
+2 -2
View File
@@ -6,7 +6,7 @@ import "core:slice"
import "wgpu:wgpu" import "wgpu:wgpu"
import "sindri:base" import "sindri:core"
// ----------------------------------------- // -----------------------------------------
// Variables // Variables
@@ -33,7 +33,7 @@ state: struct {
// ----------------------------------------- // -----------------------------------------
// Constructor/destructor // Constructor/destructor
instance_init :: proc(settings: base.Settings) { instance_init :: proc(settings: core.Settings) {
state.ctx = context state.ctx = context
wgpu.SetLogCallback(log_callback, nil) wgpu.SetLogCallback(log_callback, nil)
+5
View File
@@ -0,0 +1,5 @@
package test
test: int = 0
test_proc: proc()