Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework Input action pressed state to support multiple controllers #84943

Merged
merged 1 commit into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 91 additions & 82 deletions core/input/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ bool Input::is_anything_pressed() const {
return true;
}

for (const KeyValue<StringName, Input::Action> &E : action_state) {
if (E.value.pressed) {
for (const KeyValue<StringName, Input::ActionState> &E : action_states) {
if (E.value.cache.pressed) {
return true;
}
}
Expand Down Expand Up @@ -285,12 +285,17 @@ bool Input::is_joy_button_pressed(int p_device, JoyButton p_button) const {

bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
return action_state.has(p_action) && action_state[p_action].pressed > 0 && (p_exact ? action_state[p_action].exact : true);
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
}

return E->value.cache.pressed && (p_exact ? E->value.exact : true);
}

bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
HashMap<StringName, Action>::ConstIterator E = action_state.find(p_action);
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
}
Expand All @@ -300,7 +305,7 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con
}

// Backward compatibility for legacy behavior, only return true if currently pressed.
bool pressed_requirement = legacy_just_pressed_behavior ? E->value.pressed : true;
bool pressed_requirement = legacy_just_pressed_behavior ? E->value.cache.pressed : true;

if (Engine::get_singleton()->is_in_physics_frame()) {
return pressed_requirement && E->value.pressed_physics_frame == Engine::get_singleton()->get_physics_frames();
Expand All @@ -311,7 +316,7 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con

bool Input::is_action_just_released(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
HashMap<StringName, Action>::ConstIterator E = action_state.find(p_action);
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return false;
}
Expand All @@ -321,7 +326,7 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co
}

// Backward compatibility for legacy behavior, only return true if currently released.
bool released_requirement = legacy_just_pressed_behavior ? !E->value.pressed : true;
bool released_requirement = legacy_just_pressed_behavior ? !E->value.cache.pressed : true;

if (Engine::get_singleton()->is_in_physics_frame()) {
return released_requirement && E->value.released_physics_frame == Engine::get_singleton()->get_physics_frames();
Expand All @@ -332,7 +337,7 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co

float Input::get_action_strength(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
HashMap<StringName, Action>::ConstIterator E = action_state.find(p_action);
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return 0.0f;
}
Expand All @@ -341,12 +346,12 @@ float Input::get_action_strength(const StringName &p_action, bool p_exact) const
return 0.0f;
}

return E->value.strength;
return E->value.cache.strength;
}

float Input::get_action_raw_strength(const StringName &p_action, bool p_exact) const {
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
HashMap<StringName, Action>::ConstIterator E = action_state.find(p_action);
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
if (!E) {
return 0.0f;
}
Expand All @@ -355,7 +360,7 @@ float Input::get_action_raw_strength(const StringName &p_action, bool p_exact) c
return 0.0f;
}

return E->value.raw_strength;
return E->value.cache.raw_strength;
}

