This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew_agent/worker/
spawner.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::fmt;
4use std::marker::PhantomData;
5use std::rc::{Rc, Weak};
6
7use js_sys::Array;
8use serde::de::Deserialize;
9use serde::ser::Serialize;
10use web_sys::{Blob, BlobPropertyBag, Url, WorkerOptions, WorkerType};
11
12use super::bridge::{CallbackMap, WorkerBridge};
13use super::handler_id::HandlerId;
14use super::messages::FromWorker;
15use super::native_worker::{DedicatedWorker, NativeWorkerExt};
16use super::traits::Worker;
17use super::{Callback, Shared};
18use crate::codec::{Bincode, Codec};
19use crate::utils::window;
20
21/// A spawner to create workers.
22#[derive(Clone)]
23pub struct WorkerSpawner<W, CODEC = Bincode>
24where
25    W: Worker,
26    CODEC: Codec,
27{
28    _marker: PhantomData<(W, CODEC)>,
29    callback: Option<Callback<W::Output>>,
30    with_loader: bool,
31    as_module: bool,
32}
33
34impl<W, CODEC> fmt::Debug for WorkerSpawner<W, CODEC>
35where
36    W: Worker,
37    CODEC: Codec,
38{
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        f.write_str("WorkerScope<_>")
41    }
42}
43
44impl<W, CODEC> Default for WorkerSpawner<W, CODEC>
45where
46    W: Worker + 'static,
47    CODEC: Codec,
48{
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl<W, CODEC> WorkerSpawner<W, CODEC>
55where
56    W: Worker + 'static,
57    CODEC: Codec,
58{
59    /// Creates a [WorkerSpawner].
60    pub const fn new() -> Self {
61        Self {
62            _marker: PhantomData,
63            callback: None,
64            with_loader: false,
65            as_module: false,
66        }
67    }
68
69    /// Sets a new message encoding.
70    pub fn encoding<C>(&mut self) -> WorkerSpawner<W, C>
71    where
72        C: Codec,
73    {
74        WorkerSpawner {
75            _marker: PhantomData,
76            callback: self.callback.clone(),
77            with_loader: self.with_loader,
78            as_module: self.as_module,
79        }
80    }
81
82    /// Sets a callback.
83    pub fn callback<F>(&mut self, cb: F) -> &mut Self
84    where
85        F: 'static + Fn(W::Output),
86    {
87        self.callback = Some(Rc::new(cb));
88
89        self
90    }
91
92    /// Indicates that [`spawn`](WorkerSpawner#method.spawn) should expect a
93    /// `path` to a loader shim script (e.g. when using Trunk, created by using
94    /// the [`data-loader-shim`](https://trunkrs.dev/assets/#link-asset-types)
95    /// asset type) and one does not need to be generated. `false` by default.
96    pub fn with_loader(&mut self, with_loader: bool) -> &mut Self {
97        self.with_loader = with_loader;
98
99        self
100    }
101
102    /// Determines whether the worker will be spawned with
103    /// [`options.type`](https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker#type)
104    /// set to `module`. `true` by default.
105    ///
106    /// This option should be un-set if the worker was created with the
107    /// `--target no-modules` flag of `wasm-bindgen`. If using Trunk, see the
108    /// [`data-bindgen-target`](https://trunkrs.dev/assets/#link-asset-types)
109    /// asset type.
110    pub fn as_module(&mut self, as_module: bool) -> &mut Self {
111        self.as_module = as_module;
112
113        self
114    }
115
116    /// Spawns a Worker.
117    pub fn spawn(&self, path: &str) -> WorkerBridge<W>
118    where
119        W::Input: Serialize + for<'de> Deserialize<'de>,
120        W::Output: Serialize + for<'de> Deserialize<'de>,
121    {
122        let worker = self.create_worker(path).expect("failed to spawn worker");
123
124        self.spawn_inner(worker)
125    }
126
127    fn create_worker(&self, path: &str) -> Option<DedicatedWorker> {
128        let path = if self.with_loader {
129            std::borrow::Cow::Borrowed(path)
130        } else {
131            let js_shim_url = Url::new_with_base(
132                path,
133                &window().location().href().expect("failed to read href."),
134            )
135            .expect("failed to create url for javascript entrypoint")
136            .to_string();
137
138            let wasm_url = js_shim_url.replace(".js", "_bg.wasm");
139
140            let array = Array::new();
141            let shim = if self.as_module {
142                format!(r#"import init from '{js_shim_url}';await init();"#)
143            } else {
144                format!(r#"importScripts("{js_shim_url}");wasm_bindgen("{wasm_url}");"#)
145            };
146            array.push(&shim.into());
147            let blob_property = BlobPropertyBag::new();
148            blob_property.set_type("application/javascript");
149            let blob = Blob::new_with_str_sequence_and_options(&array, &blob_property).unwrap();
150            let url = Url::create_object_url_with_blob(&blob).unwrap();
151            std::borrow::Cow::Owned(url)
152        };
153        let path = path.as_ref();
154
155        if self.as_module {
156            let options = WorkerOptions::new();
157            options.set_type(WorkerType::Module);
158            DedicatedWorker::new_with_options(path, &options).ok()
159        } else {
160            DedicatedWorker::new(path).ok()
161        }
162    }
163
164    fn spawn_inner(&self, worker: DedicatedWorker) -> WorkerBridge<W>
165    where
166        W::Input: Serialize + for<'de> Deserialize<'de>,
167        W::Output: Serialize + for<'de> Deserialize<'de>,
168    {
169        let pending_queue = Rc::new(RefCell::new(Some(Vec::new())));
170        let handler_id = HandlerId::new();
171        let mut callbacks = HashMap::new();
172
173        if let Some(m) = self.callback.as_ref().map(Rc::downgrade) {
174            callbacks.insert(handler_id, m);
175        }
176
177        let callbacks: Shared<CallbackMap<W>> = Rc::new(RefCell::new(callbacks));
178
179        let handler = {
180            let pending_queue = pending_queue.clone();
181            let callbacks = callbacks.clone();
182
183            let worker = worker.clone();
184
185            move |msg: FromWorker<W>| match msg {
186                FromWorker::WorkerLoaded => {
187                    if let Some(pending_queue) = pending_queue.borrow_mut().take() {
188                        for to_worker in pending_queue.into_iter() {
189                            worker.post_packed_message::<_, CODEC>(to_worker);
190                        }
191                    }
192                }
193                FromWorker::ProcessOutput(id, output) => {
194                    let mut callbacks = callbacks.borrow_mut();
195
196                    if let Some(m) = callbacks.get(&id) {
197                        if let Some(m) = Weak::upgrade(m) {
198                            m(output);
199                        } else {
200                            callbacks.remove(&id);
201                        }
202                    }
203                }
204            }
205        };
206
207        worker.set_on_packed_message::<_, CODEC, _>(handler);
208
209        WorkerBridge::<W>::new::<CODEC>(
210            handler_id,
211            worker,
212            pending_queue,
213            callbacks,
214            self.callback.clone(),
215        )
216    }
217}