Skip to content
Permalink
Browse files

ControllerInterface: Input detection improvements.

  • Loading branch information
jordan-woyak committed Feb 27, 2019
1 parent 13b2b93 commit 48b69ca01841ba724049895a83d8d88ec95d66de
@@ -2,18 +2,24 @@
// Licensed under GPLv2+ // Licensed under GPLv2+
// Refer to the license.txt file included. // Refer to the license.txt file included.


#include "InputCommon/ControlReference/ControlReference.h"

#include <vector>

#include "Common/Thread.h" #include "Common/Thread.h"
// For InputGateOn() // For InputGateOn()
// This is a bad layering violation, but it's the cleanest // This is a bad layering violation, but it's the cleanest
// place I could find to put it. // place I could find to put it.
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Host.h" #include "Core/Host.h"


#include "InputCommon/ControlReference/ControlReference.h"

using namespace ciface::ExpressionParser; using namespace ciface::ExpressionParser;


namespace
{
// Compared to an input's current state (ideally 1.0) minus abs(initial_state) (ideally 0.0).
constexpr ControlState INPUT_DETECT_THRESHOLD = 0.55; constexpr ControlState INPUT_DETECT_THRESHOLD = 0.55;
} // namespace


bool ControlReference::InputGateOn() bool ControlReference::InputGateOn()
{ {
@@ -110,56 +116,53 @@ ControlState OutputReference::State(const ControlState state)
return 0.0; return 0.0;
} }


// // Wait for input on a particular device.
// InputReference :: Detect // Inputs are considered if they are first seen in a neutral state.
// // This is useful for crazy flightsticks that have certain buttons that are always held down
// Wait for input on all binded devices // and also properly handles detection when using "FullAnalogSurface" inputs.
// supports not detecting inputs that were held down at the time of Detect start, // Upon input, return a pointer to the detected Control, else return nullptr.
// which is useful for those crazy flightsticks that have certain buttons that are always held down
// or some crazy axes or something
// upon input, return pointer to detected Control
// else return nullptr
//
ciface::Core::Device::Control* InputReference::Detect(const unsigned int ms, ciface::Core::Device::Control* InputReference::Detect(const unsigned int ms,
ciface::Core::Device* const device) ciface::Core::Device* const device)
{ {
unsigned int time = 0; struct InputState
std::vector<bool> states(device->Inputs().size()); {
ciface::Core::Device::Input& input;
ControlState initial_state;
};


if (device->Inputs().empty()) std::vector<InputState> input_states;
return nullptr; for (auto* input : device->Inputs())
{
// Don't detect things like absolute cursor position.
if (!input->IsDetectable())
continue;

// Undesirable axes will have negative values here when trying to map a "FullAnalogSurface".
input_states.push_back({*input, input->GetState()});
}


// get starting state of all inputs, if (input_states.empty())
// so we can ignore those that were activated at time of Detect start return nullptr;
std::vector<ciface::Core::Device::Input*>::const_iterator i = device->Inputs().begin(),
e = device->Inputs().end();
for (std::vector<bool>::iterator state = states.begin(); i != e; ++i)
*state++ = ((*i)->GetState() > (1 - INPUT_DETECT_THRESHOLD));


unsigned int time = 0;
while (time < ms) while (time < ms)
{ {
Common::SleepCurrentThread(10);
time += 10;

device->UpdateInput(); device->UpdateInput();
i = device->Inputs().begin(); for (auto& input_state : input_states)
for (std::vector<bool>::iterator state = states.begin(); i != e; ++i, ++state)
{ {
// detected an input // We want an input that was initially 0.0 and currently 1.0.
if ((*i)->IsDetectable() && (*i)->GetState() > INPUT_DETECT_THRESHOLD) const auto detection_score =
{ (input_state.input.GetState() - std::abs(input_state.initial_state));
// input was released at some point during Detect call
// return the detected input if (detection_score > INPUT_DETECT_THRESHOLD)
if (false == *state) return &input_state.input;
return *i;
}
else if ((*i)->GetState() < (1 - INPUT_DETECT_THRESHOLD))
{
*state = false;
}
} }
Common::SleepCurrentThread(10);
time += 10;
} }


// no input was detected // No input was detected. :'(
return nullptr; return nullptr;
} }


@@ -217,7 +217,19 @@ class ControlExpression : public Expression
std::shared_ptr<Device> m_device; std::shared_ptr<Device> m_device;