float Input::get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const {
Expand Down Expand Up @@ -440,6 +445,18 @@ static String _hex_str(uint8_t p_byte) {

void Input::joy_connection_changed(int p_idx, bool p_connected, String p_name, String p_guid, Dictionary p_joypad_info) {
_THREAD_SAFE_METHOD_

// Clear the pressed status if a Joypad gets disconnected.
if (!p_connected) {
for (KeyValue<StringName, ActionState> &E : action_states) {
HashMap<int, ActionState::DeviceState>::Iterator it = E.value.device_states.find(p_idx);
if (it) {
E.value.device_states.remove(it);
_update_action_cache(E.key, E.value);
}
}
}

Joypad js;
js.name = p_connected ? p_name : "";
js.uid = p_connected ? p_guid : "";
Expand Down Expand Up @@ -699,30 +716,35 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
if (event_index == -1) {
continue;
}
ERR_FAIL_COND_MSG(event_index >= (int)MAX_EVENT, vformat("Input singleton does not support more than %d events assigned to an action.", MAX_EVENT));

int device_id = p_event->get_device();
bool is_pressed = p_event->is_action_pressed(E.key, true);
ActionState &action_state = action_states[E.key];

// Update the action's per-device state.
ActionState::DeviceState &device_state = action_state.device_states[device_id];
device_state.pressed[event_index] = is_pressed;
device_state.strength[event_index] = p_event->get_action_strength(E.key);
device_state.raw_strength[event_index] = p_event->get_action_raw_strength(E.key);

// Update the action's global state and cache.
if (!is_pressed) {
action_state.api_pressed = false; // Always release the event from action_press() method.
action_state.api_strength = 0.0;
}
action_state.exact = InputMap::get_singleton()->event_is_action(p_event, E.key, true);

Action &action = action_state[E.key];
if (!p_event->is_echo()) {
if (p_event->is_action_pressed(E.key)) {
if (!action.pressed) {
action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
action.pressed_process_frame = Engine::get_singleton()->get_process_frames();
}
action.pressed |= ((uint64_t)1 << event_index);
} else {
action.pressed &= ~((uint64_t)1 << event_index);
action.pressed &= ~(1 << MAX_EVENT); // Always release the event from action_press() method.

if (!action.pressed) {
action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
action.released_process_frame = Engine::get_singleton()->get_process_frames();
}
_update_action_strength(action, MAX_EVENT, 0.0);
_update_action_raw_strength(action, MAX_EVENT, 0.0);
}
action.exact = InputMap::get_singleton()->event_is_action(p_event, E.key, true);
bool was_pressed = action_state.cache.pressed;
_update_action_cache(E.key, action_state);
if (action_state.cache.pressed && !was_pressed) {
action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames();
}
if (!action_state.cache.pressed && was_pressed) {
action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames();
action_state.released_process_frame = Engine::get_singleton()->get_process_frames();
}
_update_action_strength(action, event_index, p_event->get_action_strength(E.key));
_update_action_raw_strength(action, event_index, p_event->get_action_raw_strength(E.key));
}

if (event_dispatch_function) {
Expand Down Expand Up @@ -837,32 +859,30 @@ Point2i Input::warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, con

void Input::action_press(const StringName &p_action, float p_strength) {
groud marked this conversation as resolved.
Show resolved Hide resolved
// Create or retrieve existing action.
Action &action = action_state[p_action];
ActionState &action_state = action_states[p_action];

if (!action.pressed) {
action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
action.pressed_process_frame = Engine::get_singleton()->get_process_frames();
if (!action_state.cache.pressed) {
action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames();
}
action.pressed |= 1 << MAX_EVENT;
_update_action_strength(action, MAX_EVENT, p_strength);
_update_action_raw_strength(action, MAX_EVENT, p_strength);
action.exact = true;
action_state.exact = true;
action_state.api_pressed = true;
action_state.api_strength = p_strength;
_update_action_cache(p_action, action_state);
}

void Input::action_release(const StringName &p_action) {
// Create or retrieve existing action.
Action &action = action_state[p_action];

action.pressed = 0;
action.strength = 0.0;
action.raw_strength = 0.0;
action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
action.released_process_frame = Engine::get_singleton()->get_process_frames();
for (uint64_t i = 0; i <= MAX_EVENT; i++) {
action.strengths[i] = 0.0;
action.raw_strengths[i] = 0.0;
}
action.exact = true;
ActionState &action_state = action_states[p_action];
action_state.cache.pressed = 0;
action_state.cache.strength = 0.0;
action_state.cache.raw_strength = 0.0;
action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames();
action_state.released_process_frame = Engine::get_singleton()->get_process_frames();
action_state.device_states.clear();
action_state.exact = true;
action_state.api_pressed = false;
action_state.api_strength = 0.0;
}

void Input::set_emulate_touch_from_mouse(bool p_emulate) {
Expand Down Expand Up @@ -1020,10 +1040,8 @@ void Input::release_pressed_events() {
joy_buttons_pressed.clear();
_joy_axis.clear();

for (KeyValue<StringName, Input::Action> &E : action_state) {
if (E.value.pressed > 0) {
// Make sure the action is really released.
E.value.pressed = 1;
for (KeyValue<StringName, Input::ActionState> &E : action_states) {
if (E.value.cache.pressed) {
action_release(E.key);
}
}
Expand Down Expand Up @@ -1190,35 +1208,26 @@ void Input::_axis_event(int p_device, JoyAxis p_axis, float p_value) {
parse_input_event(ievent);
}

void Input::_update_action_strength(Action &p_action, int p_event_index, float p_strength) {
ERR_FAIL_INDEX(p_event_index, (int)MAX_EVENT + 1);
void Input::_update_action_cache(const StringName &p_action_name, ActionState &r_action_state) {
// Update the action cache, computed from the per-device and per-event states.
r_action_state.cache.pressed = false;
r_action_state.cache.strength = 0.0;
r_action_state.cache.raw_strength = 0.0;

float old_strength = p_action.strengths[p_event_index];
p_action.strengths[p_event_index] = p_strength;

if (p_strength > p_action.strength) {
p_action.strength = p_strength;
} else if (Math::is_equal_approx(old_strength, p_action.strength)) {
p_action.strength = p_strength;
for (uint64_t i = 0; i <= MAX_EVENT; i++) {
p_action.strength = MAX(p_action.strength, p_action.strengths[i]);
int max_event = InputMap::get_singleton()->action_get_events(p_action_name)->size();
for (const KeyValue<int, ActionState::DeviceState> &kv : r_action_state.device_states) {
const ActionState::DeviceState &device_state = kv.value;
for (int i = 0; i < max_event; i++) {
r_action_state.cache.pressed = r_action_state.cache.pressed || device_state.pressed[i];
r_action_state.cache.strength = MAX(r_action_state.cache.strength, device_state.strength[i]);
r_action_state.cache.raw_strength = MAX(r_action_state.cache.raw_strength, device_state.raw_strength[i]);
}
}
}

void Input::_update_action_raw_strength(Action &p_action, int p_event_index, float p_strength) {
ERR_FAIL_INDEX(p_event_index, (int)MAX_EVENT + 1);

float old_strength = p_action.raw_strengths[p_event_index];
p_action.raw_strengths[p_event_index] = p_strength;

if (p_strength > p_action.raw_strength) {
p_action.raw_strength = p_strength;
} else if (Math::is_equal_approx(old_strength, p_action.raw_strength)) {
p_action.raw_strength = p_strength;
for (uint64_t i = 0; i <= MAX_EVENT; i++) {
p_action.raw_strength = MAX(p_action.raw_strength, p_action.raw_strengths[i]);
}
if (r_action_state.api_pressed) {
r_action_state.cache.pressed = true;
r_action_state.cache.strength = MAX(r_action_state.cache.strength, r_action_state.api_strength);
r_action_state.cache.raw_strength = MAX(r_action_state.cache.raw_strength, r_action_state.api_strength); // Use the strength as raw_strength for API-pressed states.
}
}

Expand Down
40 changes: 20 additions & 20 deletions core/input/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class Input : public Object {

static Input *singleton;

static constexpr uint64_t MAX_EVENT = 31;
static constexpr uint64_t MAX_EVENT = 32;

public:
enum MouseMode {
Expand Down Expand Up @@ -100,30 +100,31 @@ class Input : public Object {
int64_t mouse_window = 0;
bool legacy_just_pressed_behavior = false;

struct Action {
struct ActionState {
uint64_t pressed_physics_frame = UINT64_MAX;
uint64_t pressed_process_frame = UINT64_MAX;
uint64_t released_physics_frame = UINT64_MAX;
uint64_t released_process_frame = UINT64_MAX;
uint64_t pressed = 0;
bool exact = true;
float strength = 0.0f;
float raw_strength = 0.0f;
LocalVector<float> strengths;
LocalVector<float> raw_strengths;

Action() {
strengths.resize(MAX_EVENT + 1);
raw_strengths.resize(MAX_EVENT + 1);

for (uint64_t i = 0; i <= MAX_EVENT; i++) {
strengths[i] = 0.0;
raw_strengths[i] = 0.0;
}
}

struct DeviceState {
bool pressed[MAX_EVENT] = { false };
float strength[MAX_EVENT] = { 0.0 };
float raw_strength[MAX_EVENT] = { 0.0 };
};
bool api_pressed = false;
float api_strength = 0.0;
HashMap<int, DeviceState> device_states;

// Cache.
struct ActionStateCache {
bool pressed = false;
float strength = false;
float raw_strength = false;
} cache;
};

HashMap<StringName, Action> action_state;
HashMap<StringName, ActionState> action_states;

bool emulate_touch_from_mouse = false;
bool emulate_mouse_from_touch = false;
Expand Down Expand Up @@ -240,8 +241,7 @@ class Input : public Object {
JoyAxis _get_output_axis(String output);
void _button_event(int p_device, JoyButton p_index, bool p_pressed);
void _axis_event(int p_device, JoyAxis p_axis, float p_value);
void _update_action_strength(Action &p_action, int p_event_index, float p_strength);
void _update_action_raw_strength(Action &p_action, int p_event_index, float p_strength);
void _update_action_cache(const StringName &p_action_name, ActionState &r_action_state);

void _parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_emulated);

Expand Down
Loading