bug 980876 - be smarter about sending gamepad updates from the background thread. r=smaug
authorTed Mielczarek <ted@mielczarek.org>
Mon, 07 Apr 2014 14:28:08 -0400
changeset 195819 aff859f63f703e6e450e81f539c8c70e18bdfa4f
parent 195818 e35851f07b6703bee6830b4ebcd2990f41629238
child 195820 c65cceb8ce46134cb57621401765fcb7e32d4bb3
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs980876
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 980876 - be smarter about sending gamepad updates from the background thread. r=smaug
hal/windows/WindowsGamepad.cpp
--- a/hal/windows/WindowsGamepad.cpp
+++ b/hal/windows/WindowsGamepad.cpp
@@ -15,39 +15,45 @@
 
 #include "nsIComponentManager.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsITimer.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 #include "mozilla/dom/GamepadService.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/Services.h"
 
 namespace {
 
 using mozilla::dom::GamepadService;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
 
 const LONG kMaxAxisValue = 65535;
 const DWORD BUTTON_DOWN_MASK = 0x80;
 // Multiple devices-changed notifications can be sent when a device
 // is connected, because USB devices consist of multiple logical devices.
 // Therefore, we wait a bit after receiving one before looking for
 // device changes.
 const uint32_t kDevicesChangedStableDelay = 200;
 
+class WindowsGamepadService;
+WindowsGamepadService* gService = nullptr;
+
 typedef struct {
   float x,y;
 } HatState;
 
 struct Gamepad {
   // From DirectInput, unique to this device+computer combination.
   GUID guidInstance;
   // The ID assigned by the base GamepadService
-  int id;
+  int globalID;
   // A somewhat unique string consisting of the USB vendor/product IDs,
   // and the controller name.
   char idstring[128];
   // USB vendor and product IDs
   int vendorID;
   int productID;
   // Information about the physical device.
   int numAxes;
@@ -59,16 +65,19 @@ struct Gamepad {
   nsRefPtr<IDirectInputDevice8> device;
   // A handle that DirectInput signals when there is new data from
   // the device.
   HANDLE event;
   // The state of any POV hats on the device.
   HatState hatState[4];
   // Used during rescan to find devices that were disconnected.
   bool present;
+  // Passed back from the main thread to indicate a device can
+  // now be removed.
+  bool remove;
 };
 
 // Given DWORD |hatPos| representing the position of the POV hat per:
 // http://msdn.microsoft.com/en-us/library/ee418260%28v=VS.85%29.aspx
 // fill |axes| with the position of the x and y axes.
 //
 //XXX: ostensibly the values could be arbitrary degrees for a hat with
 // full rotation, but we'll punt on that for now. This should handle
@@ -117,115 +126,16 @@ HatPosToAxes(DWORD hatPos, HatState& axe
   }
   else if (hatPos == 315 * DI_DEGREES) {
     // Up-left
     axes.x = -1.0;
     axes.y = -1.0;
   }
 }
 
-// Used to post events from the background thread to the foreground thread.
-class GamepadEvent : public nsRunnable {
-public:
-  typedef enum {
-    Axis,
-    Button,
-    HatX,
-    HatY,
-    HatXY,
-    Unknown
-  } Type;
-
-  GamepadEvent(const Gamepad& gamepad,
-               Type type,
-               int which,
-               DWORD data) : mGamepad(gamepad),
-                             mType(type),
-                             mWhich(which),
-                             mData(data) {
-  }
-
-  NS_IMETHOD Run() {
-    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
-
-    switch (mType) {
-    case Button:
-      gamepadsvc->NewButtonEvent(mGamepad.id, mWhich, mData & BUTTON_DOWN_MASK);
-      break;
-    case Axis: {
-      float adjustedData = ((float)mData * 2.0f) / (float)kMaxAxisValue - 1.0f;
-      gamepadsvc->NewAxisMoveEvent(mGamepad.id, mWhich, adjustedData);
-    }
-    case HatX:
-    case HatY:
-    case HatXY: {
-      // Synthesize 2 axes per POV hat for convenience.
-      HatState hatState;
-      HatPosToAxes(mData, hatState);
-      int xAxis = mGamepad.numAxes + 2 * mWhich;
-      int yAxis = mGamepad.numAxes + 2 * mWhich + 1;
-      //TODO: ostensibly we could not fire an event if one axis hasn't
-      // changed, but it's a pain to track that.
-      if (mType == HatX || mType == HatXY) {
-        gamepadsvc->NewAxisMoveEvent(mGamepad.id, xAxis, hatState.x);
-      }
-      if (mType == HatY || mType == HatXY) {
-        gamepadsvc->NewAxisMoveEvent(mGamepad.id, yAxis, hatState.y);
-      }
-      break;
-    }
-    case Unknown:
-      break;
-    }
-    return NS_OK;
-  }
-
-  const Gamepad& mGamepad;
-  // Type of event
-  Type mType;
-  // Which button/axis is involved
-  int mWhich;
-  // Data specific to event
-  DWORD mData;
-};
-
-class GamepadChangeEvent : public nsRunnable {
-public:
-  enum Type {
-    Added,
-    Removed
-  };
-  GamepadChangeEvent(Gamepad& gamepad,
-                     Type type) : mGamepad(gamepad),
-                                  mID(gamepad.id),
-                                  mType(type) {
-  }
-
-  NS_IMETHOD Run() {
-    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
-    if (mType == Added) {
-      mGamepad.id = gamepadsvc->AddGamepad(mGamepad.idstring,
-                                           mozilla::dom::NoMapping,
-                                           mGamepad.numButtons,
-                                           mGamepad.numAxes +
-                                           mGamepad.numHats*2);
-    } else {
-      gamepadsvc->RemoveGamepad(mID);
-    }
-    return NS_OK;
-  }
-
-private:
-  Gamepad& mGamepad;
-  uint32_t mID;
-  Type mType;
-};
-
-class WindowsGamepadService;
-
 class Observer : public nsIObserver {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   Observer(WindowsGamepadService& svc) : mSvc(svc),
                                          mObserving(true) {
     nsresult rv;
@@ -286,16 +196,18 @@ public:
 
   enum DeviceChangeType {
     DeviceChangeNotification,
     DeviceChangeStable
   };
   void DevicesChanged(DeviceChangeType type);
   void Startup();
   void Shutdown();
+  void SetGamepadID(int localID, int globalID);
+  void RemoveGamepad(int localID);
 
 private:
   void ScanForDevices();
   void Cleanup();
   void CleanupGamepad(Gamepad& gamepad);
   // Callback for enumerating axes on a device
   static BOOL CALLBACK EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi,
                                            LPVOID pvRef);
@@ -307,28 +219,147 @@ private:
   // Used to signal the background thread to exit.
   HANDLE mThreadExitEvent;
   // Used to signal the background thread to rescan devices.
   HANDLE mThreadRescanEvent;
   HANDLE mThread;
 
   // List of connected devices.
   nsTArray<Gamepad> mGamepads;
+  // Used to lock mutation of mGamepads.
+  Mutex mMutex;
   // List of event handles used for signaling.
   nsTArray<HANDLE> mEvents;
 
   LPDIRECTINPUT8 dinput;
 
   nsRefPtr<Observer> mObserver;
 };
 
+// Used to post events from the background thread to the foreground thread.
+class GamepadEvent : public nsRunnable {
+public:
+  typedef enum {
+    Axis,
+    Button,
+    HatX,
+    HatY,
+    HatXY,
+    Unknown
+  } Type;
+
+  GamepadEvent(const Gamepad& gamepad,
+               Type type,
+               int which,
+               DWORD data) : mGlobalID(gamepad.globalID),
+                             mGamepadAxes(gamepad.numAxes),
+                             mType(type),
+                             mWhich(which),
+                             mData(data) {
+  }
+
+  NS_IMETHOD Run() {
+    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
+
+    switch (mType) {
+    case Button:
+      gamepadsvc->NewButtonEvent(mGlobalID, mWhich, mData & BUTTON_DOWN_MASK);
+      break;
+    case Axis: {
+      float adjustedData = ((float)mData * 2.0f) / (float)kMaxAxisValue - 1.0f;
+      gamepadsvc->NewAxisMoveEvent(mGlobalID, mWhich, adjustedData);
+    }
+    case HatX:
+    case HatY:
+    case HatXY: {
+      // Synthesize 2 axes per POV hat for convenience.
+      HatState hatState;
+      HatPosToAxes(mData, hatState);
+      int xAxis = mGamepadAxes + 2 * mWhich;
+      int yAxis = mGamepadAxes + 2 * mWhich + 1;
+      //TODO: ostensibly we could not fire an event if one axis hasn't
+      // changed, but it's a pain to track that.
+      if (mType == HatX || mType == HatXY) {
+        gamepadsvc->NewAxisMoveEvent(mGlobalID, xAxis, hatState.x);
+      }
+      if (mType == HatY || mType == HatXY) {
+        gamepadsvc->NewAxisMoveEvent(mGlobalID, yAxis, hatState.y);
+      }
+      break;
+    }
+    case Unknown:
+      break;
+    }
+    return NS_OK;
+  }
+
+  int mGlobalID;
+  int mGamepadAxes;
+  // Type of event
+  Type mType;
+  // Which button/axis is involved
+  int mWhich;
+  // Data specific to event
+  DWORD mData;
+};
+
+class GamepadChangeEvent : public nsRunnable {
+public:
+  enum Type {
+    Added,
+    Removed
+  };
+  GamepadChangeEvent(Gamepad& gamepad,
+                     int localID,
+                     Type type) : mLocalID(localID),
+                                  mName(gamepad.idstring),
+                                  mGlobalID(gamepad.globalID),
+                                  mGamepadButtons(gamepad.numButtons),
+                                  mGamepadAxes(gamepad.numAxes),
+                                  mGamepadHats(gamepad.numHats),
+                                  mType(type) {
+  }
+
+  NS_IMETHOD Run() {
+    nsRefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
+    if (mType == Added) {
+      int globalID = gamepadsvc->AddGamepad(mName.get(),
+                                            mozilla::dom::NoMapping,
+                                            mGamepadButtons,
+                                            mGamepadAxes +
+                                            mGamepadHats*2);
+      if (gService) {
+        gService->SetGamepadID(mLocalID, globalID);
+      }
+    } else {
+      gamepadsvc->RemoveGamepad(mGlobalID);
+      if (gService) {
+        gService->RemoveGamepad(mLocalID);
+      }
+    }
+    return NS_OK;
+  }
+
+private:
+  // ID in WindowsGamepadService::mGamepads
+  int mLocalID;
+  nsCString mName;
+  int mGamepadButtons;
+  int mGamepadAxes;
+  int mGamepadHats;
+  // ID from GamepadService
+  uint32_t mGlobalID;
+  Type mType;
+};
+
 WindowsGamepadService::WindowsGamepadService()
   : mThreadExitEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)),
     mThreadRescanEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)),
     mThread(nullptr),
