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

Controls: Make the analog/digital mapping clash resolution more gentle. #17960

Merged
merged 2 commits into from
Aug 24, 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
4 changes: 4 additions & 0 deletions Common/Input/InputState.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
};
Expand Down
77 changes: 47 additions & 30 deletions Core/ControlMapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,12 @@ void ControlMapper::UpdateAnalogOutput(int stick) {
void ControlMapper::ForceReleaseVKey(int vkey) {
std::vector<KeyMap::MultiInputMapping> 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);
UpdatePSPState(mapping, now);
}
}
}
Expand All @@ -178,12 +179,15 @@ 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, 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);
}
return value;
if ((sample.value > 0.0f && sample.value < 0.05f) || (sample.value < 0.0f && sample.value > -0.05f)) {
sample.value = 0.0f;
}
return sample;
}

float ControlMapper::MapAxisValue(float value, int vkId, const InputMapping &mapping, const InputMapping &changedMapping, bool *oppositeTouched) {
Expand All @@ -199,7 +203,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) {
Expand Down Expand Up @@ -247,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.
Expand Down Expand Up @@ -290,7 +295,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;
}
Expand All @@ -307,6 +312,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<MultiInputMapping> inputMappings;
Expand Down Expand Up @@ -334,9 +340,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;
Expand All @@ -361,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);
}
}
}
}
Expand Down Expand Up @@ -412,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<std::mutex> 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<std::mutex> guard(mutex_);

if (key.flags & KEY_DOWN) {
curInput_[mapping] = 1.0f;
curInput_[mapping] = { 1.0f, now };
} else if (key.flags & KEY_UP) {
curInput_[mapping] = 0.0f;
curInput_[mapping] = { 0.0f, now};
}

// TODO: See if this can be simplified further somehow.
Expand All @@ -433,7 +447,7 @@ bool ControlMapper::Key(const KeyInput &key, bool *pauseTrigger) {
}
}

return UpdatePSPState(mapping);
return UpdatePSPState(mapping, now);
}

void ControlMapper::ToggleSwapAxes() {
Expand Down Expand Up @@ -462,34 +476,37 @@ void ControlMapper::ToggleSwapAxes() {
}

void ControlMapper::Axis(const AxisInput &axis) {
double now = time_now_d();

std::lock_guard<std::mutex> 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;
curInput_[opposite] = 0.0f;
UpdatePSPState(mapping);
UpdatePSPState(opposite);
curInput_[mapping] = { axis.value, now };
curInput_[opposite] = { 0.0f, now };
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;
curInput_[opposite] = 0.0f;
UpdatePSPState(mapping);
UpdatePSPState(opposite);
curInput_[mapping] = { -axis.value, now };
curInput_[opposite] = { 0.0f, now };
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)));

Expand Down Expand Up @@ -583,7 +600,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;
Expand Down
13 changes: 10 additions & 3 deletions Core/ControlMapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -42,8 +42,13 @@ class ControlMapper {

void GetDebugString(char *buffer, size_t bufSize) const;

struct InputSample {
float value;
double timestamp;
};

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);

Expand All @@ -57,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]{};
Expand All @@ -71,7 +78,7 @@ class ControlMapper {
// Protects basically all the state.
std::mutex mutex_;

std::map<InputMapping, float> curInput_;
std::map<InputMapping, InputSample> curInput_;

// Callbacks
std::function<void(int, bool)> onVKey_;
Expand Down
2 changes: 1 addition & 1 deletion UI/ControlMappingScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]) {
Expand Down
10 changes: 6 additions & 4 deletions UI/EmuScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1117,7 +1117,9 @@ void EmuScreen::update() {
if (invalid_)
return;

controlMapper_.Update();
double now = time_now_d();

controlMapper_.Update(now);

if (pauseTrigger_) {
pauseTrigger_ = false;
Expand All @@ -1137,18 +1139,18 @@ 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);
}
}

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);
}
}
Expand Down