From a07079bdf999fb6e8d0aed3144e343c4ec6253c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 23 Aug 2023 17:52:30 +0200 Subject: [PATCH 1/2] Timestamp control inputs --- Core/ControlMapper.cpp | 37 ++++++++++++++++++++----------------- Core/ControlMapper.h | 7 ++++++- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/Core/ControlMapper.cpp b/Core/ControlMapper.cpp index 07945a02c5b9..072de182e6f4 100644 --- a/Core/ControlMapper.cpp +++ b/Core/ControlMapper.cpp @@ -156,9 +156,10 @@ void ControlMapper::UpdateAnalogOutput(int stick) { void ControlMapper::ForceReleaseVKey(int vkey) { std::vector multiMappings; if (KeyMap::InputMappingsFromPspButton(vkey, &multiMappings, true)) { + double now = time_now_d(); for (const auto &entry : multiMappings) { for (const auto &mapping : entry.mappings) { - curInput_[mapping] = 0.0f; + curInput_[mapping] = { 0.0f, now }; // Different logic for signed axes? UpdatePSPState(mapping); } @@ -178,12 +179,12 @@ static int RotatePSPKeyCode(int x) { } // Used to decay analog values when clashing with digital ones. -static float ReduceMagnitude(float value) { - value *= 0.75f; - if ((value > 0.0f && value < 0.05f) || (value < 0.0f && value > -0.05f)) { - value = 0.0f; +static ControlMapper::InputSample ReduceMagnitude(ControlMapper::InputSample sample) { + sample.value *= 0.75f; + if ((sample.value > 0.0f && sample.value < 0.05f) || (sample.value < 0.0f && sample.value > -0.05f)) { + sample.value = 0.0f; } - return value; + return sample; } float ControlMapper::MapAxisValue(float value, int vkId, const InputMapping &mapping, const InputMapping &changedMapping, bool *oppositeTouched) { @@ -199,7 +200,7 @@ float ControlMapper::MapAxisValue(float value, int vkId, const InputMapping &map if (other == changedMapping) { *oppositeTouched = true; } - float valueOther = curInput_[other]; + float valueOther = curInput_[other].value; float signedValue = value - valueOther; float ranged = (signedValue + 1.0f) * 0.5f; if (direction == -1) { @@ -290,7 +291,7 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) { bool all = true; for (auto mapping : multiMapping.mappings) { auto iter = curInput_.find(mapping); - bool down = iter != curInput_.end() && iter->second > GetDeviceAxisThreshold(iter->first.deviceId); + bool down = iter != curInput_.end() && iter->second.value > GetDeviceAxisThreshold(iter->first.deviceId); if (!down) all = false; } @@ -307,6 +308,7 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) { bool updateAnalogSticks = false; // OK, handle all the virtual keys next. For these we need to do deltas here and send events. + // Note that virtual keys include the analog directions, as they are driven by them. for (int i = 0; i < VIRTKEY_COUNT; i++) { int vkId = i + VIRTKEY_FIRST; std::vector inputMappings; @@ -334,9 +336,9 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) { if (iter != curInput_.end()) { if (mapping.IsAxis()) { threshold = GetDeviceAxisThreshold(iter->first.deviceId); - product *= MapAxisValue(iter->second, idForMapping, mapping, changedMapping, &touchedByMapping); + product *= MapAxisValue(iter->second.value, idForMapping, mapping, changedMapping, &touchedByMapping); } else { - product *= iter->second; + product *= iter->second.value; } } else { product = 0.0f; @@ -418,9 +420,9 @@ bool ControlMapper::Key(const KeyInput &key, bool *pauseTrigger) { InputMapping mapping(key.deviceId, key.keyCode); if (key.flags & KEY_DOWN) { - curInput_[mapping] = 1.0f; + curInput_[mapping] = { 1.0f, time_now_d() }; } else if (key.flags & KEY_UP) { - curInput_[mapping] = 0.0f; + curInput_[mapping] = { 0.0f, time_now_d() }; } // TODO: See if this can be simplified further somehow. @@ -463,18 +465,19 @@ void ControlMapper::ToggleSwapAxes() { void ControlMapper::Axis(const AxisInput &axis) { std::lock_guard guard(mutex_); + double now = time_now_d(); if (axis.value >= 0.0f) { InputMapping mapping(axis.deviceId, axis.axisId, 1); InputMapping opposite(axis.deviceId, axis.axisId, -1); - curInput_[mapping] = axis.value; - curInput_[opposite] = 0.0f; + curInput_[mapping] = { axis.value, now }; + curInput_[opposite] = { 0.0f, now }; UpdatePSPState(mapping); UpdatePSPState(opposite); } else if (axis.value < 0.0f) { InputMapping mapping(axis.deviceId, axis.axisId, -1); InputMapping opposite(axis.deviceId, axis.axisId, 1); - curInput_[mapping] = -axis.value; - curInput_[opposite] = 0.0f; + curInput_[mapping] = { -axis.value, now }; + curInput_[opposite] = { 0.0f, now }; UpdatePSPState(mapping); UpdatePSPState(opposite); } @@ -583,7 +586,7 @@ void ControlMapper::GetDebugString(char *buffer, size_t bufSize) const { for (auto iter : curInput_) { char temp[256]; iter.first.FormatDebug(temp, sizeof(temp)); - str << temp << ": " << iter.second << std::endl; + str << temp << ": " << iter.second.value << std::endl; } for (int i = 0; i < ARRAY_SIZE(virtKeys_); i++) { int vkId = VIRTKEY_FIRST + i; diff --git a/Core/ControlMapper.h b/Core/ControlMapper.h index bca32ff0e8f5..d796d4b94542 100644 --- a/Core/ControlMapper.h +++ b/Core/ControlMapper.h @@ -42,6 +42,11 @@ class ControlMapper { void GetDebugString(char *buffer, size_t bufSize) const; + struct InputSample { + float value; + double timestamp; + }; + private: bool UpdatePSPState(const InputMapping &changedMapping); float MapAxisValue(float value, int vkId, const InputMapping &mapping, const InputMapping &changedMapping, bool *oppositeTouched); @@ -71,7 +76,7 @@ class ControlMapper { // Protects basically all the state. std::mutex mutex_; - std::map curInput_; + std::map curInput_; // Callbacks std::function onVKey_; From be2f81c3eb708d7df91fca7881c488d01b47f5d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 23 Aug 2023 18:42:20 +0200 Subject: [PATCH 2/2] Controls: Make the analog/digital mapping clash resolution more gentle. Now takes the time into account, so clashing digital input will only shrink analog inputs once it's a few seconds old. Also fixes a bug where if there are both inputs, it was hard to reach the limits because the digital input itself ended up getting shrunk. This might help #17860 --- Common/Input/InputState.h | 4 +++ Core/ControlMapper.cpp | 50 ++++++++++++++++++++++++------------- Core/ControlMapper.h | 6 +++-- UI/ControlMappingScreen.cpp | 2 +- UI/EmuScreen.cpp | 10 +++++--- 5 files changed, 47 insertions(+), 25 deletions(-) diff --git a/Common/Input/InputState.h b/Common/Input/InputState.h index 840a8ae1d47b..c01680a0ae64 100644 --- a/Common/Input/InputState.h +++ b/Common/Input/InputState.h @@ -36,6 +36,7 @@ enum InputDeviceID { DEVICE_ID_XR_CONTROLLER_LEFT = 40, DEVICE_ID_XR_CONTROLLER_RIGHT = 41, DEVICE_ID_TOUCH = 42, + DEVICE_ID_COUNT, }; inline InputDeviceID operator +(InputDeviceID deviceID, int addend) { @@ -121,6 +122,9 @@ class InputMapping { if (keyCode != other.keyCode) return false; return true; } + bool operator != (const InputMapping &other) const { + return !(*this == other); + } void FormatDebug(char *buffer, size_t bufSize) const; }; diff --git a/Core/ControlMapper.cpp b/Core/ControlMapper.cpp index 072de182e6f4..c3808150ecc5 100644 --- a/Core/ControlMapper.cpp +++ b/Core/ControlMapper.cpp @@ -161,7 +161,7 @@ void ControlMapper::ForceReleaseVKey(int vkey) { for (const auto &mapping : entry.mappings) { curInput_[mapping] = { 0.0f, now }; // Different logic for signed axes? - UpdatePSPState(mapping); + UpdatePSPState(mapping, now); } } } @@ -179,8 +179,11 @@ static int RotatePSPKeyCode(int x) { } // Used to decay analog values when clashing with digital ones. -static ControlMapper::InputSample ReduceMagnitude(ControlMapper::InputSample sample) { - sample.value *= 0.75f; +static ControlMapper::InputSample ReduceMagnitude(ControlMapper::InputSample sample, double now) { + float reduction = std::min(std::max(0.0f, (float)(now - sample.timestamp) - 2.0f), 1.0f); + if (reduction > 0.0f) { + sample.value *= (1.0f - reduction); + } if ((sample.value > 0.0f && sample.value < 0.05f) || (sample.value < 0.0f && sample.value > -0.05f)) { sample.value = 0.0f; } @@ -248,7 +251,8 @@ void ControlMapper::SwapMappingIfEnabled(uint32_t *vkey) { } // Can only be called from Key or Axis. -bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) { +// TODO: We should probably make a batched version of this. +bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping, double now) { // Instead of taking an input key and finding what it outputs, we loop through the OUTPUTS and // see if the input that corresponds to it has a value. That way we can easily implement all sorts // of crazy input combos if needed. @@ -363,7 +367,11 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) { if (!changedMapping.IsAxis()) { for (auto &multiMapping : inputMappings) { for (auto &mapping : multiMapping.mappings) { - curInput_[mapping] = ReduceMagnitude(curInput_[mapping]); + if (mapping != changedMapping && curInput_[mapping].value > 0.0f) { + // Note that this takes the time into account now - values will + // decay after a while, not immediately. + curInput_[mapping] = ReduceMagnitude(curInput_[mapping], now); + } } } } @@ -414,15 +422,19 @@ bool ControlMapper::Key(const KeyInput &key, bool *pauseTrigger) { // Claim that we handled this. Prevents volume key repeats from popping up the volume control on Android. return true; } - - std::lock_guard guard(mutex_); + double now = time_now_d(); + if (key.deviceId < DEVICE_ID_COUNT) { + deviceTimestamps_[(int)key.deviceId] = now; + } InputMapping mapping(key.deviceId, key.keyCode); + std::lock_guard guard(mutex_); + if (key.flags & KEY_DOWN) { - curInput_[mapping] = { 1.0f, time_now_d() }; + curInput_[mapping] = { 1.0f, now }; } else if (key.flags & KEY_UP) { - curInput_[mapping] = { 0.0f, time_now_d() }; + curInput_[mapping] = { 0.0f, now}; } // TODO: See if this can be simplified further somehow. @@ -435,7 +447,7 @@ bool ControlMapper::Key(const KeyInput &key, bool *pauseTrigger) { } } - return UpdatePSPState(mapping); + return UpdatePSPState(mapping, now); } void ControlMapper::ToggleSwapAxes() { @@ -464,35 +476,37 @@ void ControlMapper::ToggleSwapAxes() { } void ControlMapper::Axis(const AxisInput &axis) { - std::lock_guard guard(mutex_); double now = time_now_d(); + + std::lock_guard guard(mutex_); + if (axis.deviceId < DEVICE_ID_COUNT) { + deviceTimestamps_[(int)axis.deviceId] = now; + } if (axis.value >= 0.0f) { InputMapping mapping(axis.deviceId, axis.axisId, 1); InputMapping opposite(axis.deviceId, axis.axisId, -1); curInput_[mapping] = { axis.value, now }; curInput_[opposite] = { 0.0f, now }; - UpdatePSPState(mapping); - UpdatePSPState(opposite); + UpdatePSPState(mapping, now); + UpdatePSPState(opposite, now); } else if (axis.value < 0.0f) { InputMapping mapping(axis.deviceId, axis.axisId, -1); InputMapping opposite(axis.deviceId, axis.axisId, 1); curInput_[mapping] = { -axis.value, now }; curInput_[opposite] = { 0.0f, now }; - UpdatePSPState(mapping); - UpdatePSPState(opposite); + UpdatePSPState(mapping, now); + UpdatePSPState(opposite, now); } } -void ControlMapper::Update() { +void ControlMapper::Update(double now) { if (autoRotatingAnalogCW_) { - const double now = time_now_d(); // Clamp to a square float x = std::min(1.0f, std::max(-1.0f, 1.42f * (float)cos(now * -g_Config.fAnalogAutoRotSpeed))); float y = std::min(1.0f, std::max(-1.0f, 1.42f * (float)sin(now * -g_Config.fAnalogAutoRotSpeed))); setPSPAnalog_(0, x, y); } else if (autoRotatingAnalogCCW_) { - const double now = time_now_d(); float x = std::min(1.0f, std::max(-1.0f, 1.42f * (float)cos(now * g_Config.fAnalogAutoRotSpeed))); float y = std::min(1.0f, std::max(-1.0f, 1.42f * (float)sin(now * g_Config.fAnalogAutoRotSpeed))); diff --git a/Core/ControlMapper.h b/Core/ControlMapper.h index d796d4b94542..c90ae68daea3 100644 --- a/Core/ControlMapper.h +++ b/Core/ControlMapper.h @@ -11,7 +11,7 @@ // Main use is of course from EmuScreen.cpp, but also useful from control settings etc. class ControlMapper { public: - void Update(); + void Update(double now); // Inputs to the table-based mapping // These functions are free-threaded. @@ -48,7 +48,7 @@ class ControlMapper { }; private: - bool UpdatePSPState(const InputMapping &changedMapping); + bool UpdatePSPState(const InputMapping &changedMapping, double now); float MapAxisValue(float value, int vkId, const InputMapping &mapping, const InputMapping &changedMapping, bool *oppositeTouched); void SwapMappingIfEnabled(uint32_t *vkey); @@ -62,6 +62,8 @@ class ControlMapper { float virtKeys_[VIRTKEY_COUNT]{}; bool virtKeyOn_[VIRTKEY_COUNT]{}; // Track boolean output separaately since thresholds may differ. + double deviceTimestamps_[42]{}; + int lastNonDeadzoneDeviceID_[2]{}; float history_[2][2]{}; diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index 5841b6d56dec..6fa0676f9e21 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -512,7 +512,7 @@ AnalogSetupScreen::AnalogSetupScreen(const Path &gamePath) : UIDialogScreenWithG } void AnalogSetupScreen::update() { - mapper_.Update(); + mapper_.Update(time_now_d()); // We ignore the secondary stick for now and just use the two views // for raw and psp input. if (stickView_[0]) { diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 4ecbb02402e7..8dd904df1de0 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1117,7 +1117,9 @@ void EmuScreen::update() { if (invalid_) return; - controlMapper_.Update(); + double now = time_now_d(); + + controlMapper_.Update(now); if (pauseTrigger_) { pauseTrigger_ = false; @@ -1137,7 +1139,7 @@ void EmuScreen::update() { saveStatePreview_->SetFilename(fn); if (!fn.empty()) { saveStatePreview_->SetVisibility(UI::V_VISIBLE); - saveStatePreviewShownTime_ = time_now_d(); + saveStatePreviewShownTime_ = now; } else { saveStatePreview_->SetVisibility(UI::V_GONE); } @@ -1145,10 +1147,10 @@ void EmuScreen::update() { if (saveStatePreview_->GetVisibility() == UI::V_VISIBLE) { double endTime = saveStatePreviewShownTime_ + 2.0; - float alpha = clamp_value((endTime - time_now_d()) * 4.0, 0.0, 1.0); + float alpha = clamp_value((endTime - now) * 4.0, 0.0, 1.0); saveStatePreview_->SetColor(colorAlpha(0x00FFFFFF, alpha)); - if (time_now_d() - saveStatePreviewShownTime_ > 2) { + if (now - saveStatePreviewShownTime_ > 2) { saveStatePreview_->SetVisibility(UI::V_GONE); } }