+    mMutex("Windows Gamepad Service"),
     dinput(nullptr) {
   mObserver = new Observer(*this);
   // Initialize DirectInput
   CoInitialize(nullptr);
   if (CoCreateInstance(CLSID_DirectInput8,
                        nullptr,
                        CLSCTX_INPROC_SERVER,
                        IID_IDirectInput8W,
@@ -360,21 +391,24 @@ WindowsGamepadService::EnumObjectsCallba
 
 // static
 BOOL CALLBACK
 WindowsGamepadService::EnumCallback(LPCDIDEVICEINSTANCE lpddi,
                                     LPVOID pvRef) {
   WindowsGamepadService* self =
     reinterpret_cast<WindowsGamepadService*>(pvRef);
   // See if this device is already present in our list.
-  for (unsigned int i = 0; i < self->mGamepads.Length(); i++) {
-    if (memcmp(&lpddi->guidInstance, &self->mGamepads[i].guidInstance,
-               sizeof(GUID)) == 0) {
-      self->mGamepads[i].present = true;
-      return DIENUM_CONTINUE;
+  {
+    MutexAutoLock lock(self->mMutex);
+    for (unsigned int i = 0; i < self->mGamepads.Length(); i++) {
+      if (memcmp(&lpddi->guidInstance, &self->mGamepads[i].guidInstance,
+                 sizeof(GUID)) == 0) {
+        self->mGamepads[i].present = true;
+        return DIENUM_CONTINUE;
+      }
     }
   }
 
   Gamepad gamepad;
   memset(&gamepad, 0, sizeof(Gamepad));
   if (self->dinput->CreateDevice(lpddi->guidInstance,
                                  getter_AddRefs(gamepad.device),
                                  nullptr)
@@ -417,60 +451,76 @@ WindowsGamepadService::EnumCallback(LPCD
     dp.dwData = 64; // arbitrary
     // Create event so DInput can signal us when there's new data.
     gamepad.event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
     // Set data format, event notification, and acquire device
     if (gamepad.device->SetDataFormat(&c_dfDIJoystick) == DI_OK &&
         gamepad.device->SetProperty(DIPROP_BUFFERSIZE, &dp.diph) == DI_OK &&
         gamepad.device->SetEventNotification(gamepad.event) == DI_OK &&
         gamepad.device->Acquire() == DI_OK) {
+      MutexAutoLock lock(self->mMutex);
       self->mGamepads.AppendElement(gamepad);
       // Inform the GamepadService
+      int localID = self->mGamepads.Length() - 1;
       nsRefPtr<GamepadChangeEvent> event =
-        new GamepadChangeEvent(self->mGamepads[self->mGamepads.Length() - 1],
+        new GamepadChangeEvent(self->mGamepads[localID],
+                               localID,
                                GamepadChangeEvent::Added);
       NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
     }
     else {
       if (gamepad.device) {
         gamepad.device->SetEventNotification(nullptr);
       }
       CloseHandle(gamepad.event);
     }
   }
   return DIENUM_CONTINUE;
 }
 
 void
 WindowsGamepadService::ScanForDevices() {
-  for (unsigned int i = 0; i < mGamepads.Length(); i++) {
-    mGamepads[i].present = false;
+  {
+    MutexAutoLock lock(mMutex);
+    for (int i = mGamepads.Length() - 1; i >= 0; i--) {
+      if (mGamepads[i].remove) {
+
+        // Main thread has already handled this, safe to remove.
+        CleanupGamepad(mGamepads[i]);
+        mGamepads.RemoveElementAt(i);
+      } else {
+        mGamepads[i].present = false;
+      }
+    }
   }
 
   dinput->EnumDevices(DI8DEVCLASS_GAMECTRL,
                       (LPDIENUMDEVICESCALLBACK)EnumCallback,
                       this,
                       DIEDFL_ATTACHEDONLY);
 
-  // Look for devices that were removed.
-  for (int i = mGamepads.Length() - 1; i >= 0; i--) {
-    if (!mGamepads[i].present) {
-      nsRefPtr<GamepadChangeEvent> event =
-        new GamepadChangeEvent(mGamepads[i],
-                               GamepadChangeEvent::Removed);
-      NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-      CleanupGamepad(mGamepads[i]);
-      mGamepads.RemoveElementAt(i);
+  // Look for devices that are no longer present and inform the main thread.
+  {
+    MutexAutoLock lock(mMutex);
+    for (int i = mGamepads.Length() - 1; i >= 0; i--) {
+      if (!mGamepads[i].present) {
+        nsRefPtr<GamepadChangeEvent> event =
+          new GamepadChangeEvent(mGamepads[i],
+                                 i,
+                                 GamepadChangeEvent::Removed);
+        NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+      }
+    }
+
+    mEvents.Clear();
+    for (unsigned int i = 0; i < mGamepads.Length(); i++) {
+      mEvents.AppendElement(mGamepads[i].event);
     }
   }
 
-  mEvents.Clear();
-  for (unsigned int i = 0; i < mGamepads.Length(); i++) {
-    mEvents.AppendElement(mGamepads[i].event);
-  }
 
   // These events must be the  last elements in the array, so that
   // the other elements match mGamepads in order.
   mEvents.AppendElement(mThreadRescanEvent);
   mEvents.AppendElement(mThreadExitEvent);
 }
 
 // static
@@ -493,76 +543,79 @@ WindowsGamepadService::DInputThread(LPVO
     unsigned int i = result - WAIT_OBJECT_0;
 
     if (i == self->mEvents.Length() - 2) {
       // Main thread is signaling for a device rescan.
       self->ScanForDevices();
       continue;
     }
 
-    if (i >= self->mGamepads.Length()) {
-      // Something would be terribly wrong here, possibly we got
-      // a WAIT_ABANDONED_x result.
-      continue;
-    }
+    {
+      MutexAutoLock lock(self->mMutex);
+      if (i >= self->mGamepads.Length()) {
+        // Something would be terribly wrong here, possibly we got
+        // a WAIT_ABANDONED_x result.
+        continue;
+      }
 
-    // first query for the number of items in the buffer
-    DWORD items = INFINITE;
-    nsRefPtr<IDirectInputDevice8> device = self->mGamepads[i].device;
-    if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
-                              nullptr,
-                              &items,
-                              DIGDD_PEEK)== DI_OK) {
-      while (items > 0) {
-        // now read each buffered event
-        //TODO: read more than one event at a time
-        DIDEVICEOBJECTDATA data;
-        DWORD readCount = sizeof(data) / sizeof(DIDEVICEOBJECTDATA);
-        if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
-                                  &data, &readCount, 0) == DI_OK) {
-          //TODO: data.dwTimeStamp
-          GamepadEvent::Type type = GamepadEvent::Unknown;
-          int which;
-          if (data.dwOfs >= DIJOFS_BUTTON0 && data.dwOfs < DIJOFS_BUTTON(32)) {
-            type = GamepadEvent::Button;
-            which = data.dwOfs - DIJOFS_BUTTON0;
+      // first query for the number of items in the buffer
+      DWORD items = INFINITE;
+      nsRefPtr<IDirectInputDevice8> device = self->mGamepads[i].device;
+      if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
+                                nullptr,
+                                &items,
+                                DIGDD_PEEK)== DI_OK) {
+        while (items > 0) {
+          // now read each buffered event
+          //TODO: read more than one event at a time
+          DIDEVICEOBJECTDATA data;
+          DWORD readCount = sizeof(data) / sizeof(DIDEVICEOBJECTDATA);
+          if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),
+                                    &data, &readCount, 0) == DI_OK) {
+            //TODO: data.dwTimeStamp
+            GamepadEvent::Type type = GamepadEvent::Unknown;
+            int which;
+            if (data.dwOfs >= DIJOFS_BUTTON0 && data.dwOfs < DIJOFS_BUTTON(32)) {
+              type = GamepadEvent::Button;
+              which = data.dwOfs - DIJOFS_BUTTON0;
+            }
+            else if(data.dwOfs >= DIJOFS_X  && data.dwOfs < DIJOFS_SLIDER(2)) {
+              // axis/slider
+              type = GamepadEvent::Axis;
+              which = (data.dwOfs - DIJOFS_X) / sizeof(LONG);
+            }
+            else if (data.dwOfs >= DIJOFS_POV(0) && data.dwOfs < DIJOFS_POV(4)) {
+              HatState hatState;
+              HatPosToAxes(data.dwData, hatState);
+              which = (data.dwOfs - DIJOFS_POV(0)) / sizeof(DWORD);
+              // Only send out axis move events for the axes that moved
+              // in this hat move.
+              if (hatState.x != self->mGamepads[i].hatState[which].x) {
+                type = GamepadEvent::HatX;
+              }
+              if (hatState.y != self->mGamepads[i].hatState[which].y) {
+                if (type == GamepadEvent::HatX) {
+                  type = GamepadEvent::HatXY;
+                }
+                else {
+                  type = GamepadEvent::HatY;
+                }
+              }
+              self->mGamepads[i].hatState[which].x = hatState.x;
+              self->mGamepads[i].hatState[which].y = hatState.y;
+            }
+
+            if (type != GamepadEvent::Unknown) {
+              nsRefPtr<GamepadEvent> event =
+                new GamepadEvent(self->mGamepads[i], type, which, data.dwData);
+              NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+            }
           }
-          else if(data.dwOfs >= DIJOFS_X  && data.dwOfs < DIJOFS_SLIDER(2)) {
-            // axis/slider
-            type = GamepadEvent::Axis;
-            which = (data.dwOfs - DIJOFS_X) / sizeof(LONG);
-          }
-          else if (data.dwOfs >= DIJOFS_POV(0) && data.dwOfs < DIJOFS_POV(4)) {
-            HatState hatState;
-            HatPosToAxes(data.dwData, hatState);
-            which = (data.dwOfs - DIJOFS_POV(0)) / sizeof(DWORD);
-            // Only send out axis move events for the axes that moved
-            // in this hat move.
-            if (hatState.x != self->mGamepads[i].hatState[which].x) {
-              type = GamepadEvent::HatX;
-            }
-            if (hatState.y != self->mGamepads[i].hatState[which].y) {
-              if (type == GamepadEvent::HatX) {
-                type = GamepadEvent::HatXY;
-              }
-              else {
-                type = GamepadEvent::HatY;
-              }
-            }
-            self->mGamepads[i].hatState[which].x = hatState.x;
-            self->mGamepads[i].hatState[which].y = hatState.y;
-          }
-
-          if (type != GamepadEvent::Unknown) {
-            nsRefPtr<GamepadEvent> event =
-              new GamepadEvent(self->mGamepads[i], type, which, data.dwData);
-            NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
-          }
+          items--;
         }
-        items--;
       }
     }
   }
   return 0;
 }
 
 void
 WindowsGamepadService::Startup() {
@@ -579,16 +632,31 @@ WindowsGamepadService::Shutdown() {
   if (mThread) {
     SetEvent(mThreadExitEvent);
     WaitForSingleObject(mThread, INFINITE);
     CloseHandle(mThread);
   }
   Cleanup();
 }
 
+// This method is called from the main thread.
+void
+WindowsGamepadService::SetGamepadID(int localID, int globalID) {
+  MutexAutoLock lock(mMutex);
+  mGamepads[localID].globalID = globalID;
+}
+
+// This method is called from the main thread.
+void WindowsGamepadService::RemoveGamepad(int localID) {
+  MutexAutoLock lock(mMutex);
+  mGamepads[localID].remove = true;
+  // Signal background thread to remove device.
+  DevicesChanged(DeviceChangeStable);
+}
+
 void
 WindowsGamepadService::Cleanup() {
   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
     CleanupGamepad(mGamepads[i]);
   }
   mGamepads.Clear();
 }
 
@@ -615,17 +683,16 @@ Observer::Observe(nsISupports* aSubject,
   if (strcmp(aTopic, "timer-callback") == 0) {
     mSvc.DevicesChanged(WindowsGamepadService::DeviceChangeStable);
   } else if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) {
     Stop();
   }
   return NS_OK;
 }
 
-WindowsGamepadService* gService = nullptr;
 HWND sHWnd = nullptr;
 
 static
 LRESULT CALLBACK
 GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
   const unsigned int DBT_DEVICEARRIVAL        = 0x8000;
   const unsigned int DBT_DEVICEREMOVECOMPLETE = 0x8004;
   const unsigned int DBT_DEVNODES_CHANGED     = 0x7;