summaryrefslogtreecommitdiffstats
path: root/content
diff options
context:
space:
mode:
authorscottmg@chromium.org <scottmg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-01 00:01:38 +0000
committerscottmg@chromium.org <scottmg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-01 00:01:38 +0000
commit4324e61da53e9cf22c819b90f3478cbf20bf52b5 (patch)
tree532c04ec1c6a7b890dcdae509ffea1e93a6b20d8 /content
parent9052ccad29e11d91d7a6c9850ac128dda5e75c5e (diff)
downloadchromium_src-4324e61da53e9cf22c819b90f3478cbf20bf52b5.zip
chromium_src-4324e61da53e9cf22c819b90f3478cbf20bf52b5.tar.gz
chromium_src-4324e61da53e9cf22c819b90f3478cbf20bf52b5.tar.bz2
Renderer reading side of gamepad
The is the glue code that reads from the shared memory buffer on the renderer side. The writer is a background thread the browser. The data here is returned back up into WebKit code via the Chromium port. The writer on the browser side was in this patch: http://codereview.chromium.org/8568029/ And the rest of the related gamepad changes are here: http://codereview.chromium.org/8345027/ BUG=79050 Review URL: http://codereview.chromium.org/8689011 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@112345 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content')
-rw-r--r--content/browser/gamepad/data_fetcher.h8
-rw-r--r--content/browser/gamepad/data_fetcher_win.cc12
-rw-r--r--content/browser/gamepad/data_fetcher_win.h8
-rw-r--r--content/browser/gamepad/gamepad_provider.cc56
-rw-r--r--content/browser/gamepad/gamepad_provider.h26
-rw-r--r--content/browser/gamepad/gamepad_provider_unittest.cc70
-rw-r--r--content/browser/renderer_host/gamepad_browser_message_filter.cc2
-rw-r--r--content/browser/renderer_host/gamepad_browser_message_filter.h2
-rw-r--r--content/common/gamepad_hardware_buffer.h20
-rw-r--r--content/common/gamepad_seqlock.cc49
-rw-r--r--content/common/gamepad_seqlock.h45
-rw-r--r--content/common/gamepad_seqlock_unittest.cc98
-rw-r--r--content/content_common.gypi2
-rw-r--r--content/content_renderer.gypi2
-rw-r--r--content/content_tests.gypi1
-rw-r--r--content/renderer/gamepad_shared_memory_reader.cc84
-rw-r--r--content/renderer/gamepad_shared_memory_reader.h32
-rw-r--r--content/renderer/renderer_webkitplatformsupport_impl.cc11
-rw-r--r--content/renderer/renderer_webkitplatformsupport_impl.h7
19 files changed, 426 insertions, 109 deletions
diff --git a/content/browser/gamepad/data_fetcher.h b/content/browser/gamepad/data_fetcher.h
index dc61d89..587969f 100644
--- a/content/browser/gamepad/data_fetcher.h
+++ b/content/browser/gamepad/data_fetcher.h
@@ -9,15 +9,15 @@ namespace WebKit {
class WebGamepads;
}
-namespace gamepad {
+namespace content {
-class DataFetcher {
+class GamepadDataFetcher {
public:
- virtual ~DataFetcher() {}
+ virtual ~GamepadDataFetcher() {}
virtual void GetGamepadData(WebKit::WebGamepads* pads,
bool devices_changed_hint) = 0;
};
-} // namespace gamepad
+} // namespace content
#endif // CONTENT_BROWSER_GAMEPAD_DATA_FETCHER_H_
diff --git a/content/browser/gamepad/data_fetcher_win.cc b/content/browser/gamepad/data_fetcher_win.cc
index edd76a7..b823f27 100644
--- a/content/browser/gamepad/data_fetcher_win.cc
+++ b/content/browser/gamepad/data_fetcher_win.cc
@@ -13,7 +13,7 @@
#pragma comment(lib, "delayimp.lib")
#pragma comment(lib, "xinput.lib")
-namespace gamepad {
+namespace content {
using namespace WebKit;
@@ -78,13 +78,13 @@ bool EnableXInput() {
}
-DataFetcherWindows::DataFetcherWindows()
+GamepadDataFetcherWindows::GamepadDataFetcherWindows()
: xinput_available_(EnableXInput()) {
}
-void DataFetcherWindows::GetGamepadData(WebGamepads* pads,
+void GamepadDataFetcherWindows::GetGamepadData(WebGamepads* pads,
bool devices_changed_hint) {
- TRACE_EVENT0("GAMEPAD", "DataFetcherWindows::GetGamepadData");
+ TRACE_EVENT0("GAMEPAD", "GetGamepadData");
// If there's no XInput DLL on the system, early out so that we don't
// call any other XInput functions.
@@ -104,7 +104,7 @@ void DataFetcherWindows::GetGamepadData(WebGamepads* pads,
if (devices_changed_hint) {
for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) {
WebGamepad& pad = pads->items[i];
- TRACE_EVENT1("GAMEPAD", "DataFetcherWindows::GetCapabilities", "id", i);
+ TRACE_EVENT1("GAMEPAD", "GetCapabilities", "id", i);
XINPUT_CAPABILITIES caps;
DWORD res = XInputGetCapabilities(i, XINPUT_FLAG_GAMEPAD, &caps);
if (res == ERROR_DEVICE_NOT_CONNECTED) {
@@ -170,4 +170,4 @@ void DataFetcherWindows::GetGamepadData(WebGamepads* pads,
}
}
-} // namespace gamepad
+} // namespace content
diff --git a/content/browser/gamepad/data_fetcher_win.h b/content/browser/gamepad/data_fetcher_win.h
index 46a3d5a..9193f67 100644
--- a/content/browser/gamepad/data_fetcher_win.h
+++ b/content/browser/gamepad/data_fetcher_win.h
@@ -16,17 +16,17 @@
#include "base/compiler_specific.h"
#include "content/browser/gamepad/data_fetcher.h"
-namespace gamepad {
+namespace content {
-class DataFetcherWindows : public DataFetcher {
+class GamepadDataFetcherWindows : public GamepadDataFetcher {
public:
- DataFetcherWindows();
+ GamepadDataFetcherWindows();
virtual void GetGamepadData(WebKit::WebGamepads* pads,
bool devices_changed_hint) OVERRIDE;
private:
bool xinput_available_;
};
-} // namespace gamepad
+} // namespace content
#endif // CONTENT_BROWSER_GAMEPAD_DATA_FETCHER_WIN_H_
diff --git a/content/browser/gamepad/gamepad_provider.cc b/content/browser/gamepad/gamepad_provider.cc
index 642e244..c8eed65 100644
--- a/content/browser/gamepad/gamepad_provider.cc
+++ b/content/browser/gamepad/gamepad_provider.cc
@@ -21,31 +21,29 @@
#include "content/browser/gamepad/data_fetcher_win.h"
#endif
-namespace gamepad {
+namespace content {
-Provider* Provider::instance_ = NULL;
+GamepadProvider* GamepadProvider::instance_ = NULL;
-using namespace content;
-
-// Define the default data fetcher that Provider will use if none is supplied.
-// (PlatformDataFetcher).
+// Define the default data fetcher that GamepadProvider will use if none is
+// supplied. (GamepadPlatformDataFetcher).
#if defined(OS_WIN)
-typedef DataFetcherWindows PlatformDataFetcher;
+typedef GamepadDataFetcherWindows GamepadPlatformDataFetcher;
#else
-class EmptyDataFetcher : public DataFetcher {
+class GamepadEmptyDataFetcher : public GamepadDataFetcher {
public:
void GetGamepadData(WebKit::WebGamepads* pads, bool) {
pads->length = 0;
}
};
-typedef EmptyDataFetcher PlatformDataFetcher;
+typedef GamepadEmptyDataFetcher GamepadPlatformDataFetcher;
#endif
-Provider::Provider(DataFetcher* fetcher)
+GamepadProvider::GamepadProvider(GamepadDataFetcher* fetcher)
: creator_loop_(MessageLoop::current()->message_loop_proxy()),
provided_fetcher_(fetcher),
devices_changed_(true),
@@ -59,25 +57,25 @@ Provider::Provider(DataFetcher* fetcher)
memset(hwbuf, 0, sizeof(GamepadHardwareBuffer));
}
-Provider::~Provider() {
+GamepadProvider::~GamepadProvider() {
base::SystemMonitor* monitor = base::SystemMonitor::Get();
if (monitor)
monitor->RemoveDevicesChangedObserver(this);
Stop();
}
-base::SharedMemoryHandle Provider::GetRendererSharedMemoryHandle(
+base::SharedMemoryHandle GamepadProvider::GetRendererSharedMemoryHandle(
base::ProcessHandle process) {
base::SharedMemoryHandle renderer_handle;
gamepad_shared_memory_.ShareToProcess(process, &renderer_handle);
return renderer_handle;
}
-void Provider::OnDevicesChanged() {
+void GamepadProvider::OnDevicesChanged() {
devices_changed_ = true;
}
-void Provider::Start() {
+void GamepadProvider::Start() {
DCHECK(MessageLoop::current()->message_loop_proxy() == creator_loop_);
if (polling_thread_.get())
@@ -93,21 +91,21 @@ void Provider::Start() {
MessageLoop* polling_loop = polling_thread_->message_loop();
polling_loop->PostTask(
FROM_HERE,
- base::Bind(&Provider::DoInitializePollingThread, this));
+ base::Bind(&GamepadProvider::DoInitializePollingThread, this));
}
-void Provider::Stop() {
+void GamepadProvider::Stop() {
DCHECK(MessageLoop::current()->message_loop_proxy() == creator_loop_);
polling_thread_.reset();
data_fetcher_.reset();
}
-void Provider::DoInitializePollingThread() {
+void GamepadProvider::DoInitializePollingThread() {
DCHECK(MessageLoop::current() == polling_thread_->message_loop());
if (!provided_fetcher_.get())
- provided_fetcher_.reset(new PlatformDataFetcher);
+ provided_fetcher_.reset(new GamepadPlatformDataFetcher);
// Pass ownership of fetcher to provider_.
data_fetcher_.swap(provided_fetcher_);
@@ -116,30 +114,38 @@ void Provider::DoInitializePollingThread() {
ScheduleDoPoll();
}
-void Provider::DoPoll() {
+void GamepadProvider::DoPoll() {
DCHECK(MessageLoop::current() == polling_thread_->message_loop());
GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();
- base::subtle::Barrier_AtomicIncrement(&hwbuf->start_marker, 1);
+
+ ANNOTATE_BENIGN_RACE_SIZED(
+ &hwbuf->buffer,
+ sizeof(WebKit::WebGamepads),
+ "Racey reads are discarded");
+
+ // 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, devices_changed_);
- base::subtle::Barrier_AtomicIncrement(&hwbuf->end_marker, 1);
+ hwbuf->sequence.WriteEnd();
devices_changed_ = false;
// Schedule our next interval of polling.
ScheduleDoPoll();
}
-void Provider::ScheduleDoPoll() {
+void GamepadProvider::ScheduleDoPoll() {
DCHECK(MessageLoop::current() == polling_thread_->message_loop());
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
- base::Bind(&Provider::DoPoll, weak_factory_.GetWeakPtr()),
+ base::Bind(&GamepadProvider::DoPoll, weak_factory_.GetWeakPtr()),
kDesiredSamplingIntervalMs);
}
-GamepadHardwareBuffer* Provider::SharedMemoryAsHardwareBuffer() {
+GamepadHardwareBuffer* GamepadProvider::SharedMemoryAsHardwareBuffer() {
void* mem = gamepad_shared_memory_.memory();
DCHECK(mem);
return static_cast<GamepadHardwareBuffer*>(mem);
}
-} // namespace gamepad
+} // namespace content
diff --git a/content/browser/gamepad/gamepad_provider.h b/content/browser/gamepad/gamepad_provider.h
index a97c352..a40047d 100644
--- a/content/browser/gamepad/gamepad_provider.h
+++ b/content/browser/gamepad/gamepad_provider.h
@@ -21,13 +21,13 @@ class Thread;
struct GamepadMsg_Updated_Params;
-namespace gamepad {
+namespace content {
-class CONTENT_EXPORT Provider :
- public base::RefCountedThreadSafe<Provider>,
+class CONTENT_EXPORT GamepadProvider :
+ public base::RefCountedThreadSafe<GamepadProvider>,
public base::SystemMonitor::DevicesChangedObserver {
public:
- explicit Provider(DataFetcher* fetcher);
+ explicit GamepadProvider(GamepadDataFetcher* fetcher);
// Starts or Stops the provider. Called from creator_loop_.
void Start();
@@ -36,14 +36,14 @@ class CONTENT_EXPORT Provider :
base::ProcessHandle renderer_process);
private:
- friend class base::RefCountedThreadSafe<Provider>;
+ friend class base::RefCountedThreadSafe<GamepadProvider>;
- virtual ~Provider();
+ virtual ~GamepadProvider();
// Method for starting the polling, runs on polling_thread_.
void DoInitializePollingThread();
- // Method for polling a DataFetcher. Runs on the polling_thread_.
+ // Method for polling a GamepadDataFetcher. Runs on the polling_thread_.
void DoPoll();
void ScheduleDoPoll();
@@ -56,23 +56,23 @@ class CONTENT_EXPORT Provider :
// The Message Loop on which this object was created.
// Typically the I/O loop, but may be something else during testing.
scoped_refptr<base::MessageLoopProxy> creator_loop_;
- scoped_ptr<DataFetcher> provided_fetcher_;
+ scoped_ptr<GamepadDataFetcher> provided_fetcher_;
// When polling_thread_ is running, members below are only to be used
// from that thread.
- scoped_ptr<DataFetcher> data_fetcher_;
+ scoped_ptr<GamepadDataFetcher> data_fetcher_;
base::SharedMemory gamepad_shared_memory_;
bool devices_changed_;
// Polling is done on this background thread.
scoped_ptr<base::Thread> polling_thread_;
- static Provider* instance_;
- base::WeakPtrFactory<Provider> weak_factory_;
+ static GamepadProvider* instance_;
+ base::WeakPtrFactory<GamepadProvider> weak_factory_;
- DISALLOW_COPY_AND_ASSIGN(Provider);
+ DISALLOW_COPY_AND_ASSIGN(GamepadProvider);
};
-} // namespace gamepad
+} // namespace content
#endif // CONTENT_BROWSER_GAMEPAD_PROVIDER_H_
diff --git a/content/browser/gamepad/gamepad_provider_unittest.cc b/content/browser/gamepad/gamepad_provider_unittest.cc
index 00d48c6..b016405 100644
--- a/content/browser/gamepad/gamepad_provider_unittest.cc
+++ b/content/browser/gamepad/gamepad_provider_unittest.cc
@@ -10,39 +10,39 @@
#include "content/common/gamepad_messages.h"
#include "testing/gtest/include/gtest/gtest.h"
+namespace content {
+
namespace {
-class MockDataFetcher : public gamepad::DataFetcher {
+using WebKit::WebGamepads;
+
+class MockDataFetcher : public GamepadDataFetcher {
public:
- MockDataFetcher() : read_data_(false, false) {
- memset(&test_data, 0, sizeof(test_data));
+ MockDataFetcher(WebGamepads& test_data) : read_data_(false, false) {
+ test_data_ = test_data;
}
- virtual void GetGamepadData(WebKit::WebGamepads* pads,
+ virtual void GetGamepadData(WebGamepads* pads,
bool devices_changed_hint) OVERRIDE {
- *pads = test_data;
+ *pads = test_data_;
read_data_.Signal();
}
- void SetData(WebKit::WebGamepads& data) {
- test_data = data;
- }
-
void WaitForDataRead() { return read_data_.Wait(); }
- WebKit::WebGamepads test_data;
+ WebGamepads test_data_;
base::WaitableEvent read_data_;
};
// Main test fixture
class GamepadProviderTest : public testing::Test {
public:
- gamepad::Provider* CreateProvider() {
+ GamepadProvider* CreateProvider(WebGamepads& test_data) {
#if defined(OS_MACOSX)
base::SystemMonitor::AllocateSystemIOPorts();
#endif
system_monitor_.reset(new base::SystemMonitor);
- mock_data_fetcher_ = new MockDataFetcher;
- provider_ = new gamepad::Provider(mock_data_fetcher_);
+ mock_data_fetcher_ = new MockDataFetcher(test_data);
+ provider_ = new GamepadProvider(mock_data_fetcher_);
return provider_.get();
}
@@ -53,24 +53,20 @@ class GamepadProviderTest : public testing::Test {
MessageLoop main_message_loop_;
scoped_ptr<base::SystemMonitor> system_monitor_;
MockDataFetcher* mock_data_fetcher_;
- scoped_refptr<gamepad::Provider> provider_;
+ scoped_refptr<GamepadProvider> provider_;
};
TEST_F(GamepadProviderTest, BasicStartStop) {
- gamepad::Provider* provider = CreateProvider();
+ WebGamepads test_data;
+ memset(&test_data, 0, sizeof(test_data));
+ GamepadProvider* provider = CreateProvider(test_data);
provider->Start();
provider->Stop();
// Just ensure that there's no asserts on startup, shutdown, or destroy.
}
-// http://crbug.com/105348
-TEST_F(GamepadProviderTest, FLAKY_PollingAccess) {
- using namespace gamepad;
-
- Provider* provider = CreateProvider();
- provider->Start();
-
- WebKit::WebGamepads test_data;
+TEST_F(GamepadProviderTest, PollingAccess) {
+ WebGamepads test_data;
test_data.length = 1;
test_data.items[0].connected = true;
test_data.items[0].timestamp = 0;
@@ -79,7 +75,9 @@ TEST_F(GamepadProviderTest, FLAKY_PollingAccess) {
test_data.items[0].buttons[0] = 1.f;
test_data.items[0].axes[0] = -1.f;
test_data.items[0].axes[1] = .5f;
- mock_data_fetcher_->SetData(test_data);
+
+ GamepadProvider* provider = CreateProvider(test_data);
+ provider->Start();
main_message_loop_.RunAllPending();
@@ -95,22 +93,14 @@ TEST_F(GamepadProviderTest, FLAKY_PollingAccess) {
GamepadHardwareBuffer* hwbuf = static_cast<GamepadHardwareBuffer*>(mem);
// See gamepad_hardware_buffer.h for details on the read discipline.
- base::subtle::Atomic32 start, end;
- WebKit::WebGamepads output;
- int contention_count;
-
- // Here we're attempting to test the read discipline during contention. If
- // we fail to read this many times, then the read thread is starving, and we
- // should fail the test.
- for (contention_count = 0; contention_count < 1000; ++contention_count) {
- end = base::subtle::Acquire_Load(&hwbuf->end_marker);
+ WebGamepads output;
+
+ base::subtle::Atomic32 version;
+ do {
+ version = hwbuf->sequence.ReadBegin();
memcpy(&output, &hwbuf->buffer, sizeof(output));
- start = base::subtle::Acquire_Load(&hwbuf->start_marker);
- if (start == end)
- break;
- base::PlatformThread::YieldCurrentThread();
- }
- EXPECT_GT(1000, contention_count);
+ } while (hwbuf->sequence.ReadRetry(version));
+
EXPECT_EQ(1u, output.length);
EXPECT_EQ(1u, output.items[0].buttonsLength);
EXPECT_EQ(1.f, output.items[0].buttons[0]);
@@ -122,3 +112,5 @@ TEST_F(GamepadProviderTest, FLAKY_PollingAccess) {
}
} // namespace
+
+} // namespace content
diff --git a/content/browser/renderer_host/gamepad_browser_message_filter.cc b/content/browser/renderer_host/gamepad_browser_message_filter.cc
index 06d38d8..683bff5 100644
--- a/content/browser/renderer_host/gamepad_browser_message_filter.cc
+++ b/content/browser/renderer_host/gamepad_browser_message_filter.cc
@@ -29,7 +29,7 @@ bool GamepadBrowserMessageFilter::OnMessageReceived(
void GamepadBrowserMessageFilter::OnGamepadStartPolling(
base::SharedMemoryHandle* renderer_handle) {
if (!provider_) {
- provider_ = new gamepad::Provider(NULL);
+ provider_ = new content::GamepadProvider(NULL);
provider_->Start();
}
*renderer_handle = provider_->GetRendererSharedMemoryHandle(peer_handle());
diff --git a/content/browser/renderer_host/gamepad_browser_message_filter.h b/content/browser/renderer_host/gamepad_browser_message_filter.h
index 57271e6..dc991e0 100644
--- a/content/browser/renderer_host/gamepad_browser_message_filter.h
+++ b/content/browser/renderer_host/gamepad_browser_message_filter.h
@@ -23,7 +23,7 @@ class GamepadBrowserMessageFilter : public BrowserMessageFilter {
void OnGamepadStartPolling(base::SharedMemoryHandle* renderer_handle);
void OnGamepadStopPolling();
- scoped_refptr<gamepad::Provider> provider_;
+ scoped_refptr<content::GamepadProvider> provider_;
DISALLOW_COPY_AND_ASSIGN(GamepadBrowserMessageFilter);
};
diff --git a/content/common/gamepad_hardware_buffer.h b/content/common/gamepad_hardware_buffer.h
index d984522..c673d06a 100644
--- a/content/common/gamepad_hardware_buffer.h
+++ b/content/common/gamepad_hardware_buffer.h
@@ -5,10 +5,10 @@
#ifndef CONTENT_COMMON_GAMEPAD_HARDWARE_BUFFER_H_
#define CONTENT_COMMON_GAMEPAD_HARDWARE_BUFFER_H_
-#include "base/atomicops.h"
+#include "content/common/gamepad_seqlock.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebGamepads.h"
-namespace gamepad {
+namespace content {
/*
@@ -19,25 +19,13 @@ we want low latency (so would like to avoid explicit communication via IPC
between producer and consumer) and relatively large data size.
Writer and reader operate on the same buffer assuming contention is low, and
-start_marker and end_marker are used to detect inconsistent reads.
-
-The writer atomically increments the start_marker counter before starting,
-then fills in the gamepad data, then increments end_marker to the same value
-as start_marker. The readers first reads end_marker, then the the data and
-then start_marker, and if the reader finds that the start and end markers were
-different, then it must retry as the buffer was updated while being read.
-
-There is a requirement for memory barriers between the accesses to the markers
-and the main data to ensure that both the reader and write see a consistent
-view of those values. In the current implementation, the writer uses an
-Barrier_AtomicIncrement for the counter, and the reader uses an Acquire_Load.
+contention is detected by using the associated SeqLock.
*/
struct GamepadHardwareBuffer {
- base::subtle::Atomic32 start_marker;
+ GamepadSeqLock sequence;
WebKit::WebGamepads buffer;
- base::subtle::Atomic32 end_marker;
};
}
diff --git a/content/common/gamepad_seqlock.cc b/content/common/gamepad_seqlock.cc
new file mode 100644
index 0000000..7f7e14f
--- /dev/null
+++ b/content/common/gamepad_seqlock.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2011 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 "content/common/gamepad_seqlock.h"
+
+namespace content {
+
+GamepadSeqLock::GamepadSeqLock()
+ : sequence_(0) {
+}
+
+base::subtle::Atomic32 GamepadSeqLock::ReadBegin() {
+ base::subtle::Atomic32 version;
+ for (;;) {
+ version = base::subtle::NoBarrier_Load(&sequence_);
+
+ // If the counter is even, then the associated data might be in a
+ // consistent state, so we can try to read.
+ if ((version & 1) == 0)
+ break;
+
+ // Otherwise, the writer is in the middle of an update. Retry the read.
+ base::PlatformThread::YieldCurrentThread();
+ }
+ return version;
+}
+
+bool GamepadSeqLock::ReadRetry(base::subtle::Atomic32 version) {
+ // If the sequence number was updated then a read should be re-attempted.
+ // -- Load fence, read membarrier
+ return base::subtle::Release_Load(&sequence_) != version;
+}
+
+void GamepadSeqLock::WriteBegin() {
+ // Increment the sequence number to odd to indicate the beginning of a write
+ // update.
+ base::subtle::Barrier_AtomicIncrement(&sequence_, 1);
+ // -- Store fence, write membarrier
+}
+
+void GamepadSeqLock::WriteEnd() {
+ // Increment the sequence to an even number to indicate the completion of
+ // a write update.
+ // -- Store fence, write membarrier
+ base::subtle::Barrier_AtomicIncrement(&sequence_, 1);
+}
+
+} // namespace content
diff --git a/content/common/gamepad_seqlock.h b/content/common/gamepad_seqlock.h
new file mode 100644
index 0000000..811aeca
--- /dev/null
+++ b/content/common/gamepad_seqlock.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 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.
+
+#ifndef CONTENT_COMMON_GAMEPAD_SEQLOCK_H_
+#define CONTENT_COMMON_GAMEPAD_SEQLOCK_H_
+
+#include "base/atomicops.h"
+#include "base/threading/platform_thread.h"
+
+namespace content {
+
+// This SeqLock handles only *one* writer and multiple readers. It may be
+// suitable for low-contention with relatively infrequent writes, and many
+// readers. See:
+// http://en.wikipedia.org/wiki/Seqlock
+// http://www.concurrencykit.org/doc/ck_sequence.html
+// This implementation is based on ck_sequence.h from http://concurrencykit.org.
+//
+// Currently, this is used in only one location. It may make sense to
+// generalize with a higher-level construct that owns both the lock and the
+// data buffer, if it is to be used more widely.
+//
+// You must be very careful not to operate on potentially inconsistent read
+// buffers. If the read must be retry'd, the data in the read buffer could
+// contain any random garbage. e.g., contained pointers might be
+// garbage, or indices could be out of range. Probably the only suitable thing
+// to do during the read loop is to make a copy of the data, and operate on it
+// only after the read was found to be consistent.
+class GamepadSeqLock {
+ public:
+ GamepadSeqLock();
+ base::subtle::Atomic32 ReadBegin();
+ bool ReadRetry(base::subtle::Atomic32 version);
+ void WriteBegin();
+ void WriteEnd();
+
+ private:
+ base::subtle::Atomic32 sequence_;
+ DISALLOW_COPY_AND_ASSIGN(GamepadSeqLock);
+};
+
+} // namespace content
+
+#endif // CONTENT_COMMON_GAMEPAD_SEQLOCK_H_
diff --git a/content/common/gamepad_seqlock_unittest.cc b/content/common/gamepad_seqlock_unittest.cc
new file mode 100644
index 0000000..777d804
--- /dev/null
+++ b/content/common/gamepad_seqlock_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright (c) 2011 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 "content/common/gamepad_seqlock.h"
+
+#include <stdlib.h>
+
+#include "base/atomic_ref_count.h"
+#include "base/threading/platform_thread.h"
+#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+// Basic test to make sure that basic operation works correctly.
+
+struct TestData {
+ unsigned a, b, c;
+};
+
+class BasicSeqLockTestThread : public PlatformThread::Delegate {
+ public:
+ BasicSeqLockTestThread() {}
+
+ void Init(
+ content::GamepadSeqLock* seqlock,
+ TestData* data,
+ base::subtle::Atomic32* ready) {
+ seqlock_ = seqlock;
+ data_ = data;
+ ready_ = ready;
+ }
+ virtual void ThreadMain() {
+ while (AtomicRefCountIsZero(ready_)) {
+ PlatformThread::YieldCurrentThread();
+ }
+
+ for (unsigned i = 0; i < 10000; ++i) {
+ TestData copy;
+ base::subtle::Atomic32 version;
+ do {
+ version = seqlock_->ReadBegin();
+ copy = *data_;
+ } while (seqlock_->ReadRetry(version));
+
+ EXPECT_EQ(copy.a + 100, copy.b);
+ EXPECT_EQ(copy.c, copy.b + copy.a);
+ }
+
+ AtomicRefCountDec(ready_);
+ }
+
+ private:
+ content::GamepadSeqLock* seqlock_;
+ TestData* data_;
+ base::AtomicRefCount* ready_;
+
+ DISALLOW_COPY_AND_ASSIGN(BasicSeqLockTestThread);
+};
+
+TEST(GamepadSeqLockTest, ManyThreads) {
+ content::GamepadSeqLock seqlock;
+ TestData data = { 0, 0, 0 };
+ base::AtomicRefCount ready = 0;
+
+ ANNOTATE_BENIGN_RACE_SIZED(&data, sizeof(data), "Racey reads are discarded");
+
+ static const unsigned kNumReaderThreads = 100;
+ BasicSeqLockTestThread threads[kNumReaderThreads];
+ PlatformThreadHandle handles[kNumReaderThreads];
+
+ for (unsigned i = 0; i < kNumReaderThreads; ++i)
+ threads[i].Init(&seqlock, &data, &ready);
+ for (unsigned i = 0; i < kNumReaderThreads; ++i)
+ ASSERT_TRUE(PlatformThread::Create(0, &threads[i], &handles[i]));
+
+ // The main thread is the writer, and the spawned are readers.
+ unsigned counter = 0;
+ for (;;) {
+ seqlock.WriteBegin();
+ data.a = counter++;
+ data.b = data.a + 100;
+ data.c = data.b + data.a;
+ seqlock.WriteEnd();
+
+ if (counter == 1)
+ base::AtomicRefCountIncN(&ready, kNumReaderThreads);
+
+ if (AtomicRefCountIsZero(&ready))
+ break;
+ }
+
+ for (unsigned i = 0; i < kNumReaderThreads; ++i)
+ PlatformThread::Join(handles[i]);
+}
+
+} // namespace base
diff --git a/content/content_common.gypi b/content/content_common.gypi
index 08defbb..6cd80a6 100644
--- a/content/content_common.gypi
+++ b/content/content_common.gypi
@@ -134,6 +134,8 @@
'common/font_list_x11.cc',
'common/gamepad_hardware_buffer.h',
'common/gamepad_messages.h',
+ 'common/gamepad_seqlock.cc',
+ 'common/gamepad_seqlock.h',
'common/geolocation_messages.h',
'common/geoposition.cc',
'common/geoposition.h',
diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi
index bbb00bb..f5c75e3 100644
--- a/content/content_renderer.gypi
+++ b/content/content_renderer.gypi
@@ -52,6 +52,8 @@
'renderer/devtools_client.h',
'renderer/external_popup_menu.cc',
'renderer/external_popup_menu.h',
+ 'renderer/gamepad_shared_memory_reader.cc',
+ 'renderer/gamepad_shared_memory_reader.h',
'renderer/geolocation_dispatcher.cc',
'renderer/geolocation_dispatcher.h',
'renderer/gpu/compositor_thread.cc',
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index b9ba0be..f2c33ac 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -226,6 +226,7 @@
'browser/trace_subscriber_stdio_unittest.cc',
'common/mac/attributed_string_coder_unittest.mm',
'common/mac/font_descriptor_unittest.mm',
+ 'common/gamepad_seqlock_unittest.cc',
'common/gpu/gpu_feature_flags_unittest.cc',
'common/gpu/gpu_info_unittest.cc',
'common/hi_res_timer_manager_unittest.cc',
diff --git a/content/renderer/gamepad_shared_memory_reader.cc b/content/renderer/gamepad_shared_memory_reader.cc
new file mode 100644
index 0000000..f4241e2
--- /dev/null
+++ b/content/renderer/gamepad_shared_memory_reader.cc
@@ -0,0 +1,84 @@
+// Copyright (c) 2011 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 "content/renderer/gamepad_shared_memory_reader.h"
+
+#include "base/debug/trace_event.h"
+#include "base/metrics/histogram.h"
+#include "content/common/gamepad_messages.h"
+#include "content/public/renderer/render_thread.h"
+#include "content/common/gamepad_hardware_buffer.h"
+#include "ipc/ipc_sync_message_filter.h"
+
+namespace content {
+
+GamepadSharedMemoryReader::GamepadSharedMemoryReader() {
+ memset(ever_interacted_with_, 0, sizeof(ever_interacted_with_));
+ CHECK(RenderThread::Get()->Send(new GamepadHostMsg_StartPolling(
+ &renderer_shared_memory_handle_)));
+ renderer_shared_memory_.reset(
+ new base::SharedMemory(renderer_shared_memory_handle_, true));
+ CHECK(renderer_shared_memory_->Map(sizeof(GamepadHardwareBuffer)));
+ void *memory = renderer_shared_memory_->memory();
+ CHECK(memory);
+ gamepad_hardware_buffer_ =
+ static_cast<GamepadHardwareBuffer*>(memory);
+}
+
+void GamepadSharedMemoryReader::SampleGamepads(WebKit::WebGamepads& gamepads) {
+ WebKit::WebGamepads read_into;
+ TRACE_EVENT0("GAMEPAD", "SampleGamepads");
+
+ // Only try to read this many times before failing to avoid waiting here
+ // very long in case of contention with the writer. TODO(scottmg) Tune this
+ // number (as low as 1?) if histogram shows distribution as mostly
+ // 0-and-maximum.
+ const int kMaximumContentionCount = 10;
+ int contention_count = -1;
+ base::subtle::Atomic32 version;
+ do {
+ version = gamepad_hardware_buffer_->sequence.ReadBegin();
+ memcpy(&read_into, &gamepad_hardware_buffer_->buffer, sizeof(read_into));
+ ++contention_count;
+ if (contention_count == kMaximumContentionCount)
+ break;
+ } while (gamepad_hardware_buffer_->sequence.ReadRetry(version));
+ HISTOGRAM_COUNTS("Gamepad.ReadContentionCount", contention_count);
+
+ if (contention_count >= kMaximumContentionCount) {
+ // We failed to successfully read, presumably because the hardware
+ // thread was taking unusually long. Don't copy the data to the output
+ // buffer, and simply leave what was there before.
+ return;
+ }
+
+ // New data was read successfully, copy it into the output buffer.
+ memcpy(&gamepads, &read_into, sizeof(gamepads));
+
+ // Override the "connected" with false until the user has interacted
+ // with the gamepad. This is to prevent fingerprinting on drive-by pages.
+ for (unsigned i = 0; i < WebKit::WebGamepads::itemsLengthCap; ++i) {
+ WebKit::WebGamepad& pad = gamepads.items[i];
+ // If the device is physically connected, then check if we should
+ // keep it disabled. We track if any of the primary 4 buttons have been
+ // pressed to determine a reasonable intentional interaction from the user.
+ if (pad.connected) {
+ if (ever_interacted_with_[i])
+ continue;
+ const unsigned kPrimaryInteractionButtons = 4;
+ for (unsigned j = 0; j < kPrimaryInteractionButtons; ++j)
+ ever_interacted_with_[i] |= pad.buttons[j] > 0.5f;
+ // If we've not previously set, and the user still hasn't touched
+ // these buttons, then don't pass the data on to the Chromium port.
+ if (!ever_interacted_with_[i])
+ pad.connected = false;
+ }
+ }
+}
+
+GamepadSharedMemoryReader::~GamepadSharedMemoryReader() {
+ RenderThread::Get()->Send(new GamepadHostMsg_StopPolling());
+}
+
+} // namespace content
diff --git a/content/renderer/gamepad_shared_memory_reader.h b/content/renderer/gamepad_shared_memory_reader.h
new file mode 100644
index 0000000..d8492ab
--- /dev/null
+++ b/content/renderer/gamepad_shared_memory_reader.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2011 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.
+
+#ifndef GAMEPAD_UTIL_H_
+#define GAMEPAD_UTIL_H_
+
+#include "base/shared_memory.h"
+#include "base/memory/scoped_ptr.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebGamepads.h"
+
+namespace content {
+
+struct GamepadHardwareBuffer;
+
+class GamepadSharedMemoryReader {
+ public:
+ GamepadSharedMemoryReader();
+ virtual ~GamepadSharedMemoryReader();
+ void SampleGamepads(WebKit::WebGamepads&);
+
+ private:
+ base::SharedMemoryHandle renderer_shared_memory_handle_;
+ scoped_ptr<base::SharedMemory> renderer_shared_memory_;
+ GamepadHardwareBuffer* gamepad_hardware_buffer_;
+
+ bool ever_interacted_with_[WebKit::WebGamepads::itemsLengthCap];
+};
+
+} // namespace content
+
+#endif // GAMEPAD_UTIL_H_
diff --git a/content/renderer/renderer_webkitplatformsupport_impl.cc b/content/renderer/renderer_webkitplatformsupport_impl.cc
index e571411..55d07f6 100644
--- a/content/renderer/renderer_webkitplatformsupport_impl.cc
+++ b/content/renderer/renderer_webkitplatformsupport_impl.cc
@@ -20,6 +20,7 @@
#include "content/common/webmessageportchannel_impl.h"
#include "content/public/common/content_switches.h"
#include "content/public/renderer/content_renderer_client.h"
+#include "content/renderer/gamepad_shared_memory_reader.h"
#include "content/renderer/gpu/webgraphicscontext3d_command_buffer_impl.h"
#include "content/renderer/media/audio_device.h"
#include "content/renderer/media/audio_hardware.h"
@@ -33,6 +34,7 @@
#include "ipc/ipc_sync_message_filter.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebBlobRegistry.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebGamepads.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebGraphicsContext3D.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBFactory.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBKey.h"
@@ -76,6 +78,7 @@ using WebKit::WebAudioDevice;
using WebKit::WebBlobRegistry;
using WebKit::WebFileSystem;
using WebKit::WebFrame;
+using WebKit::WebGamepads;
using WebKit::WebIDBFactory;
using WebKit::WebIDBKey;
using WebKit::WebIDBKeyPath;
@@ -619,6 +622,14 @@ WebBlobRegistry* RendererWebKitPlatformSupportImpl::blobRegistry() {
return blob_registry_.get();
}
+//------------------------------------------------------------------------------
+
+void RendererWebKitPlatformSupportImpl::sampleGamepads(WebGamepads& gamepads) {
+ if (!gamepad_shared_memory_reader_.get())
+ gamepad_shared_memory_reader_.reset(new content::GamepadSharedMemoryReader);
+ gamepad_shared_memory_reader_->SampleGamepads(gamepads);
+}
+
WebKit::WebString RendererWebKitPlatformSupportImpl::userAgent(
const WebKit::WebURL& url) {
return WebKitPlatformSupportImpl::userAgent(url);
diff --git a/content/renderer/renderer_webkitplatformsupport_impl.h b/content/renderer/renderer_webkitplatformsupport_impl.h
index c646799..335a15a 100644
--- a/content/renderer/renderer_webkitplatformsupport_impl.h
+++ b/content/renderer/renderer_webkitplatformsupport_impl.h
@@ -16,6 +16,10 @@ class RendererClipboardClient;
class WebSharedWorkerRepositoryImpl;
class WebFileSystemImpl;
+namespace content {
+class GamepadSharedMemoryReader;
+}
+
namespace IPC {
class SyncMessage;
}
@@ -84,6 +88,7 @@ class CONTENT_EXPORT RendererWebKitPlatformSupportImpl
size_t buffer_size, unsigned channels, double sample_rate,
WebKit::WebAudioDevice::RenderCallback* callback) OVERRIDE;
virtual WebKit::WebBlobRegistry* blobRegistry() OVERRIDE;
+ virtual void sampleGamepads(WebKit::WebGamepads&) OVERRIDE;
virtual WebKit::WebString userAgent(const WebKit::WebURL& url) OVERRIDE;
virtual void GetPlugins(bool refresh,
std::vector<webkit::WebPluginInfo>* plugins) OVERRIDE;
@@ -121,6 +126,8 @@ class CONTENT_EXPORT RendererWebKitPlatformSupportImpl
scoped_ptr<WebFileSystemImpl> web_file_system_;
scoped_ptr<WebKit::WebBlobRegistry> blob_registry_;
+
+ scoped_ptr<content::GamepadSharedMemoryReader> gamepad_shared_memory_reader_;
};
#endif // CONTENT_RENDERER_RENDERER_WEBKITPLATFORMSUPPORT_IMPL_H_