From 85f77cb91cd43385729c8ee23a355c287b9059b1 Mon Sep 17 00:00:00 2001 From: Jun Wu Date: Sat, 2 Feb 2019 01:26:49 -0800 Subject: [PATCH 1/2] SendRumble less frequently to reduce input lag I was using two Joycons as a single controller. One (often right, sometimes both, sometimes left) of the Joycons seems to have random lags. This patch reliably removes the lag for me. Potentially fixes #32. More context: I first excluded the possibility that the Bluetooth connection is lagging, because when I click the "locate" button, both Joycons can rumble immediately without lag. Reading the existing issues seems to suggest something was wrong with threading. When I tried moving both Joycons' "Poll" to a single thread, by calling both ReceiveRaw() and other.ReceiveRaw() in a single Joycon Poll thread, and removing "lock { ...}"s), lag still exists. I then suspected the I/O loop is too busy so the code does not have time to consume events in time. By adding some debug prints, I was able to confirm that at the time the code receives an event (after hid_read), it was already too late. However, it is strange that a) hid_read almost always returns immediately, b) a single "poll" still takes a few milliseconds. Finally, I found "SendRumble" is the culprit causing the majority of the few milliseconds delay. Therefore the patch. The 5 seconds is not a scientific choice. It works for me. I don't have a USB controller to test it right now. --- BetterJoyForCemu/Joycon.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/BetterJoyForCemu/Joycon.cs b/BetterJoyForCemu/Joycon.cs index c1a0088..4ed51b6 100644 --- a/BetterJoyForCemu/Joycon.cs +++ b/BetterJoyForCemu/Joycon.cs @@ -452,8 +452,16 @@ namespace BetterJoyForCemu { private Thread PollThreadObj; // pro times out over time randomly if it was USB and then bluetooth?? private void Poll() { int attempts = 0; + Stopwatch watch = new Stopwatch(); + watch.Start(); while (!stop_polling & state > state_.NO_JOYCONS) { - SendRumble(rumble_obj.GetData()); // Needed for EVERYTHING to not time out. Never remove pls + if (watch.ElapsedMilliseconds >= 5000) { + // Send a no-op operation as heartbeat to keep connection alive. + // Do not send this too frequently, otherwise I/O would be too heavy and cause lag. + // Needed for both BLUETOOTH and USB to not time out. Never remove pls + SendRumble(rumble_obj.GetData()); + watch.Restart(); + } int a = ReceiveRaw(); if (a > 0) { From 5dd969da05ddee98807a2d2a024a9598eefb95c4 Mon Sep 17 00:00:00 2001 From: Jun Wu Date: Sat, 2 Feb 2019 01:30:41 -0800 Subject: [PATCH 2/2] Read Joycon inputs in a non-blocking way This makes sure ReceiveRaw() won't block forever, so Poll() has chance to SendRumble(). Practically, it seems the Joycons frequently send inputs so it's not necessary, at least for the bluetooth version I got. --- BetterJoyForCemu/Joycon.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/BetterJoyForCemu/Joycon.cs b/BetterJoyForCemu/Joycon.cs index 4ed51b6..f5412ea 100644 --- a/BetterJoyForCemu/Joycon.cs +++ b/BetterJoyForCemu/Joycon.cs @@ -411,9 +411,9 @@ namespace BetterJoyForCemu { private byte ts_en; private int ReceiveRaw() { if (handle == IntPtr.Zero) return -2; - HIDapi.hid_set_nonblocking(handle, 0); + HIDapi.hid_set_nonblocking(handle, 1); byte[] raw_buf = new byte[report_len]; - int ret = HIDapi.hid_read(handle, raw_buf, new UIntPtr(report_len)); + int ret = HIDapi.hid_read_timeout(handle, raw_buf, new UIntPtr(report_len), 5000); if (ret > 0) { // Process packets as soon as they come for (int n = 0; n < 3; n++) { @@ -473,11 +473,15 @@ namespace BetterJoyForCemu { DebugPrint("Connection lost. Is the Joy-Con connected?", DebugType.ALL); break; - } else { + } else if (a < 0) { + // An error on read. //form.AppendTextBox("Pause 5ms"); Thread.Sleep((Int32)5); + ++attempts; + } else if (a == 0) { + // The non-blocking read timed out. No need to sleep. + // No need to increase attempts because it's not an error. } - ++attempts; } }