SDL: Improve event processing latency when gamepad/sensor is open (43f0a)

From 43f0ae1e540dfb9fb4c653f1e9fede114b609a48 Mon Sep 17 00:00:00 2001
From: Cameron Gutman <[EMAIL REDACTED]>
Date: Mon, 18 Mar 2024 20:16:04 -0500
Subject: [PATCH] Improve event processing latency when gamepad/sensor is open

By using the SDL_WaitEventTimeout_Device() path even when polling is required,
we can still achieve sub-millisecond latency for non-gamepad/sensor events when
a gamepad or sensor is in use by the application.
---
 src/events/SDL_events.c | 71 +++++++++++++++++++++--------------------
 1 file changed, 36 insertions(+), 35 deletions(-)

diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index 47f57214a1252..ec8ebfd3cbd59 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -44,8 +44,11 @@
 /* An arbitrary limit so we don't have unbounded growth */
 #define SDL_MAX_QUEUED_EVENTS 65535
 
-/* Determines how often we wake to call SDL_PumpEvents() in SDL_WaitEventTimeout_Device() */
-#define PERIODIC_POLL_INTERVAL_MS 3000
+/* Determines how often we pump events if joystick or sensor subsystems are active */
+#define ENUMERATION_POLL_INTERVAL_MS 3000
+
+/* Determines how often to pump events if joysticks or sensors are actively being read */
+#define EVENT_POLL_INTERVAL_MS 1
 
 typedef struct SDL_EventWatcher
 {
@@ -967,27 +970,41 @@ int SDL_PollEvent(SDL_Event *event)
     return SDL_WaitEventTimeout(event, 0);
 }
 
-static SDL_bool SDL_events_need_periodic_poll(void)
+static Sint16 SDL_events_get_polling_interval(void)
 {
-    SDL_bool need_periodic_poll = SDL_FALSE;
+    Sint16 poll_interval = SDL_MAX_SINT16;
 
 #ifndef SDL_JOYSTICK_DISABLED
-    need_periodic_poll =
-        SDL_WasInit(SDL_INIT_JOYSTICK) && SDL_update_joysticks;
+    if (SDL_WasInit(SDL_INIT_JOYSTICK) && SDL_update_joysticks) {
+        if (SDL_NumJoysticks() > 0) {
+            /* If we have joysticks open, we need to poll rapidly for events */
+            poll_interval = SDL_min(poll_interval, EVENT_POLL_INTERVAL_MS);
+        } else {
+            /* If not, just poll every few seconds to enumerate new joysticks */
+            poll_interval = SDL_min(poll_interval, ENUMERATION_POLL_INTERVAL_MS);
+        }
+    }
 #endif
 
 #ifndef SDL_SENSOR_DISABLED
-    need_periodic_poll = need_periodic_poll ||
-                         (SDL_WasInit(SDL_INIT_SENSOR) && SDL_update_sensors);
+    if (SDL_WasInit(SDL_INIT_SENSOR) && SDL_update_sensors) {
+        if (SDL_NumSensors() > 0) {
+            /* If we have sensors open, we need to poll rapidly for events */
+            poll_interval = SDL_min(poll_interval, EVENT_POLL_INTERVAL_MS);
+        } else {
+            /* If not, just poll every few seconds to enumerate new sensors */
+            poll_interval = SDL_min(poll_interval, ENUMERATION_POLL_INTERVAL_MS);
+        }
+    }
 #endif
 
-    return need_periodic_poll;
+    return poll_interval;
 }
 
 static int SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Event *event, Uint32 start, int timeout)
 {
     int loop_timeout = timeout;
-    SDL_bool need_periodic_poll = SDL_events_need_periodic_poll();
+    Sint16 poll_interval = SDL_events_get_polling_interval();
 
     for (;;) {
         /* Pump events on entry and each time we wake to ensure:
@@ -1029,17 +1046,20 @@ static int SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Eve
             }
             loop_timeout = (int)((Uint32)timeout - elapsed);
         }
-        if (need_periodic_poll) {
+
+        /* Adjust the timeout for any polling requirements we currently have. */
+        if (poll_interval != SDL_MAX_SINT16) {
             if (loop_timeout >= 0) {
-                loop_timeout = SDL_min(loop_timeout, PERIODIC_POLL_INTERVAL_MS);
+                loop_timeout = SDL_min(loop_timeout, poll_interval);
             } else {
-                loop_timeout = PERIODIC_POLL_INTERVAL_MS;
+                loop_timeout = poll_interval;
             }
         }
+
         status = _this->WaitEventTimeout(_this, loop_timeout);
         /* Set wakeup_window to NULL without holding the lock. */
         _this->wakeup_window = NULL;
-        if (status == 0 && need_periodic_poll && loop_timeout == PERIODIC_POLL_INTERVAL_MS) {
+        if (status == 0 && poll_interval != SDL_MAX_SINT16 && loop_timeout == poll_interval) {
             /* We may have woken up to poll. Try again */
             continue;
         } else if (status <= 0) {
@@ -1052,25 +1072,6 @@ static int SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Eve
     return 0;
 }
 
-static SDL_bool SDL_events_need_polling(void)
-{
-    SDL_bool need_polling = SDL_FALSE;
-
-#ifndef SDL_JOYSTICK_DISABLED
-    need_polling =
-        SDL_WasInit(SDL_INIT_JOYSTICK) &&
-        SDL_update_joysticks &&
-        (SDL_NumJoysticks() > 0);
-#endif
-
-#ifndef SDL_SENSOR_DISABLED
-    need_polling = need_polling ||
-                   (SDL_WasInit(SDL_INIT_SENSOR) && SDL_update_sensors && (SDL_NumSensors() > 0));
-#endif
-
-    return need_polling;
-}
-
 static SDL_Window *SDL_find_active_window(SDL_VideoDevice *_this)
 {
     SDL_Window *window;
@@ -1143,7 +1144,7 @@ int SDL_WaitEventTimeout(SDL_Event *event, int timeout)
         expiration = 0;
     }
 
-    if (_this && _this->WaitEventTimeout && _this->SendWakeupEvent && !SDL_events_need_polling()) {
+    if (_this && _this->WaitEventTimeout && _this->SendWakeupEvent) {
         /* Look if a shown window is available to send the wakeup event. */
         wakeup_window = SDL_find_active_window(_this);
         if (wakeup_window) {
@@ -1167,7 +1168,7 @@ int SDL_WaitEventTimeout(SDL_Event *event, int timeout)
                 /* Timeout expired and no events */
                 return 0;
             }
-            SDL_Delay(1);
+            SDL_Delay(EVENT_POLL_INTERVAL_MS);
             break;
         default:
             /* Has events */