summaryrefslogtreecommitdiffstats
path: root/native_client_sdk
diff options
context:
space:
mode:
authorbinji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-04 18:14:03 +0000
committerbinji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-04 18:14:03 +0000
commit17d8c988f05ec86ecdac1b8dccb19000ce90ff4f (patch)
tree3f9f00c9272902f6361b5dca99aeb78577fa3e3b /native_client_sdk
parentd03f4d4224b568111e11adf44db5c84f38c85b05 (diff)
downloadchromium_src-17d8c988f05ec86ecdac1b8dccb19000ce90ff4f.zip
chromium_src-17d8c988f05ec86ecdac1b8dccb19000ce90ff4f.tar.gz
chromium_src-17d8c988f05ec86ecdac1b8dccb19000ce90ff4f.tar.bz2
[NaCl SDK] Cleanup Pong example.
BUG=none NOTRY=true Review URL: https://chromiumcodereview.appspot.com/10990043 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@160166 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'native_client_sdk')
-rw-r--r--native_client_sdk/src/examples/pong/example.dsc17
-rw-r--r--native_client_sdk/src/examples/pong/example.js12
-rw-r--r--native_client_sdk/src/examples/pong/index.html4
-rw-r--r--native_client_sdk/src/examples/pong/pong.cc421
-rw-r--r--native_client_sdk/src/examples/pong/pong.h92
-rw-r--r--native_client_sdk/src/examples/pong/pong_input.cc53
-rw-r--r--native_client_sdk/src/examples/pong/pong_input.h36
-rw-r--r--native_client_sdk/src/examples/pong/pong_instance.cc142
-rw-r--r--native_client_sdk/src/examples/pong/pong_instance.h65
-rw-r--r--native_client_sdk/src/examples/pong/pong_model.cc303
-rw-r--r--native_client_sdk/src/examples/pong/pong_model.h153
-rw-r--r--native_client_sdk/src/examples/pong/pong_module.cc10
-rw-r--r--native_client_sdk/src/examples/pong/pong_view.cc204
-rw-r--r--native_client_sdk/src/examples/pong/pong_view.h51
-rw-r--r--native_client_sdk/src/examples/pong/view.cc161
-rw-r--r--native_client_sdk/src/examples/pong/view.h75
16 files changed, 1023 insertions, 776 deletions
diff --git a/native_client_sdk/src/examples/pong/example.dsc b/native_client_sdk/src/examples/pong/example.dsc
index 4c50e42..cd4dd4e 100644
--- a/native_client_sdk/src/examples/pong/example.dsc
+++ b/native_client_sdk/src/examples/pong/example.dsc
@@ -5,11 +5,15 @@
'NAME' : 'pong',
'TYPE' : 'main',
'SOURCES' : [
- 'pong.cc',
- 'pong.h',
+ 'pong_instance.cc',
+ 'pong_instance.h',
+ 'pong_input.cc',
+ 'pong_input.h',
+ 'pong_model.cc',
+ 'pong_model.h',
'pong_module.cc',
- 'view.cc',
- 'view.h',
+ 'pong_view.cc',
+ 'pong_view.h'
],
'LIBS': ['ppapi_cpp', 'ppapi', 'pthread']
}
@@ -19,9 +23,8 @@
'NAME': 'pong',
'TITLE': 'Pong',
'DESC': """
-The Pong example demonstrates how to create a basic 2D video game and
-how to store application information in a local persistent file. This game
+The Pong example demonstrates how to create a basic 2D video game. This game
uses up and down arrow keyboard input events to move the paddle.""",
- 'INFO': 'File I/O, 2D graphics, input events.'
+ 'INFO': '2D graphics, input events.'
}
diff --git a/native_client_sdk/src/examples/pong/example.js b/native_client_sdk/src/examples/pong/example.js
index dea4894..cbf90d6 100644
--- a/native_client_sdk/src/examples/pong/example.js
+++ b/native_client_sdk/src/examples/pong/example.js
@@ -3,18 +3,6 @@
// found in the LICENSE file.
// Called by the common.js module.
-function domContentLoaded(name, tc, config, width, height) {
- window.webkitStorageInfo.requestQuota(window.PERSISTENT, 1024,
- function(bytes) {
- common.updateStatus(
- 'Allocated '+bytes+' bytes of persistent storage.');
- common.createNaClModule(name, tc, config, 800, 600);
- common.attachDefaultListeners();
- },
- function(e) { alert('Failed to allocate space'); });
-}
-
-// Called by the common.js module.
function attachListeners() {
document.getElementById('resetScore').addEventListener('click', resetScore);
}
diff --git a/native_client_sdk/src/examples/pong/index.html b/native_client_sdk/src/examples/pong/index.html
index ab2a3c6..db70108 100644
--- a/native_client_sdk/src/examples/pong/index.html
+++ b/native_client_sdk/src/examples/pong/index.html
@@ -12,8 +12,8 @@ found in the LICENSE file.
<script type="text/javascript" src="common.js"></script>
<script type="text/javascript" src="example.js"></script>
</head>
-<body data-name="<NAME>" data-tc="<tc>" data-path="<path>"
- data-custom-load="true">
+<body data-name="<NAME>" data-tc="<tc>" data-path="<path>" data-width="800"
+ data-height="600">
<h1><TITLE></h1>
<h2>Status: <code id="statusField">NO-STATUS</code></h2>
<!-- The NaCl plugin will be embedded inside the element with id "listener".
diff --git a/native_client_sdk/src/examples/pong/pong.cc b/native_client_sdk/src/examples/pong/pong.cc
deleted file mode 100644
index f650c76..0000000
--- a/native_client_sdk/src/examples/pong/pong.cc
+++ /dev/null
@@ -1,421 +0,0 @@
-// 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 <stdio.h>
-#include <cmath>
-#include <string>
-#include "ppapi/c/pp_file_info.h"
-#include "ppapi/c/ppb_file_io.h"
-#include "ppapi/cpp/completion_callback.h"
-#include "ppapi/cpp/file_io.h"
-#include "ppapi/cpp/file_ref.h"
-#include "ppapi/cpp/file_system.h"
-#include "ppapi/cpp/input_event.h"
-#include "ppapi/cpp/rect.h"
-#include "ppapi/cpp/var.h"
-
-#include "pong.h"
-#include "view.h"
-
-namespace {
-
-const uint32_t kSpaceBar = 0x20;
-const uint32_t kUpArrow = 0x26;
-const uint32_t kDownArrow = 0x28;
-
-const int32_t kMaxPointsAllowed = 256;
-const std::string kResetScoreMethodId = "resetScore";
-const uint32_t kUpdateDistance = 4;
-const int32_t kUpdateInterval = 17; // milliseconds
-
-} // namespace
-
-namespace pong {
-
-// Callbacks that are called asynchronously by the system as a result of various
-// pp::FileIO methods.
-namespace AsyncCallbacks {
-// Callback that is called as a result of pp::FileIO::Flush
-void FlushCallback(void*, int32_t) {
-}
-
-// Callback that is called as a result of pp::FileIO::SetLength
-void SetLengthCallback(void* data, int32_t result) {
- if (result != PP_OK)
- return;
- Pong* pong = static_cast<Pong*>(data);
- pong->file_io_->Flush(pp::CompletionCallback(FlushCallback, NULL));
-}
-
-// Callback that is called as a result of pp::FileIO::Write
-void WriteCallback(void* data, int32_t bytes_written) {
- if (bytes_written < 0)
- return; // error
- Pong* pong = static_cast<Pong*>(data);
- pong->offset_ += bytes_written;
- if (pong->offset_ == pong->bytes_buffer_.length()) {
- // Truncate the file.
- pong->file_io_->SetLength(pong->offset_,
- pp::CompletionCallback(SetLengthCallback, pong));
- } else {
- // Not all the bytes to be written have been written, so call
- // pp::FileIO::Write again.
- pong->file_io_->Write(pong->offset_, &pong->bytes_buffer_[pong->offset_],
- pong->bytes_buffer_.length() - pong->offset_,
- pp::CompletionCallback(WriteCallback, pong));
- }
-}
-
-// Callback that is called as a result of pp::FileSystem::Open
-void FileSystemOpenCallback(void* data, int32_t result) {
- if (result != PP_OK)
- return;
- Pong* pong = static_cast<Pong*>(data);
- pong->UpdateScoreFromFile();
-}
-
-// Callback that is called as a result of pp::FileIO::Read
-void ReadCallback(void* data, int32_t bytes_read) {
- if (bytes_read < 0)
- return; // error
- Pong* pong = static_cast<Pong*>(data);
- pong->bytes_to_read_ -= bytes_read;
- if (pong->bytes_to_read_ == 0) {
- // File has been read to completion. Parse the bytes to get the scores.
- pong->UpdateScoreFromBuffer();
- } else {
- pong->offset_ += bytes_read;
- pong->file_io_->Read(pong->offset_,
- &pong->bytes_buffer_[pong->offset_],
- pong->bytes_to_read_,
- pp::CompletionCallback(ReadCallback, pong));
- }
-}
-
-// Callback that is called as a result of pp::FileIO::Query
-void QueryCallback(void* data, int32_t result) {
- if (result != PP_OK)
- return;
- Pong* pong = static_cast<Pong*>(data);
- pong->bytes_to_read_ = pong->file_info_.size;
- pong->offset_ = 0;
- pong->bytes_buffer_.resize(pong->bytes_to_read_);
-
- // Check if there is anything to read.
- if (pong->bytes_to_read_ != 0) {
- pong->file_io_->Read(pong->offset_,
- &pong->bytes_buffer_[0],
- pong->bytes_to_read_,
- pp::CompletionCallback(ReadCallback, pong));
- }
-}
-
-// Callback that is called as a result of pp::FileIO::Open
-void FileOpenCallback(void*data, int32_t result) {
- if (result != PP_OK) {
- return;
- }
- Pong* pong = static_cast<Pong*>(data);
- // Query the file in order to get the file size.
- pong->file_io_->Query(&pong->file_info_, pp::CompletionCallback(QueryCallback,
- pong));
-}
-
-// Callback that is called as a result of pp::Core::CallOnMainThread
-void UpdateCallback(void* data, int32_t /*result*/) {
- Pong* pong = static_cast<Pong*>(data);
- pong->Update();
-}
-
-} // namespace AsyncCallbacks
-
-class UpdateScheduler {
- public:
- UpdateScheduler(int32_t delay, Pong* pong)
- : delay_(delay), pong_(pong) {}
- ~UpdateScheduler() {
- pp::Core* core = pp::Module::Get()->core();
- core->CallOnMainThread(delay_, pp::CompletionCallback(
- AsyncCallbacks::UpdateCallback, pong_));
- }
-
- private:
- int32_t delay_; // milliseconds
- Pong* pong_; // weak
-};
-
-Pong::Pong(PP_Instance instance)
- : pp::Instance(instance),
- bytes_to_read_(0),
- offset_(0),
- file_io_(NULL),
- file_ref_(NULL),
- file_system_(NULL),
- view_(NULL),
- delta_x_(0),
- delta_y_(0),
- player_score_(0),
- computer_score_(0) {
- // Request to receive input events.
- RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_KEYBOARD);
-}
-
-Pong::~Pong() {
- delete view_;
- file_io_->Close();
- delete file_io_;
- delete file_ref_;
- delete file_system_;
-}
-
-bool Pong::Init(uint32_t argc, const char* argn[], const char* argv[]) {
- view_ = new View(this);
- // Read the score from file.
- file_system_ = new pp::FileSystem(this, PP_FILESYSTEMTYPE_LOCALPERSISTENT);
- file_ref_ = new pp::FileRef(*file_system_, "/pong_score");
- // We kick off a series of callbacks which open a file, query the file for
- // it's length in bytes, read the file contents, and then update the score
- // display based on the file contents.
- int32_t rv = file_system_->Open(
- 1024, pp::CompletionCallback(AsyncCallbacks::FileSystemOpenCallback,
- this));
- if (rv != PP_OK_COMPLETIONPENDING) {
- PostMessage(pp::Var("ERROR: Could not open local persistent file system."));
- return true;
- }
- UpdateScoreDisplay();
- UpdateScheduler(kUpdateInterval, this);
- return true;
-}
-
-void Pong::DidChangeView(const pp::View& view) {
- pp::Size view_size = view_->GetSize();
- const bool view_was_empty = view_size.IsEmpty();
- view_->UpdateView(view.GetRect(), view.GetClipRect(), this);
- if (view_was_empty)
- ResetPositions();
-}
-
-bool Pong::HandleInputEvent(const pp::InputEvent& event) {
- if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEUP) {
- // By notifying the browser mouse clicks are handled, the application window
- // is able to get focus and receive key events.
- return true;
- } else if (event.GetType() == PP_INPUTEVENT_TYPE_KEYUP) {
- pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event);
- return view_->KeyUp(key);
- } else if (event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN) {
- pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event);
- return view_->KeyDown(key);
- }
- return false;
-}
-
-
-void Pong::HandleMessage(const pp::Var& var_message) {
- if (!var_message.is_string())
- return;
- std::string message = var_message.AsString();
- if (message == kResetScoreMethodId) {
- ResetScore();
- }
-}
-
-void Pong::Update() {
- // Schedule another update
- UpdateScheduler(kUpdateInterval, this);
-
- const uint32_t key_code = view_->last_key_code();
- if (key_code == kSpaceBar) {
- ResetPositions();
- return;
- }
- if (ball_.right() < court_.x()) {
- ++computer_score_;
- if (computer_score_ > kMaxPointsAllowed) {
- ResetScore();
- } else {
- WriteScoreToFile();
- UpdateScoreDisplay();
- }
- ResetPositions();
- return;
- }
- if (ball_.x() > court_.right()) {
- ++player_score_;
- if (player_score_ > kMaxPointsAllowed) {
- ResetScore();
- } else {
- WriteScoreToFile();
- UpdateScoreDisplay();
- }
- ResetPositions();
- return;
- }
- // Update human controlled paddle
- if (key_code == kUpArrow) {
- left_paddle_.Offset(0, -kUpdateDistance);
- if (left_paddle_.y() - 1 < court_.y()) {
- left_paddle_.Offset(0, court_.y() - left_paddle_.y() + 1);
- }
- } else if (key_code == kDownArrow) {
- left_paddle_.Offset(0, kUpdateDistance);
- if (left_paddle_.bottom() + 1 > court_.bottom()) {
- left_paddle_.Offset(0, court_.bottom() - left_paddle_.bottom() - 1);
- }
- }
-
- // Update AI controlled paddle
- BallDirection direction = RightPaddleNextMove();
- if (direction == kUpDirection) {
- right_paddle_.Offset(0, -kUpdateDistance);
- if (right_paddle_.y() < court_.y() + 1) {
- right_paddle_.Offset(0, court_.y() - right_paddle_.y() + 1);
- }
- } else if (direction == kDownDirection) {
- right_paddle_.Offset(0, kUpdateDistance);
- if (right_paddle_.bottom() > court_.bottom() - 1) {
- right_paddle_.Offset(0, court_.bottom() - right_paddle_.bottom() - 1);
- }
- }
-
- // Bounce ball off bottom of screen
- if (ball_.bottom() >= court_.bottom() - 1) {
- ball_.Offset(0, court_.bottom() - ball_.bottom() - 1);
- delta_y_ = -delta_y_;
- }
- // Bounce ball off top of screen
- if (ball_.y() <= court_.y() + 1) {
- ball_.Offset(0, court_.y() - ball_.y() + 1);
- delta_y_ = -delta_y_;
- }
- // Bounce ball off human controlled paddle
- if (left_paddle_.Intersects(ball_)) {
- delta_x_ = abs(delta_x_);
- if (ball_.CenterPoint().y() <
- left_paddle_.y() + left_paddle_.height() / 5) {
- delta_y_ -= kUpdateDistance;
- if (delta_y_ == 0)
- delta_y_ = -kUpdateDistance;
- } else if (ball_.CenterPoint().y() >
- left_paddle_.bottom() - left_paddle_.height() / 5) {
- delta_y_ += kUpdateDistance;
- if (delta_y_ == 0)
- delta_y_ = kUpdateDistance;
- }
- }
- // Bounce ball off ai controlled paddle
- if (right_paddle_.Intersects(ball_)) {
- delta_x_ = -abs(delta_x_);
- if (ball_.CenterPoint().y() >
- right_paddle_.bottom() - right_paddle_.height() / 5) {
- delta_y_ += kUpdateDistance;
- if (delta_y_ == 0)
- delta_y_ = kUpdateDistance;
- } else if (ball_.CenterPoint().y() <
- right_paddle_.y() + right_paddle_.height() / 5) {
- delta_y_ -= kUpdateDistance;
- if (delta_y_ == 0)
- delta_y_ -= kUpdateDistance;
- }
- }
-
- // Move ball
- ball_.Offset(delta_x_, delta_y_);
-
- view_->set_ball_rect(ball_);
- view_->set_left_paddle_rect(left_paddle_);
- view_->set_right_paddle_rect(right_paddle_);
- view_->Draw();
-}
-
-void Pong::UpdateScoreFromBuffer() {
- size_t pos = bytes_buffer_.find_first_of(':');
- player_score_ = ::atoi(bytes_buffer_.substr(0, pos).c_str());
- computer_score_ = ::atoi(bytes_buffer_.substr(pos + 1).c_str());
- UpdateScoreDisplay();
-}
-
-void Pong::UpdateScoreFromFile() {
- file_io_ = new pp::FileIO(this);
- file_io_->Open(*file_ref_,
- PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE |
- PP_FILEOPENFLAG_CREATE,
- pp::CompletionCallback(AsyncCallbacks::FileOpenCallback,
- this));
-}
-
-void Pong::WriteScoreToFile() {
- if (file_io_ == NULL)
- return;
- // Write the score in <player score>:<computer score> format.
- char buffer[100];
- snprintf(&buffer[0], sizeof(buffer), "%d:%d", player_score_, computer_score_);
- bytes_buffer_ = std::string(&buffer[0]);
- offset_ = 0; // overwrite score in file.
- file_io_->Write(offset_,
- bytes_buffer_.c_str(),
- bytes_buffer_.length(),
- pp::CompletionCallback(AsyncCallbacks::WriteCallback, this));
-}
-
-void Pong::ResetPositions() {
- pp::Size court_size = view_->GetSize();
- pp::Rect court_rect(court_size);
- court_.SetRect(court_rect);
-
- pp::Rect rect;
- rect.set_width(20);
- rect.set_height(20);
- rect.set_x((court_rect.x() + court_rect.width()) / 2 - rect.width() / 2);
- rect.set_y(court_rect.y() + court_rect.height() - rect.height());
- ball_.SetRect(rect);
-
- const float paddle_width = 10;
- const float paddle_height = 99;
- const float paddle_pos_y =
- (court_rect.y() + court_rect.height()) / 2 - rect.height() / 2;
- rect.set_width(paddle_width);
- rect.set_height(paddle_height);
- rect.set_x(court_rect.x() + court_rect.width() / 5 + 1);
- rect.set_y(paddle_pos_y);
- left_paddle_.SetRect(rect);
-
- rect.set_width(paddle_width);
- rect.set_height(paddle_height);
- rect.set_x(court_rect.x() + 4 * court_rect.width() / 5 - rect.width() - 1);
- rect.set_y(paddle_pos_y);
- right_paddle_.SetRect(rect);
-
- delta_x_ = delta_y_ = kUpdateDistance;
-}
-
-void Pong::ResetScore() {
- player_score_ = 0;
- computer_score_ = 0;
- UpdateScoreDisplay();
-}
-
-Pong::BallDirection Pong::RightPaddleNextMove() const {
- static int32_t last_ball_y = 0;
- int32_t ball_y = ball_.CenterPoint().y();
- BallDirection ball_direction =
- ball_y < last_ball_y ? kUpDirection : kDownDirection;
- last_ball_y = ball_y;
-
- if (ball_y < right_paddle_.y())
- return kUpDirection;
- if (ball_y > right_paddle_.bottom())
- return kDownDirection;
- return ball_direction;
-}
-
-void Pong::UpdateScoreDisplay() {
- char buffer[100];
- snprintf(&buffer[0], sizeof(buffer), "You %d: Computer %d", player_score_,
- computer_score_);
- PostMessage(pp::Var(buffer));
-}
-
-} // namespace pong
diff --git a/native_client_sdk/src/examples/pong/pong.h b/native_client_sdk/src/examples/pong/pong.h
deleted file mode 100644
index 5887a53..0000000
--- a/native_client_sdk/src/examples/pong/pong.h
+++ /dev/null
@@ -1,92 +0,0 @@
-// 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.
-
-#ifndef EXAMPLES_PONG_PONG_H_
-#define EXAMPLES_PONG_PONG_H_
-
-#include <string>
-
-#include "ppapi/c/pp_file_info.h"
-#include "ppapi/cpp/graphics_2d.h"
-#include "ppapi/cpp/image_data.h"
-#include "ppapi/cpp/instance.h"
-#include "ppapi/cpp/rect.h"
-#include "ppapi/cpp/size.h"
-
-namespace pp {
-class FileIO;
-class FileRef;
-class FileSystem;
-class Rect;
-} // namespace pp
-
-namespace pong {
-
-class View;
-
-// The Instance class. One of these exists for each instance of your NaCl
-// module on the web page. The browser will ask the Module object to create
-// a new Instance for each occurrence of the <embed> tag that has these
-// attributes:
-// type="application/x-nacl"
-// nacl="pong.nmf"
-class Pong : public pp::Instance {
- public:
- explicit Pong(PP_Instance instance);
- virtual ~Pong();
-
- // Open the file (if available) that stores the game scores.
- virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
-
- // Update the graphics context to the new size, and regenerate |pixel_buffer_|
- // to fit the new size as well.
- virtual void DidChangeView(const pp::View& view);
-
- virtual bool HandleInputEvent(const pp::InputEvent& event);
-
- // Called by the browser to handle the postMessage() call in Javascript.
- // The message in this case is expected to contain the string 'update', or
- // 'resetScore' in order to invoke either the Update or ResetScore function
- // respectively.
- virtual void HandleMessage(const pp::Var& var_message);
-
- void Update();
- void UpdateScoreFromBuffer();
- void UpdateScoreFromFile();
- void WriteScoreToFile();
-
- PP_FileInfo file_info_;
- int32_t bytes_to_read_;
- int64_t offset_;
- pp::FileIO* file_io_;
- pp::FileRef* file_ref_;
- pp::FileSystem* file_system_;
- std::string bytes_buffer_;
-
- private:
- Pong(const Pong&); // Disallow copy
-
- enum BallDirection {
- kUpDirection = 0,
- kDownDirection
- };
- void ResetPositions();
- void ResetScore();
- BallDirection RightPaddleNextMove() const;
- void UpdateScoreDisplay();
-
- View* view_;
- pp::Rect left_paddle_;
- pp::Rect right_paddle_;
- pp::Rect ball_;
- pp::Rect court_;
- int32_t delta_x_;
- int32_t delta_y_;
- int player_score_;
- int computer_score_;
-};
-
-} // namespace pong
-
-#endif // EXAMPLES_PONG_PONG_H_
diff --git a/native_client_sdk/src/examples/pong/pong_input.cc b/native_client_sdk/src/examples/pong/pong_input.cc
new file mode 100644
index 0000000..4d3f903
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/pong_input.cc
@@ -0,0 +1,53 @@
+// 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 "pong_input.h"
+
+#include <stdlib.h>
+
+namespace {
+
+// Input event key codes. PPAPI uses Windows Virtual key codes.
+const uint32_t kUpArrow = 0x26;
+const uint32_t kDownArrow = 0x28;
+
+/** When the distance between the ball and the center of the paddle is within
+ * kPaddleAIDeadZone pixels, don't move the paddle. This keeps the paddle from
+ * bouncing if it can move faster than the ball.
+ */
+const int kPaddleAIDeadZone = 20;
+
+}
+
+PongInputKeyboard::PongInputKeyboard(PongInputKeyboardDelegate* delegate)
+ : delegate_(delegate) {
+}
+
+MoveDirection PongInputKeyboard::GetMove(const PongModel& model,
+ bool is_left_paddle) {
+ if (delegate_->IsKeyDown(kUpArrow))
+ return MOVE_UP;
+ else if (delegate_->IsKeyDown(kDownArrow))
+ return MOVE_DOWN;
+ else
+ return MOVE_NONE;
+}
+
+MoveDirection PongInputAI::GetMove(const PongModel& model,
+ bool is_left_paddle) {
+ // A highly advanced AI algorithm that moves the paddle toward the y position
+ // of the ball.
+ const PaddleModel& paddle = is_left_paddle ? model.left_paddle() :
+ model.right_paddle();
+ int ball_center_y = model.ball().rect.CenterPoint().y();
+ int paddle_center_y = paddle.rect.CenterPoint().y();
+ int distance_y = labs(paddle_center_y - ball_center_y);
+
+ if (distance_y < kPaddleAIDeadZone)
+ return MOVE_NONE;
+ else if (paddle_center_y > ball_center_y)
+ return MOVE_UP;
+ else
+ return MOVE_DOWN;
+}
diff --git a/native_client_sdk/src/examples/pong/pong_input.h b/native_client_sdk/src/examples/pong/pong_input.h
new file mode 100644
index 0000000..85bcf08
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/pong_input.h
@@ -0,0 +1,36 @@
+// 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.
+
+#ifndef EXAMPLES_PONG_PONG_INPUT_H_
+#define EXAMPLES_PONG_PONG_INPUT_H_
+
+#include "pong_model.h"
+
+class PongInput {
+ public:
+ virtual ~PongInput() {}
+ virtual MoveDirection GetMove(const PongModel& model,
+ bool is_left_paddle) = 0;
+};
+
+class PongInputKeyboardDelegate {
+ public:
+ virtual bool IsKeyDown(int key_code) = 0;
+};
+
+class PongInputKeyboard : public PongInput {
+ public:
+ PongInputKeyboard(PongInputKeyboardDelegate* delegate);
+ virtual MoveDirection GetMove(const PongModel& model, bool is_left_paddle);
+
+ private:
+ PongInputKeyboardDelegate* delegate_; // Weak pointer.
+};
+
+class PongInputAI : public PongInput {
+ public:
+ virtual MoveDirection GetMove(const PongModel& model, bool is_left_paddle);
+};
+
+#endif // EXAMPLES_PONG_PONG_INPUT_H_
diff --git a/native_client_sdk/src/examples/pong/pong_instance.cc b/native_client_sdk/src/examples/pong/pong_instance.cc
new file mode 100644
index 0000000..3da5df9
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/pong_instance.cc
@@ -0,0 +1,142 @@
+// 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 <stdio.h>
+#include <string.h>
+#include <cmath>
+#include <string>
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/input_event.h"
+#include "ppapi/cpp/rect.h"
+#include "ppapi/cpp/var.h"
+
+#include "pong_instance.h"
+#include "pong_view.h"
+
+namespace {
+
+const uint32_t kUpArrow = 0x26;
+const uint32_t kDownArrow = 0x28;
+
+const int32_t kMaxPointsAllowed = 256;
+const int32_t kUpdateInterval = 17; // milliseconds
+
+const char kResetScoreMethodId[] = "resetScore";
+
+} // namespace
+
+PongInstance::PongInstance(PP_Instance instance)
+ : pp::Instance(instance),
+ factory_(this),
+ model_(NULL),
+ view_(NULL),
+ is_initial_view_change_(true) {
+ // Request to receive input events.
+ RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_KEYBOARD);
+}
+
+PongInstance::~PongInstance() {
+ delete right_paddle_input_;
+ delete left_paddle_input_;
+ delete view_;
+ delete model_;
+}
+
+bool PongInstance::Init(uint32_t argc, const char* argn[], const char* argv[]) {
+ model_ = new PongModel(this);
+ view_ = new PongView(model_);
+ left_paddle_input_ = new PongInputKeyboard(this);
+ right_paddle_input_ = new PongInputAI();
+
+ UpdateScoreDisplay();
+ return true;
+}
+
+void PongInstance::DidChangeView(const pp::View& view) {
+ if (!view_->DidChangeView(this, view, is_initial_view_change_)) {
+ PostMessage(pp::Var(
+ "ERROR DidChangeView failed. Could not bind graphics?"));
+ return;
+ }
+
+ model_->SetCourtSize(view_->GetSize());
+ model_->ResetPositions();
+
+ if (is_initial_view_change_) {
+ ScheduleUpdate();
+ is_initial_view_change_ = false;
+ }
+}
+
+bool PongInstance::HandleInputEvent(const pp::InputEvent& event) {
+ if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEUP ||
+ event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN) {
+ // By notifying the browser mouse clicks are handled, the application window
+ // is able to get focus and receive key events.
+ return true;
+ } else if (event.GetType() == PP_INPUTEVENT_TYPE_KEYUP) {
+ pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event);
+ key_map_[key.GetKeyCode()] = false;
+ return true;
+ } else if (event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN) {
+ pp::KeyboardInputEvent key = pp::KeyboardInputEvent(event);
+ key_map_[key.GetKeyCode()] = true;
+ return true;
+ }
+ return false;
+}
+
+void PongInstance::HandleMessage(const pp::Var& var_message) {
+ if (!var_message.is_string())
+ return;
+ std::string message = var_message.AsString();
+ if (message == kResetScoreMethodId) {
+ ResetScore();
+ }
+}
+
+void PongInstance::OnScoreChanged() {
+ if (model_->left_score() > kMaxPointsAllowed ||
+ model_->right_score() > kMaxPointsAllowed) {
+ ResetScore();
+ return;
+ }
+
+ UpdateScoreDisplay();
+}
+
+void PongInstance::OnPlayerScored() {
+ model_->ResetPositions();
+}
+
+bool PongInstance::IsKeyDown(int key_code) {
+ return key_map_[key_code];
+}
+
+void PongInstance::ScheduleUpdate() {
+ pp::Module::Get()->core()->CallOnMainThread(
+ kUpdateInterval,
+ factory_.NewCallback(&PongInstance::UpdateCallback));
+}
+
+void PongInstance::UpdateCallback(int32_t result) {
+ // This is the game loop; UpdateCallback schedules another call to itself to
+ // occur kUpdateInterval milliseconds later.
+ ScheduleUpdate();
+
+ MoveDirection left_move = left_paddle_input_->GetMove(*model_, true);
+ MoveDirection right_move = right_paddle_input_->GetMove(*model_, false);
+ model_->Update(left_move, right_move);
+}
+
+void PongInstance::ResetScore() {
+ model_->SetScore(0, 0);
+}
+
+void PongInstance::UpdateScoreDisplay() {
+ char buffer[100];
+ snprintf(&buffer[0], sizeof(buffer), "You %d: Computer %d",
+ model_->left_score(), model_->right_score());
+ PostMessage(pp::Var(buffer));
+}
diff --git a/native_client_sdk/src/examples/pong/pong_instance.h b/native_client_sdk/src/examples/pong/pong_instance.h
new file mode 100644
index 0000000..d571b7d
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/pong_instance.h
@@ -0,0 +1,65 @@
+// 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.
+
+#ifndef EXAMPLES_PONG_PONG_H_
+#define EXAMPLES_PONG_PONG_H_
+
+#include <map>
+#include "ppapi/cpp/instance.h"
+#include "ppapi/utility/completion_callback_factory.h"
+#include "pong_input.h"
+#include "pong_model.h"
+
+class PongView;
+
+class PongInstance : public pp::Instance,
+ public PongModelDelegate,
+ public PongInputKeyboardDelegate {
+ public:
+ explicit PongInstance(PP_Instance instance);
+ virtual ~PongInstance();
+
+ /** Called when the module is initialized. */
+ virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]);
+
+ /** Called when the module's size changes, or when its visibility changes. */
+ virtual void DidChangeView(const pp::View& view);
+
+ /** Called when the module is given mouse or keyboard input. */
+ virtual bool HandleInputEvent(const pp::InputEvent& event);
+
+ /** Called when JavaScript calls postMessage() on this embedded module.
+ * The only message handled here is 'resetScore', which resets the score for
+ * both players to 0.
+ */
+ virtual void HandleMessage(const pp::Var& var_message);
+
+ // PongModelDelegate implementation.
+ virtual void OnScoreChanged();
+ virtual void OnPlayerScored();
+
+ // PongInputKeyboardDelegate implementation.
+ virtual bool IsKeyDown(int key_code);
+
+ private:
+ void ScheduleUpdate();
+ void UpdateCallback(int32_t result);
+
+ void ResetScore();
+ void UpdateScoreDisplay();
+
+ pp::CompletionCallbackFactory<PongInstance> factory_;
+ PongModel* model_;
+ PongView* view_;
+ PongInput* left_paddle_input_;
+ PongInput* right_paddle_input_;
+ std::map<uint32_t, bool> key_map_;
+ bool is_initial_view_change_;
+
+ // Disallow copy constructor and assignment operator.
+ PongInstance(const PongInstance&);
+ PongInstance& operator=(const PongInstance&);
+};
+
+#endif // EXAMPLES_PONG_PONG_H_
diff --git a/native_client_sdk/src/examples/pong/pong_model.cc b/native_client_sdk/src/examples/pong/pong_model.cc
new file mode 100644
index 0000000..20592fa
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/pong_model.cc
@@ -0,0 +1,303 @@
+// 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 "pong_model.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstdlib>
+#include <cstdio>
+#include <limits>
+
+namespace {
+
+/** Width of the ball in pixels. */
+const int kBallWidth = 20;
+/** Height of the ball in pixels. */
+const int kBallHeight = 20;
+/** Width of each paddle in pixels. */
+const int kPaddleWidth = 10;
+/** Height of each paddle in pixels. */
+const int kPaddleHeight = 99;
+/** Starting X position of the ball, in the range [0, 1]. */
+const float kBallStartX = 0.5f;
+/** Starting Y position of the ball, in the range [0, 1]. */
+const float kBallStartY = 0.9f;
+/** Starting X position of the left paddle, in the range [0, 1]. */
+const float kLeftPaddleStartX = 0.2f;
+/** Starting Y position of the left paddle, in the range [0, 1]. */
+const float kLeftPaddleStartY = 0.5f;
+/** Starting X position of the right paddle, in the range [0, 1]. */
+const float kRightPaddleStartX = 0.8f;
+/** Starting Y position of the right paddle, in the range [0, 1]. */
+const float kRightPaddleStartY = 0.5f;
+/** The number of pixels the ball moves along each axis, per update, by
+ * default. */
+const int kBallUpdateDistance = 4;
+/** The number of pixels the paddle moves per update. */
+const int kPaddleUpdateDistance = 4;
+/** If the ball hits the paddle in the range [0, kPaddleSpinUp), then the ball
+ * will bounce up (even if it was moving down). */
+const float kPaddleSpinUp = 0.2f;
+/** If the ball hits the paddle in the range (kPaddleSpinDown, 1], then the ball
+ * will bounce down (even if it was moving up). */
+const float kPaddleSpinDown = 0.8f;
+
+/**
+ * Gets the position of an object in a given range.
+ * This functions ensures that if fraction is in the range [0, 1], then the
+ * return value will be in the range [0, region_max - object_width].
+ *
+ * fraction == 0.0: return => region_min
+ * fraction == 0.5: return => (region_min + region_max - object_width) / 2
+ * fraction == 1.0: return => region_max - object_width
+ *
+ * @param[in] region_min The minimum value of the range.
+ * @param[in] region_max The maximum value of the range.
+ * @param[in] object_width The width of the object.
+ * @param[in] fraction A value in the range [0, 1].
+ * @return The left position of the object, in the range [0, region_max -
+ * object_width].
+ */
+int GetFractionalPos(int region_min, int region_max,
+ int object_width,
+ float fraction) {
+ return region_min +
+ static_cast<int>((region_max - object_width - region_min) * fraction);
+}
+
+/**
+ * Set the position of a pp::Rect (i.e. ball or paddle) given the court size.
+ *
+ * @param[in] court The bounds to place |object| in.
+ * @param[in,out] object The object to place in |court|.
+ * @param[in] x_fraction A value in the range [0, 1]. 0 is on the left, 1 is on
+ * the right of the court.
+ * @param[in] y_fraction A value in the range [0, 1]. 0 is on the top, 1 is on
+ * the bottom of the court.
+ */
+void SetFractionalPosition(const pp::Rect& court, pp::Rect* object,
+ float x_fraction, float y_fraction) {
+ object->set_x(GetFractionalPos(court.x(), court.right(), object->width(),
+ x_fraction));
+ object->set_y(GetFractionalPos(court.y(), court.bottom(), object->height(),
+ y_fraction));
+}
+
+} // namespace
+
+PaddleModel::PaddleModel()
+ : rect(kPaddleWidth, kPaddleHeight) {
+}
+
+void PaddleModel::SetPosition(const pp::Rect& court,
+ float x_fraction, float y_fraction) {
+ SetFractionalPosition(court, &rect, x_fraction, y_fraction);
+}
+
+void PaddleModel::Move(const pp::Rect& court,
+ const BallModel& ball,
+ MoveDirection dir) {
+ if (dir == MOVE_NONE)
+ return;
+
+ int dy = dir == MOVE_UP ? -kPaddleUpdateDistance : kPaddleUpdateDistance;
+ rect.Offset(0, dy);
+
+ // Don't allow the paddle to move on top of the ball.
+ if (rect.Intersects(ball.rect)) {
+ rect.Offset(0, -dy);
+ return;
+ }
+
+ if (rect.y() < court.y()) {
+ rect.set_y(court.y());
+ } else if (rect.bottom() > court.bottom()) {
+ rect.set_y(court.bottom() - rect.height());
+ }
+}
+
+PaddleCollision PaddleModel::GetPaddleCollision(float collision_y) const {
+ float paddle_relative_y_fraction = (collision_y - rect.y()) / rect.height();
+ if (paddle_relative_y_fraction < kPaddleSpinUp)
+ return PADDLE_COLLISION_SPIN_UP;
+ else if (paddle_relative_y_fraction > kPaddleSpinDown)
+ return PADDLE_COLLISION_SPIN_DOWN;
+ else
+ return PADDLE_COLLISION_NORMAL;
+}
+
+BallModel::BallModel()
+ : rect(kBallWidth, kBallHeight),
+ dx(kBallUpdateDistance),
+ dy(kBallUpdateDistance) {
+}
+
+void BallModel::SetPosition(const pp::Rect& court,
+ float x_fraction, float y_fraction) {
+ SetFractionalPosition(court, &rect, x_fraction, y_fraction);
+}
+
+void BallModel::Move(float dt) {
+ rect.Offset(static_cast<int>(dt * dx), static_cast<int>(dt * dy));
+}
+
+float BallModel::GetPaddleCollisionTime(const PaddleModel& paddle) const {
+ assert(!rect.Intersects(paddle.rect));
+
+ // Calculate the range of time when the ball will be colliding with the x
+ // range of the paddle.
+ float float_dx = static_cast<float>(dx);
+ float x_collision_t_min = (paddle.rect.right() - rect.x()) / float_dx;
+ float x_collision_t_max = (paddle.rect.x() - rect.right()) / float_dx;
+ if (x_collision_t_min > x_collision_t_max)
+ std::swap(x_collision_t_min, x_collision_t_max);
+
+ // If the last point in time that the ball is colliding, along the x axis, is
+ // negative, then the ball would have to move backward to collide with the
+ // paddle.
+ if (x_collision_t_max <= 0)
+ return std::numeric_limits<float>::max();
+
+ // Calculate the range of time when the ball will be colliding with the y
+ // range of the paddle.
+ float float_dy = static_cast<float>(dy);
+ float y_collision_t_min = (paddle.rect.bottom() - rect.y()) / float_dy;
+ float y_collision_t_max = (paddle.rect.y() - rect.bottom()) / float_dy;
+ if (y_collision_t_min > y_collision_t_max)
+ std::swap(y_collision_t_min, y_collision_t_max);
+
+ // See above for the early out logic.
+ if (y_collision_t_max <= 0)
+ return std::numeric_limits<float>::max();
+
+ // The ball will be colliding with the paddle only when the x range and the y
+ // ranges intersect. The first time this happens is the whichever happens
+ // later: entering the x range or entering the y range.
+ // However, if either range's maximum is less than this value, then the
+ // ranges are disjoint, and the ball does not collide with the paddle (it
+ // will pass by it instead).
+ float max_of_mins = std::max(x_collision_t_min, y_collision_t_min);
+ if (x_collision_t_max > max_of_mins && y_collision_t_max > max_of_mins)
+ return max_of_mins;
+
+ return std::numeric_limits<float>::max();
+}
+
+float BallModel::GetCourtCollisionTime(const pp::Rect& court) const {
+ assert(rect.y() >= court.y());
+ assert(rect.bottom() <= court.bottom());
+
+ // When the ball is moving up, only check for collision with the top of the
+ // court. Likewise, if the ball is moving down only check for collision with
+ // the bottom of the court.
+ float y_collision_t = dy < 0 ?
+ (court.y() - rect.y()) / static_cast<float>(dy) :
+ (court.bottom() - rect.bottom()) / static_cast<float>(dy);
+
+ return y_collision_t;
+}
+
+void BallModel::ApplyPaddleCollision(PaddleCollision collision) {
+ switch (collision) {
+ case PADDLE_COLLISION_SPIN_UP:
+ dy -= kBallUpdateDistance;
+ if (dy == 0)
+ dy -= kBallUpdateDistance;
+ break;
+ case PADDLE_COLLISION_SPIN_DOWN:
+ dy += kBallUpdateDistance;
+ if (dy == 0)
+ dy += kBallUpdateDistance;
+ break;
+ case PADDLE_COLLISION_NORMAL:
+ default:
+ break;
+ }
+}
+
+PongModel::PongModel(PongModelDelegate* delegate)
+ : delegate_(delegate),
+ left_score_(0),
+ right_score_(0) {
+}
+
+void PongModel::SetCourtSize(const pp::Size& size) {
+ court_.set_size(size);
+}
+
+void PongModel::SetScore(int left_score, int right_score) {
+ left_score_ = left_score;
+ right_score_ = right_score;
+ delegate_->OnScoreChanged();
+}
+
+void PongModel::ResetPositions() {
+ ball_.SetPosition(court_, kBallStartX, kBallStartY);
+ left_paddle_.SetPosition(court_, kLeftPaddleStartX, kLeftPaddleStartY);
+ right_paddle_.SetPosition(court_, kRightPaddleStartX, kRightPaddleStartY);
+ ball_.dx = kBallUpdateDistance;
+ ball_.dy = kBallUpdateDistance;
+}
+
+void PongModel::Update(MoveDirection left_movement,
+ MoveDirection right_movement) {
+ left_paddle_.Move(court_, ball_, left_movement);
+ right_paddle_.Move(court_, ball_, right_movement);
+ UpdateBall();
+
+ if (ball_.rect.x() < court_.x()) {
+ right_score_++;
+ delegate_->OnScoreChanged();
+ delegate_->OnPlayerScored();
+ } else if (ball_.rect.right() > court_.right()) {
+ left_score_++;
+ delegate_->OnScoreChanged();
+ delegate_->OnPlayerScored();
+ }
+}
+
+void PongModel::UpdateBall() {
+ // If the ball's update distance is large enough, it could collide with
+ // multiple objects in one update.
+ // To detect this, we calculate when the ball will collide with all objects.
+ // If any collisions occur in this update, move the ball to this collision
+ // point, and repeat with the rest of the time that's left.
+ float dt = 1;
+ while (dt > 0) {
+ float left_paddle_t = ball_.GetPaddleCollisionTime(left_paddle_);
+ float right_paddle_t = ball_.GetPaddleCollisionTime(right_paddle_);
+ float min_paddle_t = std::min(left_paddle_t, right_paddle_t);
+ float court_t = ball_.GetCourtCollisionTime(court_);
+ float min_t = std::min(court_t, min_paddle_t);
+
+ if (min_t > dt) {
+ // The ball doesn't hit anything this time.
+ ball_.Move(dt);
+ return;
+ }
+
+ // Move the ball up to the point of collision.
+ ball_.Move(min_t);
+ dt -= min_t;
+
+ if (court_t < min_paddle_t) {
+ // Just bounce off the court, flip ball's dy.
+ ball_.dy = -ball_.dy;
+ } else {
+ // Bouncing off the paddle flips the ball's dx, but also can potentially
+ // add "spin" when the ball bounces off the top or bottom of the paddle.
+ ball_.dx = -ball_.dx;
+
+ float collision_y = ball_.rect.y() + ball_.rect.height() / 2;
+ PaddleCollision paddle_collision;
+ if (left_paddle_t < right_paddle_t)
+ paddle_collision = left_paddle_.GetPaddleCollision(collision_y);
+ else
+ paddle_collision = right_paddle_.GetPaddleCollision(collision_y);
+
+ ball_.ApplyPaddleCollision(paddle_collision);
+ }
+ }
+}
diff --git a/native_client_sdk/src/examples/pong/pong_model.h b/native_client_sdk/src/examples/pong/pong_model.h
new file mode 100644
index 0000000..d864e27
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/pong_model.h
@@ -0,0 +1,153 @@
+// 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.
+
+#ifndef EXAMPLES_PONG_PONG_MODEL_H_
+#define EXAMPLES_PONG_PONG_MODEL_H_
+
+#include "ppapi/cpp/rect.h"
+#include "ppapi/cpp/size.h"
+
+struct BallModel;
+
+enum MoveDirection {
+ MOVE_NONE,
+ MOVE_UP,
+ MOVE_DOWN
+};
+
+enum PaddleCollision {
+ PADDLE_COLLISION_NORMAL,
+ PADDLE_COLLISION_SPIN_UP,
+ PADDLE_COLLISION_SPIN_DOWN
+};
+
+struct PaddleModel {
+ PaddleModel();
+
+ /**
+ * Set the position of the paddle, given a court.
+ * @param[in] court The bounds to place the paddle in.
+ * @param[in] x_fraction A value in the range [0, 1]. 0 is on the left, 1 is
+ * on the right of the court.
+ * @param[in] y_fraction A value in the range [0, 1]. 0 is on the top, 1 is on
+ * the bottom of the court.
+ */
+ void SetPosition(const pp::Rect& court, float x_fraction, float y_fraction);
+
+ /**
+ * Move the paddle, given |dir|. The paddle will not move in if it will
+ * collide with |ball| or exit |court| after moving.
+ * @param[in] court The bounds to move the ball in.
+ * @param[in] ball The ball to avoid colliding with.
+ * @param[in] dir The direction to move the paddle.
+ */
+ void Move(const pp::Rect& court, const BallModel& ball, MoveDirection dir);
+
+ /**
+ * Given that a ball has collided with this paddle, determine if "spin"
+ * should be applied to the ball.
+ * @param[in] collision_y The y-coordinate of the ball that collided with the
+ * paddle.
+ * @return The paddle collision type.
+ */
+ PaddleCollision GetPaddleCollision(float collision_y) const;
+
+ pp::Rect rect;
+};
+
+struct BallModel {
+ BallModel();
+
+ /**
+ * Set the position of the ball, given a court.
+ * @param[in] court The bounds to place the ball in.
+ * @param[in] x_fraction A value in the range [0, 1]. 0 is on the left, 1 is
+ * on the right of the court.
+ * @param[in] y_fraction A value in the range [0, 1]. 0 is on the top, 1 is on
+ * the bottom of the court.
+ */
+ void SetPosition(const pp::Rect& court, float x_fraction, float y_fraction);
+
+ /** Move the ball in its current direction.
+ * @param[in] dt A time delta value in the range [0, 1]. The ball will move
+ * its update distance, multiplied by dt for each axis.
+ * For example, if dt==1, the ball will move its full update distance. If
+ * dt==0.5, it will move half its update distance, etc.
+ */
+ void Move(float dt);
+
+ /** Given that the ball collided with the paddle, determine how it should
+ * bounce off it, applying "spin". If the ball hits the top of the paddle it
+ * will spin up, if the ball hits the bottom of the paddle it will spin down.
+ * @param[in] collision The type of collision with the paddle.
+ */
+ void ApplyPaddleCollision(PaddleCollision collision);
+
+ /** Calculate the time (as a fraction of the number of updates), until the
+ * ball collides with |paddle|.
+ * @param[in] paddle The paddle to test for collision with.
+ * @return The time (maybe negative) when the ball will collide with the
+ * paddle.
+ * If the time is in the range [0, 1], the ball will collide with the
+ * paddle during this update.
+ * If the time is > 1, then the ball will collide with the paddle after
+ * ceil(time) updates, assuming the paddle doesn't move.
+ * Because we want to find the most recent collision (i.e. smallest time
+ * value), we use the value FLT_MAX to represent a collision that will
+ * never happen (the paddle is behind the ball).
+ */
+ float GetPaddleCollisionTime(const PaddleModel& paddle) const;
+
+ /** Calculate the time (as a fraction of the number of updates), until the
+ * ball collides with |court|.
+ * @param[in] court The bounds of the court to test for collision with.
+ * @return The time (maybe negative) when the ball will collide with bottom
+ * or top of the court.
+ * @see GetPaddleCollisionTime to understand the meaning of the return time.
+ */
+ float GetCourtCollisionTime(const pp::Rect& court) const;
+
+ pp::Rect rect;
+ int dx;
+ int dy;
+};
+
+class PongModelDelegate {
+ public:
+ /** Called when the score changes, whether due to PongModel::SetScore, or
+ * when a player scores. */
+ virtual void OnScoreChanged() = 0;
+ /** Called only when a player scores, not when the score is changed
+ * programatically. */
+ virtual void OnPlayerScored() = 0;
+};
+
+class PongModel {
+ public:
+ explicit PongModel(PongModelDelegate* delegate);
+
+ void SetCourtSize(const pp::Size& size);
+ void SetScore(int left_score, int right_score);
+ void ResetPositions();
+ void Update(MoveDirection left_movement, MoveDirection right_movement);
+
+ const PaddleModel& left_paddle() const { return left_paddle_; }
+ const PaddleModel& right_paddle() const { return right_paddle_; }
+ const BallModel& ball() const { return ball_; }
+ int left_score() const { return left_score_; }
+ int right_score() const { return right_score_; }
+
+ private:
+ void UpdateBall();
+
+ PongModelDelegate* delegate_; // Weak pointer.
+ PaddleModel left_paddle_;
+ PaddleModel right_paddle_;
+ BallModel ball_;
+ pp::Rect court_;
+ int left_score_;
+ int right_score_;
+};
+
+#endif // EXAMPLES_PONG_PONG_MODEL_H_
diff --git a/native_client_sdk/src/examples/pong/pong_module.cc b/native_client_sdk/src/examples/pong/pong_module.cc
index 6d330aa..2d05a29 100644
--- a/native_client_sdk/src/examples/pong/pong_module.cc
+++ b/native_client_sdk/src/examples/pong/pong_module.cc
@@ -4,9 +4,8 @@
#include <ppapi/cpp/module.h>
-#include "pong.h"
+#include "pong_instance.h"
-namespace pong {
// The Module class. The browser calls the CreateInstance() method to create
// an instance of your NaCl module on the web page. The browser creates a new
// instance for each <embed> tag with type="application/x-nacl".
@@ -15,12 +14,11 @@ class PongModule : public pp::Module {
PongModule() : pp::Module() {}
virtual ~PongModule() {}
- // Create and return a PiGeneratorInstance object.
+ // Create and return a PongInstance object.
virtual pp::Instance* CreateInstance(PP_Instance instance) {
- return new Pong(instance);
+ return new PongInstance(instance);
}
};
-} // namespace pong
// Factory function called by the browser when the module is first loaded.
// The browser keeps a singleton of this module. It calls the
@@ -29,6 +27,6 @@ class PongModule : public pp::Module {
// point for your NaCl module with the browser.
namespace pp {
Module* CreateModule() {
- return new pong::PongModule();
+ return new PongModule();
}
} // namespace pp
diff --git a/native_client_sdk/src/examples/pong/pong_view.cc b/native_client_sdk/src/examples/pong/pong_view.cc
new file mode 100644
index 0000000..35fec2c
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/pong_view.cc
@@ -0,0 +1,204 @@
+// 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 "pong_view.h"
+
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/graphics_2d.h"
+#include "ppapi/cpp/image_data.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/instance_handle.h"
+#include "ppapi/cpp/point.h"
+
+namespace {
+
+const uint32_t kBlack = 0xff000000;
+const uint32_t kWhite = 0xffffffff;
+
+pp::Rect ClipRect(const pp::Rect& rect, const pp::Rect& clip) {
+ return clip.Intersect(rect);
+}
+
+} // namespace
+
+PongView::PongView(PongModel* model)
+ : factory_(this),
+ model_(model),
+ graphics_2d_(NULL),
+ pixel_buffer_(NULL),
+ needs_erase_(false) {
+}
+
+PongView::~PongView() {
+ delete graphics_2d_;
+ delete pixel_buffer_;
+}
+
+bool PongView::DidChangeView(pp::Instance* instance,
+ const pp::View& view,
+ bool first_view_change) {
+ pp::Size old_size = GetSize();
+ pp::Size new_size = view.GetRect().size();
+ if (old_size == new_size)
+ return true;
+
+ delete graphics_2d_;
+ graphics_2d_ = new pp::Graphics2D(instance, new_size,
+ true); // is_always_opaque
+ if (!instance->BindGraphics(*graphics_2d_)) {
+ delete graphics_2d_;
+ graphics_2d_ = NULL;
+ return false;
+ }
+
+ // Create a new pixel buffer, the same size as the graphics context. We'll
+ // write to this buffer directly, and copy regions of it to the graphics
+ // context's backing store to draw to the screen.
+ delete pixel_buffer_;
+ pixel_buffer_ = new pp::ImageData(instance, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
+ new_size,
+ true); // init_to_zero
+
+ needs_erase_ = false;
+
+ if (first_view_change)
+ DrawCallback(0); // Start the draw loop.
+
+ return true;
+}
+
+pp::Size PongView::GetSize() const {
+ return graphics_2d_ ? graphics_2d_->size() : pp::Size();
+}
+
+void PongView::DrawCallback(int32_t result) {
+ assert(graphics_2d_);
+ assert(pixel_buffer_);
+
+ if (needs_erase_) {
+ EraseBall(old_ball_);
+ ErasePaddle(old_left_paddle_);
+ ErasePaddle(old_right_paddle_);
+ }
+
+ // Draw the image to pixel_buffer_.
+ DrawBall(model_->ball());
+ DrawPaddle(model_->left_paddle());
+ DrawPaddle(model_->right_paddle());
+
+ // Paint to the graphics context's backing store.
+ if (needs_erase_) {
+ // Update the regions for the old positions of the ball and both paddles,
+ // as well as the new positions.
+ PaintRectToGraphics2D(old_ball_.rect, model_->ball().rect);
+ PaintRectToGraphics2D(old_left_paddle_.rect, model_->left_paddle().rect);
+ PaintRectToGraphics2D(old_right_paddle_.rect, model_->right_paddle().rect);
+ } else {
+ // Only update the regions for the new positions. The old positions are
+ // invalid.
+ PaintRectToGraphics2D(model_->ball().rect);
+ PaintRectToGraphics2D(model_->left_paddle().rect);
+ PaintRectToGraphics2D(model_->right_paddle().rect);
+ }
+
+ // Save the locations of the ball and paddles to erase next time.
+ old_ball_ = model_->ball();
+ old_left_paddle_ = model_->left_paddle();
+ old_right_paddle_ = model_->right_paddle();
+ needs_erase_ = true;
+
+ // Graphics2D::Flush writes all paints to the graphics context's backing
+ // store. When it is finished, it calls the callback. By hooking our draw
+ // function to the Flush callback, we will be able to draw as quickly as
+ // possible.
+ graphics_2d_->Flush(factory_.NewCallback(&PongView::DrawCallback));
+}
+
+void PongView::DrawRect(const pp::Rect& rect, uint32_t color) {
+ assert(pixel_buffer_);
+ uint32_t* pixel_data = static_cast<uint32_t*>(pixel_buffer_->data());
+ assert(pixel_data);
+
+ int width = pixel_buffer_->size().width();
+ int height = pixel_buffer_->size().height();
+
+ // We shouldn't be drawing rectangles outside of the pixel buffer bounds,
+ // but just in case, clip the rectangle first.
+ pp::Rect clipped_rect = ClipRect(rect, pp::Rect(pixel_buffer_->size()));
+ int offset = clipped_rect.y() * width + clipped_rect.x();
+
+ for (int y = 0; y < clipped_rect.height(); ++y) {
+ for (int x = 0; x < clipped_rect.width(); ++x) {
+ // As a sanity check, make sure that we aren't about to stomp memory.
+ assert(offset >= 0 && offset < width * height);
+ pixel_data[offset++] = color;
+ }
+ offset += width - clipped_rect.width();
+ }
+}
+
+void PongView::DrawBall(const BallModel& ball) {
+ assert(pixel_buffer_);
+ uint32_t* pixel_data = static_cast<uint32_t*>(pixel_buffer_->data());
+ assert(pixel_data);
+
+ int width = pixel_buffer_->size().width();
+ int height = pixel_buffer_->size().height();
+
+ // We shouldn't be drawing outside of the pixel buffer bounds, but just in
+ // case, clip the rectangle first.
+ pp::Rect clipped_rect = ClipRect(ball.rect, pp::Rect(pixel_buffer_->size()));
+
+ int radius2 = (ball.rect.width() / 2) * (ball.rect.width() / 2);
+ pp::Point ball_center = ball.rect.CenterPoint() - clipped_rect.point();
+
+ int offset = clipped_rect.y() * width + clipped_rect.x();
+
+ for (int y = 0; y < clipped_rect.height(); ++y) {
+ for (int x = 0; x < clipped_rect.width(); ++x) {
+ int distance_x = x - ball_center.x();
+ int distance_y = y - ball_center.y();
+ int distance2 = distance_x * distance_x + distance_y * distance_y;
+
+ // As a sanity check, make sure that we aren't about to stomp memory.
+ assert(offset >= 0 && offset < width * height);
+ // Common optimization here: compare the distance from the center of the
+ // ball to the current pixel squared versus the radius squared. This way
+ // we avoid the sqrt.
+ pixel_data[offset++] = distance2 <= radius2 ? kWhite : kBlack;
+ }
+
+ offset += width - clipped_rect.width();
+ }
+}
+
+void PongView::DrawPaddle(const PaddleModel& paddle) {
+ DrawRect(paddle.rect, kWhite);
+}
+
+void PongView::EraseBall(const BallModel& ball) {
+ DrawRect(ball.rect, kBlack);
+}
+
+void PongView::ErasePaddle(const PaddleModel& paddle) {
+ DrawRect(paddle.rect, kBlack);
+}
+
+void PongView::PaintRectToGraphics2D(const pp::Rect& rect) {
+ const pp::Point top_left(0, 0);
+ graphics_2d_->PaintImageData(*pixel_buffer_, top_left, rect);
+}
+
+void PongView::PaintRectToGraphics2D(const pp::Rect& old_rect,
+ const pp::Rect& rect) {
+ // Try a little optimization here: since the paddles and balls don't move
+ // very much per frame, they often will overlap their old bounding boxes. When
+ // they do, update the union of the two boxes to save some time.
+ if (old_rect.Intersects(rect)) {
+ PaintRectToGraphics2D(old_rect.Union(rect));
+ } else {
+ PaintRectToGraphics2D(old_rect);
+ PaintRectToGraphics2D(rect);
+ }
+}
diff --git a/native_client_sdk/src/examples/pong/pong_view.h b/native_client_sdk/src/examples/pong/pong_view.h
new file mode 100644
index 0000000..8f80e6f
--- /dev/null
+++ b/native_client_sdk/src/examples/pong/pong_view.h
@@ -0,0 +1,51 @@
+// 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.
+
+#ifndef EXAMPLES_PONG_VIEW_H_
+#define EXAMPLES_PONG_VIEW_H_
+
+#include "ppapi/cpp/rect.h"
+#include "ppapi/utility/completion_callback_factory.h"
+#include "pong_model.h"
+
+namespace pp {
+class Graphics2D;
+class ImageData;
+class Instance;
+class Size;
+class View;
+} // namespace pp
+
+class PongView {
+ public:
+ explicit PongView(PongModel* model);
+ ~PongView();
+
+ bool DidChangeView(pp::Instance* instance, const pp::View& view,
+ bool first_view_change);
+ pp::Size GetSize() const;
+ void StartDrawLoop();
+
+ private:
+ void DrawCallback(int32_t result);
+ void DrawRect(const pp::Rect& rect, uint32_t color);
+ void DrawBall(const BallModel& ball);
+ void DrawPaddle(const PaddleModel& paddle);
+ void EraseBall(const BallModel& ball);
+ void ErasePaddle(const PaddleModel& paddle);
+ void PaintRectToGraphics2D(const pp::Rect& rect);
+ void PaintRectToGraphics2D(const pp::Rect& old_rect, const pp::Rect& rect);
+
+ pp::CompletionCallbackFactory<PongView> factory_;
+ PongModel* model_; // Weak pointer.
+ pp::Graphics2D* graphics_2d_;
+ pp::ImageData* pixel_buffer_;
+
+ bool needs_erase_;
+ PaddleModel old_left_paddle_;
+ PaddleModel old_right_paddle_;
+ BallModel old_ball_;
+};
+
+#endif // EXAMPLES_PONG_VIEW_H_
diff --git a/native_client_sdk/src/examples/pong/view.cc b/native_client_sdk/src/examples/pong/view.cc
deleted file mode 100644
index 0801712..0000000
--- a/native_client_sdk/src/examples/pong/view.cc
+++ /dev/null
@@ -1,161 +0,0 @@
-// 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 <math.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "ppapi/cpp/completion_callback.h"
-#include "ppapi/cpp/graphics_2d.h"
-#include "ppapi/cpp/image_data.h"
-#include "ppapi/cpp/input_event.h"
-#include "ppapi/cpp/instance.h"
-#include "ppapi/cpp/point.h"
-#include "ppapi/cpp/var.h"
-
-#include "view.h"
-
-// Input event key codes. PPAPI uses Windows Virtual key codes.
-const uint32_t kSpaceBar = 0x20;
-const uint32_t kUpArrow = 0x26;
-const uint32_t kDownArrow = 0x28;
-
-namespace {
-
-const uint32_t kOpaqueColorMask = 0xff000000; // Opaque pixels.
-const uint32_t kWhiteMask = 0xffffff;
-
-// This is called by the browser when the 2D context has been flushed to the
-// browser window.
-void FlushCallback(void* data, int32_t result) {
- static_cast<pong::View*>(data)->set_flush_pending(false);
-}
-
-} // namespace
-
-namespace pong {
-View::View(pp::Instance* instance)
- : instance_(instance), last_key_code_(0x0), flush_pending_(false),
- graphics_2d_context_(NULL), pixel_buffer_(NULL) {}
-
-View::~View() {
- DestroyContext();
- delete pixel_buffer_;
-}
-
-pp::Size View::GetSize() const {
- pp::Size size;
- if (graphics_2d_context_) {
- size.SetSize(graphics_2d_context_->size().width(),
- graphics_2d_context_->size().height());
- }
- return size;
-}
-
-bool View::KeyDown(const pp::KeyboardInputEvent& key) {
- last_key_code_ = key.GetKeyCode();
- if (last_key_code_ == kSpaceBar || last_key_code_ == kUpArrow ||
- last_key_code_ == kDownArrow)
- return true;
- return false;
-}
-
-bool View::KeyUp(const pp::KeyboardInputEvent& key) {
- if (last_key_code_ == key.GetKeyCode()) {
- last_key_code_ = 0x0; // Indicates key code is not set.
- }
- return false;
-}
-
-void View::Draw() {
- uint32_t* pixels = static_cast<uint32_t*>(pixel_buffer_->data());
- if (NULL == pixels)
- return;
- // Clear the buffer
- const int32_t height = pixel_buffer_->size().height();
- const int32_t width = pixel_buffer_->size().width();
- const float radius2 = (ball_rect_.width() / 2) * (ball_rect_.width() / 2);
- for (int32_t py = 0; py < height; ++py) {
- for (int32_t px = 0; px < width; ++px) {
- const int32_t pos = px + py * width;
- uint32_t color = kOpaqueColorMask;
- // Draw the paddles
- if (left_paddle_rect_.Contains(px, py) ||
- right_paddle_rect_.Contains(px, py)) {
- color |= kWhiteMask;
- } else {
- pp::Point center_point = ball_rect_.CenterPoint();
- float distance_x = px - center_point.x();
- float distance_y = py - center_point.y();
- float distance2 = distance_x * distance_x + distance_y * distance_y;
- // Draw the ball
- if (distance2 <= radius2)
- color |= kWhiteMask;
- }
- pixels[pos] = color;
- }
- }
-
- FlushPixelBuffer();
-}
-
-void View::UpdateView(const pp::Rect& position,
- const pp::Rect& clip,
- pp::Instance* instance) {
- const int32_t width =
- pixel_buffer_ ? pixel_buffer_->size().width() : 0;
- const int32_t height =
- pixel_buffer_ ? pixel_buffer_->size().height() : 0;
-
- if (position.size().width() == width &&
- position.size().height() == height)
- return; // Size didn't change, no need to update anything.
-
- // Create a new device context with the new size.
- DestroyContext();
- CreateContext(position.size(), instance);
- // Delete the old pixel buffer and create a new one.
- delete pixel_buffer_;
- pixel_buffer_ = NULL;
- if (graphics_2d_context_ != NULL) {
- pixel_buffer_ = new pp::ImageData(instance, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
- graphics_2d_context_->size(),
- false);
- }
-}
-
-void View::CreateContext(const pp::Size& size, pp::Instance* instance) {
- if (IsContextValid())
- return;
- graphics_2d_context_ = new pp::Graphics2D(instance, size,
- false);
- if (!instance->BindGraphics(*graphics_2d_context_)) {
- instance_->PostMessage(pp::Var("ERROR: Couldn't bind the device context"));
- }
-}
-
-void View::DestroyContext() {
- if (!IsContextValid())
- return;
- delete graphics_2d_context_;
- graphics_2d_context_ = NULL;
-}
-
-void View::FlushPixelBuffer() {
- if (!IsContextValid())
- return;
- // Note that the pixel lock is held while the buffer is copied into the
- // device context and then flushed.
- graphics_2d_context_->PaintImageData(*pixel_buffer_, pp::Point());
- if (flush_pending_)
- return;
- flush_pending_ = true;
- graphics_2d_context_->Flush(pp::CompletionCallback(&FlushCallback, this));
-}
-
-bool View::IsContextValid() const {
- return graphics_2d_context_ != NULL;
-}
-
-} // namespace pong
diff --git a/native_client_sdk/src/examples/pong/view.h b/native_client_sdk/src/examples/pong/view.h
deleted file mode 100644
index 7dd59d1..0000000
--- a/native_client_sdk/src/examples/pong/view.h
+++ /dev/null
@@ -1,75 +0,0 @@
-// 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.
-
-#ifndef EXAMPLES_PONG_VIEW_H_
-#define EXAMPLES_PONG_VIEW_H_
-
-#include "ppapi/cpp/rect.h"
-
-namespace pp {
-class Graphics2D;
-class ImageData;
-class Instance;
-class KeyboardInputEvent;
-class Rect;
-class Size;
-} // namespace pp
-
-namespace pong {
-
-class View {
- public:
- explicit View(pp::Instance* instance);
- ~View();
-
- const uint32_t& last_key_code() const {
- return last_key_code_;
- }
- void set_left_paddle_rect(const pp::Rect& left_paddle_rect) {
- left_paddle_rect_ = left_paddle_rect;
- }
- void set_right_paddle_rect(const pp::Rect& right_paddle_rect) {
- right_paddle_rect_ = right_paddle_rect;
- }
- void set_ball_rect(const pp::Rect& ball_rect) {
- ball_rect_ = ball_rect;
- }
- void set_flush_pending(bool flush_pending) {
- flush_pending_ = flush_pending;
- }
- pp::Size GetSize() const;
- bool KeyDown(const pp::KeyboardInputEvent& key);
- bool KeyUp(const pp::KeyboardInputEvent& key);
- void Draw();
- void UpdateView(const pp::Rect& position,
- const pp::Rect& clip,
- pp::Instance* instance);
-
- private:
- pp::Instance* const instance_; // weak
- // Create and initialize the 2D context used for drawing.
- void CreateContext(const pp::Size& size, pp::Instance* instance);
- // Destroy the 2D drawing context.
- void DestroyContext();
- // Push the pixels to the browser, then attempt to flush the 2D context. If
- // there is a pending flush on the 2D context, then update the pixels only
- // and do not flush.
- void FlushPixelBuffer();
- bool IsContextValid() const;
-
- uint32_t last_key_code_;
- // Geometry for drawing
- pp::Rect left_paddle_rect_;
- pp::Rect right_paddle_rect_;
- pp::Rect ball_rect_;
- // Drawing stuff
- bool flush_pending_;
- pp::Graphics2D* graphics_2d_context_;
- pp::ImageData* pixel_buffer_;
-};
-
-
-} // namespace pong
-
-#endif // EXAMPLES_PONG_VIEW_H_