From f1028acb10544867f75d6e9f5f40d8ce64d0d5ba Mon Sep 17 00:00:00 2001 From: Riyyi Date: Sat, 12 Sep 2026 15:08:50 +0200 Subject: [PATCH] Implement type-safe input handling --- build.sh | 4 +- game/game.odin | 8 +- src/event/event.odin | 23 +++-- src/input/input.odin | 15 +++ src/input/keycodes.odin | 183 +++++++++++++++++++++++++++++++++++ src/main.odin | 17 ++-- src/{ => platform}/glfw.odin | 77 +++++++++++++-- src/{ => platform}/wgpu.odin | 2 +- 8 files changed, 301 insertions(+), 28 deletions(-) create mode 100644 src/input/input.odin create mode 100644 src/input/keycodes.odin rename src/{ => platform}/glfw.odin (75%) rename src/{ => platform}/wgpu.odin (99%) diff --git a/build.sh b/build.sh index 62c3767..3556e4d 100755 --- a/build.sh +++ b/build.sh @@ -5,6 +5,8 @@ set -eu PROJECT="sindri" VERSION="dev-$(date -u '+%Y-%m-%d')-$(git rev-parse --short HEAD)" +OPTION="${1:-}" + # ------------------------------------------ # Setup compiled wgpu binary @@ -39,7 +41,7 @@ if pgrep -x $PROJECT >/dev/null; then exit 0 fi -if [ "$1" = "debug" ]; then +if [ "$OPTION" = "debug" ]; then shift odin build src/ -show-timings \ diff --git a/game/game.odin b/game/game.odin index 9725ff0..bcdabe9 100644 --- a/game/game.odin +++ b/game/game.odin @@ -1,5 +1,6 @@ package game +import "sindri:input" import "core:time" import "core:fmt" @@ -8,6 +9,7 @@ import "sindri:core" // ----------------------------------------- Game_Memory :: struct { + should_close: bool } g: ^Game_Memory @@ -64,6 +66,10 @@ init :: proc() { @(export) update :: proc(dt: f32) { fmt.println("dt:", dt) + + if input.key_state(.Key_Escape) == .Press { + g.should_close = true + } } @(export) @@ -79,7 +85,7 @@ should_close :: proc() -> bool { seconds := time.duration_seconds(elapsed) if seconds > 4 do return true - return false + return g.should_close } @(export) diff --git a/src/event/event.odin b/src/event/event.odin index 0a6fd05..bda2c35 100644 --- a/src/event/event.odin +++ b/src/event/event.odin @@ -1,6 +1,9 @@ package event import "core:fmt" + +import "sindri:input" + // ----------------------------------------- // event category, bitfield (?) @@ -41,32 +44,32 @@ Joystick_Disconnect_Event :: struct { Key_Press_Event :: struct { handled: bool, - key: i32, - mods: i32, + key: input.Key, + mods: input.Mod_Set, } Key_Release_Event :: struct { handled: bool, - key: i32, - mods: i32, + key: input.Key, + mods: input.Mod_Set, } Key_Repeat_Event :: struct { handled: bool, - key: i32, - mods: i32, + key: input.Key, + mods: input.Mod_Set, } Mouse_Button_Press_Event :: struct { handled: bool, - button: i32, - mods: i32, + button: input.Mouse_Button, + mods: input.Mod_Set, } Mouse_Button_Release_Event :: struct { handled: bool, - button: i32, - mods: i32, + button: input.Mouse_Button, + mods: input.Mod_Set, } Mouse_Position_Event :: struct { diff --git a/src/input/input.odin b/src/input/input.odin new file mode 100644 index 0000000..1277589 --- /dev/null +++ b/src/input/input.odin @@ -0,0 +1,15 @@ +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 + +// ----------------------------------------- +// Private functions diff --git a/src/input/keycodes.odin b/src/input/keycodes.odin new file mode 100644 index 0000000..21ccb77 --- /dev/null +++ b/src/input/keycodes.odin @@ -0,0 +1,183 @@ +package input + +// ----------------------------------------- + +// Button/Key states +Action :: enum i8 { + None = -1, + Release = 0, + Press = 1, + Repeat = 2, +} + +Key :: enum i16 { + // The unknown key + Key_Unknown = -1, + + // --- Printable keys --- + + // Named printable keys + Key_Space = 32, + Key_Apostrophe = 39, // ' + Key_Comma = 44, // , + Key_Minus = 45, // - + Key_Period = 46, // . + Key_Slash = 47, // / + Key_Semicolon = 59, // ; + Key_Equal = 61, // = + Key_Left_Bracket = 91, // [ + Key_Backslash = 92, // \ + Key_Right_Bracket = 93, // ] + Key_Grave_Accent = 96, // ` + Key_World_1 = 161, // non-US #1 + Key_World_2 = 162, // non-US #2 + + // Alphanumeric characters + Key_0 = 48, + Key_1 = 49, + Key_2 = 50, + Key_3 = 51, + Key_4 = 52, + Key_5 = 53, + Key_6 = 54, + Key_7 = 55, + Key_8 = 56, + Key_9 = 57, + Key_A = 65, + Key_B = 66, + Key_C = 67, + Key_D = 68, + Key_E = 69, + Key_F = 70, + Key_G = 71, + Key_H = 72, + Key_I = 73, + Key_J = 74, + Key_K = 75, + Key_L = 76, + Key_M = 77, + Key_N = 78, + Key_O = 79, + Key_P = 80, + Key_Q = 81, + Key_R = 82, + Key_S = 83, + Key_T = 84, + Key_U = 85, + Key_V = 86, + Key_W = 87, + Key_X = 88, + Key_Y = 89, + Key_Z = 90, + + // --- Function keys --- + + // Named non-printable keys + Key_Escape = 256, + Key_Enter = 257, + Key_Tab = 258, + Key_Backspace = 259, + Key_Insert = 260, + Key_Delete = 261, + Key_Right = 262, + Key_Left = 263, + Key_Down = 264, + Key_Up = 265, + Key_Page_Up = 266, + Key_Page_Down = 267, + Key_Home = 268, + Key_End = 269, + Key_Caps_Lock = 280, + Key_Scroll_Lock = 281, + Key_Num_Lock = 282, + Key_Print_Screen = 283, + Key_Pause = 284, + + // Function keys + Key_F1 = 290, + Key_F2 = 291, + Key_F3 = 292, + Key_F4 = 293, + Key_F5 = 294, + Key_F6 = 295, + Key_F7 = 296, + Key_F8 = 297, + Key_F9 = 298, + Key_F10 = 299, + Key_F11 = 300, + Key_F12 = 301, + Key_F13 = 302, + Key_F14 = 303, + Key_F15 = 304, + Key_F16 = 305, + Key_F17 = 306, + Key_F18 = 307, + Key_F19 = 308, + Key_F20 = 309, + Key_F21 = 310, + Key_F22 = 311, + Key_F23 = 312, + Key_F24 = 313, + Key_F25 = 314, + + // Keypad numbers + Key_KP_0 = 320, + Key_KP_1 = 321, + Key_KP_2 = 322, + Key_KP_3 = 323, + Key_KP_4 = 324, + Key_KP_5 = 325, + Key_KP_6 = 326, + Key_KP_7 = 327, + Key_KP_8 = 328, + Key_KP_9 = 329, + + // Keypad named function keys + Key_KP_Decimal = 330, + Key_KP_Divide = 331, + Key_KP_Multiply = 332, + Key_KP_Subtract = 333, + Key_KP_Add = 334, + Key_KP_Enter = 335, + Key_KP_Equal = 336, + + // Modifier keys + Key_Left_Shift = 340, + Key_Left_Control = 341, + Key_Left_Alt = 342, + Key_Left_Super = 343, + Key_Right_Shift = 344, + Key_Right_Control = 345, + Key_Right_Alt = 346, + Key_Right_Super = 347, + Key_Menu = 348, + Key_Last = Key_Menu, +} + +// Bitmask for modifier keys +Mod :: enum i8 { + Shift = 0, + Control = 1, + Alt = 2, + Super = 3, + Caps_Lock = 4, + Num_Lock = 5, +} +Mod_Set :: bit_set[Mod] + +// Mouse buttons +Mouse_Button :: enum i8 { + Button_1 = 0, + Button_2 = 1, + Button_3 = 2, + Button_4 = 3, + Button_5 = 4, + Button_6 = 5, + Button_7 = 6, + Button_8 = 7, + // Alias names + Last = Button_8, + Left = Button_1, + Right = Button_2, + Middle = Button_3, +} diff --git a/src/main.odin b/src/main.odin index 7e059d7..0471a73 100644 --- a/src/main.odin +++ b/src/main.odin @@ -4,6 +4,7 @@ import "core:fmt" import "core:time" import "sindri:hot_reload" +import "sindri:platform" VERSION :: #config(VERSION, "dev") @@ -20,13 +21,13 @@ main :: proc() { settings := api.settings() // Initialize Window - os_init(settings) - os_set_monitor(settings) - defer os_destroy() + platform.os_init(settings) + platform.os_set_monitor(settings) + defer platform.os_destroy() // Initialize GPU resources - instance_init() - defer instance_destroy() + platform.instance_init() + defer platform.instance_destroy() api.init_once() api.init() @@ -34,13 +35,13 @@ main :: proc() { gt: f64 = 0 dt: f32 - for !os_should_close() && !hot_reload.should_close(&hr) { + for !platform.os_should_close() && !hot_reload.should_close(&hr) { start := time.tick_now() - os_poll_events() + platform.os_poll_events() api.update(dt) - frame(dt) + platform.frame(dt) dt = f32(time.duration_seconds(time.tick_since(start))) gt += f64(dt) diff --git a/src/glfw.odin b/src/platform/glfw.odin similarity index 75% rename from src/glfw.odin rename to src/platform/glfw.odin index 0c5e32b..04ca6b8 100644 --- a/src/glfw.odin +++ b/src/platform/glfw.odin @@ -1,4 +1,4 @@ -package sindri +package platform import "core:fmt" import "core:strings" @@ -9,6 +9,7 @@ import "wgpu:wgpu/glfwglue" import "sindri:core" import "sindri:event" +import "sindri:input" // ----------------------------------------- // Types @@ -48,6 +49,10 @@ os_init :: proc(settings: core.Settings) { 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 + // TODO: Figure out proper vsync, found 3 spots so far // - glfw.SwapInterval(0) this is only for OpenGL it seems? // - glfw.SetWindowMonitor(refresh) @@ -143,6 +148,40 @@ os_get_surface :: proc(instance: wgpu.Instance) -> wgpu.Surface { // ----------------------------------------- // Private functions +@(private = "file") +key_state :: proc(key: input.Key) -> input.Action { + return input_action(glfw.GetKey(state.os.window, i32(key))) +} + +@(private = "file") +mouse_button_state :: proc(button: input.Mouse_Button) -> input.Action { + return input_action(glfw.GetMouseButton(state.os.window, i32(button))) +} + +@(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") + return .None +} + +@(private = "file") +input_key :: proc(key: i32) -> input.Key { + return input.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 +} + +@(private = "file") +input_mouse_button :: proc(button: i32) -> input.Mouse_Button { + return input.Mouse_Button(button) // values match +} + // Error callback @(private = "file") error_callback :: proc "c" (error: i32, description: cstring) { @@ -171,15 +210,29 @@ key_callback :: proc "c" ( ) { context = state.ctx if action == glfw.PRESS { - event.on_event(event.Key_Press_Event{key = key, mods = mods}) + event.on_event( + event.Key_Press_Event { + key = input_key(key), + mods = input_mod_set(mods), + }, + ) } if action == glfw.RELEASE { - event.on_event(event.Key_Release_Event{key = key, mods = mods}) + event.on_event( + event.Key_Release_Event { + key = input_key(key), + mods = input_mod_set(mods), + }, + ) } if action == glfw.REPEAT { - event.on_event(event.Key_Repeat_Event{key = key, mods = mods}) + event.on_event( + event.Key_Repeat_Event { + key = input_key(key), + mods = input_mod_set(mods), + }, + ) } - } // GLFWkeyfun // Mouse button callback @@ -190,10 +243,20 @@ mouse_button_callback :: proc "c" ( ) { context = state.ctx if action == glfw.PRESS { - event.on_event(event.Mouse_Button_Press_Event{button = button}) + event.on_event( + event.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{button = button}) + event.on_event( + event.Mouse_Button_Release_Event { + button = input_mouse_button(button), + mods = input_mod_set(mods), + }, + ) } } // GLFWmousebuttonfun diff --git a/src/wgpu.odin b/src/platform/wgpu.odin similarity index 99% rename from src/wgpu.odin rename to src/platform/wgpu.odin index e6bba39..762ef5e 100644 --- a/src/wgpu.odin +++ b/src/platform/wgpu.odin @@ -1,4 +1,4 @@ -package sindri +package platform import "base:runtime" import "core:fmt"