use std::panic::AssertUnwindSafe;
use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, Ordering};
use js_sys::{Function, Promise};
use wasm_bindgen::{JsCast, JsError, JsValue};
use crate::binding;
use crate::binding_constants::{WBG_TARGET_NO_MODULES, WBG_TARGET_WEB};
use crate::join::JoinHandle;
use crate::util::{
DispatchPayload, DispatchReceiver, DispatchSender, ThreadProc, js_arg_vec, js_type,
raw_ptr_type,
};
pub fn init_bg_no_modules(bg_js: JsValue, wasm_module: JsValue) -> ThreadDispatcherInit {
ThreadDispatcherInit {
bg_target: WBG_TARGET_NO_MODULES,
bg_js,
wasm_module,
}
}
pub fn init_bg_web(bg_js: JsValue, wasm_module: JsValue) -> ThreadDispatcherInit {
ThreadDispatcherInit {
bg_target: WBG_TARGET_WEB,
bg_js,
wasm_module,
}
}
#[must_use = "This is the builder and the thread dispatcher is not created until you call create_dispatcher() or create_dispatcher_promise() and wait on the future/promise"]
pub struct ThreadDispatcherInit {
bg_target: u32,
bg_js: JsValue,
wasm_module: JsValue,
}
impl ThreadDispatcherInit {
pub fn create_dispatcher_promise(self) -> Promise {
js_sys::futures::future_to_promise(AssertUnwindSafe(async move {
self.create_dispatcher().await?;
Ok(JsValue::undefined())
}))
}
pub async fn create_dispatcher(self) -> Result<(), JsValue> {
{
let dispatcher_guard = DISPATCHER.lock().expect("cannot lock the dispatcher");
if dispatcher_guard.is_some() {
drop(dispatcher_guard);
panic!("{DISPATCHER_ALREADY_INIT_WARNING}");
}
}
let create_dispatcher = Function::new_with_args("ARGS", include_str!("dispatcher.js"));
let (send, recv) = tokio::sync::mpsc::unbounded_channel::<DispatchPayload>();
let (signal_send, signal_recv) = oneshot::channel::<()>();
let signal_recv = AssertUnwindSafe(signal_recv);
let creator_args = js_arg_vec! {
[
bg_target: js_type!(number) = self.bg_target.into(),
bg_js: js_type!(string) = self.bg_js,
wasm_module: js_type!(OpaqueWebAssemblyModule | BufferSource) = self.wasm_module,
memory: js_type!(WebAssembly.Memory) = wasm_bindgen::memory(),
recv_ptr: *mut DispatchReceiver = binding::into_js(recv),
dispatcher_start_signal_send_ptr: raw_ptr_type!(SignalSender) = signal_send.into_raw(),
] as ThreadCreatorArgs
};
let _ = create_dispatcher
.call1(&JsValue::null(), &JsValue::from(creator_args))?
.dyn_into::<Promise>()?
.await?;
let yield_fn = Function::new_no_args("return new Promise(r=>setTimeout(r,0))");
yield_fn
.call0(&JsValue::null())?
.dyn_into::<Promise>()?
.await?;
loop {
match signal_recv.try_recv() {
Err(oneshot::TryRecvError::Empty) => {
yield_fn
.call0(&JsValue::null())?
.dyn_into::<Promise>()?
.await?;
}
Err(oneshot::TryRecvError::Disconnected) => {
return Err(JsError::new(
"The wasm-bindgen-spawn thread dispatcher disconnected!",
)
.into());
}
_ => break,
}
}
{
let mut dispatcher_guard = DISPATCHER.lock().expect("cannot lock the dispatcher");
if dispatcher_guard.is_some() {
drop(dispatcher_guard);
panic!("{DISPATCHER_ALREADY_INIT_WARNING}");
}
*dispatcher_guard = Some(send);
}
Ok(())
}
}
static NEXT_THREAD_ID: AtomicUsize = AtomicUsize::new(1);
static DISPATCHER: Mutex<Option<DispatchSender>> = Mutex::new(None);
static DISPATCHER_ALREADY_INIT_WARNING: &str = "The wasm-bindgen-spawn thread dispatcher is already initialized! The dispatcher is a global, in the shared memory, not a thread-local, so all threads have access to it and you do not need to initialize it per-thread";
#[inline(always)]
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
match try_spawn(f) {
Ok(x) => x,
Err(e) => panic!("Failed to spawn thread with wasm-bindgen-spawn: {e}"),
}
}
#[inline(always)]
pub fn try_spawn<F, T>(f: F) -> Result<JoinHandle<T>, SpawnError>
where
F: FnOnce() -> T + Send + 'static,
T: Send + 'static,
{
let f_boxed: ThreadProc = Box::new(move || {
let value = f();
let wrapped_f = std::future::ready(Box::new(value).into());
Box::pin(wrapped_f)
});
spawn_impl(f_boxed)
}
#[inline(always)]
pub fn spawn_async<TFn, TFuture, T>(f: TFn) -> JoinHandle<T>
where
TFn: FnOnce() -> TFuture + Send + 'static,
TFuture: Future<Output = T> + 'static,
T: Send + 'static,
{
match try_spawn_async(f) {
Ok(x) => x,
Err(e) => panic!("Failed to spawn thread with wasm-bindgen-spawn: {e}"),
}
}
#[inline(always)]
pub fn try_spawn_async<TFn, TFuture, T>(f: TFn) -> Result<JoinHandle<T>, SpawnError>
where
TFn: FnOnce() -> TFuture + Send + 'static,
TFuture: Future<Output = T> + 'static,
T: Send + 'static,
{
let f_boxed: ThreadProc = Box::new(move || {
let fut = f();
let wrapped_f = async move {
let value = fut.await;
Box::new(value).into()
};
Box::pin(wrapped_f)
});
spawn_impl(f_boxed)
}
fn spawn_impl<T>(f: ThreadProc) -> Result<JoinHandle<T>, SpawnError>
where
T: Send + 'static,
{
let dispatcher = {
let dispatcher = match DISPATCHER.lock() {
Err(_) => {
return Err(SpawnError::DispatcherPoisoned);
}
Ok(x) => x,
};
let Some(dispatcher) = &*dispatcher else {
return Err(SpawnError::NotInit);
};
dispatcher.clone()
};
let next_id = NEXT_THREAD_ID.fetch_add(1, Ordering::Relaxed);
let (send, recv) = oneshot::channel();
dispatcher
.send((f, send))
.map_err(|_| SpawnError::Disconnected)?;
Ok(JoinHandle::new(next_id, recv))
}
#[derive(Debug, thiserror::Error)]
pub enum SpawnError {
#[error(
"The wasm-bindgen-spawn thread dispatcher was not initialized. You must call one of the wasm_bindgen_spawn::init_bg_* functions before spawning threads"
)]
NotInit,
#[error("The wasm-bindgen-spawn thread dispatcher was poisoned.")]
DispatcherPoisoned,
#[error("The wasm-bindgen-spawn thread dispatcher has disconnected")]
Disconnected,
}
pub fn terminate_dispatcher() {
if let Ok(mut dispatcher) = DISPATCHER.lock() {
*dispatcher = None;
}
}