From a504bae0f6c4f1e4774dce4449bdb70975b4e566 Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 22 Nov 2024 18:16:06 +0100 Subject: [PATCH] feat: input system --- src/core/input/input_effects.cpp | 86 ++++++++++++++++++++++++++++++++ src/core/input/input_effects.hpp | 53 ++++++++++++++++++++ src/core/input/input_map.cpp | 45 +++++++++++++++++ src/core/input/input_map.hpp | 61 ++++++++++++++++++++++ src/core/input/input_value.cpp | 76 ++++++++++++++++++++++++++++ src/core/input/input_value.hpp | 33 ++++++++++++ 6 files changed, 354 insertions(+) create mode 100644 src/core/input/input_effects.cpp create mode 100644 src/core/input/input_effects.hpp create mode 100644 src/core/input/input_map.cpp create mode 100644 src/core/input/input_map.hpp create mode 100644 src/core/input/input_value.cpp create mode 100644 src/core/input/input_value.hpp diff --git a/src/core/input/input_effects.cpp b/src/core/input/input_effects.cpp new file mode 100644 index 0000000..4a6b757 --- /dev/null +++ b/src/core/input/input_effects.cpp @@ -0,0 +1,86 @@ +#include "input_effects.hpp" + +namespace ce { +bool GamepadButton::evaluate(SDL_Event const &evt, InputValue &value) const { + switch(evt.type) { + case SDL_CONTROLLERBUTTONDOWN: + if(this->button == evt.cbutton.button && this->which == evt.cbutton.which) { + value = true; + return true; + } + break; + case SDL_CONTROLLERBUTTONUP: + if(this->button == evt.cbutton.button && this->which == evt.cbutton.which) { + value = false; + return true; + } + break; + default: break; + } + return false; +} + +bool GamepadAxis::evaluate(SDL_Event const &evt, InputValue &value) const { + switch(evt.type) { + case SDL_CONTROLLERAXISMOTION: + if(evt.caxis.axis == this->axis && evt.caxis.which == this->which) { + value = float(evt.caxis.value) / 32767.f; + return true; + } + break; + default: break; + } + return false; +} + +bool KeyboardScancode::evaluate(SDL_Event const &evt, InputValue &value) const { + switch(evt.type) { + case SDL_KEYDOWN: + if(evt.key.keysym.scancode == this->code) { + value = 1.f; + return true; + } + break; + case SDL_KEYUP: + if(evt.key.keysym.scancode == this->code) { + value = 0.f; + return true; + } + break; + default: break; + } + return false; +} + +bool KeyboardKey::evaluate(SDL_Event const &evt, InputValue &value) const { + switch(evt.type) { + case SDL_KEYDOWN: + if(evt.key.keysym.sym == this->key) { + value = 1.f; + return true; + } + break; + case SDL_KEYUP: + if(evt.key.keysym.scancode == this->key) { + value = 0.f; + return true; + } + break; + default: break; + } + return false; +} + +bool ButtonAxis::evaluate(SDL_Event const &evt, InputValue &value) const { + bool changed{false}; + InputValue neg_value{0.f}; + InputValue pos_value{0.f}; + if(neg != nullptr) changed |= neg->evaluate(evt, neg_value); + if(pos != nullptr) changed |= pos->evaluate(evt, pos_value); + if(changed && neg_value.is() && pos_value.is()) { + value = pos_value.get().value() - neg_value.get().value(); + return true; + } + return false; +} +} diff --git a/src/core/input/input_effects.hpp b/src/core/input/input_effects.hpp new file mode 100644 index 0000000..3d12f1b --- /dev/null +++ b/src/core/input/input_effects.hpp @@ -0,0 +1,53 @@ +#ifndef CORE_INPUT_EFFECTS_HPP +#define CORE_INPUT_EFFECTS_HPP + +#include "input_value.hpp" +#include +#include + +namespace ce { +struct InputEffect { + typedef std::unique_ptr Ptr; + template Ptr make(Param... p); + virtual bool evaluate(SDL_Event const &evt, InputValue &value) const = 0; +}; + +struct GamepadButton : InputEffect { + GamepadButton(SDL_JoystickID device, SDL_GameControllerButton button); + SDL_JoystickID which{0}; + SDL_GameControllerButton button{SDL_CONTROLLER_BUTTON_A}; + virtual bool evaluate(SDL_Event const &evt, InputValue &value) const override; +}; + +struct GamepadAxis : InputEffect { + GamepadAxis(SDL_JoystickID device, SDL_GameControllerAxis axis); + SDL_JoystickID which{0}; + SDL_GameControllerAxis axis{SDL_CONTROLLER_AXIS_LEFTX}; + virtual bool evaluate(SDL_Event const &evt, InputValue &value) const override; +}; + +struct KeyboardScancode : InputEffect { + KeyboardScancode(SDL_Scancode code); + SDL_Scancode code{SDL_SCANCODE_0}; + virtual bool evaluate(SDL_Event const &evt, InputValue &value) const override; +}; + +struct KeyboardKey : InputEffect { + KeyboardKey(SDL_Keycode code); + SDL_Keycode key{SDLK_KP_0}; + virtual bool evaluate(SDL_Event const &evt, InputValue &value) const override; +}; + +struct ButtonAxis : InputEffect { + ButtonAxis(InputEffect::Ptr &negative, InputEffect::Ptr &positive); + InputEffect::Ptr neg{}; + InputEffect::Ptr pos{}; + virtual bool evaluate(SDL_Event const &evt, InputValue &value) const override; +}; + +template InputEffect::Ptr InputEffect::make(Param... p) { + return std::dynamic_pointer_cast>(std::make_unique(p...)); +} +} + +#endif // !CORE_INPUT_EFFECTS_HPP diff --git a/src/core/input/input_map.cpp b/src/core/input/input_map.cpp new file mode 100644 index 0000000..1152ade --- /dev/null +++ b/src/core/input/input_map.cpp @@ -0,0 +1,45 @@ +#include "input_map.hpp" +#include +#include +#include + +namespace ce { +InputAction::InputAction(InputEffect::Ptr &effect) { + this->effects.push_back(std::move(effect)); +} + +InputAction::InputAction(std::vector &effects) { + for(InputEffect::Ptr &effect : effects) + this->effects.push_back(std::move(effect)); +} + +void InputAction::add_listener(ListenerFunction function, void *handle) { + ListenerHandle actual{ListenerHandle(handle)}; + this->listeners.push_back({function, actual}); +} + +void InputAction::remove_listeners(void *handle) { + ListenerHandle actual{ListenerHandle(handle)}; + std::erase_if(this->listeners, + [&actual](Listener const &listener) -> bool { + return listener.second == actual; + }); +} + +void InputAction::process_event(SDL_Event const &evt) { + bool changed{false}; // set to true if *any* effect returns as changed + InputValue value{}; // final value of input + // evaluate each event in order + for(InputEffect::Ptr &effect : this->effects) + changed |= effect->evaluate(evt, value); + // invoke listeners if any value in the chain was changed + if(changed) + for(Listener &listener : this->listeners) + std::invoke(listener.first, value); +} + +void InputMap::bind_input(std::string const &name, std::vector &effects) { + InputAction action{InputAction(effects)}; + this->bindings.insert({name, std::move(action)}); +} +} diff --git a/src/core/input/input_map.hpp b/src/core/input/input_map.hpp new file mode 100644 index 0000000..d1fe6d8 --- /dev/null +++ b/src/core/input/input_map.hpp @@ -0,0 +1,61 @@ +#ifndef CORE_INPUT_MAP_HPP +#define CORE_INPUT_MAP_HPP + +#include "input_effects.hpp" +#include "input_value.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ce { + +/*! Represents a collection of bindings. + */ +class InputAction { +typedef std::uintptr_t ListenerHandle; //!< handle used to connect listener functions to origin +typedef std::function ListenerFunction; +typedef std::pair Listener; +private: + std::vector effects{}; //!< list of all effects used to produce final result + std::vector listeners{}; //!< list of all listeners to invoke on completion +public: + InputAction() = default; + InputAction(InputEffect::Ptr &effect); + InputAction(std::vector &effects); + + void add_listener(ListenerFunction function, void *handle); //!< add a listener identified under a specific handle + void remove_listeners(void *handle); //!< remove all listeners identified under this handle + void process_event(SDL_Event const &evt); //!< process an incoming OS event + + template Effect *get_effect_of_type(); //!< get the first effect from the effects list that is of type Effect +}; + +/*! Map input values to functions. + */ +class InputMap { + std::map bindings{}; +public: + void bind_input(std::string const &name, std::vector &binds); + InputAction &get_action(std::string const &action_id); +}; + +template Effect *InputAction::get_effect_of_type() { + std::vector::iterator itr{std::find_if(this->effects.begin(), this->effects.end(), + [](InputEffect::Ptr const &effect) { + return dynamic_cast(effect.get()) != nullptr; + })}; + return itr == this->effects.end() ? nullptr : dynamic_cast(itr->get()); +} +} + +#endif // !CORE_INPUT_MAP_HPP diff --git a/src/core/input/input_value.cpp b/src/core/input/input_value.cpp new file mode 100644 index 0000000..0b86627 --- /dev/null +++ b/src/core/input/input_value.cpp @@ -0,0 +1,76 @@ +#include "input_value.hpp" +#include + +namespace ce { +InputValue::InputValue() +: type{INPUTVALUE_BOOL} { + std::memset(&this->type, 0x0, sizeof(this->data)); +} +InputValue::InputValue(bool b) : InputValue() { + this->type = INPUTVALUE_BOOL; + this->data.bool_value = b; +} +InputValue::InputValue(float f) : InputValue() { + this->type = INPUTVALUE_FLOAT; + this->data.float_value = f; +} +InputValue::InputValue(Vecf v) : InputValue() { + this->type = INPUTVALUE_VEC; + this->data.vec_value[0] = v.x; + this->data.vec_value[1] = v.y; +} +InputValue::InputValue(InputValue const &src) : InputValue() { + this->type = src.type; + this->data = src.data; +} + +InputValue &InputValue::operator=(InputValue const &src) { + this->type = src.type; + this->data = src.data; + return *this; +} + +template <> bool InputValue::is() const { + return this->type == INPUTVALUE_BOOL; +} + +template <> bool InputValue::is() const { + return this->type == INPUTVALUE_FLOAT; +} + +template <> bool InputValue::is() const { + return this->type == INPUTVALUE_VEC; +} + +template <> std::optional InputValue::get() const { + switch(this->type) { + case INPUTVALUE_BOOL: + return this->data.bool_value; + case INPUTVALUE_FLOAT: + return this->data.float_value > 0.f; + case INPUTVALUE_VEC: + return this->data.vec_value[0] != 0.f && this->data.vec_value[1] != 0.f; + default: return std::nullopt; + } +} + +template <> std::optional InputValue::get() const { + switch(this->type) { + case INPUTVALUE_BOOL: + return this->data.bool_value ? 1.f : 0.f; + case INPUTVALUE_FLOAT: + return this->data.float_value; + case INPUTVALUE_VEC: + return Vecf(this->data.vec_value[0], this->data.vec_value[1]).magnitude(); + default: return std::nullopt; + } +} + +template <> std::optional InputValue::get() const { + switch(this->type) { + case INPUTVALUE_VEC: + return Vecf(this->data.vec_value[0], this->data.vec_value[1]); + default: return std::nullopt; + } +} +} diff --git a/src/core/input/input_value.hpp b/src/core/input/input_value.hpp new file mode 100644 index 0000000..39ff7fc --- /dev/null +++ b/src/core/input/input_value.hpp @@ -0,0 +1,33 @@ +#ifndef CORE_INPUT_VALUE_HPP +#define CORE_INPUT_VALUE_HPP + +#include "core/math/vector.hpp" +#include + +namespace ce { +class InputValue { + enum Type { + INPUTVALUE_BOOL, + INPUTVALUE_FLOAT, + INPUTVALUE_VEC, + } type{INPUTVALUE_BOOL}; +private: + union { + bool bool_value; + float float_value; + float vec_value[2]; + } data; +public: + InputValue(); + InputValue(bool b); + InputValue(float f); + InputValue(Vecf v); + InputValue(InputValue const &src); + InputValue(InputValue const &&src); + InputValue &operator=(InputValue const &src); + template bool is() const; + template std::optional get() const; +}; +} + +#endif // !CORE_INPUT_VALUE_HPP