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:gram=vendor/gram/src \
|
||||||
-collection:wgpu=vendor \
|
-collection:wgpu=vendor \
|
||||||
-build-mode:dynamic \
|
-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,
|
# 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 +49,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 -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
|
exit 0
|
||||||
fi
|
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
|
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?
|
||||||
|
|||||||
+167
-35
@@ -1,5 +1,7 @@
|
|||||||
package base
|
package base
|
||||||
|
|
||||||
|
import "base:intrinsics"
|
||||||
|
import "base:runtime"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
@@ -7,8 +9,7 @@ import "core:fmt"
|
|||||||
|
|
||||||
// event category, bitfield (?)
|
// event category, bitfield (?)
|
||||||
|
|
||||||
// Event_Data :: union {
|
Event_Data :: union {
|
||||||
Event :: union {
|
|
||||||
Window_Close_Event,
|
Window_Close_Event,
|
||||||
Window_Resize_Event,
|
Window_Resize_Event,
|
||||||
Joystick_Connect_Event,
|
Joystick_Connect_Event,
|
||||||
@@ -22,115 +23,246 @@ Event :: union {
|
|||||||
Mouse_Scroll_Event,
|
Mouse_Scroll_Event,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event :: struct {
|
Event :: struct {
|
||||||
// data: Event_Data,
|
data: Event_Data,
|
||||||
// handled: bool,
|
|
||||||
// }
|
|
||||||
|
|
||||||
Window_Close_Event :: struct {
|
|
||||||
handled: bool,
|
handled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Window_Close_Event :: struct {}
|
||||||
|
|
||||||
Window_Resize_Event :: struct {
|
Window_Resize_Event :: struct {
|
||||||
handled: bool,
|
|
||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
Joystick_Connect_Event :: struct {
|
|
||||||
handled: bool,
|
|
||||||
id: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
Joystick_Disconnect_Event :: struct {
|
|
||||||
handled: bool,
|
|
||||||
id: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
Key_Press_Event :: struct {
|
Key_Press_Event :: struct {
|
||||||
handled: bool,
|
|
||||||
key: Key,
|
key: Key,
|
||||||
mods: Mod_Set,
|
mods: Mod_Set,
|
||||||
}
|
}
|
||||||
|
|
||||||
Key_Release_Event :: struct {
|
Key_Release_Event :: struct {
|
||||||
handled: bool,
|
|
||||||
key: Key,
|
key: Key,
|
||||||
mods: Mod_Set,
|
mods: Mod_Set,
|
||||||
}
|
}
|
||||||
|
|
||||||
Key_Repeat_Event :: struct {
|
Key_Repeat_Event :: struct {
|
||||||
handled: bool,
|
|
||||||
key: Key,
|
key: Key,
|
||||||
mods: Mod_Set,
|
mods: Mod_Set,
|
||||||
}
|
}
|
||||||
|
|
||||||
Mouse_Button_Press_Event :: struct {
|
Mouse_Button_Press_Event :: struct {
|
||||||
handled: bool,
|
|
||||||
button: Mouse_Button,
|
button: Mouse_Button,
|
||||||
mods: Mod_Set,
|
mods: Mod_Set,
|
||||||
}
|
}
|
||||||
|
|
||||||
Mouse_Button_Release_Event :: struct {
|
Mouse_Button_Release_Event :: struct {
|
||||||
handled: bool,
|
|
||||||
button: Mouse_Button,
|
button: Mouse_Button,
|
||||||
mods: Mod_Set,
|
mods: Mod_Set,
|
||||||
}
|
}
|
||||||
|
|
||||||
Mouse_Position_Event :: struct {
|
Mouse_Position_Event :: struct {
|
||||||
handled: bool,
|
|
||||||
x_pos: f32,
|
x_pos: f32,
|
||||||
y_pos: f32,
|
y_pos: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
Mouse_Scroll_Event :: struct {
|
Mouse_Scroll_Event :: struct {
|
||||||
handled: bool,
|
|
||||||
x_offset: f32,
|
x_offset: f32,
|
||||||
y_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
|
// Variables
|
||||||
|
|
||||||
// I want to have a list of Listeners per Event type, but this is state
|
event_bus: Event_Bus
|
||||||
// listeners: map[typeid][dynamic]Listener
|
|
||||||
|
// -----------------------------------------
|
||||||
|
// 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
|
// Public functions
|
||||||
|
|
||||||
// Dispatcher(Event) -> forwards to the right function
|
event_subscribe :: proc(
|
||||||
on_event :: proc(event: Event) {
|
$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!")
|
fmt.println("toby!")
|
||||||
|
|
||||||
#partial switch e in event {
|
for &event in event_bus.queue {
|
||||||
|
|
||||||
|
#partial switch e in event.data {
|
||||||
case Window_Close_Event:
|
case Window_Close_Event:
|
||||||
|
event_dispatch(Window_Close_Event, &event)
|
||||||
fmt.println("Close")
|
fmt.println("Close")
|
||||||
case Window_Resize_Event:
|
case Window_Resize_Event:
|
||||||
|
event_dispatch(Window_Resize_Event, &event)
|
||||||
fmt.println("Resize:", e.width, e.height)
|
fmt.println("Resize:", e.width, e.height)
|
||||||
|
|
||||||
case Key_Press_Event:
|
case Key_Press_Event:
|
||||||
|
event_dispatch(Key_Press_Event, &event)
|
||||||
fmt.println("Key press", e.key, e.mods)
|
fmt.println("Key press", e.key, e.mods)
|
||||||
case Key_Release_Event:
|
case Key_Release_Event:
|
||||||
|
event_dispatch(Key_Release_Event, &event)
|
||||||
fmt.println("Key release", e.key, e.mods)
|
fmt.println("Key release", e.key, e.mods)
|
||||||
case Key_Repeat_Event:
|
case Key_Repeat_Event:
|
||||||
|
event_dispatch(Key_Repeat_Event, &event)
|
||||||
fmt.println("Key repeat", e.key, e.mods)
|
fmt.println("Key repeat", e.key, e.mods)
|
||||||
|
|
||||||
case Mouse_Button_Press_Event:
|
case Mouse_Button_Press_Event:
|
||||||
|
event_dispatch(Mouse_Button_Press_Event, &event)
|
||||||
fmt.println("Mouse press", e.button, e.mods)
|
fmt.println("Mouse press", e.button, e.mods)
|
||||||
case Mouse_Button_Release_Event:
|
case Mouse_Button_Release_Event:
|
||||||
|
event_dispatch(Mouse_Button_Release_Event, &event)
|
||||||
fmt.println("Mouse release", e.button, e.mods)
|
fmt.println("Mouse release", e.button, e.mods)
|
||||||
case Mouse_Position_Event:
|
case Mouse_Position_Event:
|
||||||
|
event_dispatch(Mouse_Position_Event, &event)
|
||||||
fmt.printfln("Mouse pos: {}x{}", e.x_pos, e.y_pos)
|
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:
|
case Joystick_Connect_Event:
|
||||||
|
event_dispatch(Joystick_Connect_Event, &event)
|
||||||
fmt.println("Joy con:", e.id)
|
fmt.println("Joy con:", e.id)
|
||||||
case Joystick_Disconnect_Event:
|
case Joystick_Disconnect_Event:
|
||||||
|
event_dispatch(Joystick_Disconnect_Event, &event)
|
||||||
fmt.println("Joy dis:", e.id)
|
fmt.println("Joy dis:", e.id)
|
||||||
|
|
||||||
|
case:
|
||||||
|
panic("[event] unimplemented event")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
clear(&event_bus.queue)
|
||||||
Notes:
|
}
|
||||||
|
|
||||||
Have the events queued up into a frame buffer that gets drained at a specific time.
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ main :: proc() {
|
|||||||
hr, err := base.hot_reload_init()
|
hr, err := base.hot_reload_init()
|
||||||
defer base.hot_reload_destroy(&hr)
|
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)
|
api := base.active_lib(&hr)
|
||||||
settings := api.settings()
|
settings := api.settings()
|
||||||
|
|
||||||
@@ -39,6 +45,7 @@ main :: proc() {
|
|||||||
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)
|
||||||
|
|
||||||
platform.frame(dt)
|
platform.frame(dt)
|
||||||
@@ -53,3 +60,8 @@ main :: proc() {
|
|||||||
|
|
||||||
api.destroy()
|
api.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test :: proc(e: ^base.Key_Press_Event, _: rawptr) -> bool {
|
||||||
|
fmt.println("main: @@@@@@@@@@@", e)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user