From 38ceeec36f1899b6d7fb1ce33090a05947a2b292 Mon Sep 17 00:00:00 2001 From: Sara Date: Tue, 24 Sep 2024 15:26:34 +0200 Subject: [PATCH] feat: implemented input module --- src/core/engine_loop.c | 4 + src/core/input.c | 187 +++++++++++++++++++++++++++++++++++++++++ src/core/input.h | 43 ++++++---- src/core/resources.c | 6 +- src/main.c | 12 ++- src/test_object.c | 29 ++++++- src/test_object.h | 14 ++- src/utils | 2 +- 8 files changed, 267 insertions(+), 30 deletions(-) create mode 100644 src/core/input.c diff --git a/src/core/engine_loop.c b/src/core/engine_loop.c index bdecb30..8029aa7 100644 --- a/src/core/engine_loop.c +++ b/src/core/engine_loop.c @@ -1,6 +1,7 @@ #include "engine_loop.h" #include "engine_global.h" #include "render.h" +#include "input.h" #include "resources.h" #include "utils/debug.h" #include "stdlib.h" @@ -25,6 +26,7 @@ void RunGame(Scene *initial_scene) { while (!WindowShouldClose()) { SceneTick(GetMainScene(), GetFrameTime()); RenderNextFrame(); + Internal_UpdateInput(); } ShutDown(); UNREACHABLE("RunGame: Reached beyond ShutDown call"); @@ -36,6 +38,7 @@ void InitializeEngine() { // initialize engine subsystems InitializeResourceSubsystem(); InitializeRenderingSubsystem(); + InitializeInputSubsystem(); } void ShutDown() { @@ -43,6 +46,7 @@ void ShutDown() { if(GetMainScene() != NULL) DestroyScene(GetMainScene()); // clean up subsystem resources + CleanInputSubsystem(); CleanResourceSubsystem(); CleanupRenderingSubsystem(); CloseWindow(); diff --git a/src/core/input.c b/src/core/input.c new file mode 100644 index 0000000..78ccef1 --- /dev/null +++ b/src/core/input.c @@ -0,0 +1,187 @@ +#include "input.h" +#include "string.h" +#include "utils/debug.h" +#include "utils/hash_map.h" +#include "utils/list.h" +#include "utils/strutil.h" +#include + +typedef struct InputObserver { + List listeners; + unsigned char listener_type; +} InputObserver; + +typedef struct MappedInput { + InputObserver *observer; + int device_idx; //!< For gamepad inputs, the gamepad index + int input_idx; //!< key/button/axis to check + InputType type; //!< type of input to check + union { + int last_int_value; //!< last value for buttons + float last_float_value; //!< Last value for analog axes + }; +} MappedInput; + +static HashMap g_input_map; +static List g_mapped_actions; + +static +uintptr_t HashStrPtrPtr(char **ptr) { + return strhash(*ptr); +} + +void InitializeInputSubsystem() { + g_input_map = hash_map_from_types(char*, InputObserver*, HashStrPtrPtr); + g_mapped_actions = list_from_type(MappedInput); +} + +void CleanInputSubsystem() { + // free both input map keys and values manually, as neither is managed elsewhere + List input_values = hash_map_values(&g_input_map); + for(size_t i = 0; i < input_values.len; ++i) { + InputObserver *observer = **list_at_as(InputObserver**, &input_values, i); + list_empty(&observer->listeners); // free the listener array + free(observer); + } + list_empty(&input_values); + List input_keys = hash_map_keys(&g_input_map); + for(size_t i = 0; i < input_keys.len; ++i) { + free(**list_at_as(char**, &input_keys, i)); + } + list_empty(&input_keys); + // clear the collection containers + hash_map_empty(&g_input_map); + list_empty(&g_mapped_actions); +} + +static +void Internal_UpdateGamepadButtonInput(MappedInput *self) { + if(!IsGamepadAvailable(self->device_idx)) return; + int const value = IsGamepadButtonDown(self->device_idx, self->input_idx); + if(value != self->last_int_value) + list_foreach(InputListener *,listener, &self->observer->listeners) + listener->button_listener(listener->object, value != 0); + self->last_int_value = value; +} + +static +void Internal_UpdateGamepadAxisInput(MappedInput *self) { + if(!IsGamepadAvailable(self->device_idx)) return; + float const value = GetGamepadAxisMovement(self->device_idx, self->input_idx); + if(value != self->last_float_value) + list_foreach(InputListener *,listener, &self->observer->listeners) + listener->axis_listener(listener->object, value); + self->last_float_value = value; +} + +static +void Internal_UpdateKeyboardInput(MappedInput *self) { + int const value = IsKeyDown(self->input_idx); + if(value != self->last_int_value) + list_foreach(InputListener *,listener, &self->observer->listeners) + listener->button_listener(listener->object, value); + self->last_int_value = value; +} + +static +void Internal_UpdateMouseInput(MappedInput *self) { + float value = 0.f; + if(self->input_idx == 1) + value = GetMouseDelta().y; + else value = GetMouseDelta().x; + if(value != self->last_float_value) + list_foreach(InputListener *,listener, &self->observer->listeners) + listener->axis_listener(listener->object, self->last_float_value); + self->last_float_value = value; +} + +void Internal_UpdateInput() { + list_foreach(MappedInput *,input, &g_mapped_actions) { + switch(input->type) { + case INPUT_LISTENER_GAMEPAD_BUTTON: + Internal_UpdateGamepadButtonInput(input); + break; + case INPUT_LISTENER_GAMEPAD_AXIS: + Internal_UpdateGamepadAxisInput(input); + break; + case INPUT_LISTENER_KEY: + Internal_UpdateKeyboardInput(input); + break; + case INPUT_LISTENER_MOUSE: + Internal_UpdateMouseInput(input); + break; + } + } +} + +static +InputObserver *Internal_CreateNewAction(char const *action_name, unsigned char listener_type) { + char *key = malloc(strlen(action_name) + 1); + strcpy(key, action_name); + InputObserver *value = new(InputObserver); + *value = (InputObserver) { + .listeners = list_from_type(InputListener), + .listener_type = listener_type + }; + hash_map_insert(&g_input_map, &key, &value); + return value; +} + +//! Either returns existing observer or adds new observer. +static +InputObserver *Internal_GetInputObserver(char const *action_name, unsigned char listener_type) { + InputObserver** observer = hash_map_get_as(InputObserver*, &g_input_map, &action_name); + ASSERT_RETURN(observer == NULL || listener_type != 0 || (*observer)->listener_type != listener_type, NULL, + "Internal_GetInputObserver: Device type is not supported by found action"); + return observer != NULL ? *observer : Internal_CreateNewAction(action_name, listener_type); +} + +void AddAction(char const *action_name, InputType dev_type, int device, int input) { + // assert that there is a valid name to map to an observer + ASSERT_RETURN(action_name != NULL,, "AddAction: action_name has to be passed with a valid string"); + InputObserver *observer = Internal_GetInputObserver(action_name, dev_type & 0xF); + // assert that there is an observer to work with + ASSERT_RETURN(observer != NULL,, "AddAction: Could not find or create %s", action_name); + MappedInput mapped = { .observer = observer, .input_idx = input, .device_idx = device, .type = dev_type }; + if((0xF & dev_type) == 0x01) // initialize last_ _value based on device type + mapped.last_int_value = 0; + else mapped.last_float_value = 0.f; + list_add(&g_mapped_actions, &mapped); +} + +void AddListener(char const *action_name, InputListener listener) { + InputObserver *observer = Internal_GetInputObserver(action_name, ((listener.axis_listener != 0) << 1) | (listener.button_listener != NULL)); + // assert that there is actually an observer to work with + ASSERT_RETURN(observer != NULL,, "AddListener: Could not find or create action called %s", action_name); + // assert that either the observer and listener are both for button events, or both for axis events. + ASSERT_RETURN(observer->listener_type != 0x2 || listener.button_listener == NULL && listener.axis_listener != NULL,, + "AddListener: Axis listener is not supported by button action."); + ASSERT_RETURN(observer->listener_type != 0x1 || listener.axis_listener == NULL && listener.button_listener != NULL,, + "AddListener: Button listenertype is not supported by axis action."); + list_add(&observer->listeners, &listener); +} + +void RemoveListener(char const *action_name, void *object) { + InputObserver *observer = Internal_GetInputObserver(action_name, 0); + ASSERT_RETURN(observer != NULL,, "RemoveListener: Could not find or create action called %s", action_name); + // linear search for input, as the list is unsorted & not indexed + for(size_t i = 0; i < observer->listeners.len; ++i) { + if(list_at_as(InputListener, &observer->listeners, i)->object == object) { + list_erase(&observer->listeners, i); + return; + } + } +} + +void RemoveAllListeners(void *object) { + List observers = hash_map_values(&g_input_map); + // linear search through observers for listeners that match the passed object + for(size_t observer_idx = 0; observer_idx < observers.len; ++observer_idx) { + List *listeners = &(**list_at_as(InputObserver**, &observers, observer_idx))->listeners; + for(size_t listener_idx = 0; listener_idx < listeners->len;) { + if(list_at_as(InputListener, listeners, listener_idx)->object == object) { + list_erase(listeners, listener_idx); + } else ++listener_idx; + } + } +} diff --git a/src/core/input.h b/src/core/input.h index 61d505d..88107c5 100644 --- a/src/core/input.h +++ b/src/core/input.h @@ -3,30 +3,37 @@ #include "stdbool.h" -typedef enum InputListenerType { - INPUT_LISTENER_INT, - INPUT_LISTENER_BOOL, - INPUT_LISTENER_FLOAT, -} InputListenerType; +//! Input type, second nibble is an index. +//! First nibble is a bitmask of what type of listener will work. +//! Where 0b0001 is float and 0b0010 is bool listeners. +typedef enum InputType { + INPUT_LISTENER_KEY = 0x11, + INPUT_LISTENER_GAMEPAD_BUTTON = 0x21, + INPUT_LISTENER_GAMEPAD_AXIS = 0x32, + INPUT_LISTENER_MOUSE = 0x42, +} InputType; -typedef void(*IntListener)(int); -typedef void(*BoolListener)(bool); -typedef void(*FloatListener)(float); +typedef void(*BoolListener)(void*,bool); +typedef void(*FloatListener)(void*,float); typedef struct InputListener { void *object; - InputListenerType type; - union { - IntListener int_listener; - BoolListener bool_listener; - FloatListener float_listener; - }; + BoolListener button_listener; + FloatListener axis_listener; } InputListener; -void InitInputSubmodule(); -void CleanInputSubmodule(); +#define ButtonInputListener(object_, function_) ((InputListener){.object = object_, .button_listener = (void(*)(void*, bool))function_, .axis_listener = NULL}) +#define AxisInputListener(object_, function_) ((InputListener){.object = object_, .button_listener = NULL, .axis_listener = (void(*)(void*, float))function_}) -void AddListener(); -void RemoveListener(); +extern void InitializeInputSubsystem(); +extern void CleanInputSubsystem(); + +extern void Internal_UpdateInput(); + +extern void AddAction(char const *action_name, InputType dev_type, int device, int input); + +extern void AddListener(char const *action, InputListener listener); +extern void RemoveListener(char const *action, void *object); +extern void RemoveAllListeners(void *object); #endif // !INPUT_H diff --git a/src/core/resources.c b/src/core/resources.c index 6332c39..9464fe0 100644 --- a/src/core/resources.c +++ b/src/core/resources.c @@ -119,9 +119,9 @@ void InitializeResourceSubsystem() { void CleanResourceSubsystem() { List resources = hash_map_values(&g_resource_map); - list_foreach(ResourceContainer *,resource, &resources) - if(resource->is_loaded) - g_unload_functions[resource->type](resource); + list_foreach(ResourceContainer **,resource, &resources) + if((*resource)->is_loaded) + g_unload_functions[(*resource)->type](*resource); hash_map_empty(&g_resource_map); UnloadDirectoryFiles(g_resource_files); } diff --git a/src/main.c b/src/main.c index ad5ce4e..af3b917 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,6 @@ #include "raylib.h" #include "test_object.h" +#include "core/input.h" #include "core/camera_node.h" #include "core/engine_global.h" #include "core/engine_loop.h" @@ -15,7 +16,7 @@ SceneNode *CreateCameraScene() { // set camera parent offset Transformable transformable = TC_CAST(camera_parent->entity, Transformable); Transform transform = transformable.tc->get_transform(transformable.data); - transform.translation = (Vector3){3.f, 4.f, -10.f}; + transform.translation = (Vector3){0.f, 4.f, -10.f}; transform.rotation = QuaternionFromEuler(7.5f * DEG2RAD, 0.f, 0.f); transformable.tc->set_transform(transformable.data, transform); return camera_parent; @@ -30,7 +31,6 @@ SceneNode *CreateModelScene() { // move the renderer's parent transform Transformable transformable = TC_CAST(model_parent->entity, Transformable); Transform transform = transformable.tc->get_global_transform(transformable.data); - transform.translation.y += 10; transformable.tc->set_global_transform(transformable.data, transform); return model_parent; } @@ -43,8 +43,16 @@ Scene *CreateInitialScene() { return CreateScene(root); } +void ConfigureInput() { + AddAction("pitch_up", INPUT_LISTENER_KEY, 0, KEY_S); + AddAction("pitch_down", INPUT_LISTENER_KEY, 0, KEY_W); + AddAction("roll_left", INPUT_LISTENER_KEY, 0, KEY_A); + AddAction("roll_right", INPUT_LISTENER_KEY, 0, KEY_D); +} + int main() { InitializeEngine(); + ConfigureInput(); RunGame(CreateInitialScene()); UNREACHABLE("main: End of main function reached. This is an error, call ShutDown() when exiting instead."); } diff --git a/src/test_object.c b/src/test_object.c index 5c5454c..b3697e1 100644 --- a/src/test_object.c +++ b/src/test_object.c @@ -1,5 +1,7 @@ #include "test_object.h" #include "core/render.h" +#include "utils/debug.h" +#include "core/input.h" #include "raylib.h" START_REFLECT(TestObject); @@ -34,6 +36,10 @@ void DestroyTestObject(TestObject *self) { void TestObjectEnterTree(TestObject *self) { self->transform = TC_CAST(self->node->parent->entity, Transformable); AddRenderable(TestObject_as_Renderable(self)); + AddListener("pitch_up", ButtonInputListener(self, TestObjectUpInput)); + AddListener("pitch_down", ButtonInputListener(self, TestObjectDownInput)); + AddListener("roll_left", ButtonInputListener(self, TestObjectLeftInput)); + AddListener("roll_right", ButtonInputListener(self, TestObjectRightInput)); } void TestObjectExitTree(TestObject *self) { @@ -46,8 +52,27 @@ void TestObjectDraw(TestObject *self) { void TestObjectTick(TestObject *self, double delta) { Transform global_transform = self->transform.tc->get_global_transform(self->transform.data); Matrix global_matrix = TransformGetMatrix(global_transform); - global_transform.rotation = QuaternionMultiply(global_transform.rotation, QuaternionFromAxisAngle(MATRIX_RIGHT(global_matrix), -3.f * delta)); - global_matrix = TransformGetMatrix(global_transform); global_transform.translation = Vector3Add(global_transform.translation, Vector3Scale(MATRIX_FORWARD(global_matrix), 2.f * delta)); + global_transform.rotation = QuaternionMultiply(QuaternionFromAxisAngle(MATRIX_FORWARD(global_matrix), self->fly_input.x * 3.f * delta), global_transform.rotation); + global_transform.rotation = QuaternionMultiply(QuaternionFromAxisAngle(MATRIX_RIGHT(global_matrix), self->fly_input.y * 3.f * delta), global_transform.rotation); self->transform.tc->set_global_transform(self->transform.data, global_transform); } + + +void TestObjectLeftInput(TestObject *self, bool value) { + self->fly_input.x += value ? -1 : +1; +} + +void TestObjectRightInput(TestObject *self, bool value) { + self->fly_input.x += value ? +1 : -1; + LOG_INFO("Input Right"); +} + +void TestObjectUpInput(TestObject *self, bool value) { + self->fly_input.y += value ? -1 : +1; + LOG_INFO("Input Up"); +} + +void TestObjectDownInput(TestObject *self, bool value) { + self->fly_input.y += value ? +1 : -1; +} diff --git a/src/test_object.h b/src/test_object.h index 0b47274..ce83a0e 100644 --- a/src/test_object.h +++ b/src/test_object.h @@ -12,15 +12,21 @@ typedef struct TestObject { SceneNode *node; Transformable transform; + Vector2 fly_input; } TestObject; SceneNode *CreateTestObject(); void DestroyTestObject(TestObject *self); -void TestObjectEnterTree(TestObject *self); -void TestObjectExitTree(TestObject *self); -void TestObjectDraw(TestObject *self); -void TestObjectTick(TestObject *self, double delta); +extern void TestObjectEnterTree(TestObject *self); +extern void TestObjectExitTree(TestObject *self); +extern void TestObjectDraw(TestObject *self); +extern void TestObjectTick(TestObject *self, double delta); + +extern void TestObjectLeftInput(TestObject *self, bool value); +extern void TestObjectRightInput(TestObject *self, bool value); +extern void TestObjectUpInput(TestObject *self, bool value); +extern void TestObjectDownInput(TestObject *self, bool value); DECL_REFLECT(TestObject); decl_typeclass_impl(SceneNodeEntity, TestObject); diff --git a/src/utils b/src/utils index af50174..2021092 160000 --- a/src/utils +++ b/src/utils @@ -1 +1 @@ -Subproject commit af50174b71853f3ca887fc0e064e88258c412f2c +Subproject commit 2021092a17b810d08d655f48cadca945d26258f5