Program Listing for File inputsystem.hpp¶
↰ Return to documentation for file (/home/runner/work/Legion-Engine/Legion-Engine/legion/engine/application/input/inputsystem.hpp
)
#pragma once
#include <application/events/inputevents.hpp>
#include <application/events/windowinputevents.hpp>
#include <numeric>
namespace legion::application
{
class InputSystem : public core::System<InputSystem>
{
using action_callback = delegate<void(InputSystem*, bool, inputmap::modifier_keys, inputmap::method, float, float)>;
struct action_data
{
action_callback callback;
float trigger_value;
inputmap::method last_method;
inputmap::modifier_keys last_mods;
bool last_state;
bool repeat;
};
using axis_callback = delegate<void(InputSystem*, float, inputmap::modifier_keys, inputmap::method, float)>;
struct axis_data
{
axis_callback callback;
inputmap::method last_method;
inputmap::modifier_keys last_mods;
float last_value;
};
struct axis_command_queue
{
std::vector<float> values;
std::vector<inputmap::modifier_keys> mods;
std::vector<inputmap::method> methods;
delegate<void()> invoke;
};
public:
void setup() override
{
//subscribe to the raw events emitted from the window system
bindToEvent<key_input, &InputSystem::onKey>();
bindToEvent<mouse_moved, &InputSystem::onMouseMove>();
bindToEvent<mouse_button, &InputSystem::onMouseButton>();
bindToEvent<mouse_scrolled, &InputSystem::onMouseScrolled>();
//create Update Process
createProcess<&InputSystem::onUpdate>("Input", 1.f/300.f);
//make sure we get the joystick-callback on initialization of GLFW
ContextHelper::addOnInitCallback(delegate<void()>::create([]
{
ContextHelper::setJoystickCallback(&InputSystem::onCheckGamepadPresence);
//also enumerate Joysticks at the beginning of the Engine, the callback is not called on Joysticks that
//that are already connected
//note that GLFW only supports 16 gamepads!
for (size_type i = 0; i < inputmap::modifier_keys::MAX_SIZE - inputmap::modifier_keys::JOYSTICK0; ++i)
{
if (ContextHelper::joystickPresent(i))
{
m_presentGamepads.insert(i);
}
}
}));
//make sure the mappings match here!
//ContextHelper::updateGamepadMappings("assets/conf/gamepad.conf");
}
inline static math::dvec2 getMousePosition()
{
return m_mousePos;
}
inline static math::dvec2 getMouseDelta()
{
return m_mouseDelta;
}
template <class Event>
static void createBinding(inputmap::method k, float value = 1) {
static_assert(std::is_base_of_v<input_action<Event>, Event> ||
std::is_base_of_v<input_axis<Event>, Event>,
"Event needs to either be an input_action or an input_axis");
if constexpr (std::is_base_of_v<input_action<Event>, Event>)
{
//are we dealing with a family of methods ?
if (inputmap::is_family(k))
{
for (inputmap::method m : inputmap::get_family(k))
{
if (inputmap::is_key(m))
{
bindKeyToAction<Event>(m);
}
if (inputmap::is_axis(m))
{
bindAxisToAction<Event>(m, value);
}
}
}
else
{
if (inputmap::is_key(k))
{
bindKeyToAction<Event>(k);
}
if (inputmap::is_axis(k))
{
bindAxisToAction<Event>(k, value);
}
}
}
else
{
if (inputmap::is_family(k))
{
for (inputmap::method m : inputmap::get_family(k))
{
if (inputmap::is_key(m))
{
bindKeyToAxis<Event>(m, value);
}
if (inputmap::is_axis(m))
{
bindAxisToAxis<Event>(m, value);
}
}
}
else
{
if (inputmap::is_key(k))
{
bindKeyToAxis<Event>(k, value);
}
if (inputmap::is_axis(k))
{
bindAxisToAxis<Event>(k, value);
}
}
}
}
template<class Event>
static void removeBinding(inputmap::method met)
{
static_assert(std::is_base_of_v<input_action<Event>, Event> ||
std::is_base_of_v<input_axis<Event>, Event>,
"Event needs to either be an input_action or an input_axis");
if constexpr (std::is_base_of_v<input_action<Event>, Event>)
{
//are we dealing with a family of methods ?
if (inputmap::is_family(met))
{
for (inputmap::method member : inputmap::get_family(met))
{
if (inputmap::is_key(member))
{
m_actions[member][typeHash<Event>()].callback.clear();
}
if (inputmap::is_axis(member))
{
m_axes[member][typeHash<Event>()].callback.clear();
}
}
}
else
{
if (inputmap::is_key(met))
{
m_actions[met][typeHash<Event>()].callback.clear();
}
if (inputmap::is_axis(met))
{
m_axes[met][typeHash<Event>()].callback.clear();
}
}
}
else
{
if (inputmap::is_family(met))
{
for (inputmap::method member : inputmap::get_family(met))
{
if (inputmap::is_key(member))
{
m_actions[member][typeHash<Event>()].callback.clear();
}
if (inputmap::is_axis(member))
{
m_axes[member][typeHash<Event>()].callback.clear();
}
}
}
else
{
if (inputmap::is_key(met))
{
m_actions[met][typeHash<Event>()].callback.clear();
}
if (inputmap::is_axis(met))
{
m_axes[met][typeHash<Event>()].callback.clear();
}
}
}
}
private:
template<class Event>
static void bindKeyToAction(inputmap::method m)
{
//creates a tuple with default value 0
auto& data = m_actions[m][typeHash<Event>()];
data.callback = action_callback::create(
[](InputSystem* self, bool state, inputmap::modifier_keys mods, inputmap::method method, float def, float delta)
{
OPTICK_EVENT("Key to action callback");
(void)def;
Event e;
e.input_delta = delta;
e.set(state, mods, method);
self->raiseEvent<Event>(e);
}
);
data.last_method = m;
data.last_mods = inputmap::modifier_keys::NONE;
data.repeat = false;
}
template<class Event>
static void bindKeyToAxis(inputmap::method m, float value)
{
auto& data = m_actions[m][typeHash<Event>()];
data.callback = action_callback::create(
[](InputSystem* self, bool state, inputmap::modifier_keys mods, inputmap::method method, float def, float delta)
{
self->pushCommand<Event>(state ? def : 0.0f,mods,method);
}
);
data.trigger_value = value;
data.last_method = m;
data.last_mods = inputmap::modifier_keys::NONE;
data.last_state = false;
data.repeat = true;
}
template<class Event>
static void bindAxisToAction(inputmap::method m, float value)
{
auto& data = m_axes[m][typeHash<Event>()];
data.callback = axis_callback::create(
[](InputSystem* self, float value, inputmap::modifier_keys mods, inputmap::method method, float delta)
{
OPTICK_EVENT("Axis to action callback");
Event e;
e.input_delta = delta;
e.set(value > 0.05f || value < -0.05f, mods, method); //convert float range 0-1 to key state false:true
self->raiseEvent<Event>(e);
}
);
data.last_value = value;
data.last_method = m;
data.last_mods = inputmap::modifier_keys::NONE;
}
template<class Event>
static void bindAxisToAxis(inputmap::method m, float value)
{
auto& data = m_axes[m][typeHash<Event>()];
data.callback = axis_callback::create(
[](InputSystem* self, float value, inputmap::modifier_keys mods, inputmap::method method, float delta)
{
self->pushCommand<Event>(value,mods,method);
}
);
data.last_value = value;
data.last_method = m;
data.last_mods = inputmap::modifier_keys::NONE;
}
//joystick (dis)connect callback
static void onCheckGamepadPresence(int jid, int event)
{
if (event == GLFW_CONNECTED)
m_presentGamepads.insert(jid);
else if (event == GLFW_DISCONNECTED)
m_presentGamepads.erase(jid);
}
void onUpdate(time::time_span<fast_time> deltaTime)
{
OPTICK_EVENT();
onJoystick(deltaTime);
{
OPTICK_EVENT("Update axes");
//update all axis with their current values
for (auto [_, inner_map] : m_axes)
{
for (auto [_, axis] : inner_map)
{
axis.callback(this, axis.last_value, axis.last_mods, axis.last_method, deltaTime);
}
}
}
{
OPTICK_EVENT("Action repeating callbacks");
for (auto [_, inner_map] : m_actions)
{
for (auto [_, action] : inner_map)
{
if (action.repeat)
action.callback(this, action.last_state, action.last_mods, action.last_method, action.trigger_value, deltaTime);
}
}
}
raiseCommandQueues(deltaTime);
onMouseReset();
}
void matchGLFWAxisWithSignalAxis(const GLFWgamepadstate& state, inputmap::modifier_keys joystick,
const size_type glfw, inputmap::method m)
{
OPTICK_EVENT();
const float value = state.axes[glfw];
for (auto [_, axis] : m_axes[m])
{
axis.last_value = value;
axis.last_method = m;
axis.last_mods = joystick;
}
}
void onMouseReset()
{
for (auto [_, axis] : m_axes[inputmap::method::MOUSE_X])
{
axis.last_value = 0.0f;
axis.last_mods = inputmap::modifier_keys::NONE;
axis.last_method = inputmap::method::MOUSE_X;
}
for (auto [_, axis] : m_axes[inputmap::method::MOUSE_Y])
{
axis.last_value = 0.0f;
axis.last_mods = inputmap::modifier_keys::NONE;
axis.last_method = inputmap::method::MOUSE_Y;
}
}
void onJoystick(float dt)
{
OPTICK_EVENT();
for (int glfw_joystick_id : m_presentGamepads)
{
using mods = inputmap::modifier_keys;
using method = inputmap::method;
GLFWgamepadstate state;
if (!ContextHelper::getGamepadSate(glfw_joystick_id, &state)) continue;
const auto joystick = mods::JOYSTICK0 + glfw_joystick_id;
for (auto [_, action] : m_actions[method::GAMEPAD_A])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_A], joystick, method::GAMEPAD_A, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_B])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_B], joystick, method::GAMEPAD_B, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_X])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_X], joystick, method::GAMEPAD_X, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_Y])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_Y], joystick, method::GAMEPAD_Y, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_LEFT_BUMPER])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER], joystick, method::GAMEPAD_LEFT_BUMPER, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_RIGHT_BUMPER])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER], joystick, method::GAMEPAD_RIGHT_BUMPER, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_BACK])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_BACK], joystick, method::GAMEPAD_BACK, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_START])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_START], joystick, method::GAMEPAD_START, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_DPAD_UP])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_UP], joystick, method::GAMEPAD_DPAD_UP, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_DPAD_RIGHT])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT], joystick, method::GAMEPAD_DPAD_RIGHT, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_DPAD_LEFT])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT], joystick, method::GAMEPAD_DPAD_LEFT, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_DPAD_DOWN])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_DOWN], joystick, method::GAMEPAD_DPAD_DOWN, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_LEFT_THUMB])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_THUMB], joystick, method::GAMEPAD_LEFT_THUMB, action.trigger_value, dt);
for (auto [_, action] : m_actions[method::GAMEPAD_RIGHT_THUMB])
action.callback(this, state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_THUMB], joystick, method::GAMEPAD_RIGHT_THUMB, action.trigger_value, dt);
matchGLFWAxisWithSignalAxis(state, joystick, GLFW_GAMEPAD_AXIS_LEFT_X, method::GAMEPAD_LEFT_X);
matchGLFWAxisWithSignalAxis(state, joystick, GLFW_GAMEPAD_AXIS_LEFT_Y, method::GAMEPAD_LEFT_Y);
matchGLFWAxisWithSignalAxis(state, joystick, GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, method::GAMEPAD_LEFT_TRIGGER);
matchGLFWAxisWithSignalAxis(state, joystick, GLFW_GAMEPAD_AXIS_RIGHT_X, method::GAMEPAD_RIGHT_X);
matchGLFWAxisWithSignalAxis(state, joystick, GLFW_GAMEPAD_AXIS_RIGHT_Y, method::GAMEPAD_RIGHT_Y);
matchGLFWAxisWithSignalAxis(state, joystick, GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, method::GAMEPAD_RIGHT_TRIGGER);
}
}
static inputmap::modifier_keys translateModifierKeys(int glfw_mods)
{
using mods = inputmap::modifier_keys;
mods result = mods::NONE;
if (glfw_mods & GLFW_MOD_ALT)
result = static_cast<mods>(result & inputmap::modifier_keys::ALT);
if (glfw_mods & GLFW_MOD_SHIFT)
result = static_cast<mods>(result & inputmap::modifier_keys::SHIFT);
if (glfw_mods & GLFW_MOD_CONTROL)
result = static_cast<mods>(result & inputmap::modifier_keys::CTRL);
return result;
}
void onKey(key_input* window_key_event)
{
const auto m = static_cast<inputmap::method>(window_key_event->key);
for (auto [_, action] : m_actions[m])
{
action.last_state = window_key_event->action != GLFW_RELEASE;
action.last_mods = translateModifierKeys(window_key_event->mods);
action.last_method = m;
if (!action.repeat)
action.callback(this, action.last_state, action.last_mods, action.last_method, action.trigger_value, 0.0f);
}
}
void onMouseMove(mouse_moved* window_mouse_event)
{
m_mouseDelta = window_mouse_event->position - m_mousePos;
m_mousePos = window_mouse_event->position;
if (math::abs(m_mouseDelta.x) < 0.0001)
m_mouseDelta.x = 0.0;
if (math::abs(m_mouseDelta.y) < 0.0001)
m_mouseDelta.y = 0.0;
for (auto [_, axis] : m_axes[inputmap::method::MOUSE_X])
{
axis.last_value = static_cast<float>(m_mouseDelta.x);
axis.last_mods = inputmap::modifier_keys::NONE;
axis.last_method = inputmap::method::MOUSE_X;
}
for (auto [_, axis] : m_axes[inputmap::method::MOUSE_Y])
{
axis.last_value = static_cast<float>(m_mouseDelta.y);
axis.last_mods = inputmap::modifier_keys::NONE;
axis.last_method = inputmap::method::MOUSE_Y;
}
}
void onMouseButton(mouse_button* window_mouse_event)
{
switch (window_mouse_event->button)
{
case GLFW_MOUSE_BUTTON_LEFT: {
for (auto [_, action] : m_actions[inputmap::method::MOUSE_LEFT])
{
action.last_state = window_mouse_event->action != GLFW_RELEASE;
action.last_mods = translateModifierKeys(window_mouse_event->mods);
action.last_method = inputmap::method::MOUSE_LEFT;
if (!action.repeat)
action.callback(this, action.last_state, action.last_mods, action.last_method, action.trigger_value, 0.0f);
}
break;
}
case GLFW_MOUSE_BUTTON_MIDDLE: {
for (auto [_, action] : m_actions[inputmap::method::MOUSE_MIDDLE])
{
action.last_state = window_mouse_event->action != GLFW_RELEASE;
action.last_mods = translateModifierKeys(window_mouse_event->mods);
action.last_method = inputmap::method::MOUSE_MIDDLE;
if (!action.repeat)
action.callback(this, action.last_state, action.last_mods, action.last_method, action.trigger_value, 0.0f);
}
break;
}
case GLFW_MOUSE_BUTTON_RIGHT: {
for (auto [_, action] : m_actions[inputmap::method::MOUSE_RIGHT])
{
action.last_state = window_mouse_event->action != GLFW_RELEASE;
action.last_mods = translateModifierKeys(window_mouse_event->mods);
action.last_method = inputmap::method::MOUSE_RIGHT;
if (!action.repeat)
action.callback(this, action.last_state, action.last_mods, action.last_method, action.trigger_value, 0.0f);
}
break;
}
}
}
void onMouseScrolled(mouse_scrolled* window_mouse_event)
{
const auto pos = window_mouse_event->offset;
for (auto [_, axis] : m_axes[inputmap::method::HSCROLL])
{
axis.last_value += static_cast<float>(pos.x);
axis.last_mods = inputmap::modifier_keys::NONE;
axis.last_method = inputmap::method::HSCROLL;
}
for (auto [_, axis] : m_axes[inputmap::method::VSCROLL])
{
axis.last_value += static_cast<float>(pos.y);
axis.last_mods = inputmap::modifier_keys::NONE;
axis.last_method = inputmap::method::VSCROLL;
}
}
template <class Event>
void pushCommand(float value,inputmap::modifier_keys mods, inputmap::method method)
{
auto& cq = m_axes_command_queues[Event::id];
cq.values.push_back(value);
cq.mods.push_back(mods);
cq.methods.push_back(method);
}
void raiseCommandQueues(float delta)
{
OPTICK_EVENT();
for(auto [key,value] : m_axes_command_queues){
auto axis = std::make_unique<input_axis<std::nullptr_t>>();
axis->value_parts = value.values;
axis->mods_parts = value.mods;
axis->identifier_parts = value.methods;
axis->input_delta = delta;
axis->value = std::accumulate(value.values.begin(),value.values.end(),0.0f);
raiseEventUnsafe(std::move(axis),key);
value.mods.clear();
value.values.clear();
value.methods.clear();
}
}
static math::dvec2 m_mousePos;
static math::dvec2 m_mouseDelta;
static std::set<int> m_presentGamepads;
static sparse_map<inputmap::method, sparse_map<id_type, action_data>> m_actions;
static sparse_map<inputmap::method, sparse_map<id_type, axis_data>> m_axes;
static sparse_map<id_type,axis_command_queue> m_axes_command_queues;
};
}