gecko-dev/dom/gamepad/GamepadPlatformService.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

333 строки
11 KiB
C++
Исходник Обычный вид История

/* -*- 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/dom/GamepadMonitoring.h"
#include "mozilla/dom/GamepadTestChannelParent.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/Mutex.h"
#include "mozilla/Unused.h"
#include "nsCOMPtr.h"
#include "nsHashKeys.h"
using namespace mozilla::ipc;
namespace mozilla::dom {
namespace {
// This is the singleton instance of GamepadPlatformService, can be called
// by both background and monitor thread.
StaticRefPtr<GamepadPlatformService> gGamepadPlatformServiceSingleton;
} // namespace
// static
GamepadMonitoringState& GamepadMonitoringState::GetSingleton() {
static GamepadMonitoringState sInstance{};
return sInstance;
}
void GamepadMonitoringState::AddObserver(GamepadTestChannelParent* aParent) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aParent);
MOZ_ALWAYS_TRUE(mObservers.append(aParent));
}
void GamepadMonitoringState::RemoveObserver(GamepadTestChannelParent* aParent) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aParent);
WeakPtr<GamepadTestChannelParent>* observer = nullptr;
for (auto& item : mObservers) {
if (item == aParent) {
observer = &item;
}
}
MOZ_ASSERT(
observer,
"Attempted to remove a GamepadTestChannelParent that was never added");
std::swap(*observer, mObservers.back());
mObservers.popBack();
}
bool GamepadMonitoringState::IsMonitoring() const {
AssertIsOnBackgroundThread();
return mIsMonitoring;
}
void GamepadMonitoringState::Set(bool aIsMonitoring) {
AssertIsOnBackgroundThread();
if (mIsMonitoring != aIsMonitoring) {
mIsMonitoring = aIsMonitoring;
for (auto& observer : mObservers) {
// Since each GamepadTestChannelParent removes itself in its dtor, this
// should never be nullptr
MOZ_RELEASE_ASSERT(observer);
observer->OnMonitoringStateChanged(aIsMonitoring);
}
}
}
GamepadPlatformService::GamepadPlatformService()
: mGamepadIndex(0), mMutex("mozilla::dom::GamepadPlatformService") {}
GamepadPlatformService::~GamepadPlatformService() { Cleanup(); }
// static
already_AddRefed<GamepadPlatformService>
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<GamepadPlatformService> service(gGamepadPlatformServiceSingleton);
return service.forget();
}
template <class T>
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);
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,
uint32_t aNumLightIndicator, uint32_t aNumTouchEvents) {
// 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, aNumLightIndicator,
aNumTouchEvents);
mGamepadAdded.emplace(index, a);
NotifyGamepadChange<GamepadAdded>(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<GamepadRemoved>(aIndex, a);
mGamepadAdded.erase(aIndex);
}
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<GamepadButtonInformation>(aIndex, a);
}
void GamepadPlatformService::NewButtonEvent(uint32_t aIndex, uint32_t aButton,
bool aPressed, double aValue) {
// 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, aValue);
}
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<GamepadAxisInformation>(aIndex, a);
}
void GamepadPlatformService::NewLightIndicatorTypeEvent(
uint32_t aIndex, uint32_t aLight, GamepadLightIndicatorType aType) {
// This method is called by monitor thread populated in
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
GamepadLightIndicatorTypeInformation a(aLight, aType);
NotifyGamepadChange<GamepadLightIndicatorTypeInformation>(aIndex, a);
}
void GamepadPlatformService::NewPoseEvent(uint32_t aIndex,
const GamepadPoseState& aState) {
// This method is called by monitor thread populated in
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
GamepadPoseInformation a(aState);
NotifyGamepadChange<GamepadPoseInformation>(aIndex, a);
}
void GamepadPlatformService::NewMultiTouchEvent(
uint32_t aIndex, uint32_t aTouchArrayIndex,
const GamepadTouchState& aState) {
// This method is called by monitor thread populated in
// platform-dependent backends
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(!NS_IsMainThread());
GamepadTouchInformation a(aTouchArrayIndex, aState);
NotifyGamepadChange<GamepadTouchInformation>(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
Bug 1660660 - Fix deadlock in gamepad initialization code. r=cmartin StartGamepadMonitoring() can end up in AddGamepad, and acquire the lock again on the same thread, effectively dead-locking. This is a regression from bug 1657404. Relevant stack: (gdb) bt #0 0x00007fd19bace801 in clock_nanosleep@GLIBC_2.2.5 () at /lib64/libc.so.6 #1 0x00007fd19bad4157 in nanosleep () at /lib64/libc.so.6 #2 0x00007fd19bad408e in sleep () at /lib64/libc.so.6 #3 0x00007fd195233e87 in ah_crap_handler(int) (signum=11) at /home/emilio/src/moz/gecko-4/toolkit/xre/nsSigHandlers.cpp:95 #4 0x00007fd1952165c4 in nsProfileLock::FatalSignalHandler(int, siginfo_t*, void*) (signo=11, info=0x7fd14abb9db0, context=0x7fd14abb9c80) at /home/emilio/src/moz/gecko-4/toolkit/profile/nsProfileLock.cpp:177 #5 0x00007fd1964973b2 in WasmTrapHandler(int, siginfo_t*, void*) (signum=11, info=<optimized out>, context=<optimized out>) at /home/emilio/src/moz/gecko-4/js/src/wasm/WasmSignalHandlers.cpp:978 #6 0x00007fd19bf3ca90 in <signal handler called> () at /lib64/libpthread.so.0 #7 mozilla::detail::MutexImpl::mutexLock() (this=<optimized out>) at /home/emilio/src/moz/gecko-4/mozglue/misc/Mutex_posix.cpp:118 #8 mozilla::detail::MutexImpl::lock() (this=<optimized out>) at /home/emilio/src/moz/gecko-4/mozglue/misc/Mutex_posix.cpp:142 #9 0x00007fd190cc795a in mozilla::OffTheBooksMutex::Lock() (this=0x7fd136649398) at /home/emilio/src/moz/gecko-4/xpcom/threads/BlockingResourceBase.cpp:318 #10 0x00007fd19326e65e in mozilla::detail::BaseAutoLock<mozilla::Mutex&>::BaseAutoLock(mozilla::Mutex&) (this=<optimized out>, aLock=...) at /home/emilio/src/moz/gecko-4/obj-debug-no-sccache/dist/include/mozilla/Mutex.h:159 #11 mozilla::dom::GamepadPlatformService::NotifyGamepadChange<mozilla::dom::GamepadAdded>(unsigned int, mozilla::dom::GamepadAdded const&) (this=0x7fd136649380, aIndex=1, aInfo=...) at /home/emilio/src/moz/gecko-4/dom/gamepad/GamepadPlatformService.cpp:65 #12 0x00007fd193269178 in mozilla::dom::GamepadPlatformService::AddGamepad(char const*, mozilla::dom::GamepadMappingType, mozilla::dom::GamepadHand, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int) (this=0x7fd136649380, aID=<optimized out>, aMapping=mozilla::dom::GamepadMappingType::_empty, aHand=mozilla::dom::GamepadHand::_empty, aNumButtons=11, aNumAxes=8, aHaptics=0, aNumLightIndicator=0, aNumTouchEvents=0) at /home/emilio/src/moz/gecko-4/dom/gamepad/GamepadPlatformService.cpp:96 #13 0x00007fd19326de4a in (anonymous namespace)::LinuxGamepadService::AddDevice(mozilla::udev_device*) (this=<optimized out>, dev=<optimized out>) at /home/emilio/src/moz/gecko-4/dom/gamepad/linux/LinuxGamepad.cpp:139 #14 0x00007fd19326a156 in (anonymous namespace)::LinuxGamepadService::ScanForDevices() (this=<optimized out>) at /home/emilio/src/moz/gecko-4/dom/gamepad/linux/LinuxGamepad.cpp:188 #15 (anonymous namespace)::LinuxGamepadService::Startup() (this=<optimized out>) at /home/emilio/src/moz/gecko-4/dom/gamepad/linux/LinuxGamepad.cpp:233 #16 mozilla::dom::StartGamepadMonitoring() () at /home/emilio/src/moz/gecko-4/dom/gamepad/linux/LinuxGamepad.cpp:334 #17 0x00007fd193269c6b in mozilla::dom::GamepadPlatformService::AddChannelParent(mozilla::dom::GamepadEventChannelParent*) (this=<optimized out>, aParent=<optimized out>) at /home/emilio/src/moz/gecko-4/dom/gamepad/GamepadPlatformService.cpp:225 #18 0x00007fd19326d175 in mozilla::dom::GamepadEventChannelParent::Init() (this=0x7fd136e76a00) at /home/emilio/src/moz/gecko-4/dom/gamepad/ipc/GamepadEventChannelParent.cpp:50 #19 0x00007fd1913ba3a6 in mozilla::ipc::BackgroundParentImpl::RecvPGamepadEventChannelConstructor(mozilla::dom::PGamepadEventChannelParent*) (this=0x7fd13f888000, aActor=0x0) at /home/emilio/src/moz/gecko-4/ipc/glue/BackgroundParentImpl.cpp:1109 #20 0x00007fd1917c7da1 in mozilla::ipc::PBackgroundParent::OnMessageReceived(IPC::Message const&) (this=0x7fd13f888000, msg__=...) at PBackgroundParent.cpp:4967 #21 0x00007fd1913ea71d in mozilla::ipc::MessageChannel::DispatchAsyncMessage(mozilla::ipc::ActorLifecycleProxy*, IPC::Message const&) (this=0x7fd13f8880f8, aProxy=0x7fd13ff48140, aMsg=...) Differential Revision: https://phabricator.services.mozilla.com/D87967
2020-08-24 20:06:58 +03:00
{
MutexAutoLock autoLock(mMutex);
mChannelParents.AppendElement(aParent);
// For a new GamepadEventChannel, we have to send the exising GamepadAdded
// to it to make it can have the same amount of gamepads with others.
if (mChannelParents.Length() > 1) {
for (const auto& evt : mGamepadAdded) {
GamepadChangeEventBody body(evt.second);
GamepadChangeEvent e(evt.first, GamepadServiceType::Standard, body);
aParent->DispatchUpdateEvent(e);
}
}
Bug 1660660 - Fix deadlock in gamepad initialization code. r=cmartin StartGamepadMonitoring() can end up in AddGamepad, and acquire the lock again on the same thread, effectively dead-locking. This is a regression from bug 1657404. Relevant stack: (gdb) bt #0 0x00007fd19bace801 in clock_nanosleep@GLIBC_2.2.5 () at /lib64/libc.so.6 #1 0x00007fd19bad4157 in nanosleep () at /lib64/libc.so.6 #2 0x00007fd19bad408e in sleep () at /lib64/libc.so.6 #3 0x00007fd195233e87 in ah_crap_handler(int) (signum=11) at /home/emilio/src/moz/gecko-4/toolkit/xre/nsSigHandlers.cpp:95 #4 0x00007fd1952165c4 in nsProfileLock::FatalSignalHandler(int, siginfo_t*, void*) (signo=11, info=0x7fd14abb9db0, context=0x7fd14abb9c80) at /home/emilio/src/moz/gecko-4/toolkit/profile/nsProfileLock.cpp:177 #5 0x00007fd1964973b2 in WasmTrapHandler(int, siginfo_t*, void*) (signum=11, info=<optimized out>, context=<optimized out>) at /home/emilio/src/moz/gecko-4/js/src/wasm/WasmSignalHandlers.cpp:978 #6 0x00007fd19bf3ca90 in <signal handler called> () at /lib64/libpthread.so.0 #7 mozilla::detail::MutexImpl::mutexLock() (this=<optimized out>) at /home/emilio/src/moz/gecko-4/mozglue/misc/Mutex_posix.cpp:118 #8 mozilla::detail::MutexImpl::lock() (this=<optimized out>) at /home/emilio/src/moz/gecko-4/mozglue/misc/Mutex_posix.cpp:142 #9 0x00007fd190cc795a in mozilla::OffTheBooksMutex::Lock() (this=0x7fd136649398) at /home/emilio/src/moz/gecko-4/xpcom/threads/BlockingResourceBase.cpp:318 #10 0x00007fd19326e65e in mozilla::detail::BaseAutoLock<mozilla::Mutex&>::BaseAutoLock(mozilla::Mutex&) (this=<optimized out>, aLock=...) at /home/emilio/src/moz/gecko-4/obj-debug-no-sccache/dist/include/mozilla/Mutex.h:159 #11 mozilla::dom::GamepadPlatformService::NotifyGamepadChange<mozilla::dom::GamepadAdded>(unsigned int, mozilla::dom::GamepadAdded const&) (this=0x7fd136649380, aIndex=1, aInfo=...) at /home/emilio/src/moz/gecko-4/dom/gamepad/GamepadPlatformService.cpp:65 #12 0x00007fd193269178 in mozilla::dom::GamepadPlatformService::AddGamepad(char const*, mozilla::dom::GamepadMappingType, mozilla::dom::GamepadHand, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int) (this=0x7fd136649380, aID=<optimized out>, aMapping=mozilla::dom::GamepadMappingType::_empty, aHand=mozilla::dom::GamepadHand::_empty, aNumButtons=11, aNumAxes=8, aHaptics=0, aNumLightIndicator=0, aNumTouchEvents=0) at /home/emilio/src/moz/gecko-4/dom/gamepad/GamepadPlatformService.cpp:96 #13 0x00007fd19326de4a in (anonymous namespace)::LinuxGamepadService::AddDevice(mozilla::udev_device*) (this=<optimized out>, dev=<optimized out>) at /home/emilio/src/moz/gecko-4/dom/gamepad/linux/LinuxGamepad.cpp:139 #14 0x00007fd19326a156 in (anonymous namespace)::LinuxGamepadService::ScanForDevices() (this=<optimized out>) at /home/emilio/src/moz/gecko-4/dom/gamepad/linux/LinuxGamepad.cpp:188 #15 (anonymous namespace)::LinuxGamepadService::Startup() (this=<optimized out>) at /home/emilio/src/moz/gecko-4/dom/gamepad/linux/LinuxGamepad.cpp:233 #16 mozilla::dom::StartGamepadMonitoring() () at /home/emilio/src/moz/gecko-4/dom/gamepad/linux/LinuxGamepad.cpp:334 #17 0x00007fd193269c6b in mozilla::dom::GamepadPlatformService::AddChannelParent(mozilla::dom::GamepadEventChannelParent*) (this=<optimized out>, aParent=<optimized out>) at /home/emilio/src/moz/gecko-4/dom/gamepad/GamepadPlatformService.cpp:225 #18 0x00007fd19326d175 in mozilla::dom::GamepadEventChannelParent::Init() (this=0x7fd136e76a00) at /home/emilio/src/moz/gecko-4/dom/gamepad/ipc/GamepadEventChannelParent.cpp:50 #19 0x00007fd1913ba3a6 in mozilla::ipc::BackgroundParentImpl::RecvPGamepadEventChannelConstructor(mozilla::dom::PGamepadEventChannelParent*) (this=0x7fd13f888000, aActor=0x0) at /home/emilio/src/moz/gecko-4/ipc/glue/BackgroundParentImpl.cpp:1109 #20 0x00007fd1917c7da1 in mozilla::ipc::PBackgroundParent::OnMessageReceived(IPC::Message const&) (this=0x7fd13f888000, msg__=...) at PBackgroundParent.cpp:4967 #21 0x00007fd1913ea71d in mozilla::ipc::MessageChannel::DispatchAsyncMessage(mozilla::ipc::ActorLifecycleProxy*, IPC::Message const&) (this=0x7fd13f8880f8, aProxy=0x7fd13ff48140, aMsg=...) Differential Revision: https://phabricator.services.mozilla.com/D87967
2020-08-24 20:06:58 +03:00
}
StartGamepadMonitoring();
GamepadMonitoringState::GetSingleton().Set(true);
}
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);
if (!mChannelParents.IsEmpty()) {
return;
}
}
GamepadMonitoringState::GetSingleton().Set(false);
StopGamepadMonitoring();
ResetGamepadIndexes();
MaybeShutdown();
}
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<GamepadPlatformService> kungFuDeathGrip;
bool isChannelParentEmpty;
{
MutexAutoLock autoLock(mMutex);
isChannelParentEmpty = mChannelParents.IsEmpty();
if (isChannelParentEmpty) {
kungFuDeathGrip = gGamepadPlatformServiceSingleton;
gGamepadPlatformServiceSingleton = nullptr;
mGamepadAdded.clear();
}
}
}
void GamepadPlatformService::Cleanup() {
// This method is called when GamepadPlatformService is
// successfully distructed in background thread
AssertIsOnBackgroundThread();
MutexAutoLock autoLock(mMutex);
mChannelParents.Clear();
}
} // namespace mozilla::dom