SDL: Added the events SDL_EVENT_JOYSTICK_UPDATE_COMPLETE and SDL_EVENT_GAMEPAD_UPDATE_COMPLETE

From 4c9fb3e16902607d978a2c2f9ade777ad232b628 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 21 Jun 2023 13:59:53 -0700
Subject: [PATCH] Added the events SDL_EVENT_JOYSTICK_UPDATE_COMPLETE and
 SDL_EVENT_GAMEPAD_UPDATE_COMPLETE

This allows the application to tell when a joystick polling cycle is complete and can process state changes as a single atomic update. It is disabled by default, at least for now.
---
 include/SDL3/SDL_events.h      |  6 ++++--
 src/events/SDL_events.c        | 25 +++++++++++++++++++++++++
 src/joystick/SDL_gamepad.c     | 16 ++++++++++++++++
 src/joystick/SDL_joystick.c    | 20 ++++++++++++++++++++
 src/joystick/SDL_sysjoystick.h |  2 ++
 5 files changed, 67 insertions(+), 2 deletions(-)

diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index de0815913a41..cf80b5d5b784 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -151,6 +151,7 @@ typedef enum
     SDL_EVENT_JOYSTICK_ADDED,         /**< A new joystick has been inserted into the system */
     SDL_EVENT_JOYSTICK_REMOVED,       /**< An opened joystick has been removed */
     SDL_EVENT_JOYSTICK_BATTERY_UPDATED,      /**< Joystick battery level change */
+    SDL_EVENT_JOYSTICK_UPDATE_COMPLETE,      /**< Joystick update is complete (disabled by default) */
 
     /* Gamepad events */
     SDL_EVENT_GAMEPAD_AXIS_MOTION  = 0x650, /**< Gamepad axis motion */
@@ -163,6 +164,7 @@ typedef enum
     SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION,      /**< Gamepad touchpad finger was moved */
     SDL_EVENT_GAMEPAD_TOUCHPAD_UP,          /**< Gamepad touchpad finger was lifted */
     SDL_EVENT_GAMEPAD_SENSOR_UPDATE,        /**< Gamepad sensor was updated */
+    SDL_EVENT_GAMEPAD_UPDATE_COMPLETE,      /**< Gamepad update is complete (disabled by default) */
 
     /* Touch events */
     SDL_EVENT_FINGER_DOWN      = 0x700,
@@ -398,7 +400,7 @@ typedef struct SDL_JoyButtonEvent
  */
 typedef struct SDL_JoyDeviceEvent
 {
-    Uint32 type;        /**< ::SDL_EVENT_JOYSTICK_ADDED or ::SDL_EVENT_JOYSTICK_REMOVED */
+    Uint32 type;        /**< ::SDL_EVENT_JOYSTICK_ADDED or ::SDL_EVENT_JOYSTICK_REMOVED or ::SDL_EVENT_JOYSTICK_UPDATE_COMPLETE */
     Uint64 timestamp;   /**< In nanoseconds, populated using SDL_GetTicksNS() */
     SDL_JoystickID which;       /**< The joystick instance id */
 } SDL_JoyDeviceEvent;
@@ -451,7 +453,7 @@ typedef struct SDL_GamepadButtonEvent
  */
 typedef struct SDL_GamepadDeviceEvent
 {
-    Uint32 type;        /**< ::SDL_EVENT_GAMEPAD_ADDED, ::SDL_EVENT_GAMEPAD_REMOVED, or ::SDL_EVENT_GAMEPAD_REMAPPED */
+    Uint32 type;        /**< ::SDL_EVENT_GAMEPAD_ADDED, ::SDL_EVENT_GAMEPAD_REMOVED, or ::SDL_EVENT_GAMEPAD_REMAPPED or ::SDL_EVENT_GAMEPAD_UPDATE_COMPLETE */
     Uint64 timestamp;   /**< In nanoseconds, populated using SDL_GetTicksNS() */
     SDL_JoystickID which;       /**< The joystick instance id */
 } SDL_GamepadDeviceEvent;
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index 7e1130b0cf1e..865a9b063ae0 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -579,6 +579,8 @@ int SDL_StartEventLoop(void)
     SDL_SetEventEnabled(SDL_EVENT_DROP_FILE, SDL_FALSE);
     SDL_SetEventEnabled(SDL_EVENT_DROP_TEXT, SDL_FALSE);
 #endif
+    SDL_SetEventEnabled(SDL_EVENT_JOYSTICK_UPDATE_COMPLETE, SDL_FALSE);
+    SDL_SetEventEnabled(SDL_EVENT_GAMEPAD_UPDATE_COMPLETE, SDL_FALSE);
 
     SDL_EventQ.active = SDL_TRUE;
     SDL_UnlockMutex(SDL_EventQ.lock);
