diff --git a/Cargo.toml b/Cargo.toml index 743bdf0..9be933f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,20 +15,21 @@ readme = "README.md" [features] default = ["es_modules"] es_modules = [] +wasm_sync_init = [] [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2.95" web-sys = { version = "0.3.72", features = [ - "Blob", - "DedicatedWorkerGlobalScope", - "MessageEvent", - "Url", - "Worker", - "WorkerType", - "WorkerOptions", - "Window", - "Navigator", - "WorkerNavigator", + "Blob", + "DedicatedWorkerGlobalScope", + "MessageEvent", + "Url", + "Worker", + "WorkerType", + "WorkerOptions", + "Window", + "Navigator", + "WorkerNavigator", ] } js-sys = "0.3.72" futures = "0.3.31" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 4887225..b9c0517 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,5 +2,5 @@ # https://rust-lang.github.io/rustup-components-history [toolchain] channel = "nightly-2024-03-16" -components = ["rust-src", "rustfmt"] +components = ["rust-src", "rustfmt", "rust-analyzer"] targets = ["wasm32-unknown-unknown"] diff --git a/src/lib.rs b/src/lib.rs index a2685c4..7a6f222 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(thread_id_value)] #![cfg_attr(target_arch = "wasm32", feature(stdarch_wasm_atomic_wait))] // Import reusable APIs from std diff --git a/src/wasm32/js/web_worker.sync.js b/src/wasm32/js/web_worker.sync.js new file mode 100644 index 0000000..4130e28 --- /dev/null +++ b/src/wasm32/js/web_worker.sync.js @@ -0,0 +1,25 @@ +// synchronously, using the browser, import wasm_bindgen shim JS scripts +importScripts("WASM_BINDGEN_SHIM_URL"); + +// Wait for the main thread to send us the shared module/memory and work context. +// Once we've got it, initialize it all with the `wasm_bindgen` global we imported via +// `importScripts`. +self.onmessage = (event) => { + let [module, memory, work] = event.data; + try { + let wasm = wasm_bindgen(module, memory); + // Enter rust code by calling entry point defined in `lib.rs`. + // This executes closure defined by work context. + wasm.wasm_thread_entry_point(work); + + // Once done, terminate web worker + close(); + } catch (err) { + // Propagate to main `onerror`: + setTimeout(() => { + throw err; + }); + // Rethrow to keep promise rejected and prevent execution of further commands: + throw err; + } +}; diff --git a/src/wasm32/mod.rs b/src/wasm32/mod.rs index 8bcce7e..a186d54 100644 --- a/src/wasm32/mod.rs +++ b/src/wasm32/mod.rs @@ -13,7 +13,7 @@ use scoped::ScopeData; pub use scoped::{scope, Scope, ScopedJoinHandle}; use signal::Signal; use utils::SpinLockMutex; -pub use utils::{available_parallelism, get_wasm_bindgen_shim_script_path, get_worker_script, is_web_worker_thread}; +pub use utils::{available_parallelism, get_wasm_bindgen_shim_script_path, get_worker_script, is_main_thread}; use wasm_bindgen::prelude::*; use web_sys::{DedicatedWorkerGlobalScope, Worker, WorkerOptions, WorkerType}; @@ -57,8 +57,7 @@ impl WorkerMessage { pub fn post(self) { let req = Box::new(self); - js_sys::eval("self") - .unwrap() + js_sys::global() .dyn_into::() .unwrap() .post_message(&JsValue::from(Box::into_raw(req) as u32)) @@ -251,7 +250,7 @@ impl Builder { func: mem::transmute::, Box>(main), }; - if is_web_worker_thread() { + if !is_main_thread() { WorkerMessage::SpawnThread(BuilderRequest { builder: self, context }).post(); } else { self.spawn_for_context(context); diff --git a/src/wasm32/scoped.rs b/src/wasm32/scoped.rs index cd18532..df13c18 100644 --- a/src/wasm32/scoped.rs +++ b/src/wasm32/scoped.rs @@ -7,7 +7,7 @@ use std::{ }, }; -use super::{signal::Signal, utils::is_web_worker_thread, Builder, JoinInner}; +use super::{signal::Signal, utils::is_main_thread, Builder, JoinInner}; /// A scope to spawn scoped threads in. /// @@ -89,7 +89,7 @@ where F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> T, { // Fail early to avoid flaky panics that depend on execution time - if !is_web_worker_thread() { + if is_main_thread() { panic!("scope is not allowed on the main thread"); } diff --git a/src/wasm32/utils.rs b/src/wasm32/utils.rs index 1791657..78e88eb 100644 --- a/src/wasm32/utils.rs +++ b/src/wasm32/utils.rs @@ -4,16 +4,15 @@ use std::{ sync::{LockResult, Mutex, MutexGuard, TryLockError}, }; +use js_sys::Reflect; use wasm_bindgen::prelude::*; -use web_sys::{Blob, Url, WorkerGlobalScope}; +use web_sys::{Blob, Url}; pub fn available_parallelism() -> io::Result { - if let Some(window) = web_sys::window() { - return Ok(NonZeroUsize::new(window.navigator().hardware_concurrency() as usize).unwrap()); - } - - if let Ok(worker) = js_sys::eval("self").unwrap().dyn_into::() { - return Ok(NonZeroUsize::new(worker.navigator().hardware_concurrency() as usize).unwrap()); + if let Ok(navigator) = Reflect::get(&js_sys::global(), &"navigator".into()) { + if let Ok(hardware_concurrency) = Reflect::get(&navigator, &"hardwareConcurrency".into()) { + return Ok(NonZeroUsize::new(hardware_concurrency.as_f64().unwrap() as usize).unwrap()); + } } Err(io::Error::new( @@ -22,8 +21,8 @@ pub fn available_parallelism() -> io::Result { )) } -pub fn is_web_worker_thread() -> bool { - js_sys::eval("self").unwrap().dyn_into::().is_ok() +pub fn is_main_thread() -> bool { + std::thread::current().id().as_u64().get() == 1_u64 } /// Extracts path of the `wasm_bindgen` generated .js shim script. @@ -52,8 +51,10 @@ pub fn get_worker_script(wasm_bindgen_shim_url: Option) -> String { // Generate script from template #[cfg(feature = "es_modules")] let template = include_str!("js/web_worker_module.js"); - #[cfg(not(feature = "es_modules"))] + #[cfg(all(not(feature = "es_modules"), not(feature = "wasm_sync_init")))] let template = include_str!("js/web_worker.js"); + #[cfg(feature = "wasm_sync_init")] + let template = include_str!("js/web_worker.sync.js"); let script = template.replace("WASM_BINDGEN_SHIM_URL", &wasm_bindgen_shim_url);