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

HidController: loop until no more messages are available on poll #2970

Merged
merged 18 commits into from
Oct 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
10 changes: 5 additions & 5 deletions src/controllers/controllermanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,18 @@
#include "controllers/bulk/bulkenumerator.h"
#endif

namespace {
// http://developer.qt.nokia.com/wiki/Threads_Events_QObjects

// Poll every 1ms (where possible) for good controller response
#ifdef __LINUX__
// Many Linux distros ship with the system tick set to 250Hz so 1ms timer
// reportedly causes CPU hosage. See Bug #990992 rryan 6/2012
const int kPollIntervalMillis = 5;
const mixxx::Duration ControllerManager::kPollInterval = mixxx::Duration::fromMillis(5);
#else
const int kPollIntervalMillis = 1;
const mixxx::Duration ControllerManager::kPollInterval = mixxx::Duration::fromMillis(1);
#endif

namespace {
/// Strip slashes and spaces from device name, so that it can be used as config
/// key or a filename.
QString sanitizeDeviceName(QString name) {
Expand Down Expand Up @@ -97,7 +97,7 @@ ControllerManager::ControllerManager(UserSettingsPointer pConfig)
QDir().mkpath(userPresets);
}

m_pollTimer.setInterval(kPollIntervalMillis);
m_pollTimer.setInterval(kPollInterval.toIntegerMillis());
connect(&m_pollTimer, SIGNAL(timeout()),
this, SLOT(pollDevices()));

Expand Down Expand Up @@ -359,7 +359,7 @@ void ControllerManager::pollDevices() {
}

mixxx::Duration duration = mixxx::Time::elapsed() - start;
if (duration > mixxx::Duration::fromMillis(kPollIntervalMillis)) {
if (duration > kPollInterval) {
m_skipPoll = true;
}
//qDebug() << "ControllerManager::pollDevices()" << duration << start;
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/controllermanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class ControllerManager : public QObject {
ControllerManager(UserSettingsPointer pConfig);
virtual ~ControllerManager();

static const mixxx::Duration kPollInterval;

QList<Controller*> getControllers() const;
QList<Controller*> getControllerList(bool outputDevices=true, bool inputDevices=true);
ControllerLearningEventFilter* getControllerLearningEventFilter() const;
Expand Down
49 changes: 40 additions & 9 deletions src/controllers/hid/hidcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@

HidController::HidController(const hid_device_info& deviceInfo, UserSettingsPointer pConfig)
: Controller(pConfig),
m_pHidDevice(NULL) {
m_pHidDevice(nullptr),
m_iPollingBufferIndex(0) {
// Copy required variables from deviceInfo, which will be freed after
// this class is initialized by caller.
hid_vendor_id = deviceInfo.vendor_id;
Expand Down Expand Up @@ -215,6 +216,10 @@ int HidController::open() {
return -1;
}

for (int i = 0; i < kNumBuffers; i++) {
memset(m_pPollData[i], 0, kBufferSize);
}

setOpen(true);
startEngine();

Expand Down Expand Up @@ -243,16 +248,42 @@ int HidController::close() {
bool HidController::poll() {
Trace hidRead("HidController poll");

int result = hid_read(m_pHidDevice, m_pPollData, sizeof(m_pPollData) / sizeof(m_pPollData[0]));
if (result == -1) {
return false;
} else if (result > 0) {
// This loop risks becoming a high priority endless loop in case processing
// the mapping JS code takes longer than the controller polling rate.
// This could stall other low priority tasks.
// There is no safety net for this because it has not been demonstrated to be
// a problem in practice.
while (true) {
// Cycle between buffers so the memcmp below does not require deep copying to another buffer.
unsigned char* pPreviousBuffer = m_pPollData[m_iPollingBufferIndex];
m_iPollingBufferIndex = (m_iPollingBufferIndex + 1) % kNumBuffers;
unsigned char* pCurrentBuffer = m_pPollData[m_iPollingBufferIndex];

int bytesRead = hid_read(m_pHidDevice, pCurrentBuffer, kBufferSize);
if (bytesRead < 0) {
// -1 is the only error value according to hidapi documentation.
DEBUG_ASSERT(bytesRead == -1);
return false;
} else if (bytesRead == 0) {
return true;
}

Trace process("HidController process packet");
QByteArray outData(reinterpret_cast<char*>(m_pPollData), result);
receive(outData, mixxx::Time::elapsed());
// Some controllers such as the Gemini GMX continuously send input packets even if it
// is identical to the previous packet. If this loop processed all those redundant
// packets, it would be a big performance problem to run JS code for every packet and
// would be unnecessary.
// This assumes that the redundant packets all use the same report ID. In practice we
// have not encountered any controllers that send redundant packets with different report
// IDs. If any such devices exist, this may be changed to use a separate buffer to store
// the last packet for each report ID.
Copy link
Member

@JoergAtGithub JoergAtGithub Nov 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Be-ing I found an issue with the following assumption, while working on #3317:
There seems to be a bug in the Windows implementation of hid_read. It always returns the number of bytes of the largest input report. While hid_get_input_report returns exact the number of bytes that the report should have.
I filed a bug report for hidapi ( libusb/hidapi#210 ) and hope for clarification.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean this always loops infinitely??

Copy link
Contributor Author

@Be-ing Be-ing Nov 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume hid_read is correctly returning 0 when all packets have been read so there is no infinite loop otherwise I presume you or @codecat would have noticed this earlier. Can you confirm this?

What happens when reading the smaller packet past its true size? Are the bytes after that random garbage?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No infinite loop, it fills the remaining bytes with garbage (looks like data from the bigger report). I expect that this comparisition will be triggered by this garbage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, that's not as bad as an infinite loop. I suppose you could temporarily hack around in your controller script until the hidapi bug is fixed upstream. If you want to work on the hidapi bug, we could merge a fix in our bundled version of hidapi if you open a pull request upstream.

As noted in the comment, this comparison checking if the current buffer is identical to the previous buffer will not evaluate to true if the device uses multiple report IDs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've not such a controller. I stumbled over this, because I used hid_get_input_report, which behaves different (correct) than the existing code with hid_read.

if (memcmp(pCurrentBuffer, pPreviousBuffer, kBufferSize) == 0) {
continue;
}
auto incomingData = QByteArray::fromRawData(
reinterpret_cast<char*>(pCurrentBuffer), bytesRead);
receive(incomingData, mixxx::Time::elapsed());
}

return true;
}

bool HidController::isPolling() const {
Expand Down
5 changes: 4 additions & 1 deletion src/controllers/hid/hidcontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ class HidController final : public Controller {
hid_device* m_pHidDevice;
HidControllerPreset m_preset;

unsigned char m_pPollData[255];
static constexpr int kNumBuffers = 2;
static constexpr int kBufferSize = 255;
unsigned char m_pPollData[kNumBuffers][kBufferSize];
int m_iPollingBufferIndex;
};

#endif