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/. */
7 #include "mozilla/dom/GamepadPlatformService.h"
9 #include "mozilla/dom/GamepadEventChannelParent.h"
10 #include "mozilla/dom/GamepadMonitoring.h"
11 #include "mozilla/dom/GamepadTestChannelParent.h"
12 #include "mozilla/ipc/BackgroundParent.h"
13 #include "mozilla/Mutex.h"
14 #include "mozilla/Unused.h"
17 #include "nsHashKeys.h"
19 using namespace mozilla::ipc
;
21 namespace mozilla::dom
{
25 // This is the singleton instance of GamepadPlatformService, can be called
26 // by both background and monitor thread.
27 StaticRefPtr
<GamepadPlatformService
> gGamepadPlatformServiceSingleton
;
32 GamepadMonitoringState
& GamepadMonitoringState::GetSingleton() {
33 static GamepadMonitoringState sInstance
{};
37 void GamepadMonitoringState::AddObserver(GamepadTestChannelParent
* aParent
) {
38 AssertIsOnBackgroundThread();
40 MOZ_ALWAYS_TRUE(mObservers
.append(aParent
));
43 void GamepadMonitoringState::RemoveObserver(GamepadTestChannelParent
* aParent
) {
44 AssertIsOnBackgroundThread();
47 WeakPtr
<GamepadTestChannelParent
>* observer
= nullptr;
49 for (auto& item
: mObservers
) {
50 if (item
== aParent
) {
57 "Attempted to remove a GamepadTestChannelParent that was never added");
59 std::swap(*observer
, mObservers
.back());
63 bool GamepadMonitoringState::IsMonitoring() const {
64 AssertIsOnBackgroundThread();
68 void GamepadMonitoringState::Set(bool aIsMonitoring
) {
69 AssertIsOnBackgroundThread();
71 if (mIsMonitoring
!= aIsMonitoring
) {
72 mIsMonitoring
= aIsMonitoring
;
73 for (auto& observer
: mObservers
) {
74 // Since each GamepadTestChannelParent removes itself in its dtor, this
75 // should never be nullptr
76 MOZ_RELEASE_ASSERT(observer
);
77 observer
->OnMonitoringStateChanged(aIsMonitoring
);
82 GamepadPlatformService::GamepadPlatformService()
83 : mNextGamepadHandleValue(1),
84 mMutex("mozilla::dom::GamepadPlatformService") {}
86 GamepadPlatformService::~GamepadPlatformService() { Cleanup(); }
89 already_AddRefed
<GamepadPlatformService
>
90 GamepadPlatformService::GetParentService() {
91 // GamepadPlatformService can only be accessed in parent process
92 MOZ_ASSERT(XRE_IsParentProcess());
93 if (!gGamepadPlatformServiceSingleton
) {
94 // Only Background Thread can create new GamepadPlatformService instance.
95 if (IsOnBackgroundThread()) {
96 gGamepadPlatformServiceSingleton
= new GamepadPlatformService();
101 RefPtr
<GamepadPlatformService
> service(gGamepadPlatformServiceSingleton
);
102 return service
.forget();
106 void GamepadPlatformService::NotifyGamepadChange(GamepadHandle aHandle
,
108 // This method is called by monitor populated in
109 // platform-dependent backends
110 MOZ_ASSERT(XRE_IsParentProcess());
111 MOZ_ASSERT(!NS_IsMainThread());
113 GamepadChangeEventBody
body(aInfo
);
114 GamepadChangeEvent
e(aHandle
, body
);
116 // mChannelParents may be accessed by background thread in the
117 // same time, we use mutex to prevent possible race condtion
118 MutexAutoLock
autoLock(mMutex
);
120 for (uint32_t i
= 0; i
< mChannelParents
.Length(); ++i
) {
121 mChannelParents
[i
]->DispatchUpdateEvent(e
);
125 GamepadHandle
GamepadPlatformService::AddGamepad(
126 const char* aID
, GamepadMappingType aMapping
, GamepadHand aHand
,
127 uint32_t aNumButtons
, uint32_t aNumAxes
, uint32_t aHaptics
,
128 uint32_t aNumLightIndicator
, uint32_t aNumTouchEvents
) {
129 // This method is called by monitor thread populated in
130 // platform-dependent backends
131 MOZ_ASSERT(XRE_IsParentProcess());
132 MOZ_ASSERT(!NS_IsMainThread());
134 GamepadHandle gamepadHandle
{mNextGamepadHandleValue
++,
135 GamepadHandleKind::GamepadPlatformManager
};
137 // Only VR controllers has displayID, we give 0 to the general gamepads.
138 GamepadAdded
a(NS_ConvertUTF8toUTF16(nsDependentCString(aID
)), aMapping
,
139 aHand
, 0, aNumButtons
, aNumAxes
, aHaptics
, aNumLightIndicator
,
142 mGamepadAdded
.emplace(gamepadHandle
, a
);
143 NotifyGamepadChange
<GamepadAdded
>(gamepadHandle
, a
);
144 return gamepadHandle
;
147 void GamepadPlatformService::RemoveGamepad(GamepadHandle aHandle
) {
148 // This method is called by monitor thread populated in
149 // platform-dependent backends
150 MOZ_ASSERT(XRE_IsParentProcess());
151 MOZ_ASSERT(!NS_IsMainThread());
153 NotifyGamepadChange
<GamepadRemoved
>(aHandle
, a
);
154 mGamepadAdded
.erase(aHandle
);
157 void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle
,
158 uint32_t aButton
, bool aPressed
,
159 bool aTouched
, double aValue
) {
160 // This method is called by monitor thread populated in
161 // platform-dependent backends
162 MOZ_ASSERT(XRE_IsParentProcess());
163 MOZ_ASSERT(!NS_IsMainThread());
164 GamepadButtonInformation
a(aButton
, aValue
, aPressed
, aTouched
);
165 NotifyGamepadChange
<GamepadButtonInformation
>(aHandle
, a
);
168 void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle
,
169 uint32_t aButton
, bool aPressed
,
171 // This method is called by monitor thread populated in
172 // platform-dependent backends
173 MOZ_ASSERT(XRE_IsParentProcess());
174 MOZ_ASSERT(!NS_IsMainThread());
175 // When only a digital button is available the value will be synthesized.
176 NewButtonEvent(aHandle
, aButton
, aPressed
, aPressed
, aValue
);
179 void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle
,
180 uint32_t aButton
, bool aPressed
,
182 // This method is called by monitor thread populated in
183 // platform-dependent backends
184 MOZ_ASSERT(XRE_IsParentProcess());
185 MOZ_ASSERT(!NS_IsMainThread());
186 // When only a digital button is available the value will be synthesized.
187 NewButtonEvent(aHandle
, aButton
, aPressed
, aTouched
, aPressed
? 1.0L : 0.0L);
190 void GamepadPlatformService::NewButtonEvent(GamepadHandle aHandle
,
191 uint32_t aButton
, bool aPressed
) {
192 // This method is called by monitor thread populated in
193 // platform-dependent backends
194 MOZ_ASSERT(XRE_IsParentProcess());
195 MOZ_ASSERT(!NS_IsMainThread());
196 // When only a digital button is available the value will be synthesized.
197 NewButtonEvent(aHandle
, aButton
, aPressed
, aPressed
, aPressed
? 1.0L : 0.0L);
200 void GamepadPlatformService::NewAxisMoveEvent(GamepadHandle aHandle
,
201 uint32_t aAxis
, double aValue
) {
202 // This method is called by monitor thread populated in
203 // platform-dependent backends
204 MOZ_ASSERT(XRE_IsParentProcess());
205 MOZ_ASSERT(!NS_IsMainThread());
206 GamepadAxisInformation
a(aAxis
, aValue
);
207 NotifyGamepadChange
<GamepadAxisInformation
>(aHandle
, a
);
210 void GamepadPlatformService::NewLightIndicatorTypeEvent(
211 GamepadHandle aHandle
, uint32_t aLight
, GamepadLightIndicatorType aType
) {
212 // This method is called by monitor thread populated in
213 // platform-dependent backends
214 MOZ_ASSERT(XRE_IsParentProcess());
215 MOZ_ASSERT(!NS_IsMainThread());
216 GamepadLightIndicatorTypeInformation
a(aLight
, aType
);
217 NotifyGamepadChange
<GamepadLightIndicatorTypeInformation
>(aHandle
, a
);
220 void GamepadPlatformService::NewPoseEvent(GamepadHandle aHandle
,
221 const GamepadPoseState
& aState
) {
222 // This method is called by monitor thread populated in
223 // platform-dependent backends
224 MOZ_ASSERT(XRE_IsParentProcess());
225 MOZ_ASSERT(!NS_IsMainThread());
226 GamepadPoseInformation
a(aState
);
227 NotifyGamepadChange
<GamepadPoseInformation
>(aHandle
, a
);
230 void GamepadPlatformService::NewMultiTouchEvent(
231 GamepadHandle aHandle
, uint32_t aTouchArrayIndex
,
232 const GamepadTouchState
& aState
) {
233 // This method is called by monitor thread populated in
234 // platform-dependent backends
235 MOZ_ASSERT(XRE_IsParentProcess());
236 MOZ_ASSERT(!NS_IsMainThread());
238 GamepadTouchInformation
a(aTouchArrayIndex
, aState
);
239 NotifyGamepadChange
<GamepadTouchInformation
>(aHandle
, a
);
242 void GamepadPlatformService::ResetGamepadIndexes() {
243 // This method is called by monitor thread populated in
244 // platform-dependent backends
245 MOZ_ASSERT(XRE_IsParentProcess());
246 MOZ_ASSERT(!NS_IsMainThread());
247 mNextGamepadHandleValue
= 1;
250 void GamepadPlatformService::AddChannelParent(
251 GamepadEventChannelParent
* aParent
) {
252 // mChannelParents can only be modified once GamepadEventChannelParent
253 // is created or removed in Background thread
254 AssertIsOnBackgroundThread();
256 MOZ_ASSERT(!mChannelParents
.Contains(aParent
));
258 // We use mutex here to prevent race condition with monitor thread
260 MutexAutoLock
autoLock(mMutex
);
261 mChannelParents
.AppendElement(aParent
);
263 // For a new GamepadEventChannel, we have to send the exising GamepadAdded
264 // to it to make it can have the same amount of gamepads with others.
265 if (mChannelParents
.Length() > 1) {
266 for (const auto& evt
: mGamepadAdded
) {
267 GamepadChangeEventBody
body(evt
.second
);
268 GamepadChangeEvent
e(evt
.first
, body
);
269 aParent
->DispatchUpdateEvent(e
);
274 StartGamepadMonitoring();
276 GamepadMonitoringState::GetSingleton().Set(true);
279 void GamepadPlatformService::RemoveChannelParent(
280 GamepadEventChannelParent
* aParent
) {
281 // mChannelParents can only be modified once GamepadEventChannelParent
282 // is created or removed in Background thread
283 AssertIsOnBackgroundThread();
285 MOZ_ASSERT(mChannelParents
.Contains(aParent
));
287 // We use mutex here to prevent race condition with monitor thread
289 MutexAutoLock
autoLock(mMutex
);
290 mChannelParents
.RemoveElement(aParent
);
291 if (!mChannelParents
.IsEmpty()) {
296 GamepadMonitoringState::GetSingleton().Set(false);
298 StopGamepadMonitoring();
299 ResetGamepadIndexes();
303 void GamepadPlatformService::MaybeShutdown() {
304 // This method is invoked in MaybeStopGamepadMonitoring when
305 // an IPDL channel is going to be destroyed
306 AssertIsOnBackgroundThread();
308 // We have to release gGamepadPlatformServiceSingleton ouside
309 // the mutex as well as making upcoming GetParentService() call
310 // recreate new singleton, so we use this RefPtr to temporarily
311 // hold the reference, postponing the release process until this
313 RefPtr
<GamepadPlatformService
> kungFuDeathGrip
;
315 bool isChannelParentEmpty
;
317 MutexAutoLock
autoLock(mMutex
);
318 isChannelParentEmpty
= mChannelParents
.IsEmpty();
319 if (isChannelParentEmpty
) {
320 kungFuDeathGrip
= gGamepadPlatformServiceSingleton
;
321 gGamepadPlatformServiceSingleton
= nullptr;
322 mGamepadAdded
.clear();
327 void GamepadPlatformService::Cleanup() {
328 // This method is called when GamepadPlatformService is
329 // successfully distructed in background thread
330 AssertIsOnBackgroundThread();
332 MutexAutoLock
autoLock(mMutex
);
333 mChannelParents
.Clear();
336 } // namespace mozilla::dom