use async_channel::Receiver;
use futures::executor::block_on;
use std::any::Any;
use std::fmt;
use std::mem;
use std::panic;
pub use std::thread::{current, sleep, Result, Thread};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
use web_sys::{Blob, Url, Worker, WorkerOptions};
struct WebWorkerContext {
func: Box<dyn FnOnce() + Send>,
}
pub fn get_wasm_bindgen_shim_script_path() -> String {
js_sys::eval(include_str!("script_path.js"))
.unwrap()
.as_string()
.unwrap()
}
pub fn get_worker_script(wasm_bindgen_shim_url: Option<String>) -> String {
let wasm_bindgen_shim_url =
wasm_bindgen_shim_url.unwrap_or_else(get_wasm_bindgen_shim_script_path);
let template = include_str!("web_worker.js");
let script = template.replace("WASM_BINDGEN_SHIM_URL", &wasm_bindgen_shim_url);
let arr = js_sys::Array::new();
arr.set(0, JsValue::from_str(&script));
let blob = Blob::new_with_str_sequence(&arr).unwrap();
Url::create_object_url_with_blob(&blob).unwrap()
}
#[wasm_bindgen]
pub fn wasm_thread_entry_point(ptr: u32) {
let ctx = unsafe { Box::from_raw(ptr as *mut WebWorkerContext) };
(ctx.func)();
}
#[derive(Debug, Default)]
pub struct Builder {
name: Option<String>,
stack_size: Option<usize>,
wasm_bindgen_shim_url: Option<String>,
}
impl Builder {
pub fn new() -> Builder {
Builder::default()
}
pub fn name(mut self, name: String) -> Builder {
self.name = Some(name);
self
}
pub fn stack_size(mut self, size: usize) -> Builder {
self.stack_size = Some(size);
self
}
pub fn wasm_bindgen_shim_url(mut self, url: String) -> Builder {
self.wasm_bindgen_shim_url = Some(url);
self
}
pub fn spawn<F, T>(self, f: F) -> std::io::Result<JoinHandle<T>>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
unsafe { self.spawn_unchecked(f) }
}
pub unsafe fn spawn_unchecked<'a, F, T>(self, f: F) -> std::io::Result<JoinHandle<T>>
where
F: FnOnce() -> T,
F: Send + 'a,
T: Send + 'a,
{
let Builder {
name,
wasm_bindgen_shim_url,
..
} = self;
let (sender, receiver) = async_channel::bounded(1);
let script = get_worker_script(wasm_bindgen_shim_url);
let mut options = WorkerOptions::new();
if let Some(name) = name {
options.name(&name);
}
let worker = Worker::new_with_options(script.as_str(), &options).unwrap();
let main = Box::new(move || {
let res = f();
sender.try_send(res).ok();
});
let ctx = Box::new(WebWorkerContext {
func: mem::transmute::<Box<dyn FnOnce() + Send + 'a>, Box<dyn FnOnce() + Send + 'static>>(
main,
),
});
let ctx_ptr = Box::into_raw(ctx);
let init = js_sys::Array::new();
init.push(&wasm_bindgen::module());
init.push(&wasm_bindgen::memory());
init.push(&JsValue::from(ctx_ptr as u32));
match worker.post_message(&init) {
Ok(()) => Ok(worker),
Err(e) => {
drop(Box::from_raw(ctx_ptr));
Err(e)
}
}
.unwrap();
Ok(JoinHandle(JoinInner { receiver }))
}
}
struct JoinInner<T> {
receiver: Receiver<T>,
}
impl<T> JoinInner<T> {
fn join(&mut self) -> Result<T> {
let res = block_on(self.receiver.recv());
res.map_err(|e| Box::new(e) as Box<(dyn Any + Send + 'static)>)
}
async fn join_async(&mut self) -> Result<T> {
let res = self.receiver.recv().await;
res.map_err(|e| Box::new(e) as Box<(dyn Any + Send + 'static)>)
}
}
pub struct JoinHandle<T>(JoinInner<T>);
impl<T> JoinHandle<T> {
pub fn thread(&self) -> &Thread {
unimplemented!();
}
pub fn join(mut self) -> Result<T> {
self.0.join()
}
pub async fn join_async(mut self) -> Result<T> {
self.0.join_async().await
}
}
impl<T> fmt::Debug for JoinHandle<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("JoinHandle { .. }")
}
}
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
F: FnOnce() -> T,
F: Send + 'static,
T: Send + 'static,
{
Builder::new().spawn(f).expect("failed to spawn thread")
}