Make XInput haptic code respect effect timeouts.
authorRyan C. Gordon <icculus@icculus.org>
Sat, 20 Jul 2013 18:51:49 -0400
changeset 74815ff71e03d9eb
parent 7480 d0bfce3937e0
child 7482 249d8ecbbb7d
Make XInput haptic code respect effect timeouts.

This is really just a hack until this code expands to be a robust haptic mixer.

(This is also untested, beyond compiling. Sorry!)
src/haptic/windows/SDL_syshaptic.c
     1.1 --- a/src/haptic/windows/SDL_syshaptic.c	Sat Jul 20 21:55:15 2013 +0200
     1.2 +++ b/src/haptic/windows/SDL_syshaptic.c	Sat Jul 20 18:51:49 2013 -0400
     1.3 @@ -23,6 +23,9 @@
     1.4  #ifdef SDL_HAPTIC_DINPUT
     1.5  
     1.6  #include "SDL_assert.h"
     1.7 +#include "SDL_thread.h"
     1.8 +#include "SDL_mutex.h"
     1.9 +#include "SDL_timer.h"
    1.10  #include "SDL_hints.h"
    1.11  #include "SDL_haptic.h"
    1.12  #include "../SDL_syshaptic.h"
    1.13 @@ -56,6 +59,10 @@
    1.14      SDL_bool is_joystick;       /* Device is loaded as joystick. */
    1.15      Uint8 bXInputHaptic; /* Supports force feedback via XInput. */
    1.16      Uint8 userid; /* XInput userid index for this joystick */
    1.17 +    SDL_Thread *thread;
    1.18 +    SDL_mutex *mutex;
    1.19 +    volatile Uint32 stopTicks;
    1.20 +    volatile int stopThread;
    1.21  };
    1.22  
    1.23  
    1.24 @@ -102,6 +109,8 @@
    1.25                                SDL_HapticEffect * src);
    1.26  static void SDL_SYS_HapticFreeDIEFFECT(DIEFFECT * effect, int type);
    1.27  static REFGUID SDL_SYS_HapticEffectType(SDL_HapticEffect * effect);
    1.28 +static int SDLCALL SDL_RunXInputHaptic(void *arg);
    1.29 +
    1.30  /* Callbacks. */
    1.31  static BOOL CALLBACK EnumHapticsCallback(const DIDEVICEINSTANCE *
    1.32                                           pdidInstance, VOID * pContext);
    1.33 @@ -376,6 +385,7 @@
    1.34  static int
    1.35  SDL_SYS_HapticOpenFromXInput(SDL_Haptic * haptic, Uint8 userid)
    1.36  {
    1.37 +    char threadName[32];
    1.38      XINPUT_VIBRATION vibration = { 0, 0 };  /* stop any current vibration */
    1.39      XINPUTSETSTATE(userid, &vibration);
    1.40  
    1.41 @@ -406,6 +416,30 @@
    1.42      haptic->hwdata->bXInputHaptic = 1;
    1.43      haptic->hwdata->userid = userid;
    1.44  
    1.45 +    haptic->hwdata->mutex = SDL_CreateMutex();
    1.46 +    if (haptic->hwdata->mutex == NULL) {
    1.47 +        SDL_free(haptic->effects);
    1.48 +        SDL_free(haptic->hwdata);
    1.49 +        haptic->effects = NULL;
    1.50 +        return SDL_SetError("Couldn't create XInput haptic mutex");
    1.51 +    }
    1.52 +
    1.53 +    SDL_snprintf(threadName, sizeof (threadName), "SDLXInputDev%d", (int) userid);
    1.54 +
    1.55 +#if defined(__WIN32__) && !defined(HAVE_LIBC)  /* !!! FIXME: this is nasty. */
    1.56 +    #undef SDL_CreateThread
    1.57 +    haptic->hwdata->thread = SDL_CreateThread(SDL_RunXInputHaptic, threadName, haptic->hwdata, NULL, NULL);
    1.58 +#else
    1.59 +    haptic->hwdata->thread = SDL_CreateThread(SDL_RunXInputHaptic, threadName, haptic->hwdata);
    1.60 +#endif
    1.61 +    if (haptic->hwdata->thread == NULL) {
    1.62 +        SDL_DestroyMutex(haptic->hwdata->mutex);
    1.63 +        SDL_free(haptic->effects);
    1.64 +        SDL_free(haptic->hwdata);
    1.65 +        haptic->effects = NULL;
    1.66 +        return SDL_SetError("Couldn't create XInput haptic thread");
    1.67 +    }
    1.68 +
    1.69      return 0;
    1.70   }
    1.71  
    1.72 @@ -684,7 +718,11 @@
    1.73          haptic->neffects = 0;
    1.74  
    1.75          /* Clean up */
    1.76 -        if (!haptic->hwdata->bXInputHaptic) {
    1.77 +        if (haptic->hwdata->bXInputHaptic) {
    1.78 +            haptic->hwdata->stopThread = 1;
    1.79 +            SDL_WaitThread(haptic->hwdata->thread, NULL);
    1.80 +            SDL_DestroyMutex(haptic->hwdata->mutex);
    1.81 +        } else {
    1.82              IDirectInputDevice8_Unacquire(haptic->hwdata->device);
    1.83              /* Only release if isn't grabbed by a joystick. */
    1.84              if (haptic->hwdata->is_joystick == 0) {
    1.85 @@ -1295,7 +1333,11 @@
    1.86  
    1.87      if (haptic->hwdata->bXInputHaptic) {
    1.88          XINPUT_VIBRATION *vib = &effect->hweffect->vibration;
    1.89 -        return (XINPUTSETSTATE(haptic->hwdata->userid, vib) == ERROR_SUCCESS);
    1.90 +        SDL_assert(effect->effect.type == SDL_HAPTIC_SINE);  /* should catch this at higher level */
    1.91 +        SDL_LockMutex(haptic->hwdata->mutex);
    1.92 +        haptic->hwdata->stopTicks = SDL_GetTicks() + (effect->effect.periodic.length * iterations);
    1.93 +        SDL_UnlockMutex(haptic->hwdata->mutex);
    1.94 +        return (XINPUTSETSTATE(haptic->hwdata->userid, vib) == ERROR_SUCCESS) ? 0 : -1;
    1.95      }
    1.96  
    1.97      /* Check if it's infinite. */
    1.98 @@ -1324,7 +1366,10 @@
    1.99  
   1.100      if (haptic->hwdata->bXInputHaptic) {
   1.101          XINPUT_VIBRATION vibration = { 0, 0 };
   1.102 -        return (XINPUTSETSTATE(haptic->hwdata->userid, &vibration) == ERROR_SUCCESS);
   1.103 +        SDL_LockMutex(haptic->hwdata->mutex);
   1.104 +        haptic->hwdata->stopTicks = 0;
   1.105 +        SDL_UnlockMutex(haptic->hwdata->mutex);
   1.106 +        return (XINPUTSETSTATE(haptic->hwdata->userid, &vibration) == ERROR_SUCCESS) ? 0 : -1;
   1.107      }
   1.108  
   1.109      ret = IDirectInputEffect_Stop(effect->hweffect->ref);
   1.110 @@ -1483,7 +1528,10 @@
   1.111  
   1.112      if (haptic->hwdata->bXInputHaptic) {
   1.113          XINPUT_VIBRATION vibration = { 0, 0 };
   1.114 -        return (XINPUTSETSTATE(haptic->hwdata->userid, &vibration) == ERROR_SUCCESS);
   1.115 +        SDL_LockMutex(haptic->hwdata->mutex);
   1.116 +        haptic->hwdata->stopTicks = 0;
   1.117 +        SDL_UnlockMutex(haptic->hwdata->mutex);
   1.118 +        return (XINPUTSETSTATE(haptic->hwdata->userid, &vibration) == ERROR_SUCCESS) ? 0 : -1;
   1.119      }
   1.120  
   1.121      /* Try to stop the effects. */
   1.122 @@ -1496,6 +1544,41 @@
   1.123      return 0;
   1.124  }
   1.125  
   1.126 +
   1.127 +/* !!! FIXME: this is a hack, remove this later. */
   1.128 +/* Since XInput doesn't offer a way to vibrate for X time, we hook into
   1.129 + *  SDL_PumpEvents() to check if it's time to stop vibrating with some
   1.130 + *  frequency.
   1.131 + * In practice, this works for 99% of use cases. But in an ideal world,
   1.132 + *  we do this in a separate thread so that:
   1.133 + *    - we aren't bound to when the app chooses to pump the event queue.
   1.134 + *    - we aren't adding more polling to the event queue
   1.135 + *    - we can emulate all the haptic effects correctly (start on a delay,
   1.136 + *      mix multiple effects, etc).
   1.137 + *
   1.138 + * Mostly, this is here to get rumbling to work, and all the other features
   1.139 + *  are absent in the XInput path for now.  :(
   1.140 + */
   1.141 +static int SDLCALL
   1.142 +SDL_RunXInputHaptic(void *arg)
   1.143 +{
   1.144 +    struct haptic_hwdata *hwdata = (struct haptic_hwdata *) arg;
   1.145 +
   1.146 +    while (!hwdata->stopThread) {
   1.147 +        SDL_Delay(50);
   1.148 +        SDL_LockMutex(hwdata->mutex);
   1.149 +        /* If we're currently running and need to stop... */
   1.150 +        if ((hwdata->stopTicks) && (hwdata->stopTicks < SDL_GetTicks())) {
   1.151 +            XINPUT_VIBRATION vibration = { 0, 0 };
   1.152 +            hwdata->stopTicks = 0;
   1.153 +            XINPUTSETSTATE(hwdata->userid, &vibration);
   1.154 +        }
   1.155 +        SDL_UnlockMutex(hwdata->mutex);
   1.156 +    }
   1.157 +
   1.158 +    return 0;
   1.159 +}
   1.160 +
   1.161  #endif /* SDL_HAPTIC_DINPUT */
   1.162  
   1.163  /* vi: set ts=4 sw=4 expandtab: */