Add event listener subscribe/unsubsribe system
This commit is contained in:
@@ -24,7 +24,7 @@ odin build game/ -show-timings \
|
||||
-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.
|
||||
@@ -49,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
|
||||
|
||||
|
||||
@@ -4,6 +4,11 @@ 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?
|
||||
|
||||
+167
-35
@@ -1,5 +1,7 @@
|
||||
package base
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:runtime"
|
||||
import "core:fmt"
|
||||
|
||||
// -----------------------------------------
|
||||
@@ -7,8 +9,7 @@ import "core:fmt"
|
||||
|
||||
// event category, bitfield (?)
|
||||
|
||||
// Event_Data :: union {
|
||||
Event :: union {
|
||||
Event_Data :: union {
|
||||
Window_Close_Event,
|
||||
Window_Resize_Event,
|
||||
Joystick_Connect_Event,
|
||||
@@ -22,115 +23,246 @@ Event :: union {
|
||||
Mouse_Scroll_Event,
|
||||
}
|
||||
|
||||
// Event :: struct {
|
||||
// data: Event_Data,
|
||||
// handled: bool,
|
||||
// }
|
||||
|
||||
Window_Close_Event :: struct {
|
||||
Event :: struct {
|
||||
data: Event_Data,
|
||||
handled: bool,
|
||||
}
|
||||
|
||||
Window_Close_Event :: struct {}
|
||||
|
||||
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: Key,
|
||||
mods: Mod_Set,
|
||||
}
|
||||
|
||||
Key_Release_Event :: struct {
|
||||
handled: bool,
|
||||
key: Key,
|
||||
mods: Mod_Set,
|
||||
}
|
||||
|
||||
Key_Repeat_Event :: struct {
|
||||
handled: bool,
|
||||
key: Key,
|
||||
mods: Mod_Set,
|
||||
}
|
||||
|
||||
Mouse_Button_Press_Event :: struct {
|
||||
handled: bool,
|
||||
button: Mouse_Button,
|
||||
mods: Mod_Set,
|
||||
}
|
||||
|
||||
Mouse_Button_Release_Event :: struct {
|
||||
handled: bool,
|
||||
button: Mouse_Button,
|
||||
mods: 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,
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// I want to have a list of Listeners per Event type, but this is state
|
||||
// listeners: map[typeid][dynamic]Listener
|
||||
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
|
||||
|
||||
// Dispatcher(Event) -> forwards to the right function
|
||||
on_event :: proc(event: Event) {
|
||||
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!")
|
||||
|
||||
#partial switch e in event {
|
||||
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)
|
||||
}
|
||||
|
||||
/*
|
||||
Notes:
|
||||
// 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)
|
||||
|
||||
Have the events queued up into a frame buffer that gets drained at a specific time.
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ main :: proc() {
|
||||
hr, err := base.hot_reload_init()
|
||||
defer base.hot_reload_destroy(&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()
|
||||
|
||||
@@ -39,6 +45,7 @@ main :: proc() {
|
||||
start := time.tick_now()
|
||||
|
||||
platform.os_poll_events()
|
||||
base.drain_event_queue()
|
||||
api.update(dt)
|
||||
|
||||
platform.frame(dt)
|
||||
@@ -53,3 +60,8 @@ main :: proc() {
|
||||
|
||||
api.destroy()
|
||||
}
|
||||
|
||||
test :: proc(e: ^base.Key_Press_Event, _: rawptr) -> bool {
|
||||
fmt.println("main: @@@@@@@@@@@", e)
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user