Rework action pressed state to support multiple controllers

This commit is contained in:
Gilles Roudière 2023-11-15 17:42:56 +01:00
parent c851a46065
commit 8cfcd36253
2 changed files with 108 additions and 99 deletions

View File

@ -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;
}
}
@ -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;
}
@ -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();
@ -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;
}
@ -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();
@ -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;
}
@ -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;
}
@ -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 {
@ -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 : "";
@ -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));
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.
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];
if (!action.pressed) {
action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
action.released_process_frame = Engine::get_singleton()->get_process_frames();
// 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;
}
_update_action_strength(action, MAX_EVENT, 0.0);
_update_action_raw_strength(action, MAX_EVENT, 0.0);
action_state.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();
}
action.exact = InputMap::get_singleton()->event_is_action(p_event, E.key, true);
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) {
@ -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) {
// 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) {
@ -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);
}
}
@ -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.
}
}

View File

@ -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 {
@ -100,30 +100,31 @@ private:
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);
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;
for (uint64_t i = 0; i <= MAX_EVENT; i++) {
strengths[i] = 0.0;
raw_strengths[i] = 0.0;
}
}
// 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;
@ -240,8 +241,7 @@ private:
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);