OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 The Native Client Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "experimental/conways_life/life_application.h" | |
6 | |
7 #include <algorithm> | |
8 #include <cassert> | |
9 #include <cmath> | |
10 #include <cstdio> | |
11 #include <cstring> | |
12 #include <sstream> | |
13 #include <string> | |
14 | |
15 #include "experimental/conways_life/audio/web_wav_sound_resource.h" | |
16 #include "experimental/conways_life/scoped_pixel_lock.h" | |
17 #include "experimental/conways_life/threading/scoped_mutex_lock.h" | |
18 #include "ppapi/c/pp_errors.h" | |
19 #include "ppapi/cpp/completion_callback.h" | |
20 #include "ppapi/cpp/var.h" | |
21 | |
22 namespace { | |
23 const char* const kClearMethodId = "clear"; | |
24 const char* const kPutStampAtPointMethodId = "putStampAtPoint"; | |
25 const char* const kRunSimulationMethodId = "runSimulation"; | |
26 const char* const kSetAutomatonRulesMethodId = "setAutomatonRules"; | |
27 const char* const kSetCurrentStampMethodId = "setCurrentStamp"; | |
28 const char* const kSetStampSoundUrlMethodId = "setStampSoundUrl"; | |
29 const char* const kStopSimulationMethodId = "stopSimulation"; | |
30 | |
31 const int kSimulationTickInterval = 10; // Measured in msec. | |
32 const uint32_t kBackgroundColor = 0xFFFFFFFF; // Opaque white. | |
33 | |
34 // Simulation modes. These strings are matched by the browser script. | |
35 const char* const kRandomSeedModeId = "random_seed"; | |
36 const char* const kStampModeId = "stamp"; | |
37 | |
38 // Return the value of parameter named |param_name| from |parameters|. If | |
39 // |param_name| doesn't exist, then return an empty string. | |
40 std::string GetParameterNamed( | |
41 const std::string& param_name, | |
42 const scripting::MethodParameter& parameters) { | |
43 scripting::MethodParameter::const_iterator i = | |
44 parameters.find(param_name); | |
45 if (i == parameters.end()) { | |
46 return ""; | |
47 } | |
48 return i->second; | |
49 } | |
50 | |
51 // Return the int32_t equivalent of |str|. All the usual C++ rounding rules | |
52 // apply. | |
53 int32_t StringAsInt32(const std::string& str) { | |
54 std::istringstream cvt_stream(str); | |
55 double double_val; | |
56 cvt_stream >> double_val; | |
57 return static_cast<int32_t>(double_val); | |
58 } | |
59 | |
60 // Called from the browser when the delay time has elapsed. This routine | |
61 // runs a simulation update, then reschedules itself to run the next | |
62 // simulation tick. | |
63 void SimulationTickCallback(void* data, int32_t result) { | |
64 life::LifeApplication* life_app = static_cast<life::LifeApplication*>(data); | |
65 life_app->Update(); | |
66 if (life_app->is_running()) { | |
67 pp::Module::Get()->core()->CallOnMainThread( | |
68 kSimulationTickInterval, | |
69 pp::CompletionCallback(&SimulationTickCallback, data), | |
70 PP_OK); | |
71 } | |
72 } | |
73 | |
74 // Called from the browser when the 2D graphics have been flushed out to the | |
75 // device. | |
76 void FlushCallback(void* data, int32_t result) { | |
77 static_cast<life::LifeApplication*>(data)->set_flush_pending(false); | |
78 } | |
79 } // namespace | |
80 | |
81 using scripting::ScriptingBridge; | |
82 | |
83 namespace life { | |
84 LifeApplication::LifeApplication(PP_Instance instance) | |
85 : pp::Instance(instance), | |
86 graphics_2d_context_(NULL), | |
87 flush_pending_(false), | |
88 view_changed_size_(true), | |
89 audio_player_(this) { | |
90 } | |
91 | |
92 LifeApplication::~LifeApplication() { | |
93 life_simulation_.set_is_simulation_running(false); | |
94 DestroyContext(); | |
95 } | |
96 | |
97 bool LifeApplication::Init(uint32_t /* argc */, | |
98 const char* /* argn */[], | |
99 const char* /* argv */[]) { | |
100 // Add all the methods to the scripting bridge. | |
101 ScriptingBridge::SharedMethodCallbackExecutor | |
102 clear_method(new scripting::MethodCallback<LifeApplication>( | |
103 this, &LifeApplication::Clear)); | |
104 scripting_bridge_.AddMethodNamed(kClearMethodId, clear_method); | |
105 | |
106 ScriptingBridge::SharedMethodCallbackExecutor | |
107 put_stamp_method(new scripting::MethodCallback<LifeApplication>( | |
108 this, &LifeApplication::PutStampAtPoint)); | |
109 scripting_bridge_.AddMethodNamed(kPutStampAtPointMethodId, put_stamp_method); | |
110 | |
111 ScriptingBridge::SharedMethodCallbackExecutor | |
112 run_sim_method(new scripting::MethodCallback<LifeApplication>( | |
113 this, &LifeApplication::RunSimulation)); | |
114 scripting_bridge_.AddMethodNamed(kRunSimulationMethodId, run_sim_method); | |
115 | |
116 ScriptingBridge::SharedMethodCallbackExecutor | |
117 set_auto_rules_method(new scripting::MethodCallback<LifeApplication>( | |
118 this, &LifeApplication::SetAutomatonRules)); | |
119 scripting_bridge_.AddMethodNamed(kSetAutomatonRulesMethodId, | |
120 set_auto_rules_method); | |
121 | |
122 ScriptingBridge::SharedMethodCallbackExecutor | |
123 set_stamp_method(new scripting::MethodCallback<LifeApplication>( | |
124 this, &LifeApplication::SetCurrentStamp)); | |
125 scripting_bridge_.AddMethodNamed(kSetCurrentStampMethodId, set_stamp_method); | |
126 | |
127 ScriptingBridge::SharedMethodCallbackExecutor | |
128 set_stamp_sound_method(new scripting::MethodCallback<LifeApplication>( | |
129 this, &LifeApplication::SetStampSoundUrl)); | |
130 scripting_bridge_.AddMethodNamed(kSetStampSoundUrlMethodId, | |
131 set_stamp_sound_method); | |
132 | |
133 ScriptingBridge::SharedMethodCallbackExecutor | |
134 stop_sim_method(new scripting::MethodCallback<LifeApplication>( | |
135 this, &LifeApplication::StopSimulation)); | |
136 scripting_bridge_.AddMethodNamed(kStopSimulationMethodId, stop_sim_method); | |
137 | |
138 life_simulation_.StartSimulation(); | |
139 return true; | |
140 } | |
141 | |
142 void LifeApplication::HandleMessage(const pp::Var& message) { | |
143 if (!message.is_string()) | |
144 return; | |
145 scripting_bridge_.InvokeMethod(message.AsString()); | |
146 } | |
147 | |
148 void LifeApplication::DidChangeView(const pp::Rect& position, | |
149 const pp::Rect& /* clip */) { | |
150 if (position.size().width() == width() && | |
151 position.size().height() == height()) | |
152 return; // Size didn't change, no need to update anything. | |
153 // Indicate that all the buffers need to be resized at the next Update() | |
154 // call. | |
155 view_changed_size_ = true; | |
156 view_size_ = position.size(); | |
157 // Make sure the buffers get changed if the simulation isn't running. | |
158 if (!is_running()) | |
159 Update(); | |
160 } | |
161 | |
162 void LifeApplication::Update() { | |
163 if (flush_pending()) | |
164 return; // Don't attempt to flush if one is pending. | |
165 | |
166 if (view_changed_size_) { | |
167 // Delete the old pixel buffer and create a new one. | |
168 // Pause the simulation before changing all the buffer sizes. | |
169 Life::SimulationMode sim_mode = life_simulation_.simulation_mode(); | |
170 life_simulation_.set_simulation_mode(Life::kPaused); | |
171 // Create a new device context with the new size. | |
172 // Note: DestroyContext() releases the simulation's copy of the shared | |
173 // pixel buffer. *This has to happen before the reset() below!* If the | |
174 // simulation's copy of hte shared pointer is released last, then the | |
175 // underlying ImageData is released off the main thread, which is not | |
176 // supported in Pepper. | |
177 DestroyContext(); | |
178 CreateContext(view_size_); | |
179 threading::ScopedMutexLock scoped_mutex( | |
180 life_simulation_.simulation_mutex()); | |
181 if (!scoped_mutex.is_valid()) | |
182 // This potentially leaves the simulation in a paused state, but getting | |
183 // here means something is very wrong and the simulation probably can't | |
184 // run anyways. | |
185 return; | |
186 life_simulation_.DeleteCells(); | |
187 if (graphics_2d_context_ != NULL) { | |
188 shared_pixel_buffer_.reset( | |
189 new LockingImageData(this, | |
190 PP_IMAGEDATAFORMAT_BGRA_PREMUL, | |
191 graphics_2d_context_->size(), | |
192 false)); | |
193 set_flush_pending(false); | |
194 // Ok to get a non-locked version because the simulation is guraranteed | |
195 // to be paused here. | |
196 uint32_t* pixels = shared_pixel_buffer_->PixelBufferNoLock(); | |
197 if (pixels) { | |
198 const size_t size = width() * height(); | |
199 std::fill(pixels, pixels + size, kBackgroundColor); | |
200 } | |
201 life_simulation_.Resize(width(), height()); | |
202 life_simulation_.set_pixel_buffer(shared_pixel_buffer_); | |
203 life_simulation_.set_simulation_mode(sim_mode); | |
204 } | |
205 view_changed_size_ = false; | |
206 } | |
207 FlushPixelBuffer(); | |
208 } | |
209 | |
210 void LifeApplication::SetCurrentStamp( | |
211 const scripting::ScriptingBridge& bridge, | |
212 const scripting::MethodParameter& parameters) { | |
213 std::string stamp_desc = GetParameterNamed("description", parameters); | |
214 if (stamp_desc.length()) { | |
215 stamp_.InitFromDescription(stamp_desc); | |
216 } | |
217 } | |
218 | |
219 void LifeApplication::Clear( | |
220 const scripting::ScriptingBridge& bridge, | |
221 const scripting::MethodParameter& parameters) { | |
222 // Temporarily pause the the simulation while clearing the buffers. | |
223 volatile Life::SimulationMode sim_mode = life_simulation_.simulation_mode(); | |
224 if (sim_mode != Life::kPaused) | |
225 life_simulation_.set_simulation_mode(Life::kPaused); | |
226 life_simulation_.ClearCells(); | |
227 ScopedPixelLock scoped_pixel_lock(shared_pixel_buffer_); | |
228 uint32_t* pixel_buffer = scoped_pixel_lock.pixels(); | |
229 if (pixel_buffer) { | |
230 const size_t size = width() * height(); | |
231 std::fill(pixel_buffer, | |
232 pixel_buffer + size, | |
233 kBackgroundColor); | |
234 } | |
235 Update(); // Flushes the buffer correctly. | |
236 if (sim_mode != Life::kPaused) | |
237 life_simulation_.set_simulation_mode(sim_mode); | |
238 } | |
239 | |
240 void LifeApplication::SetAutomatonRules( | |
241 const scripting::ScriptingBridge& bridge, | |
242 const scripting::MethodParameter& parameters) { | |
243 std::string rules = GetParameterNamed("rules", parameters); | |
244 if (rules.length()) { | |
245 life_simulation_.SetAutomatonRules(rules); | |
246 } | |
247 } | |
248 | |
249 void LifeApplication::RunSimulation( | |
250 const scripting::ScriptingBridge& bridge, | |
251 const scripting::MethodParameter& parameters) { | |
252 std::string sim_mode = GetParameterNamed("mode", parameters); | |
253 if (sim_mode.length() == 0) { | |
254 return; | |
255 } | |
256 if (sim_mode == kRandomSeedModeId) { | |
257 life_simulation_.set_simulation_mode(life::Life::kRunRandomSeed); | |
258 } else { | |
259 life_simulation_.set_simulation_mode(life::Life::kRunStamp); | |
260 } | |
261 // Schedule a simulation tick to get things going. | |
262 pp::Module::Get()->core()->CallOnMainThread( | |
263 kSimulationTickInterval, | |
264 pp::CompletionCallback(&SimulationTickCallback, this), | |
265 PP_OK); | |
266 } | |
267 | |
268 void LifeApplication::StopSimulation( | |
269 const scripting::ScriptingBridge& bridge, | |
270 const scripting::MethodParameter& parameters) { | |
271 // This will pause the simulation on the next tick. | |
272 life_simulation_.set_simulation_mode(life::Life::kPaused); | |
273 } | |
274 | |
275 void LifeApplication::PutStampAtPoint( | |
276 const scripting::ScriptingBridge& bridge, | |
277 const scripting::MethodParameter& parameters) { | |
278 std::string x_coord = GetParameterNamed("x", parameters); | |
279 std::string y_coord = GetParameterNamed("y", parameters); | |
280 if (x_coord.length() == 0 || y_coord.length() == 0) { | |
281 return; | |
282 } | |
283 int32_t x = StringAsInt32(x_coord); | |
284 int32_t y = StringAsInt32(y_coord); | |
285 life_simulation_.PutStampAtPoint(stamp_, pp::Point(x, y)); | |
286 // Play the stamp sound. | |
287 if (audio_player_.IsReady()) | |
288 audio_player_.Play(); | |
289 // If the simulation isn't running, make sure the stamp shows up. | |
290 if (!is_running()) | |
291 Update(); | |
292 } | |
293 | |
294 void LifeApplication::SetStampSoundUrl( | |
295 const scripting::ScriptingBridge& bridge, | |
296 const scripting::MethodParameter& parameters) { | |
297 std::string sound_url = GetParameterNamed("soundUrl", parameters); | |
298 if (sound_url.length() == 0) { | |
299 return; | |
300 } | |
301 audio::WebWavSoundResource* sound = new audio::WebWavSoundResource(); | |
302 sound->Init(sound_url, this); | |
303 // |audio_player_| takes ownership of |sound| and is responsible for | |
304 // deleting it. | |
305 audio_player_.AssignAudioSource(sound); | |
306 } | |
307 | |
308 void LifeApplication::CreateContext(const pp::Size& size) { | |
309 if (IsContextValid()) | |
310 return; | |
311 graphics_2d_context_ = new pp::Graphics2D(this, size, false); | |
312 if (!BindGraphics(*graphics_2d_context_)) { | |
313 printf("Couldn't bind the device context\n"); | |
314 } | |
315 } | |
316 | |
317 void LifeApplication::DestroyContext() { | |
318 threading::ScopedMutexLock scoped_mutex(life_simulation_.simulation_mutex()); | |
319 if (!scoped_mutex.is_valid()) { | |
320 return; | |
321 } | |
322 ScopedPixelLock scoped_pixel_lock(shared_pixel_buffer_); | |
323 shared_pixel_buffer_.reset(); | |
324 life_simulation_.set_pixel_buffer(shared_pixel_buffer_); | |
325 if (!IsContextValid()) | |
326 return; | |
327 delete graphics_2d_context_; | |
328 graphics_2d_context_ = NULL; | |
329 } | |
330 | |
331 void LifeApplication::FlushPixelBuffer() { | |
332 if (!IsContextValid() || shared_pixel_buffer_ == NULL) | |
333 return; | |
334 set_flush_pending(true); | |
335 graphics_2d_context_->PaintImageData(*shared_pixel_buffer_, pp::Point()); | |
336 graphics_2d_context_->Flush(pp::CompletionCallback(&FlushCallback, this)); | |
337 } | |
338 } // namespace life | |
339 | |
OLD | NEW |