feat: implemented input module

This commit is contained in:
Sara 2024-09-24 15:26:34 +02:00
parent 4151c3429a
commit 38ceeec36f
8 changed files with 267 additions and 30 deletions

View file

@ -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();

187
src/core/input.c Normal file
View file

@ -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 <raylib.h>
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;
}
}
}

View file

@ -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

View file

@ -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);
}