mholgatem / GPIOnext

The next evolution of GPioneer! Create virtual gamepads with your GPIO pins!
MIT License
134 stars 37 forks source link

Decrease event processing latency #80

Closed theunrepentantgeek closed 1 year ago

theunrepentantgeek commented 1 year ago

Some time ago, I assembled a Retropie arcade machine, combining a Raspberry PI with this case:

image

The manual for the case includes instructions to clone the GPIONext repo.

We've found the arcade machine great fun to play, but some friends commented on a feeling of lag playing some of the games. After many false starts, investigation of that led to this PR.

With these changes in place, there is no perceptible lag - and I doubled my high score on an old favourite. 😁

The changes are:

My intuition is that the additional sleep() may have been added to try and prevent events from being lost. Given that python lists are not safe for concurrent modification, event loss was possible if pressEvents() and processQueue() both tried to modify queue at the same time. Adding locks around access to queue should prevent this from happening.

I've included below a detailed analysis of the worst-case lag and how that's addressed by these changes.

Analysis

This analysis is in two parts. The first documents current behavour of the master branch as of commit 2626e5c; the second shows how the changes in this PR reduce lag.

Assumptions

Combo delay using the default of 50ms (ref: gpionext.py L#17) Button debounce using the default of 20ms (ref: gpionext.py L#26) Event timer using the sum of combo delay and button debounce, 70ms (ref: gpionext.py L#88)

Button press events occurring every 130ms, this interval chosen to demonstrate worst case lag.

Current behaviour

Timestamp Event Actions device.py
t Button press B0 Event added to queue L#247
70ms event timer started L#254
t + 70ms Event timer triggers Event timer triggers processQueue() L#259
B0 dispatched after 70ms L#276
Sleep of 70ms starts L#279
t + 130ms Button press B1 Event added to queue L#247
Timer already started L#257
t + 140ms Sleep of 70ms ends
Timer reset L#283
t + 260ms Button press B2 Event added to queue L#247
70ms event timer started L#254
t + 330ms Event timer triggers Event timer triggers processQueue() L#259
B1 dispatched after 200ms L#276
B2 dispatched after 70ms L#276
Sleep of 70ms starts L#279
t + 390ms Sleep of 70ms ends
Timer reset L#283

The 200ms delay in dispatching Button press B1 (1/5 of a second) is long enough to impair playability of arcade games in which players routinely use split second timing.

New behaviour

Timestamp Event Actions device.py
t Button press B0 Event added to queue L#247
70ms event timer started L#254
t + 70ms Event timer triggers Event timer triggers processQueue() L#259
B0 dispatched after 70ms L#276
Timer reset L#283
t + 130ms Button press B1 Event added to queue L#247
70ms event timer started L#254
t + 200ms Event timer triggers Event timer triggers processQueue() L#259
B1 dispatched after 70ms L#276
Timer reset L#283
t + 260ms Button press B2 Event added to queue L#247
70ms event timer started L#254
t + 330ms Event timer triggers Event timer triggers processQueue() L#259
B2 dispatched after 70ms L#276
Timer reset L#283
mholgatem commented 1 year ago

This looks great. Thanks for the addition!