diff options
author | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-04 18:14:03 +0000 |
---|---|---|
committer | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-04 18:14:03 +0000 |
commit | 17d8c988f05ec86ecdac1b8dccb19000ce90ff4f (patch) | |
tree | 3f9f00c9272902f6361b5dca99aeb78577fa3e3b /native_client_sdk | |
parent | d03f4d4224b568111e11adf44db5c84f38c85b05 (diff) | |
download | chromium_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.dsc | 17 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/example.js | 12 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/index.html | 4 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/pong.cc | 421 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/pong.h | 92 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/pong_input.cc | 53 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/pong_input.h | 36 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/pong_instance.cc | 142 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/pong_instance.h | 65 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/pong_model.cc | 303 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/pong_model.h | 153 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/pong_module.cc | 10 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/pong_view.cc | 204 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/pong_view.h | 51 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/view.cc | 161 | ||||
-rw-r--r-- | native_client_sdk/src/examples/pong/view.h | 75 |
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_ |