yew_agent/worker/
spawner.rs1use 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#[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 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 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 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 pub fn with_loader(&mut self, with_loader: bool) -> &mut Self {
97 self.with_loader = with_loader;
98
99 self
100 }
101
102 pub fn as_module(&mut self, as_module: bool) -> &mut Self {
111 self.as_module = as_module;
112
113 self
114 }
115
116 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}