Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbc320b7f5 | ||
|
|
22be96ee45 | ||
|
|
eb495f67c2 | ||
|
|
5cc4f9c7f5 | ||
|
|
b436c4dff2 | ||
|
|
43bd34a505 |
@@ -7,6 +7,11 @@ $VERSION = "dev-$(Get-Date -Format 'yyyy-MM-dd')-$COMMIT"
|
||||
$OPTION = $args[0]
|
||||
if ($args) { $args = $args[1..($args.Length - 1)] } else { $args = @() }
|
||||
|
||||
# ------------------------------------------
|
||||
|
||||
# Setup compiled wgpu binary
|
||||
& (Join-Path $PSScriptRoot "scripts\wgpu-init.ps1")
|
||||
|
||||
# ------------------------------------------
|
||||
# Game compile
|
||||
|
||||
@@ -16,7 +21,7 @@ New-Item -ItemType Directory -Force -Path build | Out-Null
|
||||
odin build game/ -show-timings `
|
||||
-collection:sindri=src `
|
||||
-build-mode:dynamic `
|
||||
-out:build/game_tmp -microarch:native "-define:VERSION=$VERSION-debug" -debug @args
|
||||
-out:build/game_tmp.dll -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.
|
||||
@@ -41,7 +46,7 @@ if ($OPTION -eq "debug") {
|
||||
-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
|
||||
-out:build/$PROJECT.exe -microarch:native -use-separate-modules "-define:VERSION=$VERSION-debug" -debug @args
|
||||
exit 0
|
||||
}
|
||||
|
||||
@@ -49,4 +54,4 @@ 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
|
||||
-out:build/$PROJECT.exe -microarch:native -o:speed "-define:VERSION=$VERSION" @args
|
||||
|
||||
@@ -21,8 +21,10 @@ mkdir -p build
|
||||
# Game.dll
|
||||
odin build game/ -show-timings \
|
||||
-collection:sindri=src \
|
||||
-collection:gram=vendor/gram/src \
|
||||
-collection:wgpu=vendor \
|
||||
-build-mode:dynamic \
|
||||
-out:build/game_tmp -microarch:native -define:VERSION="$VERSION-debug" -debug "$@"
|
||||
-out:build/game_tmp -microarch:native -o:minimal -use-separate-modules -define:VERSION="$VERSION-debug" -debug "$@"
|
||||
|
||||
# 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.
|
||||
@@ -47,7 +49,7 @@ if [ "$OPTION" = "debug" ]; then
|
||||
-collection:sindri=src \
|
||||
-collection:gram=vendor/gram/src \
|
||||
-collection:wgpu=vendor \
|
||||
-out:build/$PROJECT -microarch:native -use-separate-modules -define:VERSION="$VERSION-debug" -debug "$@"
|
||||
-out:build/$PROJECT -microarch:native -o:minimal -use-separate-modules -define:VERSION="$VERSION-debug" -debug "$@"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
+53
-6
@@ -4,19 +4,66 @@ 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.
|
||||
|
||||
## Compiler
|
||||
|
||||
Odin's compiler is demand-driver: a procedure's body is only semantically
|
||||
analyzed when the proc is actually referenced.
|
||||
|
||||
## 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
|
||||
- Package globals are shared between modules, but only indirectly
|
||||
- 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,
|
||||
it keeps pointing to the package global of the host
|
||||
|
||||
The clanker states this is due to "flat-namespace symbol interposition".
|
||||
Findings (Windows):
|
||||
- Package globals are not shared between modules, they have their own copy
|
||||
|
||||
Linux "ELF has symbol preemption: when resolving references, ld.so searches
|
||||
globals scope in order — executable first, then loaded shared objects".
|
||||
Findings (Linux):
|
||||
- Package globals are not shared between modules, they have their own copy
|
||||
|
||||
Windows PE has no interposition at all, every module (exe and each DLL) has its
|
||||
own symbol table.
|
||||
Conclusion: avoid package globals, or pass pointers to the dynamic library so
|
||||
it can match its package globals with the host module.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
+8
-17
@@ -1,11 +1,10 @@
|
||||
package game
|
||||
|
||||
import "sindri:input"
|
||||
import "core:time"
|
||||
import "core:fmt"
|
||||
|
||||
import "sindri:base"
|
||||
import "sindri:core"
|
||||
import "sindri:test"
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
@@ -41,12 +40,12 @@ memory_set :: proc(mem: rawptr) {
|
||||
}
|
||||
|
||||
@(export)
|
||||
settings :: proc() -> core.Settings {
|
||||
return core.Settings {
|
||||
settings :: proc() -> base.Settings {
|
||||
return base.Settings {
|
||||
width = 960,
|
||||
height = 540,
|
||||
title = "WGPU Native Triangle",
|
||||
mode = core.WindowMode.Windowed,
|
||||
mode = base.WindowMode.Windowed,
|
||||
refresh = 60,
|
||||
vsync = true,
|
||||
}
|
||||
@@ -55,8 +54,6 @@ settings :: proc() -> core.Settings {
|
||||
@(export)
|
||||
init_once :: proc() {
|
||||
fmt.println("init once")
|
||||
|
||||
test.test_proc = proc() {}
|
||||
}
|
||||
|
||||
@(export)
|
||||
@@ -65,21 +62,15 @@ init :: proc() {
|
||||
|
||||
g = new(Game_Memory)
|
||||
memory_set(g)
|
||||
|
||||
test.test_proc = proc() { asd := 2 }
|
||||
}
|
||||
|
||||
@(export)
|
||||
update :: proc(dt: f32) {
|
||||
fmt.println("dt:", dt)
|
||||
|
||||
if input.key_state(.Key_Escape) == .Press {
|
||||
if core.key_state(.Key_Escape) == .Press {
|
||||
g.should_close = true
|
||||
}
|
||||
|
||||
fmt.println("GAME:", test.test)
|
||||
fmt.printf("pointer: %p | %p\n", &test.test, &test.test_proc)
|
||||
test.test += 1
|
||||
}
|
||||
|
||||
@(export)
|
||||
@@ -93,17 +84,17 @@ start := time.tick_now()
|
||||
should_close :: proc() -> bool {
|
||||
elapsed := time.tick_since(start)
|
||||
seconds := time.duration_seconds(elapsed)
|
||||
// if seconds > 4 do return true
|
||||
if seconds > 4 do return true
|
||||
|
||||
return g.should_close
|
||||
}
|
||||
|
||||
@(export)
|
||||
force_reload :: proc() -> bool {
|
||||
return input.key_state(.Key_F5) == .Press
|
||||
return core.key_state(.Key_F5) == .Press
|
||||
}
|
||||
|
||||
@(export)
|
||||
force_restart :: proc() -> bool {
|
||||
return input.key_state(.Key_F6) == .Press
|
||||
return core.key_state(.Key_F6) == .Press
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
$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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
package hot_reload
|
||||
package base
|
||||
|
||||
import "core:c"
|
||||
import "core:dynlib"
|
||||
import "core:fmt"
|
||||
import "core:os"
|
||||
import "core:time"
|
||||
|
||||
import "sindri:core"
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
LIB_EXT :: ".dll"
|
||||
} else when ODIN_OS == .Darwin {
|
||||
@@ -41,7 +40,7 @@ Game_API :: struct {
|
||||
memory_free: proc(),
|
||||
memory_size: proc() -> int,
|
||||
memory_set: proc(mem: rawptr),
|
||||
settings: proc() -> core.Settings,
|
||||
settings: proc() -> Settings,
|
||||
init_once: proc(),
|
||||
init: proc(),
|
||||
update: proc(_: f32),
|
||||
@@ -132,6 +131,20 @@ load_game_lib :: proc(api: ^Game_API) -> bool {
|
||||
"error: failed initializing symbols: {0}",
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
package input
|
||||
package base
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package core
|
||||
package base
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
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,138 +0,0 @@
|
||||
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,19 +0,0 @@
|
||||
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
|
||||
+18
-10
@@ -2,9 +2,8 @@ package sindri
|
||||
|
||||
import "core:fmt"
|
||||
import "core:time"
|
||||
import "sindri:test"
|
||||
|
||||
import "sindri:hot_reload"
|
||||
import "sindri:base"
|
||||
import "sindri:platform"
|
||||
|
||||
VERSION :: #config(VERSION, "dev")
|
||||
@@ -15,10 +14,16 @@ main :: proc() {
|
||||
fmt.println("hello world!")
|
||||
|
||||
// Hot reload development functionality
|
||||
hr, err := hot_reload.hot_reload_init()
|
||||
defer hot_reload.hot_reload_destroy(&hr)
|
||||
hr, err := base.hot_reload_init()
|
||||
defer base.hot_reload_destroy(&hr)
|
||||
|
||||
api := hot_reload.active_lib(&hr)
|
||||
// Event system
|
||||
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()
|
||||
|
||||
// Initialize Window
|
||||
@@ -36,24 +41,27 @@ main :: proc() {
|
||||
gt: f64 = 0
|
||||
dt: f32
|
||||
|
||||
for !platform.os_should_close() && !hot_reload.should_close(&hr) {
|
||||
for !platform.os_should_close() && !base.should_close(&hr) {
|
||||
start := time.tick_now()
|
||||
|
||||
platform.os_poll_events()
|
||||
base.drain_event_queue()
|
||||
api.update(dt)
|
||||
|
||||
fmt.println("MAIN:", test.test)
|
||||
fmt.printf("pointer: %p | %p\n", &test.test, &test.test_proc)
|
||||
|
||||
platform.frame(dt)
|
||||
|
||||
dt = f32(time.duration_seconds(time.tick_since(start)))
|
||||
gt += f64(dt)
|
||||
|
||||
// Hot reload
|
||||
api, err = hot_reload.reload_game_lib(&hr)
|
||||
api, err = base.reload_game_lib(&hr)
|
||||
if err != nil do break
|
||||
}
|
||||
|
||||
api.destroy()
|
||||
}
|
||||
|
||||
test :: proc(e: ^base.Key_Press_Event, _: rawptr) -> bool {
|
||||
fmt.println("main: @@@@@@@@@@@", e)
|
||||
return true
|
||||
}
|
||||
|
||||
+36
-46
@@ -7,9 +7,7 @@ import "vendor:glfw"
|
||||
import "wgpu:wgpu"
|
||||
import "wgpu:wgpu/glfwglue"
|
||||
|
||||
import "sindri:core"
|
||||
import "sindri:event"
|
||||
import "sindri:input"
|
||||
import "sindri:base"
|
||||
|
||||
// -----------------------------------------
|
||||
// Types
|
||||
@@ -21,7 +19,7 @@ OS :: struct {
|
||||
// -----------------------------------------
|
||||
// Constructor / destructor
|
||||
|
||||
os_init :: proc(settings: core.Settings) {
|
||||
os_init :: proc(settings: base.Settings) {
|
||||
if !glfw.Init() {
|
||||
panic("[glfw] init failure")
|
||||
}
|
||||
@@ -48,11 +46,6 @@ os_init :: proc(settings: core.Settings) {
|
||||
glfw.SetCursorPosCallback(state.os.window, cursor_pos_callback)
|
||||
glfw.SetScrollCallback(state.os.window, scroll_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() {
|
||||
@@ -63,7 +56,7 @@ os_destroy :: proc() {
|
||||
// -----------------------------------------
|
||||
// Public functions
|
||||
|
||||
os_set_monitor :: proc(settings: core.Settings) {
|
||||
os_set_monitor :: proc(settings: base.Settings) {
|
||||
monitor := glfw.GetPrimaryMonitor()
|
||||
x_pos: i32
|
||||
y_pos: i32
|
||||
@@ -140,47 +133,44 @@ os_get_surface :: proc(instance: wgpu.Instance) -> wgpu.Surface {
|
||||
return glfwglue.GetSurface(instance, state.os.window)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// Private functions
|
||||
|
||||
@(private = "file")
|
||||
key_state :: proc(key: input.Key) -> input.Action {
|
||||
os_key_state :: proc(key: base.Key) -> base.Action {
|
||||
return input_action(glfw.GetKey(state.os.window, i32(key)))
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
mouse_button_state :: proc(button: input.Mouse_Button) -> input.Action {
|
||||
os_mouse_button_state :: proc(button: base.Mouse_Button) -> base.Action {
|
||||
return input_action(glfw.GetMouseButton(state.os.window, i32(button)))
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
mouse_position :: proc() -> (x_pos: f32, y_pos: f32) {
|
||||
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 = "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
|
||||
input_action :: proc(action: i32) -> base.Action {
|
||||
if action == glfw.RELEASE do return base.Action.Release
|
||||
else if action == glfw.PRESS do return base.Action.Press
|
||||
else if action == glfw.REPEAT do return base.Action.Repeat
|
||||
when ODIN_DEBUG do panic("[glfw] unknown action")
|
||||
return .None
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
input_key :: proc(key: i32) -> input.Key {
|
||||
return input.Key(key) // values match
|
||||
input_key :: proc(key: i32) -> base.Key {
|
||||
return base.Key(key) // values match
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
input_mod_set :: proc(mods: i32) -> (set: input.Mod_Set) {
|
||||
return transmute(input.Mod_Set)i8(mods & 0x3f) // values match bit position
|
||||
input_mod_set :: proc(mods: i32) -> (set: base.Mod_Set) {
|
||||
return transmute(base.Mod_Set)i8(mods & 0x3f) // values match bit position
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
input_mouse_button :: proc(button: i32) -> input.Mouse_Button {
|
||||
return input.Mouse_Button(button) // values match
|
||||
input_mouse_button :: proc(button: i32) -> base.Mouse_Button {
|
||||
return base.Mouse_Button(button) // values match
|
||||
}
|
||||
|
||||
// Error callback
|
||||
@@ -194,7 +184,7 @@ error_callback :: proc "c" (error: i32, description: cstring) {
|
||||
@(private = "file")
|
||||
window_close_callback :: proc "c" (window: glfw.WindowHandle) {
|
||||
context = state.ctx
|
||||
event.on_event(event.Window_Close_Event{})
|
||||
base.on_event(base.Window_Close_Event{})
|
||||
} // GLFWwindowclosefun
|
||||
|
||||
// Window resize callback
|
||||
@@ -211,24 +201,24 @@ key_callback :: proc "c" (
|
||||
) {
|
||||
context = state.ctx
|
||||
if action == glfw.PRESS {
|
||||
event.on_event(
|
||||
event.Key_Press_Event {
|
||||
base.on_event(
|
||||
base.Key_Press_Event {
|
||||
key = input_key(key),
|
||||
mods = input_mod_set(mods),
|
||||
},
|
||||
)
|
||||
}
|
||||
if action == glfw.RELEASE {
|
||||
event.on_event(
|
||||
event.Key_Release_Event {
|
||||
base.on_event(
|
||||
base.Key_Release_Event {
|
||||
key = input_key(key),
|
||||
mods = input_mod_set(mods),
|
||||
},
|
||||
)
|
||||
}
|
||||
if action == glfw.REPEAT {
|
||||
event.on_event(
|
||||
event.Key_Repeat_Event {
|
||||
base.on_event(
|
||||
base.Key_Repeat_Event {
|
||||
key = input_key(key),
|
||||
mods = input_mod_set(mods),
|
||||
},
|
||||
@@ -244,16 +234,16 @@ mouse_button_callback :: proc "c" (
|
||||
) {
|
||||
context = state.ctx
|
||||
if action == glfw.PRESS {
|
||||
event.on_event(
|
||||
event.Mouse_Button_Press_Event {
|
||||
base.on_event(
|
||||
base.Mouse_Button_Press_Event {
|
||||
button = input_mouse_button(button),
|
||||
mods = input_mod_set(mods),
|
||||
},
|
||||
)
|
||||
}
|
||||
if action == glfw.RELEASE {
|
||||
event.on_event(
|
||||
event.Mouse_Button_Release_Event {
|
||||
base.on_event(
|
||||
base.Mouse_Button_Release_Event {
|
||||
button = input_mouse_button(button),
|
||||
mods = input_mod_set(mods),
|
||||
},
|
||||
@@ -268,8 +258,8 @@ cursor_pos_callback :: proc "c" (
|
||||
x_pos, y_pos: f64,
|
||||
) {
|
||||
context = state.ctx
|
||||
event.on_event(
|
||||
event.Mouse_Position_Event{x_pos = f32(x_pos), y_pos = f32(y_pos)},
|
||||
base.on_event(
|
||||
base.Mouse_Position_Event{x_pos = f32(x_pos), y_pos = f32(y_pos)},
|
||||
)
|
||||
} // GLFWcursorposfun
|
||||
|
||||
@@ -280,8 +270,8 @@ scroll_callback :: proc "c" (
|
||||
x_offset, y_offset: f64,
|
||||
) {
|
||||
context = state.ctx
|
||||
event.on_event(
|
||||
event.Mouse_Scroll_Event {
|
||||
base.on_event(
|
||||
base.Mouse_Scroll_Event {
|
||||
x_offset = f32(x_offset),
|
||||
y_offset = f32(y_offset),
|
||||
},
|
||||
@@ -293,9 +283,9 @@ scroll_callback :: proc "c" (
|
||||
joystick_callback :: proc "c" (id, connected: i32) {
|
||||
context = state.ctx
|
||||
if connected == glfw.CONNECTED {
|
||||
event.on_event(event.Joystick_Connect_Event{id = id})
|
||||
base.on_event(base.Joystick_Connect_Event{id = id})
|
||||
} else {
|
||||
event.on_event(event.Joystick_Disconnect_Event{id = id})
|
||||
base.on_event(base.Joystick_Disconnect_Event{id = id})
|
||||
}
|
||||
} // GLFWjoystickfun
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import "core:slice"
|
||||
|
||||
import "wgpu:wgpu"
|
||||
|
||||
import "sindri:core"
|
||||
import "sindri:base"
|
||||
|
||||
// -----------------------------------------
|
||||
// Variables
|
||||
@@ -33,7 +33,7 @@ state: struct {
|
||||
// -----------------------------------------
|
||||
// Constructor/destructor
|
||||
|
||||
instance_init :: proc(settings: core.Settings) {
|
||||
instance_init :: proc(settings: base.Settings) {
|
||||
state.ctx = context
|
||||
|
||||
wgpu.SetLogCallback(log_callback, nil)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package test
|
||||
|
||||
test: int = 0
|
||||
|
||||
test_proc: proc()
|
||||
Reference in New Issue
Block a user