Make sure we don't send Xbox controllers rumble so quickly that it overwhelms the firmware

Fixes https://github.com/libsdl-org/SDL/issues/6435
This commit is contained in:
Sam Lantinga 2022-11-06 01:15:20 -07:00
parent 38af459dd9
commit 6875e62af3
3 changed files with 77 additions and 6 deletions

View File

@ -36,6 +36,8 @@ typedef struct SDL_HIDAPI_RumbleRequest
SDL_HIDAPI_Device *device; SDL_HIDAPI_Device *device;
Uint8 data[2*USB_PACKET_LENGTH]; /* need enough space for the biggest report: dualshock4 is 78 bytes */ Uint8 data[2*USB_PACKET_LENGTH]; /* need enough space for the biggest report: dualshock4 is 78 bytes */
int size; int size;
SDL_HIDAPI_RumbleSentCallback callback;
void *userdata;
struct SDL_HIDAPI_RumbleRequest *prev; struct SDL_HIDAPI_RumbleRequest *prev;
} SDL_HIDAPI_RumbleRequest; } SDL_HIDAPI_RumbleRequest;
@ -83,6 +85,9 @@ static int SDLCALL SDL_HIDAPI_RumbleThread(void *data)
SDL_hid_write(request->device->dev, request->data, request->size); SDL_hid_write(request->device->dev, request->data, request->size);
} }
SDL_UnlockMutex(request->device->dev_lock); SDL_UnlockMutex(request->device->dev_lock);
if (request->callback) {
request->callback(request->userdata);
}
(void)SDL_AtomicDecRef(&request->device->rumble_pending); (void)SDL_AtomicDecRef(&request->device->rumble_pending);
SDL_free(request); SDL_free(request);
@ -116,6 +121,9 @@ SDL_HIDAPI_StopRumbleThread(SDL_HIDAPI_RumbleContext *ctx)
} }
ctx->requests_tail = request->prev; ctx->requests_tail = request->prev;
if (request->callback) {
request->callback(request->userdata);
}
(void)SDL_AtomicDecRef(&request->device->rumble_pending); (void)SDL_AtomicDecRef(&request->device->rumble_pending);
SDL_free(request); SDL_free(request);
} }
@ -192,6 +200,11 @@ SDL_bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **da
} }
int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size) int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size)
{
return SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device, data, size, NULL, NULL);
}
int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size, SDL_HIDAPI_RumbleSentCallback callback, void *userdata)
{ {
SDL_HIDAPI_RumbleContext *ctx = &rumble_context; SDL_HIDAPI_RumbleContext *ctx = &rumble_context;
SDL_HIDAPI_RumbleRequest *request; SDL_HIDAPI_RumbleRequest *request;
@ -209,6 +222,8 @@ int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data,
request->device = device; request->device = device;
SDL_memcpy(request->data, data, size); SDL_memcpy(request->data, data, size);
request->size = size; request->size = size;
request->callback = callback;
request->userdata = userdata;
SDL_AtomicIncRef(&device->rumble_pending); SDL_AtomicIncRef(&device->rumble_pending);

View File

@ -28,6 +28,8 @@
int SDL_HIDAPI_LockRumble(void); int SDL_HIDAPI_LockRumble(void);
SDL_bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size); SDL_bool SDL_HIDAPI_GetPendingRumbleLocked(SDL_HIDAPI_Device *device, Uint8 **data, int **size, int *maximum_size);
int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size); int SDL_HIDAPI_SendRumbleAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size);
typedef void (*SDL_HIDAPI_RumbleSentCallback)(void *userdata);
int SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(SDL_HIDAPI_Device *device, const Uint8 *data, int size, SDL_HIDAPI_RumbleSentCallback callback, void *userdata);
void SDL_HIDAPI_UnlockRumble(void); void SDL_HIDAPI_UnlockRumble(void);
/* Simple API, will replace any pending rumble with the new data */ /* Simple API, will replace any pending rumble with the new data */

View File

