diff --git a/build.sh b/build.sh index cacb334..32fea5b 100755 --- a/build.sh +++ b/build.sh @@ -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 diff --git a/docs/odin.md b/docs/odin.md index 32daeef..07ec1d3 100644 --- a/docs/odin.md +++ b/docs/odin.md @@ -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? diff --git a/src/base/event.odin b/src/base/event.odin index 9be7a45..9277be6 100644 --- a/src/base/event.odin +++ b/src/base/event.odin @@ -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, + width: i32, + height: i32, } Key_Press_Event :: struct { - handled: bool, - key: Key, - mods: Mod_Set, + key: Key, + mods: Mod_Set, } Key_Release_Event :: struct { - handled: bool, - key: Key, - mods: Mod_Set, + key: Key, + mods: Mod_Set, } Key_Repeat_Event :: struct { - handled: bool, - key: Key, - mods: Mod_Set, + key: Key, + mods: Mod_Set, } Mouse_Button_Press_Event :: struct { - handled: bool, - button: Mouse_Button, - mods: Mod_Set, + button: Mouse_Button, + mods: Mod_Set, } Mouse_Button_Release_Event :: struct { - handled: bool, - button: Mouse_Button, - mods: Mod_Set, + button: Mouse_Button, + mods: Mod_Set, } Mouse_Position_Event :: struct { - handled: bool, - x_pos: f32, - y_pos: f32, + 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) { - 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) - } +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) } -/* - Notes: +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) +} - Have the events queued up into a frame buffer that gets drained at a specific time. - */ +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 + } + } +} diff --git a/src/main.odin b/src/main.odin index 8d099b5..cce250a 100644 --- a/src/main.odin +++ b/src/main.odin @@ -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 +}