cranpose_core/worker_job.rs
1/// Data a [`WorkerJob`] takes or returns. It crosses to the worker and back
2/// as bytes: a Web Worker has a memory of its own, so nothing that points
3/// into the page's memory can cross.
4pub trait WorkerData: Send + Sized + 'static {
5 /// The bytes [`from_bytes`](Self::from_bytes) reads the value back from.
6 fn into_bytes(self) -> Vec<u8>;
7
8 /// The value [`into_bytes`](Self::into_bytes) wrote as `bytes`.
9 fn from_bytes(bytes: Vec<u8>) -> Self;
10}
11
12impl WorkerData for Vec<u8> {
13 fn into_bytes(self) -> Vec<u8> {
14 self
15 }
16
17 fn from_bytes(bytes: Vec<u8>) -> Self {
18 bytes
19 }
20}
21
22impl WorkerData for String {
23 fn into_bytes(self) -> Vec<u8> {
24 self.into_bytes()
25 }
26
27 fn from_bytes(bytes: Vec<u8>) -> Self {
28 Self::from_utf8(bytes)
29 .unwrap_or_else(|error| Self::from_utf8_lossy(error.as_bytes()).into_owned())
30 }
31}
32
33impl WorkerData for () {
34 fn into_bytes(self) -> Vec<u8> {
35 Vec::new()
36 }
37
38 fn from_bytes(_: Vec<u8>) -> Self {}
39}
40
41// A number crosses as its little-endian bytes; bytes that are no such number
42// read as zero.
43macro_rules! number_data {
44 ($($number:ty),*) => {$(
45 impl WorkerData for $number {
46 fn into_bytes(self) -> Vec<u8> {
47 self.to_le_bytes().to_vec()
48 }
49
50 fn from_bytes(bytes: Vec<u8>) -> Self {
51 bytes
52 .try_into()
53 .map(Self::from_le_bytes)
54 .unwrap_or_default()
55 }
56 }
57 )*};
58}
59
60number_data!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64);
61
62/// A function [`withBlocking`](crate::withBlocking) runs off the UI thread on
63/// every platform: on a pool thread where the platform has threads, in a Web
64/// Worker in a browser. The `#[worker_job]` attribute implements it for a
65/// function, and the function's `with` makes the call `withBlocking` takes:
66///
67/// ```ignore
68/// #[worker_job]
69/// fn word_count(text: String) -> u32 {
70/// text.split_whitespace().count() as u32
71/// }
72///
73/// scope.launch(async move {
74/// if let Ok(count) = withBlocking(word_count.with(text)).await {
75/// shown.set(count);
76/// }
77/// });
78/// ```
79pub trait WorkerJob: Copy + 'static {
80 /// What the job takes.
81 type Input: WorkerData;
82 /// What the job returns.
83 type Output: WorkerData;
84 /// The job's name, one of a kind in the app: a Web Worker finds the job
85 /// by it.
86 const NAME: &'static str;
87
88 /// Does the work.
89 fn run(input: Self::Input) -> Self::Output;
90}
91
92/// A [`WorkerJob`] with its input: the work [`withBlocking`](crate::withBlocking)
93/// runs. A job's `with` makes it.
94pub struct WorkerCall<J: WorkerJob> {
95 pub(crate) input: J::Input,
96}
97
98/// What the code `#[worker_job]` writes calls.
99#[doc(hidden)]
100pub mod __worker {
101 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
102 pub use wasm_bindgen;
103
104 use super::{WorkerCall, WorkerData, WorkerJob};
105
106 /// Job `J` with `input`.
107 pub fn call<J: WorkerJob>(input: J::Input) -> WorkerCall<J> {
108 WorkerCall { input }
109 }
110
111 /// Runs job `J` on the bytes of its input and returns the bytes of its
112 /// output: the call a Web Worker makes.
113 pub fn run_bytes<J: WorkerJob>(input: Vec<u8>) -> Vec<u8> {
114 J::run(J::Input::from_bytes(input)).into_bytes()
115 }
116}