/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/GamepadPlatformService.h" #include "mozilla/dom/GamepadEventChannelParent.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/Mutex.h" #include "mozilla/Unused.h" #include "nsCOMPtr.h" #include "nsHashKeys.h" #include "nsIThread.h" using namespace mozilla::ipc; namespace mozilla { namespace dom { namespace { // This is the singleton instance of GamepadPlatformService, can be called // by both background and monitor thread. StaticRefPtr gGamepadPlatformServiceSingleton; } //namepsace GamepadPlatformService::GamepadPlatformService() : mGamepadIndex(0), mMutex("mozilla::dom::GamepadPlatformService") {} GamepadPlatformService::~GamepadPlatformService() { Cleanup(); } // static already_AddRefed GamepadPlatformService::GetParentService() { //GamepadPlatformService can only be accessed in parent process MOZ_ASSERT(XRE_IsParentProcess()); if (!gGamepadPlatformServiceSingleton) { // Only Background Thread can create new GamepadPlatformService instance. if (IsOnBackgroundThread()) { gGamepadPlatformServiceSingleton = new GamepadPlatformService(); } else { return nullptr; } } RefPtr service(gGamepadPlatformServiceSingleton); return service.forget(); } template void GamepadPlatformService::NotifyGamepadChange(uint32_t aIndex, const T& aInfo) { // This method is called by monitor populated in // platform-dependent backends MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(!NS_IsMainThread()); GamepadChangeEventBody body(aInfo); GamepadChangeEvent e(aIndex, GamepadServiceType::Standard, body); // mChannelParents may be accessed by background thread in the // same time, we use mutex to prevent possible race condtion MutexAutoLock autoLock(mMutex); // Buffer all events if we have no Channel to dispatch, which // may happen when performing Mochitest. if (mChannelParents.IsEmpty()) { mPendingEvents.AppendElement(e); return; } for(uint32_t i = 0; i < mChannelParents.Length(); ++i) { mChannelParents[i]->DispatchUpdateEvent(e); } } uint32_t GamepadPlatformService::AddGamepad(const char* aID, GamepadMappingType aMapping, GamepadHand aHand, uint32_t aNumButtons, uint32_t aNumAxes, uint32_t aHaptics) { // This method is called by monitor thread populated in // platform-dependent backends MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(!NS_IsMainThread()); uint32_t index = ++mGamepadIndex; // Only VR controllers has displayID, we give 0 to the general gamepads. GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), aMapping, aHand, 0, aNumButtons, aNumAxes, aHaptics); NotifyGamepadChange(index, a); return index; } void GamepadPlatformService::RemoveGamepad(uint32_t aIndex) { // This method is called by monitor thread populated in // platform-dependent backends MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(!NS_IsMainThread()); GamepadRemoved a; NotifyGamepadChange(aIndex, a); } void GamepadPlatformService::NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed, bool aTouched, double aValue) { // This method is called by monitor thread populated in // platform-dependent backends MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(!NS_IsMainThread()); GamepadButtonInformation a(aButton, aValue, aPressed, aTouched); NotifyGamepadChange(aIndex, a); } void GamepadPlatformService::NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed, bool aTouched) { // This method is called by monitor thread populated in // platform-dependent backends MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(!NS_IsMainThread()); // When only a digital button is available the value will be synthesized. NewButtonEvent(aIndex, aButton, aPressed, aTouched, aPressed ? 1.0L : 0.0L); } void GamepadPlatformService::NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed) { // This method is called by monitor thread populated in // platform-dependent backends MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(!NS_IsMainThread()); // When only a digital button is available the value will be synthesized. NewButtonEvent(aIndex, aButton, aPressed, aPressed, aPressed ? 1.0L : 0.0L); } void GamepadPlatformService::NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue) { // This method is called by monitor thread populated in // platform-dependent backends MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(!NS_IsMainThread()); GamepadAxisInformation a(aAxis, aValue); NotifyGamepadChange(aIndex, a); } void GamepadPlatformService::NewPoseEvent(uint32_t aIndex, const GamepadPoseState& aPose) { // This method is called by monitor thread populated in // platform-dependent backends MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(!NS_IsMainThread()); GamepadPoseInformation a(aPose); NotifyGamepadChange(aIndex, a); } void GamepadPlatformService::ResetGamepadIndexes() { // This method is called by monitor thread populated in // platform-dependent backends MOZ_ASSERT(XRE_IsParentProcess()); MOZ_ASSERT(!NS_IsMainThread()); mGamepadIndex = 0; } void GamepadPlatformService::AddChannelParent(GamepadEventChannelParent* aParent) { // mChannelParents can only be modified once GamepadEventChannelParent // is created or removed in Background thread AssertIsOnBackgroundThread(); MOZ_ASSERT(aParent); MOZ_ASSERT(!mChannelParents.Contains(aParent)); // We use mutex here to prevent race condition with monitor thread MutexAutoLock autoLock(mMutex); mChannelParents.AppendElement(aParent); FlushPendingEvents(); } void GamepadPlatformService::FlushPendingEvents() { AssertIsOnBackgroundThread(); MOZ_ASSERT(!mChannelParents.IsEmpty()); if (mPendingEvents.IsEmpty()) { return; } // NOTE: This method must be called with mMutex held because it accesses // mChannelParents. for (uint32_t i=0; iDispatchUpdateEvent(mPendingEvents[j]); } } mPendingEvents.Clear(); } void GamepadPlatformService::RemoveChannelParent(GamepadEventChannelParent* aParent) { // mChannelParents can only be modified once GamepadEventChannelParent // is created or removed in Background thread AssertIsOnBackgroundThread(); MOZ_ASSERT(aParent); MOZ_ASSERT(mChannelParents.Contains(aParent)); // We use mutex here to prevent race condition with monitor thread MutexAutoLock autoLock(mMutex); mChannelParents.RemoveElement(aParent); } bool GamepadPlatformService::HasGamepadListeners() { // mChannelParents may be accessed by background thread in the // same time, we use mutex to prevent possible race condtion AssertIsOnBackgroundThread(); // We use mutex here to prevent race condition with monitor thread MutexAutoLock autoLock(mMutex); for (uint32_t i = 0; i < mChannelParents.Length(); i++) { if(mChannelParents[i]->HasGamepadListener()) { return true; } } return false; } void GamepadPlatformService::MaybeShutdown() { // This method is invoked in MaybeStopGamepadMonitoring when // an IPDL channel is going to be destroyed AssertIsOnBackgroundThread(); // We have to release gGamepadPlatformServiceSingleton ouside // the mutex as well as making upcoming GetParentService() call // recreate new singleton, so we use this RefPtr to temporarily // hold the reference, postponing the release process until this // method ends. RefPtr kungFuDeathGrip; bool isChannelParentEmpty; { MutexAutoLock autoLock(mMutex); isChannelParentEmpty = mChannelParents.IsEmpty(); if(isChannelParentEmpty) { kungFuDeathGrip = gGamepadPlatformServiceSingleton; gGamepadPlatformServiceSingleton = nullptr; } } } void GamepadPlatformService::Cleanup() { // This method is called when GamepadPlatformService is // successfully distructed in background thread AssertIsOnBackgroundThread(); MutexAutoLock autoLock(mMutex); mChannelParents.Clear(); } } // namespace dom } // namespace mozilla