GamepadPlatformService.cpp (11527B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/GamepadPlatformService.h" 8 9 #include "mozilla/Mutex.h" 10 #include "mozilla/dom/GamepadEventChannelParent.h" 11 #include "mozilla/dom/GamepadMonitoring.h" 12 #include "mozilla/dom/GamepadTestChannelParent.h" 13 #include "mozilla/ipc/BackgroundParent.h" 14 #include "nsCOMPtr.h" 15 #include "nsHashKeys.h" 16 17 using namespace mozilla::ipc; 18 19 namespace mozilla::dom { 20 21 namespace { 22 23 // This is the singleton instance of GamepadPlatformService, can be called 24 // by both background and monitor thread. 25 StaticRefPtr<GamepadPlatformService> gGamepadPlatformServiceSingleton; 26 27 } // namespace 28 29 // static 30 GamepadMonitoringState& GamepadMonitoringState::GetSingleton() { 31 static GamepadMonitoringState sInstance{}; 32 return sInstance; 33 } 34 35 void GamepadMonitoringState::AddObserver(GamepadTestChannelParent* aParent) { 36 AssertIsOnBackgroundThread(); 37 MOZ_ASSERT(aParent); 38 MOZ_ALWAYS_TRUE(mObservers.append(aParent)); 39 } 40 41 void GamepadMonitoringState::RemoveObserver(GamepadTestChannelParent* aParent) { 42 AssertIsOnBackgroundThread(); 43 MOZ_ASSERT(aParent); 44 45 WeakPtr<GamepadTestChannelParent>* observer = nullptr; 46 47 for (auto& item : mObservers) { 48 if (item == aParent) { 49 observer = &item; 50 } 51 } 52 53 MOZ_ASSERT( 54 observer, 55 "Attempted to remove a GamepadTestChannelParent that was never added"); 56 57 std::swap(*observer, mObservers.back()); 58 mObservers.popBack(); 59 } 60 61 bool GamepadMonitoringState::IsMonitoring() const { 62 AssertIsOnBackgroundThread(); 63 return mIsMonitoring; 64 } 65 66 void GamepadMonitoringState::Set(bool aIsMonitoring) { 67 AssertIsOnBackgroundThread(); 68 69 if (mIsMonitoring != aIsMonitoring) { 70 mIsMonitoring = aIsMonitoring; 71 for (auto& observer : mObservers) { 72 // Since each GamepadTestChannelParent removes itself in its dtor, this 73 // should never be nullptr 74 MOZ_RELEASE_ASSERT(observer); 75 observer->OnMonitoringStateChanged(aIsMonitoring); 76 } 77 } 78 } 79 80 GamepadPlatformService::GamepadPlatformService() 81 : mNextGamepadHandleValue(1), 82 mMutex("mozilla::dom::GamepadPlatformService") {} 83 84 GamepadPlatformService::~GamepadPlatformService() { Cleanup(); } 85 86 // static 87 already_AddRefed<GamepadPlatformService> 88 GamepadPlatformService::GetParentService() { 89 // GamepadPlatformService can only be accessed in parent process 90 MOZ_ASSERT(XRE_IsParentProcess()); 91 if (!gGamepadPlatformServiceSingleton) { 92 // Only Background Thread can create new GamepadPlatformService instance. 93 if (IsOnBackgroundThread()) { 94 gGamepadPlatformServiceSingleton = new GamepadPlatformService(); 95 } else { 96 return nullptr; 97 } 98 } 99 RefPtr<GamepadPlatformService> service(gGamepadPlatformServiceSingleton); 100 return service.forget(); 101 } 102 103 template <class T> 104 void GamepadPlatformService::NotifyGamepadChange(GamepadHandle aHandle, 105 const T& aInfo) { 106 // This method is called by monitor populated in 107 // platform-dependent backends 108 MOZ_ASSERT(XRE_IsParentProcess()); 109 MOZ_ASSERT(!NS_IsMainThread()); 110 111 GamepadChangeEventBody body(aInfo); 112 GamepadChangeEvent e(aHandle, body); 113 114 // mChannelParents may be accessed by background thread in the 115 // same time, we use mutex to prevent possible race condtion 116 MutexAutoLock autoLock(mMutex); 117 118 for (uint32_t i = 0; i < mChannelParents.Length(); ++i) { 119 mChannelParents[i]->DispatchUpdateEvent(e); 120 } 121 } 122 123 GamepadHandle GamepadPlatformService::AddGamepad( 124 const char* aID, GamepadMappingType aMapping, GamepadHand aHand, 125 uint32_t aNumButtons, uint32_t aNumAxes, uint32_t aHaptics, 126 uint32_t aNumLightIndicator, uint32_t aNumTouchEvents) { 127 // This method is called by monitor thread populated in 128 // platform-dependent backends 129 MOZ_ASSERT(XRE_IsParentProcess()); 130 MOZ_ASSERT(!NS_IsMainThread()); 131 132 GamepadHandle gamepadHandle{mNextGamepadHandleValue++, 133 GamepadHandleKind::GamepadPlatformManager}; 134 135 GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), aMapping, 136 aHand, aNumButtons, aNumAxes, aHaptics, aNumLightIndicator, 137 aNumTouchEvents); 138 139 mGamepadAdded.emplace(gamepadHandle, a); 140 NotifyGamepadChange<GamepadAdded>(gamepadHandle, a); 141 return gamepadHandle; 142 } 143 144 void GamepadPlatformService::RemoveGamepad(GamepadHandle aHandle) { 145 // This method is called by monitor thread populated in 146 // platform-dependent backends 147 MOZ_ASSERT(XRE_IsParentProcess()); 148 MOZ_ASSERT(!NS_IsMainThread()); 149 GamepadRemoved a; 150 NotifyGamepadChange<GamepadRemoved>(aHandle, a); 151 mGamepadAdded.erase(aHandle); 152 } 153 154 void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle, 155 uint32_t aButton, bool aPressed, 156 bool aTouched, double aValue) { 157 // This method is called by monitor thread populated in 158 // platform-dependent backends 159 MOZ_ASSERT(XRE_IsParentProcess()); 160 MOZ_ASSERT(!NS_IsMainThread()); 161 GamepadButtonInformation a(aButton, aValue, aPressed, aTouched); 162 NotifyGamepadChange<GamepadButtonInformation>(aHandle, a); 163 } 164 165 void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle, 166 uint32_t aButton, bool aPressed, 167 double aValue) { 168 // This method is called by monitor thread populated in 169 // platform-dependent backends 170 MOZ_ASSERT(XRE_IsParentProcess()); 171 MOZ_ASSERT(!NS_IsMainThread()); 172 // When only a digital button is available the value will be synthesized. 173 NewButtonEvent(aHandle, aButton, aPressed, aPressed, aValue); 174 } 175 176 void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle, 177 uint32_t aButton, bool aPressed, 178 bool aTouched) { 179 // This method is called by monitor thread populated in 180 // platform-dependent backends 181 MOZ_ASSERT(XRE_IsParentProcess()); 182 MOZ_ASSERT(!NS_IsMainThread()); 183 // When only a digital button is available the value will be synthesized. 184 NewButtonEvent(aHandle, aButton, aPressed, aTouched, aPressed ? 1.0L : 0.0L); 185 } 186 187 void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle, 188 uint32_t aButton, bool aPressed) { 189 // This method is called by monitor thread populated in 190 // platform-dependent backends 191 MOZ_ASSERT(XRE_IsParentProcess()); 192 MOZ_ASSERT(!NS_IsMainThread()); 193 // When only a digital button is available the value will be synthesized. 194 NewButtonEvent(aHandle, aButton, aPressed, aPressed, aPressed ? 1.0L : 0.0L); 195 } 196 197 void GamepadPlatformService::NewAxisMoveEvent(GamepadHandle aHandle, 198 uint32_t aAxis, double aValue) { 199 // This method is called by monitor thread populated in 200 // platform-dependent backends 201 MOZ_ASSERT(XRE_IsParentProcess()); 202 MOZ_ASSERT(!NS_IsMainThread()); 203 GamepadAxisInformation a(aAxis, aValue); 204 NotifyGamepadChange<GamepadAxisInformation>(aHandle, a); 205 } 206 207 void GamepadPlatformService::NewLightIndicatorTypeEvent( 208 GamepadHandle aHandle, uint32_t aLight, GamepadLightIndicatorType aType) { 209 // This method is called by monitor thread populated in 210 // platform-dependent backends 211 MOZ_ASSERT(XRE_IsParentProcess()); 212 MOZ_ASSERT(!NS_IsMainThread()); 213 GamepadLightIndicatorTypeInformation a(aLight, aType); 214 NotifyGamepadChange<GamepadLightIndicatorTypeInformation>(aHandle, a); 215 } 216 217 void GamepadPlatformService::NewPoseEvent(GamepadHandle aHandle, 218 const GamepadPoseState& aState) { 219 // This method is called by monitor thread populated in 220 // platform-dependent backends 221 MOZ_ASSERT(XRE_IsParentProcess()); 222 MOZ_ASSERT(!NS_IsMainThread()); 223 GamepadPoseInformation a(aState); 224 NotifyGamepadChange<GamepadPoseInformation>(aHandle, a); 225 } 226 227 void GamepadPlatformService::NewMultiTouchEvent( 228 GamepadHandle aHandle, uint32_t aTouchArrayIndex, 229 const GamepadTouchState& aState) { 230 // This method is called by monitor thread populated in 231 // platform-dependent backends 232 MOZ_ASSERT(XRE_IsParentProcess()); 233 MOZ_ASSERT(!NS_IsMainThread()); 234 235 GamepadTouchInformation a(aTouchArrayIndex, aState); 236 NotifyGamepadChange<GamepadTouchInformation>(aHandle, a); 237 } 238 239 void GamepadPlatformService::ResetGamepadIndexes() { 240 // This method is called by monitor thread populated in 241 // platform-dependent backends 242 MOZ_ASSERT(XRE_IsParentProcess()); 243 MOZ_ASSERT(!NS_IsMainThread()); 244 mNextGamepadHandleValue = 1; 245 } 246 247 void GamepadPlatformService::AddChannelParent( 248 GamepadEventChannelParent* aParent) { 249 // mChannelParents can only be modified once GamepadEventChannelParent 250 // is created or removed in Background thread 251 AssertIsOnBackgroundThread(); 252 MOZ_ASSERT(aParent); 253 MOZ_ASSERT(!mChannelParents.Contains(aParent)); 254 255 // We use mutex here to prevent race condition with monitor thread 256 { 257 MutexAutoLock autoLock(mMutex); 258 mChannelParents.AppendElement(aParent); 259 260 // For a new GamepadEventChannel, we have to send the exising GamepadAdded 261 // to it to make it can have the same amount of gamepads with others. 262 if (mChannelParents.Length() > 1) { 263 for (const auto& evt : mGamepadAdded) { 264 GamepadChangeEventBody body(evt.second); 265 GamepadChangeEvent e(evt.first, body); 266 aParent->DispatchUpdateEvent(e); 267 } 268 } 269 } 270 271 StartGamepadMonitoring(); 272 273 GamepadMonitoringState::GetSingleton().Set(true); 274 } 275 276 void GamepadPlatformService::RemoveChannelParent( 277 GamepadEventChannelParent* aParent) { 278 // mChannelParents can only be modified once GamepadEventChannelParent 279 // is created or removed in Background thread 280 AssertIsOnBackgroundThread(); 281 MOZ_ASSERT(aParent); 282 MOZ_ASSERT(mChannelParents.Contains(aParent)); 283 284 // We use mutex here to prevent race condition with monitor thread 285 { 286 MutexAutoLock autoLock(mMutex); 287 mChannelParents.RemoveElement(aParent); 288 if (!mChannelParents.IsEmpty()) { 289 return; 290 } 291 } 292 293 GamepadMonitoringState::GetSingleton().Set(false); 294 295 StopGamepadMonitoring(); 296 ResetGamepadIndexes(); 297 MaybeShutdown(); 298 } 299 300 void GamepadPlatformService::MaybeShutdown() { 301 // This method is invoked in MaybeStopGamepadMonitoring when 302 // an IPDL channel is going to be destroyed 303 AssertIsOnBackgroundThread(); 304 305 // We have to release gGamepadPlatformServiceSingleton ouside 306 // the mutex as well as making upcoming GetParentService() call 307 // recreate new singleton, so we use this RefPtr to temporarily 308 // hold the reference, postponing the release process until this 309 // method ends. 310 RefPtr<GamepadPlatformService> kungFuDeathGrip; 311 312 bool isChannelParentEmpty; 313 { 314 MutexAutoLock autoLock(mMutex); 315 isChannelParentEmpty = mChannelParents.IsEmpty(); 316 if (isChannelParentEmpty) { 317 kungFuDeathGrip = gGamepadPlatformServiceSingleton; 318 gGamepadPlatformServiceSingleton = nullptr; 319 mGamepadAdded.clear(); 320 } 321 } 322 } 323 324 void GamepadPlatformService::Cleanup() { 325 // This method is called when GamepadPlatformService is 326 // successfully distructed in background thread 327 AssertIsOnBackgroundThread(); 328 329 MutexAutoLock autoLock(mMutex); 330 mChannelParents.Clear(); 331 } 332 333 } // namespace mozilla::dom