#![doc = include_str!("../README.md")]
#[cfg(all(target_arch = "wasm32", not(target_feature = "atomics"), not(doc)))]
compile_error!(
"Some target features are not enabled. Please read the README and set the right rustflags"
);
use std::panic::UnwindSafe;
use std::ptr::NonNull;
use std::sync::atomic::AtomicUsize;
use std::sync::mpsc;
use js_sys::{Function, Promise};
use wasm_bindgen::prelude::*;
#[cfg(feature = "async")]
use wasm_bindgen_futures::JsFuture;
type BoxClosure = Box<dyn FnOnce() -> BoxValue + Send + UnwindSafe + 'static>;
type BoxValue = Box<dyn Send + 'static>;
type ValueSender = oneshot::Sender<Result<BoxValue, JoinError>>;
type ValueReceiver = oneshot::Receiver<Result<BoxValue, JoinError>>;
type DispatchPayload = (usize, BoxClosure, ValueSender);
type DispatchSender = mpsc::Sender<DispatchPayload>;
type DispatchReceiver = mpsc::Receiver<DispatchPayload>;
type SignalSender = oneshot::Sender<()>;
type SignalReceiver = oneshot::Receiver<()>;
#[derive(Debug, thiserror::Error)]
pub enum JoinError {
#[error("WASM thread {0} panicked")]
Panic(usize),
}
#[derive(Debug, thiserror::Error)]
pub enum SpawnError {
#[error("Cannot spawn WASM thread because the dispatcher has disconnected")]
Disconnected,
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = memory, js_namespace = wasm, thread_local_v2)]
static MEMORY: JsValue;
#[wasm_bindgen(js_name = __dispatch_poll_worker, js_namespace = wasm_bindgen, thread_local_v2)]
static DISPATCH_POLL_WORKER: JsValue;
}
pub struct ThreadCreator {
next_id: AtomicUsize,
send: DispatchSender,
}
static_assertions::assert_impl_all!(ThreadCreator: Send, Sync);
pub struct ThreadCreatorUnready {
thread_creator: ThreadCreator,
dispatcher_promise: Promise,
}
impl ThreadCreatorUnready {
pub fn into_promise_and_inner(self) -> (Promise, ThreadCreator) {
(self.dispatcher_promise, self.thread_creator)
}
#[cfg(feature = "async")]
pub async fn ready(self) -> Result<ThreadCreator, JsValue> {
JsFuture::from(self.dispatcher_promise).await?;
Ok(self.thread_creator)
}
}
impl ThreadCreator {
pub fn unready(wasm_url: &str, wbg_url: &str) -> Result<ThreadCreatorUnready, JsValue> {
let create_dispatcher =
Function::new_with_args("args", include_str!("js/createDispatcher.min.js"));
let wasm_url = JsValue::from_str(wasm_url);
let wbg_url = JsValue::from_str(wbg_url);
let memory = MEMORY.with(|memory| memory.clone());
let (send, recv) = mpsc::channel::<DispatchPayload>();
let recv_ptr = JsValue::from(NonNull::from(Box::leak(Box::new(recv))));
let (start_send, start_recv) = oneshot::channel::<()>();
let start_send = Box::into_raw(Box::new(start_send));
let start_recv = Box::into_raw(Box::new(start_recv));
let start_send_ptr = unsafe { NonNull::new_unchecked(start_send) };
let start_recv_ptr = unsafe { NonNull::new_unchecked(start_recv) };
let promise = create_dispatcher
.call1(
&JsValue::null(),
&JsValue::from(vec![
wasm_url,
wbg_url,
memory,
recv_ptr,
JsValue::from(start_send_ptr),
JsValue::from(start_recv_ptr),
DISPATCH_POLL_WORKER.with(|v| v.clone()),
]),
)?
.dyn_into::<Promise>()?;
Ok(ThreadCreatorUnready {
thread_creator: Self {
next_id: AtomicUsize::new(1),
send,
},
dispatcher_promise: promise,
})
}
pub fn spawn<F, T>(&self, f: F) -> Result<JoinHandle<T>, SpawnError>
where
F: FnOnce() -> T + Send + 'static + UnwindSafe,
T: Send + 'static,
{
let next_id = self
.next_id
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let closure: BoxClosure = Box::new(move || Box::new(f()));
let (send, recv) = oneshot::channel();
let payload = (next_id, closure, send);
self.send
.send(payload)
.map_err(|_| SpawnError::Disconnected)?;
Ok(JoinHandle {
id: next_id,
recv,
_marker: std::marker::PhantomData,
})
}
}
pub struct JoinHandle<T: Send + 'static> {
id: usize,
recv: ValueReceiver,
_marker: std::marker::PhantomData<T>,
}
impl<T: Send + 'static> JoinHandle<T> {
pub fn join(self) -> Result<T, JoinError> {
let value = self.recv.recv().map_err(|_| JoinError::Panic(self.id))?;
let value = value?;
let value_raw = Box::into_raw(value) as *mut T;
let value = unsafe { Box::from_raw(value_raw) };
Ok(*value)
}
pub fn is_finished(&self) -> bool {
self.recv.has_message() || self.recv.is_closed()
}
}
#[inline]
fn make_closure<F: FnOnce() -> BoxValue + Send + 'static + UnwindSafe>(
f: F,
) -> NonNull<BoxClosure> {
let boxed: BoxClosure = Box::new(f);
NonNull::from(Box::leak(Box::new(boxed)))
}
#[doc(hidden)]
#[wasm_bindgen]
pub fn __worker_main(f: NonNull<BoxClosure>, start: NonNull<SignalSender>) -> NonNull<BoxValue> {
__dispatch_start(start);
let f = unsafe { Box::from_raw(f.as_ptr()) };
let value = f();
let value_ptr = Box::into_raw(Box::new(value));
unsafe { NonNull::new_unchecked(value_ptr) }
}
#[doc(hidden)]
#[wasm_bindgen]
pub fn __worker_send(id: usize, send: NonNull<ValueSender>, value: Option<NonNull<BoxValue>>) {
let send_ptr = send.as_ptr();
let send = unsafe { Box::from_raw(send_ptr) };
match value {
None => {
let _ = send.send(Err(JoinError::Panic(id)));
}
Some(value) => {
let value = unsafe { Box::from_raw(value.as_ptr()) };
let _ = send.send(Ok(*value));
}
}
}
#[doc(hidden)]
#[wasm_bindgen]
pub fn __dispatch_start(start: NonNull<SignalSender>) {
let start_ptr = start.as_ptr();
let start = unsafe { Box::from_raw(start_ptr) };
let _ = start.send(());
}
#[doc(hidden)]
#[wasm_bindgen]
pub fn __dispatch_recv(recv: NonNull<DispatchReceiver>) -> Option<Vec<JsValue>> {
let recv: &DispatchReceiver = unsafe { recv.as_ref() };
let (id, closure, sender) = match recv.recv() {
Ok(v) => v,
Err(_) => return None,
};
let sender_ptr = NonNull::from(Box::leak(Box::new(sender)));
let (start_send, start_recv) = oneshot::channel::<()>();
let start_send_ptr = NonNull::from(Box::leak(Box::new(start_send)));
let start_recv_ptr = NonNull::from(Box::leak(Box::new(start_recv)));
let value_vec = vec![
id.into(),
make_closure(closure).into(),
sender_ptr.into(),
start_send_ptr.into(),
start_recv_ptr.into(),
];
Some(value_vec)
}
#[doc(hidden)]
#[wasm_bindgen]
pub fn __dispatch_poll_worker(start_recv: NonNull<SignalReceiver>) -> bool {
if unsafe { start_recv.as_ref() }.try_recv().is_ok() {
let start_recv = unsafe { Box::from_raw(start_recv.as_ptr()) };
drop(start_recv);
true
} else {
false
}
}
#[doc(hidden)]
#[wasm_bindgen]
pub fn __dispatch_drop(recv: NonNull<mpsc::Receiver<BoxClosure>>) {
let recv: Box<mpsc::Receiver<BoxClosure>> = unsafe { Box::from_raw(recv.as_ptr()) };
drop(recv);
}