@ -100,12 +100,18 @@ static const SDL_DriverXboxOne_InitPacket xboxone_init_packets[] = {
}; };
typedef enum { typedef enum {
XBOX_ONE_INIT_STATE_START_NEGOTIATING = 0, XBOX_ONE_INIT_STATE_START_NEGOTIATING,
XBOX_ONE_INIT_STATE_NEGOTIATING = 1, XBOX_ONE_INIT_STATE_NEGOTIATING,
XBOX_ONE_INIT_STATE_PREPARE_INPUT = 2, XBOX_ONE_INIT_STATE_PREPARE_INPUT,
XBOX_ONE_INIT_STATE_COMPLETE = 3 XBOX_ONE_INIT_STATE_COMPLETE,
} SDL_XboxOneInitState; } SDL_XboxOneInitState;
typedef enum {
XBOX_ONE_RUMBLE_STATE_IDLE,
XBOX_ONE_RUMBLE_STATE_QUEUED,
XBOX_ONE_RUMBLE_STATE_BUSY
} SDL_XboxOneRumbleState;
typedef struct { typedef struct {
SDL_HIDAPI_Device *device; SDL_HIDAPI_Device *device;
Uint16 vendor_id; Uint16 vendor_id;
@ -125,6 +131,9 @@ typedef struct {
Uint8 high_frequency_rumble; Uint8 high_frequency_rumble;
Uint8 left_trigger_rumble; Uint8 left_trigger_rumble;
Uint8 right_trigger_rumble; Uint8 right_trigger_rumble;
SDL_XboxOneRumbleState rumble_state;
Uint32 rumble_time;
SDL_bool rumble_pending;
Uint8 last_state[USB_PACKET_LENGTH]; Uint8 last_state[USB_PACKET_LENGTH];
} SDL_DriverXboxOne_Context; } SDL_DriverXboxOne_Context;
@ -411,6 +420,9 @@ HIDAPI_DriverXboxOne_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joyst
ctx->high_frequency_rumble = 0; ctx->high_frequency_rumble = 0;
ctx->left_trigger_rumble = 0; ctx->left_trigger_rumble = 0;
ctx->right_trigger_rumble = 0; ctx->right_trigger_rumble = 0;
ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_IDLE;
ctx->rumble_time = 0;
ctx->rumble_pending = SDL_FALSE;
SDL_zeroa(ctx->last_state); SDL_zeroa(ctx->last_state);
/* Initialize the joystick capabilities */ /* Initialize the joystick capabilities */
@ -432,11 +444,47 @@ HIDAPI_DriverXboxOne_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joyst
return SDL_TRUE; return SDL_TRUE;
} }
static void
HIDAPI_DriverXboxOne_RumbleSent(void *userdata)
{
SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)userdata;
ctx->rumble_time = SDL_GetTicks();
}
static int static int
HIDAPI_DriverXboxOne_UpdateRumble(SDL_HIDAPI_Device *device) HIDAPI_DriverXboxOne_UpdateRumble(SDL_HIDAPI_Device *device)
{ {
SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context; SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;
if (ctx->rumble_state == XBOX_ONE_RUMBLE_STATE_QUEUED) {
if (ctx->rumble_time) {
ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_BUSY;
}
}
if (ctx->rumble_state == XBOX_ONE_RUMBLE_STATE_BUSY) {
const Uint32 RUMBLE_BUSY_TIME_MS = ctx->bluetooth ? 50 : 10;
if (SDL_TICKS_PASSED(SDL_GetTicks(), ctx->rumble_time + RUMBLE_BUSY_TIME_MS)) {
ctx->rumble_time = 0;
ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_IDLE;
}
}
if (!ctx->rumble_pending) {
return 0;
}
if (ctx->rumble_state != XBOX_ONE_RUMBLE_STATE_IDLE) {
return 0;
}
/* We're no longer pending, even if we fail to send the rumble below */
ctx->rumble_pending = SDL_FALSE;
if (SDL_HIDAPI_LockRumble() < 0) {
return -1;
}
if (ctx->bluetooth) { if (ctx->bluetooth) {
Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB }; Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB };
@ -445,7 +493,7 @@ HIDAPI_DriverXboxOne_UpdateRumble(SDL_HIDAPI_Device *device)
rumble_packet[4] = ctx->low_frequency_rumble; rumble_packet[4] = ctx->low_frequency_rumble;
rumble_packet[5] = ctx->high_frequency_rumble; rumble_packet[5] = ctx->high_frequency_rumble;
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) {
return SDL_SetError("Couldn't send rumble packet"); return SDL_SetError("Couldn't send rumble packet");
} }
} else { } else {
@ -456,10 +504,13 @@ HIDAPI_DriverXboxOne_UpdateRumble(SDL_HIDAPI_Device *device)
rumble_packet[8] = ctx->low_frequency_rumble; rumble_packet[8] = ctx->low_frequency_rumble;
rumble_packet[9] = ctx->high_frequency_rumble; rumble_packet[9] = ctx->high_frequency_rumble;
if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { if (SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device, rumble_packet, sizeof(rumble_packet), HIDAPI_DriverXboxOne_RumbleSent, ctx) != sizeof(rumble_packet)) {
return SDL_SetError("Couldn't send rumble packet"); return SDL_SetError("Couldn't send rumble packet");
} }
} }
ctx->rumble_state = XBOX_ONE_RUMBLE_STATE_QUEUED;
return 0; return 0;
} }
@ -471,6 +522,7 @@ HIDAPI_DriverXboxOne_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joy
/* Magnitude is 1..100 so scale the 16-bit input here */ /* Magnitude is 1..100 so scale the 16-bit input here */
ctx->low_frequency_rumble = low_frequency_rumble / 655; ctx->low_frequency_rumble = low_frequency_rumble / 655;
ctx->high_frequency_rumble = high_frequency_rumble / 655; ctx->high_frequency_rumble = high_frequency_rumble / 655;
ctx->rumble_pending = SDL_TRUE;
return HIDAPI_DriverXboxOne_UpdateRumble(device); return HIDAPI_DriverXboxOne_UpdateRumble(device);
} }
@ -487,6 +539,7 @@ HIDAPI_DriverXboxOne_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joyst
/* Magnitude is 1..100 so scale the 16-bit input here */ /* Magnitude is 1..100 so scale the 16-bit input here */
ctx->left_trigger_rumble = left_rumble / 655; ctx->left_trigger_rumble = left_rumble / 655;
ctx->right_trigger_rumble = right_rumble / 655; ctx->right_trigger_rumble = right_rumble / 655;
ctx->rumble_pending = SDL_TRUE;
return HIDAPI_DriverXboxOne_UpdateRumble(device); return HIDAPI_DriverXboxOne_UpdateRumble(device);
} }
@ -1180,6 +1233,7 @@ HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device)
} }
HIDAPI_DriverXboxOne_UpdateInitState(device, ctx); HIDAPI_DriverXboxOne_UpdateInitState(device, ctx);
HIDAPI_DriverXboxOne_UpdateRumble(device);
if (size < 0) { if (size < 0) {
/* Read error, device is disconnected */ /* Read error, device is disconnected */