Skip to main content

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}