mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-14 20:11:37 +03:00
Update parallel raytrace example to use futures
Use the atomics support now implemented!
This commit is contained in:
parent
d122bbca13
commit
9f77f8dd00
@ -1,3 +1,4 @@
|
||||
use futures::sync::oneshot;
|
||||
use futures::Future;
|
||||
use js_sys::{Promise, Uint8ClampedArray, WebAssembly};
|
||||
use rayon::prelude::*;
|
||||
@ -69,27 +70,28 @@ impl Scene {
|
||||
// threads so we don't lock up the main thread, so we ship off a thread
|
||||
// which actually does the whole rayon business. When our returned
|
||||
// future is resolved we can pull out the final version of the image.
|
||||
let done = pool
|
||||
.run_notify(move || {
|
||||
thread_pool.install(|| {
|
||||
rgb_data
|
||||
.par_chunks_mut(4)
|
||||
.enumerate()
|
||||
.for_each(|(i, chunk)| {
|
||||
let i = i as u32;
|
||||
let x = i % width;
|
||||
let y = i / width;
|
||||
let ray = raytracer::Ray::create_prime(x, y, &scene);
|
||||
let result = raytracer::cast_ray(&scene, &ray, 0).to_rgba();
|
||||
chunk[0] = result.data[0];
|
||||
chunk[1] = result.data[1];
|
||||
chunk[2] = result.data[2];
|
||||
chunk[3] = result.data[3];
|
||||
});
|
||||
});
|
||||
let (tx, rx) = oneshot::channel();
|
||||
pool.run(move || {
|
||||
thread_pool.install(|| {
|
||||
rgb_data
|
||||
})?
|
||||
.map(move |_data| image_data(base, len, width, height).into());
|
||||
.par_chunks_mut(4)
|
||||
.enumerate()
|
||||
.for_each(|(i, chunk)| {
|
||||
let i = i as u32;
|
||||
let x = i % width;
|
||||
let y = i / width;
|
||||
let ray = raytracer::Ray::create_prime(x, y, &scene);
|
||||
let result = raytracer::cast_ray(&scene, &ray, 0).to_rgba();
|
||||
chunk[0] = result.data[0];
|
||||
chunk[1] = result.data[1];
|
||||
chunk[2] = result.data[2];
|
||||
chunk[3] = result.data[3];
|
||||
});
|
||||
});
|
||||
drop(tx.send(rgb_data));
|
||||
})?;
|
||||
let done = rx.map(move |_data| image_data(base, len, width, height).into())
|
||||
.map_err(|_| JsValue::undefined());
|
||||
|
||||
Ok(RenderingScene {
|
||||
promise: wasm_bindgen_futures::future_to_promise(done),
|
||||
|
@ -1,13 +1,8 @@
|
||||
//! A small module that's intended to provide an example of creating a pool of
|
||||
//! web workers which can be used to execute `rayon`-style work.
|
||||
|
||||
use futures::sync::oneshot;
|
||||
use futures::Future;
|
||||
use std::cell::{RefCell, UnsafeCell};
|
||||
use std::mem;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering::SeqCst};
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{DedicatedWorkerGlobalScope, MessageEvent};
|
||||
@ -141,12 +136,11 @@ impl WorkerPool {
|
||||
/// whatn it's done the worker is ready to execute more work. This method is
|
||||
/// used for all spawned workers to ensure that when the work is finished
|
||||
/// the worker is reclaimed back into this pool.
|
||||
fn reclaim_on_message(&self, worker: Worker, on_finish: impl FnOnce() + 'static) {
|
||||
fn reclaim_on_message(&self, worker: Worker) {
|
||||
let state = Rc::downgrade(&self.state);
|
||||
let worker2 = worker.clone();
|
||||
let reclaim_slot = Rc::new(RefCell::new(None));
|
||||
let slot2 = reclaim_slot.clone();
|
||||
let mut on_finish = Some(on_finish);
|
||||
let reclaim = Closure::wrap(Box::new(move |event: Event| {
|
||||
if let Some(error) = event.dyn_ref::<ErrorEvent>() {
|
||||
console_log!("error in worker: {}", error.message());
|
||||
@ -155,11 +149,9 @@ impl WorkerPool {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is a completion event then we can execute our `on_finish`
|
||||
// callback and we can also deallocate our own callback by clearing
|
||||
// out `slot2` which contains our own closure.
|
||||
// If this is a completion event then can deallocate our own
|
||||
// callback by clearing out `slot2` which contains our own closure.
|
||||
if let Some(_msg) = event.dyn_ref::<MessageEvent>() {
|
||||
on_finish.take().unwrap()();
|
||||
if let Some(state) = state.upgrade() {
|
||||
state.push(worker2.clone());
|
||||
}
|
||||
@ -193,80 +185,9 @@ impl WorkerPool {
|
||||
/// a web worker, that error is returned.
|
||||
pub fn run(&self, f: impl FnOnce() + Send + 'static) -> Result<(), JsValue> {
|
||||
let worker = self.execute(f)?;
|
||||
self.reclaim_on_message(worker, || {});
|
||||
self.reclaim_on_message(worker);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Executes the closure `f` in a web worker, returning a future of the
|
||||
/// value that `f` produces.
|
||||
///
|
||||
/// This method is the same as `run` execept that it allows recovering the
|
||||
/// return value of the closure `f` in a nonblocking fashion with the future
|
||||
/// returned.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// If an error happens while spawning a web worker or sending a message to
|
||||
/// a web worker, that error is returned.
|
||||
pub fn run_notify<T>(
|
||||
&self,
|
||||
f: impl FnOnce() -> T + Send + 'static,
|
||||
) -> Result<impl Future<Item = T, Error = JsValue> + 'static, JsValue>
|
||||
where
|
||||
T: Send + 'static,
|
||||
{
|
||||
// FIXME(#1379) we should just use the `oneshot` directly as the future,
|
||||
// but we have to use JS callbacks to ensure we don't have futures cross
|
||||
// threads as that's currently not safe to do so.
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let storage = Arc::new(AtomicValue::new(None));
|
||||
let storage2 = storage.clone();
|
||||
let worker = self.execute(move || {
|
||||
assert!(storage2.replace(Some(f())).is_ok());
|
||||
})?;
|
||||
self.reclaim_on_message(worker, move || match storage.replace(None) {
|
||||
Ok(Some(val)) => drop(tx.send(val)),
|
||||
_ => unreachable!(),
|
||||
});
|
||||
|
||||
Ok(rx.map_err(|_| JsValue::undefined()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A small helper struct representing atomic access to an internal value `T`
|
||||
///
|
||||
/// This struct only supports one API, `replace`, which will either succeed and
|
||||
/// replace the internal value with another (returning the previous one), or it
|
||||
/// will fail returning the value passed in. Failure happens when two threads
|
||||
/// try to `replace` at the same time.
|
||||
///
|
||||
/// This is only really intended to help safely transfer information between
|
||||
/// threads, it doesn't provide any synchronization capabilities itself other
|
||||
/// than a guaranteed safe API.
|
||||
struct AtomicValue<T> {
|
||||
modifying: AtomicBool,
|
||||
slot: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send for AtomicValue<T> {}
|
||||
unsafe impl<T: Send> Sync for AtomicValue<T> {}
|
||||
|
||||
impl<T> AtomicValue<T> {
|
||||
fn new(val: T) -> AtomicValue<T> {
|
||||
AtomicValue {
|
||||
modifying: AtomicBool::new(false),
|
||||
slot: UnsafeCell::new(val),
|
||||
}
|
||||
}
|
||||
|
||||
fn replace(&self, val: T) -> Result<T, T> {
|
||||
if self.modifying.swap(true, SeqCst) {
|
||||
return Err(val);
|
||||
}
|
||||
let ret = unsafe { mem::replace(&mut *self.slot.get(), val) };
|
||||
self.modifying.store(false, SeqCst);
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
impl PoolState {
|
||||
|
Loading…
Reference in New Issue
Block a user