Skip to content

Commit 9f77f8d

Browse files
committed
Update parallel raytrace example to use futures
Use the atomics support now implemented!
1 parent d122bbc commit 9f77f8d

File tree

2 files changed

+27
-104
lines changed

2 files changed

+27
-104
lines changed

examples/raytrace-parallel/src/lib.rs

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use futures::sync::oneshot;
12
use futures::Future;
23
use js_sys::{Promise, Uint8ClampedArray, WebAssembly};
34
use rayon::prelude::*;
@@ -69,27 +70,28 @@ impl Scene {
6970
// threads so we don't lock up the main thread, so we ship off a thread
7071
// which actually does the whole rayon business. When our returned
7172
// future is resolved we can pull out the final version of the image.
72-
let done = pool
73-
.run_notify(move || {
74-
thread_pool.install(|| {
75-
rgb_data
76-
.par_chunks_mut(4)
77-
.enumerate()
78-
.for_each(|(i, chunk)| {
79-
let i = i as u32;
80-
let x = i % width;
81-
let y = i / width;
82-
let ray = raytracer::Ray::create_prime(x, y, &scene);
83-
let result = raytracer::cast_ray(&scene, &ray, 0).to_rgba();
84-
chunk[0] = result.data[0];
85-
chunk[1] = result.data[1];
86-
chunk[2] = result.data[2];
87-
chunk[3] = result.data[3];
88-
});
89-
});
73+
let (tx, rx) = oneshot::channel();
74+
pool.run(move || {
75+
thread_pool.install(|| {
9076
rgb_data
91-
})?
92-
.map(move |_data| image_data(base, len, width, height).into());
77+
.par_chunks_mut(4)
78+
.enumerate()
79+
.for_each(|(i, chunk)| {
80+
let i = i as u32;
81+
let x = i % width;
82+
let y = i / width;
83+
let ray = raytracer::Ray::create_prime(x, y, &scene);
84+
let result = raytracer::cast_ray(&scene, &ray, 0).to_rgba();
85+
chunk[0] = result.data[0];
86+
chunk[1] = result.data[1];
87+
chunk[2] = result.data[2];
88+
chunk[3] = result.data[3];
89+
});
90+
});
91+
drop(tx.send(rgb_data));
92+
})?;
93+
let done = rx.map(move |_data| image_data(base, len, width, height).into())
94+
.map_err(|_| JsValue::undefined());
9395

9496
Ok(RenderingScene {
9597
promise: wasm_bindgen_futures::future_to_promise(done),

examples/raytrace-parallel/src/pool.rs

Lines changed: 5 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
//! A small module that's intended to provide an example of creating a pool of
22
//! web workers which can be used to execute `rayon`-style work.
33
4-
use futures::sync::oneshot;
5-
use futures::Future;
6-
use std::cell::{RefCell, UnsafeCell};
7-
use std::mem;
4+
use std::cell::RefCell;
85
use std::rc::Rc;
9-
use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
10-
use std::sync::Arc;
116
use wasm_bindgen::prelude::*;
127
use wasm_bindgen::JsCast;
138
use web_sys::{DedicatedWorkerGlobalScope, MessageEvent};
@@ -141,12 +136,11 @@ impl WorkerPool {
141136
/// whatn it's done the worker is ready to execute more work. This method is
142137
/// used for all spawned workers to ensure that when the work is finished
143138
/// the worker is reclaimed back into this pool.
144-
fn reclaim_on_message(&self, worker: Worker, on_finish: impl FnOnce() + 'static) {
139+
fn reclaim_on_message(&self, worker: Worker) {
145140
let state = Rc::downgrade(&self.state);
146141
let worker2 = worker.clone();
147142
let reclaim_slot = Rc::new(RefCell::new(None));
148143
let slot2 = reclaim_slot.clone();
149-
let mut on_finish = Some(on_finish);
150144
let reclaim = Closure::wrap(Box::new(move |event: Event| {
151145
if let Some(error) = event.dyn_ref::<ErrorEvent>() {
152146
console_log!("error in worker: {}", error.message());
@@ -155,11 +149,9 @@ impl WorkerPool {
155149
return;
156150
}
157151

158-
// If this is a completion event then we can execute our `on_finish`
159-
// callback and we can also deallocate our own callback by clearing
160-
// out `slot2` which contains our own closure.
152+
// If this is a completion event then can deallocate our own
153+
// callback by clearing out `slot2` which contains our own closure.
161154
if let Some(_msg) = event.dyn_ref::<MessageEvent>() {
162-
on_finish.take().unwrap()();
163155
if let Some(state) = state.upgrade() {
164156
state.push(worker2.clone());
165157
}
@@ -193,80 +185,9 @@ impl WorkerPool {
193185
/// a web worker, that error is returned.
194186
pub fn run(&self, f: impl FnOnce() + Send + 'static) -> Result<(), JsValue> {
195187
let worker = self.execute(f)?;
196-
self.reclaim_on_message(worker, || {});
188+
self.reclaim_on_message(worker);
197189
Ok(())
198190
}
199-
200-
/// Executes the closure `f` in a web worker, returning a future of the
201-
/// value that `f` produces.
202-
///
203-
/// This method is the same as `run` execept that it allows recovering the
204-
/// return value of the closure `f` in a nonblocking fashion with the future
205-
/// returned.
206-
///
207-
/// # Errors
208-
///
209-
/// If an error happens while spawning a web worker or sending a message to
210-
/// a web worker, that error is returned.
211-
pub fn run_notify<T>(
212-
&self,
213-
f: impl FnOnce() -> T + Send + 'static,
214-
) -> Result<impl Future<Item = T, Error = JsValue> + 'static, JsValue>
215-
where
216-
T: Send + 'static,
217-
{
218-
// FIXME(#1379) we should just use the `oneshot` directly as the future,
219-
// but we have to use JS callbacks to ensure we don't have futures cross
220-
// threads as that's currently not safe to do so.
221-
let (tx, rx) = oneshot::channel();
222-
let storage = Arc::new(AtomicValue::new(None));
223-
let storage2 = storage.clone();
224-
let worker = self.execute(move || {
225-
assert!(storage2.replace(Some(f())).is_ok());
226-
})?;
227-
self.reclaim_on_message(worker, move || match storage.replace(None) {
228-
Ok(Some(val)) => drop(tx.send(val)),
229-
_ => unreachable!(),
230-
});
231-
232-
Ok(rx.map_err(|_| JsValue::undefined()))
233-
}
234-
}
235-
236-
/// A small helper struct representing atomic access to an internal value `T`
237-
///
238-
/// This struct only supports one API, `replace`, which will either succeed and
239-
/// replace the internal value with another (returning the previous one), or it
240-
/// will fail returning the value passed in. Failure happens when two threads
241-
/// try to `replace` at the same time.
242-
///
243-
/// This is only really intended to help safely transfer information between
244-
/// threads, it doesn't provide any synchronization capabilities itself other
245-
/// than a guaranteed safe API.
246-
struct AtomicValue<T> {
247-
modifying: AtomicBool,
248-
slot: UnsafeCell<T>,
249-
}
250-
251-
unsafe impl<T: Send> Send for AtomicValue<T> {}
252-
unsafe impl<T: Send> Sync for AtomicValue<T> {}
253-
254-
impl<T> AtomicValue<T> {
255-
fn new(val: T) -> AtomicValue<T> {
256-
AtomicValue {
257-
modifying: AtomicBool::new(false),
258-
slot: UnsafeCell::new(val),
259-
}
260-
}
261-
262-
fn replace(&self, val: T) -> Result<T, T> {
263-
if self.modifying.swap(true, SeqCst) {
264-
return Err(val);
265-
}
266-
let ret = unsafe { mem::replace(&mut *self.slot.get(), val) };
267-
self.modifying.store(false, SeqCst);
268-
Ok(ret)
269-
}
270191
}
271192

272193
impl PoolState {

0 commit comments

Comments
 (0)