explicit ControlExpression(ControlQualifier qualifier_) : qualifier(qualifier_) {} explicit ControlExpression(ControlQualifier qualifier_) : qualifier(qualifier_) {}
ControlState GetValue() const override { return control ? control->ToInput()->GetState() : 0.0; } ControlState GetValue() const override
{
if (!control)
return 0.0;

// Note: Inputs may return negative values in situations where opposing directions are
// activated. We clamp off the negative values here.

// FYI: Clamping values greater than 1.0 is purposely not done to support unbounded values in
// the future. (e.g. raw accelerometer/gyro data)

return std::max(0.0, control->ToInput()->GetState());
}
void SetValue(ControlState value) override void SetValue(ControlState value) override
{ {
if (control) if (control)
@@ -259,7 +259,7 @@ std::string Joystick::Hat::GetName() const


ControlState Joystick::Axis::GetState() const ControlState Joystick::Axis::GetState() const
{ {
return std::max(0.0, ControlState(m_axis - m_base) / m_range); return ControlState(m_axis - m_base) / m_range;
} }


ControlState Joystick::Button::GetState() const ControlState Joystick::Button::GetState() const
@@ -4,6 +4,7 @@


#include "InputCommon/ControllerInterface/Device.h" #include "InputCommon/ControllerInterface/Device.h"


#include <cmath>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <string> #include <string>
@@ -68,6 +69,11 @@ Device::Output* Device::FindOutput(const std::string& name) const
return nullptr; return nullptr;
} }


ControlState Device::FullAnalogSurface::GetState() const
{
return (1 + std::max(0.0, m_high.GetState()) - std::max(0.0, m_low.GetState())) / 2;
}

// //
// DeviceQualifier :: ToString // DeviceQualifier :: ToString
// //
@@ -214,5 +220,5 @@ bool DeviceContainer::HasConnectedDevice(const DeviceQualifier& qualifier) const
const auto device = FindDevice(qualifier); const auto device = FindDevice(qualifier);
return device != nullptr && device->IsValid(); return device != nullptr && device->IsValid();
} }
} } // namespace Core
} } // namespace ciface
@@ -55,9 +55,22 @@ class Device
class Input : public Control class Input : public Control
{ {
public: public:
// things like absolute axes/ absolute mouse position will override this // Things like absolute axes/ absolute mouse position should override this to prevent
// undesirable behavior in our mapping logic.
virtual bool IsDetectable() { return true; } virtual bool IsDetectable() { return true; }

// Implementations should return a value from 0.0 to 1.0 across their normal range.
// One input should be provided for each "direction". (e.g. 2 for each axis)
// If possible, negative values may be returned in situations where an opposing input is
// activated. (e.g. When an underlying axis, X, is currently negative, "Axis X-", will return a
// positive value and "Axis X+" may return a negative value.)
// Doing so is solely to allow our input detection logic to better detect false positives.
// This is necessary when making use of "FullAnalogSurface" as multiple inputs will be seen
// increasing from 0.0 to 1.0 as a user tries to map just one. The negative values provide a
// view of the underlying axis. (Negative values are clamped off before they reach
// expression-parser or controller-emu)
virtual ControlState GetState() const = 0; virtual ControlState GetState() const = 0;

Input* ToInput() override { return this; } Input* ToInput() override { return this; }
}; };


@@ -96,11 +109,7 @@ class Device
{ {
public: public:
FullAnalogSurface(Input* low, Input* high) : m_low(*low), m_high(*high) {} FullAnalogSurface(Input* low, Input* high) : m_low(*low), m_high(*high) {}
ControlState GetState() const override ControlState GetState() const override;
{
return (1 + m_high.GetState() - m_low.GetState()) / 2;
}

std::string GetName() const override { return m_low.GetName() + *m_high.GetName().rbegin(); } std::string GetName() const override { return m_low.GetName() + *m_high.GetName().rbegin(); }


private: private:
@@ -228,13 +228,13 @@ ControlState Device::Trigger::GetState() const


ControlState Device::Axis::GetState() const ControlState Device::Axis::GetState() const
{ {
return std::max(0.0, ControlState(m_axis) / m_range); return ControlState(m_axis) / m_range;
} }


void Device::Motor::SetState(ControlState state) void Device::Motor::SetState(ControlState state)
{ {
m_motor = (WORD)(state * m_range); m_motor = (WORD)(state * m_range);
m_parent->UpdateMotors(); m_parent->UpdateMotors();
} }
} } // namespace XInput
} } // namespace ciface
@@ -343,7 +343,7 @@ ControlState evdevDevice::Axis::GetState() const
int value = 0; int value = 0;
libevdev_fetch_event_value(m_dev, EV_ABS, m_code, &value); libevdev_fetch_event_value(m_dev, EV_ABS, m_code, &value);


return std::max(0.0, ControlState(value - m_base) / m_range); return ControlState(value - m_base) / m_range;
} }


evdevDevice::Effect::Effect(int fd) : m_fd(fd) evdevDevice::Effect::Effect(int fd) : m_fd(fd)

0 comments on commit 48b69ca

Please sign in to comment.
You can’t perform that action at this time.