diff options
| author | 0scar <qgt268@alumni.ku.dk> | 2023-08-02 07:22:57 +0000 |
|---|---|---|
| committer | 0scar <qgt268@alumni.ku.dk> | 2023-08-02 13:35:35 +0000 |
| commit | 1713482a2af615daa83887278716736799389c3d (patch) | |
| tree | edec5cf88101dccf6f55340e7fbed73b21216fd1 | |
| parent | 760464c6b14d8f573d49ce67fbdabe490c4912d3 (diff) | |
Update input handling method
| -rw-r--r-- | CMakeLists.txt | 1 | ||||
| -rw-r--r-- | docs/input-handling.md | 96 | ||||
| -rw-r--r-- | include/engine/engine.h | 20 | ||||
| -rw-r--r-- | include/engine/input.h | 79 | ||||
| -rw-r--r-- | include/engine/state.h | 5 | ||||
| -rw-r--r-- | src/engine.c | 148 | ||||
| -rw-r--r-- | src/input.c | 94 |
7 files changed, 391 insertions, 52 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a18c25..8928417 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ set(ENGINE_SOURCES src/engine.c src/fov.c src/hashmap.c + src/input.c src/logging.c src/memory.c src/rendering.c diff --git a/docs/input-handling.md b/docs/input-handling.md new file mode 100644 index 0000000..effb69c --- /dev/null +++ b/docs/input-handling.md @@ -0,0 +1,96 @@ +# Input handling + +_This document serves as a low-level explanation of how the input system works_ + +```C +typedef enum InputType InputType; +enum InputType { + // fire once, ie. keydown. stuff like spells, semi-automatic + InputType_action, + // active while pressed, deactivate once released. Ignore key-repeat. stuff like running + InputType_state, + // ranges of motion, like mouse input or joystick, [0;1] or [-1;1] + InputType_range, /* TBD */ +}; + +typedef union action_t action_t; +union action_t { + InputType type; + + struct { + InputType type; + input_callback_t* callback; + const char* callback_str; + } action; + + struct { + InputType type; + input_callback_t* activate; + input_callback_t* deactivate; + const char* activate_str; + const char* deactivate_str; + } state; + + /* Add range at some point */ +}; + +typedef struct binding_t { + action_t action; + + scancode_t scancode; + scancode_t scancode_alt; + + u64 since_last_activation; +} binding_t; + +/* Input context */ +typedef struct i_ctx { + binding_t* bindings; + usize len; +} i_ctx; +``` + +The input context `i_ctx` simply holds a pointer to the start of an array of +`binding_t`. This array is traversed linearly whenever the window receives +a input event, for each event. + +Since it is your (the individual states) task to clean up any allocated memory, +it would be a good idea to use a statically sized array for the bindings, inside +your states struct for the specific states keybindings, as it is cleaned up +after changing the state: +```C +typedef struct mystate_state { + ... + binding_t input_bindings[NUM_ACTIONS]; + i_ctx main_binding_ctx; + ... +} mystate_state; +``` + +And then in your states initalize function: +```C +void mystate_init(mystate_state *s) { + ... + + // Simply assign wasd and arrow keys to movement, space to fire + s->input_bindings[0] = BindState(SDL_SCANCODE_W, SDL_SCANCODE_UP, player_mv_u, player_stop_mv_u); + s->input_bindings[1] = BindState(SDL_SCANCODE_A, SDL_SCANCODE_LEFT, player_mv_l, player_stop_mv_l); + s->input_bindings[2] = BindState(SDL_SCANCODE_S, SDL_SCANCODE_DOWN, player_mv_d, player_stop_mv_d); + s->input_bindings[3] = BindState(SDL_SCANCODE_D, SDL_SCANCODE_RIGHT, player_mv_r, player_stop_mv_r); + + s->input_bindings[4] = BindAction(SDL_SCANCODE_SPACE, 0, fire_weapon); + /* ... or Possibly load settings from some configuration file */ + + // Assign the bindings to the input context + s->input_ctx = (i_ctx){ + .bindings = (binding_t*)&s->input_bindings, + .len = sizeof(s->input_bindings) / sizeof(binding_t), + }; + // Push the main binding context to the engines input stack + input_ctx_push(s->main_binding_ctx); +} +``` + +If you want specific bindings for dialog windows, you can simply push a input +context to the engines stack using `input_ctx_push`, and pop it when the user +closes the dialog. diff --git a/include/engine/engine.h b/include/engine/engine.h index bd961a6..cf2283f 100644 --- a/include/engine/engine.h +++ b/include/engine/engine.h @@ -4,10 +4,11 @@ #include <stdbool.h> #include <engine/types.h> +#include <engine/logging.h> #include <engine/stack.h> #include <engine/vector.h> #include <engine/memory.h> -#include <engine/logging.h> +#include <engine/input.h> #include <engine/state.h> typedef struct { @@ -29,12 +30,7 @@ typedef struct { typedef struct Window Window; -typedef struct Keybinding { - i32 keycode; - i32 modifiers; - void (*action)(void*); -} Keybinding; - +#define NUM_GLOBAL_BINDINGS 1 typedef struct { void *data; /* Contains textures and such */ u64 data_len; @@ -45,6 +41,7 @@ typedef struct { u64 frame; f32 fps_target; + /* TODO: Move mouse data to input ctx */ v2_i32 mouse_pos; v2_i32 mousedown; @@ -62,7 +59,11 @@ typedef struct { memory *mem; - void *bindings; + i_ctx **bindings; + usize bindings_sz; + usize bindings_len; + + binding_t bindings_global[NUM_GLOBAL_BINDINGS]; } Platform; /* Essential functions */ @@ -81,7 +82,8 @@ void engine_stop(Platform *p); /* Utility functions */ void engine_fps_max(u64 cap); -void engine_bindkey(i32 key, void (*action)(void*)); +void engine_input_ctx_push(i_ctx *ctx); +void engine_input_ctx_pop(void); void render_set_zoom(f32 new_zoom); void render_adjust_zoom(f32 diff); diff --git a/include/engine/input.h b/include/engine/input.h new file mode 100644 index 0000000..d200e62 --- /dev/null +++ b/include/engine/input.h @@ -0,0 +1,79 @@ +#ifndef INPUT_H +#define INPUT_H + +#include <engine/types.h> + +typedef void input_callback_t(void*); +typedef i32 scancode_t; + +typedef enum InputType { + InputType_error = 0, + InputType_action, + InputType_state, + InputType_range, /* TBD */ +} InputType; + +typedef union action_t { + InputType type; + + struct { + InputType type; + input_callback_t* callback; + const char* callback_str; + } action; + + struct { + InputType type; + input_callback_t* activate; + input_callback_t* deactivate; + const char* activate_str; + const char* deactivate_str; + } state; +} action_t; + +typedef struct binding_t { + action_t action; + + // Change type depending on input handling back-end. like u16 for GLFW_KEY + //enum SDL_Scancode keycode; + scancode_t scancode; + scancode_t scancode_alt; + + u64 since_last_activation; +} binding_t; + +typedef struct i_ctx { + binding_t* bindings; + isize len; +} i_ctx; + +void i_flush_bindings(usize numcalls, void* state_mem, input_callback_t* c[]); +action_t i_get_action(const i_ctx *ctx, u32 time, scancode_t scancode); + +#define BindAction(key, altkey, f_action) \ + (binding_t){\ + .action = (action_t){.action = {\ + .type = InputType_action,\ + .callback = (input_callback_t*)&f_action,\ + .callback_str = #f_action,\ + }},\ + .scancode = key,\ + .scancode_alt = altkey,\ + .since_last_activation = 0\ +} + +#define BindState(key, altkey, f_activate, f_deactivate) \ + (binding_t){\ + .action = (action_t){.state = {\ + .type = InputType_state,\ + .activate = (input_callback_t*)&f_activate,\ + .deactivate = (input_callback_t*)&f_deactivate,\ + .activate_str = #f_activate,\ + .deactivate_str = #f_deactivate,\ + }},\ + .scancode = key,\ + .scancode_alt = altkey,\ + .since_last_activation = 0\ +} + +#endif diff --git a/include/engine/state.h b/include/engine/state.h index dcd2966..68228b1 100644 --- a/include/engine/state.h +++ b/include/engine/state.h @@ -23,8 +23,13 @@ StateType State_update(StateType type, memory *mem); /* Reloads shared object file associated with state */ #ifdef DAW_BUILD_HOTRELOAD bool State_reload(StateType type); + +#define get_statelib_var(type) libstate_##name +#define State_load_binding(type, function_name) dynamic_library_get_symbol(get_statelib_var(type), #function_name) + #else #define State_reload(_) true +#define State_load_binding(_, __) true #endif #endif diff --git a/src/engine.c b/src/engine.c index f43808b..677cb3f 100644 --- a/src/engine.c +++ b/src/engine.c @@ -1,4 +1,6 @@ #include <stdlib.h> +#include <errno.h> +#include <string.h> #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> @@ -40,22 +42,12 @@ extern i32 drawcall_len; #define BENCHEXPR(timevar, expr) expr #endif -i32 bindings_cmp(const Keybinding *a, const Keybinding *b) { - return a->modifiers ^ b->modifiers; -} - -i32 binding_to_int(const Keybinding *b) { - return b->keycode; -} - -/* Defines (struct) List_Keybinding */ -/* Defines (struct) hashmap_Keybinding, - * and hashmap_Keybinding_lookup, and hashmap_Keybinding_insert */ -DEFINE_HASHMAP(Keybinding, 64, bindings_cmp, binding_to_int) - static u64 FPS_CAP = 50; Platform *GLOBAL_PLATFORM = NULL; +input_callback_t *callbacks[128]; +usize callbacks_len; + i32 nproc(void) { return SDL_GetCPUCount(); } @@ -369,6 +361,10 @@ Platform *engine_init( p->edit_pos = 0; p->bindings = NULL; + p->bindings_sz = 0; + p->bindings_len = 0; + + // TODO: Add global bindings #ifdef BENCHMARK u32 init_stop = SDL_GetTicks(); @@ -497,15 +493,15 @@ i32 engine_run(Platform *p, StateType initial_state) { /* Events */ BENCHEXPR(profile_input, { - if (GLOBAL_PLATFORM->mouse_lclick) { - GLOBAL_PLATFORM->mouseup.x = -1; - GLOBAL_PLATFORM->mouseup.y = -1; - GLOBAL_PLATFORM->mousedown.x = -1; - GLOBAL_PLATFORM->mousedown.y = -1; - GLOBAL_PLATFORM->mouse_lclick = false; + if (p->mouse_lclick) { + p->mouseup.x = -1; + p->mouseup.y = -1; + p->mousedown.x = -1; + p->mousedown.y = -1; + p->mouse_lclick = false; } - if (GLOBAL_PLATFORM->mouse_rclick) { - GLOBAL_PLATFORM->mouse_rclick = false; + if (p->mouse_rclick) { + p->mouse_rclick = false; } /* Window events */ @@ -536,17 +532,17 @@ i32 engine_run(Platform *p, StateType initial_state) { { SDL_MouseMotionEvent m = e[i].motion; /* In case of a first-person game, use xrel and yrel */ - GLOBAL_PLATFORM->mouse_pos.x = m.x; - GLOBAL_PLATFORM->mouse_pos.y = m.y; + p->mouse_pos.x = m.x; + p->mouse_pos.y = m.y; } break; case SDL_MOUSEBUTTONUP: { switch (e[i].button.button) { case SDL_BUTTON_LEFT: - GLOBAL_PLATFORM->mouseup = GLOBAL_PLATFORM->mouse_pos; + p->mouseup = p->mouse_pos; - GLOBAL_PLATFORM->mouse_lclick = true; + p->mouse_lclick = true; case SDL_BUTTON_RIGHT: break; default: @@ -557,7 +553,7 @@ i32 engine_run(Platform *p, StateType initial_state) { case SDL_MOUSEBUTTONDOWN: switch (e[i].button.button) { case SDL_BUTTON_LEFT: - GLOBAL_PLATFORM->mousedown = GLOBAL_PLATFORM->mouse_pos; + p->mousedown = p->mouse_pos; break; case SDL_BUTTON_RIGHT: break; @@ -573,10 +569,14 @@ i32 engine_run(Platform *p, StateType initial_state) { } BENCHEXPR(profile_input_handling, { + if (p->bindings != NULL) { + const i_ctx *bindings = *p->bindings; + const usize bindings_len = p->bindings_len; + while ((num_events = SDL_PeepEvents(e, 8, SDL_GETEVENT, SDL_KEYDOWN, SDL_KEYUP)) > 0) { for (i32 i = 0; i < num_events; i++) { switch (e[i].type) { - case SDL_KEYDOWN: { + case SDL_KEYDOWN: if (e[i].key.keysym.sym == SDLK_F7) { INFO("Reloading %s", StateTypeStr[state]); if (!State_reload(state)) { @@ -586,15 +586,41 @@ i32 engine_run(Platform *p, StateType initial_state) { } break; } - Keybinding lookupkey = ((Keybinding){ - .keycode = e[i].key.keysym.sym, - .modifiers = e[i].key.keysym.mod - }); - Keybinding *kb = hashmap_Keybinding_lookup(GLOBAL_PLATFORM->bindings, &lookupkey); - if (kb != NULL && kb->action != NULL) kb->action(mem->data); + for (usize b = 0; b < bindings_len; b++) { + const action_t a = i_get_action(&bindings[b], e[i].key.timestamp, e[i].key.keysym.scancode); + + switch (a.type) { + case InputType_action: + if (a.action.callback != NULL) { + callbacks[callbacks_len++] = a.action.callback; + } + break; + + case InputType_state: + if (!e[i].key.repeat && a.state.activate != NULL) { + callbacks[callbacks_len++] = a.state.activate; + } + break; + + case InputType_range: + WARN("Range inputs not supported yet!"); + break; + case InputType_error: + WARN("Unhandled scancode: %lu", e[i].key.keysym.scancode); + + default: + break; + } } break; + case SDL_KEYUP: + for (usize b = 0; b < bindings_len; b++) { + const action_t a = i_get_action(&bindings[b], e[i].key.timestamp, e[i].key.keysym.scancode); + if (a.type == InputType_state && a.state.deactivate != NULL && !e[i].key.repeat) { + callbacks[callbacks_len++] = a.state.deactivate; + } + } break; default: WARN("Unhandled mouse event 0x%04x", (i32)e[i].type); @@ -602,9 +628,13 @@ i32 engine_run(Platform *p, StateType initial_state) { } } } + } }); }); + i_flush_bindings(callbacks_len, mem->data, callbacks); + callbacks_len = 0; + /* update */ StateType next_state; BENCHEXPR(profile_gameloop, {next_state = update_func((void*)(mem->data));} );//State_update(state, mem);}); @@ -618,7 +648,7 @@ i32 engine_run(Platform *p, StateType initial_state) { State_free(state, mem); memory_clear(mem); - GLOBAL_PLATFORM->bindings = NULL; + p->bindings_len = 0; state = next_state; update_func = State_updateFunc(state); @@ -688,20 +718,52 @@ void stop(Platform *p) { void engine_fps_max(u64 cap) { FPS_CAP = cap; } -void engine_bindkey(i32 key, void (*action)(void*)) { - /* TODO: Make a hashmap ++ linked list */ - //if (key >= 512) { - // ERROR("Key value too high! (got %d)\n", key); - //} +void engine_input_ctx_push(i_ctx *ctx) { if (GLOBAL_PLATFORM->bindings == NULL) { - //GLOBAL_PLATFORM->bindings = memory_allocate(GLOBAL_PLATFORM->mem, 512 * sizeof(Keybinding)); - GLOBAL_PLATFORM->bindings = memory_allocate(GLOBAL_PLATFORM->mem, sizeof(hashmap_Keybinding)); + GLOBAL_PLATFORM->bindings = calloc(8, sizeof(i_ctx*)); + GLOBAL_PLATFORM->bindings_sz = 8; + } + if (GLOBAL_PLATFORM->bindings_len + 1 >= GLOBAL_PLATFORM->bindings_sz) { + void* m = realloc(GLOBAL_PLATFORM->bindings, GLOBAL_PLATFORM->bindings_sz + 8); + if (m == NULL) { + ERROR("Failed to allocate 8 bytes (%d): %s", errno, strerror(errno)); + exit(EXIT_FAILURE); + } + GLOBAL_PLATFORM->bindings_sz += 8; } - Keybinding kb = (Keybinding){.keycode = key, .action = action}; - hashmap_Keybinding_insert(GLOBAL_PLATFORM->mem, GLOBAL_PLATFORM->bindings, &kb); + /* + LOG("Bindings in ctx:"); + for (isize i = 0; i < ctx->len; i++) { + switch (ctx->bindings[i].action.type) { + case InputType_error: + LOG("(error)"); + break; + + case InputType_action: + LOG("(action) %s", ctx->bindings[i].action.action.callback_str); + break; + + case InputType_state: + LOG("(+state) %s", ctx->bindings[i].action.state.activate_str); + LOG("(-state) %s", ctx->bindings[i].action.state.deactivate_str); + break; + case InputType_range: + LOG("(range) --unhandled--"); + break; + } + } + */ + GLOBAL_PLATFORM->bindings[GLOBAL_PLATFORM->bindings_len++] = ctx; } +void engine_input_ctx_pop(void) { + if (GLOBAL_PLATFORM->bindings == NULL || GLOBAL_PLATFORM->bindings_sz == 0) return; +} + +void engine_input_ctx_reset(void) { + GLOBAL_PLATFORM->bindings_len = 0; +} u32 get_time(void) {return SDL_GetTicks();} v2_i32 get_windowsize(void) {return GLOBAL_PLATFORM->window->windowsize;} diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..6b02ad7 --- /dev/null +++ b/src/input.c @@ -0,0 +1,94 @@ +#include <string.h> +#include <engine/input.h> +#include <engine/logging.h> + + +bool binding_action_cmp(binding_t *restrict a, binding_t *restrict b) { + InputType t = a->action.type; + if (t != b->action.type) return false; + switch (t) { + case InputType_action: + return !strcmp(a->action.action.callback_str, b->action.action.callback_str); + + case InputType_state: + return (!strcmp(a->action.state.activate_str, b->action.state.activate_str)) + && (!strcmp(a->action.state.deactivate_str, b->action.state.deactivate_str)); + + case InputType_range: // fallthrough + default: + return false; // not implemented + } + return false; +} + +bool binding_scancode_cmp(binding_t *restrict a, binding_t *restrict b) { + return a->scancode == b->scancode + || a->scancode_alt == b->scancode_alt; +} + +bool binding_scancode_cmp_i(binding_t *restrict a, scancode_t scancode) { + return a->scancode == scancode + || a->scancode_alt == scancode; +} + +// If any binding in ctx contains action, replace that entry. +bool i_update_binding(i_ctx *ctx, binding_t* binding) { + isize idx = 0; + if (ctx == NULL || binding == NULL) { + ERROR("i_update_binding received nullptr!"); + return false; + } + + while (idx < ctx->len && !binding_action_cmp(&ctx->bindings[idx], binding)) idx++; + + if (idx < ctx->len && binding_action_cmp(&ctx->bindings[idx], binding)) { + ctx->bindings[idx].scancode = binding->scancode; + ctx->bindings[idx].scancode_alt = binding->scancode_alt; + return true; + } + + return false; +} + +// If any binding in ctx contains scancode, replace that action. +bool i_update_unique_binding(i_ctx *ctx, binding_t* binding) { + isize idx = 0; + if (ctx == NULL || binding == NULL) { + ERROR("i_update_unique_binding received nullptr!"); + return false; + } + + while (idx < ctx->len && !binding_scancode_cmp(&ctx->bindings[idx], binding)) idx++; + + if (idx < ctx->len && binding_scancode_cmp(&ctx->bindings[idx], binding)) { + ctx->bindings[idx].action = binding->action; + return true; + } + + return false; +} + +void i_flush_bindings(usize numcalls, void* state_mem, input_callback_t* c[]) { + for (usize i = 0; i < numcalls; i++) { + (c[i])(state_mem); + } +} + +action_t i_get_action(const i_ctx *ctx, u32 time, scancode_t scancode) { + isize idx = 0; + + if (ctx == NULL) { + ERROR("%s received nullptr!", __func__); + return (action_t){.type = InputType_error}; + } + + while (idx < ctx->len && !binding_scancode_cmp_i(&ctx->bindings[idx], scancode)) idx++; + + if (idx < ctx->len + && binding_scancode_cmp_i(&ctx->bindings[idx], scancode)) { + ctx->bindings[idx].since_last_activation = time; + return ctx->bindings[idx].action; + } + + return (action_t){.type = InputType_error}; +} |
