// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cmath>
#include <set>
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "content/browser/gamepad/gamepad_data_fetcher.h"
#include "content/browser/gamepad/gamepad_platform_data_fetcher.h"
#include "content/browser/gamepad/gamepad_provider.h"
#include "content/common/gamepad_hardware_buffer.h"
#include "content/common/gamepad_messages.h"
#include "content/common/gamepad_user_gesture.h"
namespace content {
GamepadProvider::ClosureAndThread::ClosureAndThread(
const base::Closure& c,
const scoped_refptr<base::MessageLoopProxy>& m)
: closure(c),
message_loop(m) {
}
GamepadProvider::ClosureAndThread::~ClosureAndThread() {
}
GamepadProvider::GamepadProvider()
: is_paused_(true),
have_scheduled_do_poll_(false),
devices_changed_(true) {
Initialize(scoped_ptr<GamepadDataFetcher>());
}
GamepadProvider::GamepadProvider(scoped_ptr<GamepadDataFetcher> fetcher)
: is_paused_(true),
have_scheduled_do_poll_(false),
devices_changed_(true) {
Initialize(fetcher.Pass());
}
GamepadProvider::~GamepadProvider() {
base::SystemMonitor* monitor = base::SystemMonitor::Get();
if (monitor)
monitor->RemoveDevicesChangedObserver(this);
// Use Stop() to join the polling thread, as there may be pending callbacks
// which dereference |polling_thread_|.
polling_thread_->Stop();
data_fetcher_.reset();
}
base::SharedMemoryHandle GamepadProvider::GetSharedMemoryHandleForProcess(
base::ProcessHandle process) {
base::SharedMemoryHandle renderer_handle;
gamepad_shared_memory_.ShareToProcess(process, &renderer_handle);
return renderer_handle;
}
void GamepadProvider::Pause() {
{
base::AutoLock lock(is_paused_lock_);
is_paused_ = true;
}
base::MessageLoop* polling_loop = polling_thread_->message_loop();
polling_loop->PostTask(
FROM_HERE,
base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), true));
}
void GamepadProvider::Resume() {
{
base::AutoLock lock(is_paused_lock_);
if (!is_paused_)
return;
is_paused_ = false;
}
base::MessageLoop* polling_loop = polling_thread_->message_loop();
polling_loop->PostTask(
FROM_HERE,
base::Bind(&GamepadProvider::SendPauseHint, Unretained(this), false));
polling_loop->PostTask(
FROM_HERE,
base::Bind(&GamepadProvider::ScheduleDoPoll, Unretained(this)));
}
void GamepadProvider::RegisterForUserGesture(const base::Closure& closure) {
base::AutoLock lock(user_gesture_lock_);
user_gesture_observers_.push_back(ClosureAndThread(
closure, base::MessageLoop::current()->message_loop_proxy()));
}
void GamepadProvider::OnDevicesChanged(base::SystemMonitor::DeviceType type) {
base::AutoLock lock(devices_changed_lock_);
devices_changed_ = true;
}
void GamepadProvider::Initialize(scoped_ptr<GamepadDataFetcher> fetcher) {
size_t data_size = sizeof(GamepadHardwareBuffer);
base::SystemMonitor* monitor = base::SystemMonitor::Get();
if (monitor)
monitor->AddDevicesChangedObserver(this);
bool res = gamepad_shared_memory_.CreateAndMapAnonymous(data_size);
CHECK(res);
GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();
memset(hwbuf, 0, sizeof(GamepadHardwareBuffer));
polling_thread_.reset(new base::Thread("Gamepad polling thread"));
#if defined(OS_MACOSX)
// On Mac, the data fetcher uses IOKit which depends on CFRunLoop, so the
// message loop needs to be a UI-type loop.
const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_UI;
#else
// On Linux, the data fetcher needs to watch file descriptors, so the message
// loop needs to be a libevent loop. On Windows it doesn't matter what the
// loop is.
const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_IO;
#endif
polling_thread_->StartWithOptions(base::Thread::Options(kMessageLoopType, 0));
polling_thread_->message_loop()->PostTask(
FROM_HERE,
base::Bind(&GamepadProvider::DoInitializePollingThread,
base::Unretained(this),
base::Passed(&fetcher)));
}
void GamepadProvider::DoInitializePollingThread(
scoped_ptr<GamepadDataFetcher> fetcher) {
DCHECK(base::MessageLoop::current() == polling_thread_->message_loop());
DCHECK(!data_fetcher_.get()); // Should only initialize once.
if (!fetcher)
fetcher.reset(new GamepadPlatformDataFetcher);
data_fetcher_ = fetcher.Pass();
}
void GamepadProvider::SendPauseHint(bool paused) {
DCHECK(base::MessageLoop::current() == polling_thread_->message_loop());
if (data_fetcher_)
data_fetcher_->PauseHint(paused);
}
void GamepadProvider::DoPoll() {
DCHECK(base::MessageLoop::current() == polling_thread_->message_loop());
DCHECK(have_scheduled_do_poll_);
have_scheduled_do_poll_ = false;
bool changed;
GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();
ANNOTATE_BENIGN_RACE_SIZED(
&hwbuf->buffer,
sizeof(blink::WebGamepads),
"Racey reads are discarded");
{
base::AutoLock lock(devices_changed_lock_);
changed = devices_changed_;
devices_changed_ = false;
}
// Acquire the SeqLock. There is only ever one writer to this data.
// See gamepad_hardware_buffer.h.
hwbuf->sequence.WriteBegin();
data_fetcher_->GetGamepadData(&hwbuf->buffer, changed);
hwbuf->sequence.WriteEnd();
CheckForUserGesture();
// Schedule our next interval of polling.
ScheduleDoPoll();
}
void GamepadProvider::ScheduleDoPoll() {
DCHECK(base::MessageLoop::current() == polling_thread_->message_loop());
if (have_scheduled_do_poll_)
return;
{
base::AutoLock lock(is_paused_lock_);
if (is_paused_)
return;
}
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&GamepadProvider::DoPoll, Unretained(this)),
base::TimeDelta::FromMilliseconds(kDesiredSamplingIntervalMs));
have_scheduled_do_poll_ = true;
}
GamepadHardwareBuffer* GamepadProvider::SharedMemoryAsHardwareBuffer() {
void* mem = gamepad_shared_memory_.memory();
CHECK(mem);
return static_cast<GamepadHardwareBuffer*>(mem);
}
void GamepadProvider::CheckForUserGesture() {
base::AutoLock lock(user_gesture_lock_);
if (user_gesture_observers_.empty())
return; // Don't need to check if nobody is listening.
if (GamepadsHaveUserGesture(SharedMemoryAsHardwareBuffer()->buffer)) {
for (size_t i = 0; i < user_gesture_observers_.size(); i++) {
user_gesture_observers_[i].message_loop->PostTask(FROM_HERE,
user_gesture_observers_[i].closure);
}
user_gesture_observers_.clear();
}
}
} // namespace content