@@ -1264,6 +1266,29 @@ void SDL_SetEventEnabled(Uint32 type, SDL_bool enabled)
     if (enabled != current_state) {
         if (enabled) {
             SDL_disabled_events[hi]->bits[lo / 32] &= ~(1 << (lo & 31));
+
+            /* Gamepad events depend on joystick events */
+            switch (type) {
+            case SDL_EVENT_GAMEPAD_ADDED:
+                SDL_SetEventEnabled(SDL_EVENT_JOYSTICK_ADDED, SDL_TRUE);
+                break;
+            case SDL_EVENT_GAMEPAD_REMOVED:
+                SDL_SetEventEnabled(SDL_EVENT_JOYSTICK_REMOVED, SDL_TRUE);
+                break;
+            case SDL_EVENT_GAMEPAD_AXIS_MOTION:
+            case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
+            case SDL_EVENT_GAMEPAD_BUTTON_UP:
+                SDL_SetEventEnabled(SDL_EVENT_JOYSTICK_AXIS_MOTION, SDL_TRUE);
+                SDL_SetEventEnabled(SDL_EVENT_JOYSTICK_HAT_MOTION, SDL_TRUE);
+                SDL_SetEventEnabled(SDL_EVENT_JOYSTICK_BUTTON_DOWN, SDL_TRUE);
+                SDL_SetEventEnabled(SDL_EVENT_JOYSTICK_BUTTON_UP, SDL_TRUE);
+                break;
+            case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE:
+                SDL_SetEventEnabled(SDL_EVENT_JOYSTICK_UPDATE_COMPLETE, SDL_TRUE);
+                break;
+            default:
+                break;
+            }
         } else {
             /* Disable this event type and discard pending events */
             if (!SDL_disabled_events[hi]) {
diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index 2df24a6336af..5e9dee5ea4a0 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -392,6 +392,22 @@ static int SDLCALL SDL_GamepadEventWatcher(void *userdata, SDL_Event *event)
             SDL_PushEvent(&deviceevent);
         }
     } break;
+    case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE:
+    {
+        SDL_AssertJoysticksLocked();
+
+        for (gamepad = SDL_gamepads; gamepad; gamepad = gamepad->next) {
+            if (gamepad->joystick->instance_id == event->jdevice.which) {
+                SDL_Event deviceevent;
+
+                deviceevent.type = SDL_EVENT_GAMEPAD_UPDATE_COMPLETE;
+                deviceevent.common.timestamp = event->jdevice.timestamp;
+                deviceevent.gdevice.which = event->jdevice.which;
+                SDL_PushEvent(&deviceevent);
+                break;
+            }
+        }
+    } break;
     default:
         break;
     }
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index a4f8939ee433..463569ae1292 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -1753,6 +1753,7 @@ int SDL_SendJoystickAxis(Uint64 timestamp, SDL_Joystick *joystick, Uint8 axis, S
 
     /* Update internal joystick state */
     info->value = value;
+    joystick->update_complete = timestamp;
 
     /* Post the event, if desired */
     posted = 0;
@@ -1795,6 +1796,7 @@ int SDL_SendJoystickHat(Uint64 timestamp, SDL_Joystick *joystick, Uint8 hat, Uin
 
     /* Update internal joystick state */
     joystick->hats[hat] = value;
+    joystick->update_complete = timestamp;
 
     /* Post the event, if desired */
     posted = 0;
@@ -1853,6 +1855,7 @@ int SDL_SendJoystickButton(Uint64 timestamp, SDL_Joystick *joystick, Uint8 butto
 
     /* Update internal joystick state */
     joystick->buttons[button] = state;
+    joystick->update_complete = timestamp;
 
     /* Post the event, if desired */
     posted = 0;
@@ -1913,6 +1916,21 @@ void SDL_UpdateJoysticks(void)
         }
     }
 
+    if (SDL_EventEnabled(SDL_EVENT_JOYSTICK_UPDATE_COMPLETE)) {
+        for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
+            if (joystick->update_complete) {
+                SDL_Event event;
+
+                event.type = SDL_EVENT_JOYSTICK_UPDATE_COMPLETE;
+                event.common.timestamp = joystick->update_complete;
+                event.gdevice.which = joystick->instance_id;
+                SDL_PushEvent(&event);
+
+                joystick->update_complete = 0;
+            }
+        }
+    }
+
     /* this needs to happen AFTER walking the joystick list above, so that any
        dangling hardware data from removed devices can be free'd
      */
@@ -3178,6 +3196,7 @@ int SDL_SendJoystickTouchpad(Uint64 timestamp, SDL_Joystick *joystick, int touch
     finger_info->x = x;
     finger_info->y = y;
     finger_info->pressure = pressure;
+    joystick->update_complete = timestamp;
 
     /* Post the event, if desired */
     posted = 0;
@@ -3219,6 +3238,7 @@ int SDL_SendJoystickSensor(Uint64 timestamp, SDL_Joystick *joystick, SDL_SensorT
 
                 /* Update internal sensor state */
                 SDL_memcpy(sensor->data, data, num_values * sizeof(*data));
+                joystick->update_complete = timestamp;
 
                 /* Post the event, if desired */
 #ifndef SDL_EVENTS_DISABLED
diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h
index 99784dcf218c..f0f547e10d97 100644
--- a/src/joystick/SDL_sysjoystick.h
+++ b/src/joystick/SDL_sysjoystick.h
@@ -119,6 +119,8 @@ struct SDL_Joystick
     SDL_Sensor *gyro _guarded;
     float sensor_transform[3][3] _guarded;
 
+    Uint64 update_complete _guarded;
+
     struct SDL_JoystickDriver *driver _guarded;
 
     struct joystick_hwdata *hwdata _guarded; /* Driver dependent information */