summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--docs/input-handling.md96
-rw-r--r--include/engine/engine.h20
-rw-r--r--include/engine/input.h79
-rw-r--r--include/engine/state.h5
-rw-r--r--src/engine.c148
-rw-r--r--src/input.c94
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};
+}