quad_wasmnastics/
waiter.rs

1//! In case working with `async` code wasn't already nightmarish enough!
2
3use crate::js_convert::MaybeFromJsObject;
4
5use std::fmt::{self, Debug};
6
7/// Something that is waiting on a value from Javascript,
8/// or has the value immediately via a desktop API.
9///
10/// This struct was made for the Clipboard API, so it might be ill-suited
11/// for other APIs you may wish to implement. If you need more functionality
12/// feel free to open an issue or something.
13#[derive(Debug)]
14pub struct Waiter<T> {
15    inner: WaiterInner<T>,
16}
17
18impl<T: MaybeFromJsObject> Waiter<T> {
19    /// Make a new Waiter with an immediate value.
20    #[cfg(not(target_arch = "wasm32"))]
21    pub fn new_immediate(val: T) -> Self {
22        Self {
23            inner: WaiterInner::Available(val),
24        }
25    }
26
27    /// Make a new Waiter from a JsObject returned from `waitify`.
28    #[cfg(target_arch = "wasm32")]
29    pub fn new_waiting(waiter: sapp_jsutils::JsObject) -> Self {
30        Self {
31            inner: WaiterInner::Waiting(waiter, std::marker::PhantomData),
32        }
33    }
34
35    /// Make a new Waiter that will never return Some, in case you wanted to do that for some reason.
36    pub fn new_empty() -> Self {
37        Self {
38            inner: WaiterInner::Taken,
39        }
40    }
41
42    /// Try and get the value from this.
43    ///
44    /// You shouldn't write your code relying on this to return anytime soon.
45    /// On desktop it might, but it may take several frames on the web.
46    /// Probably best to assume for the worst.
47    ///
48    /// This will never return `Some` if the conversion from JS fails, or if
49    /// something else wrong happens in JS, like no `waiting` field.
50    /// (The JS API will try and print any exceptions to the console but you can't
51    /// rely on it from Rust.)
52    ///
53    /// Once this returns `Some` once, it will never return `Some` again (for a specific `Waiter`).
54    /// The value will have moved out of it.
55    pub fn try_get(&mut self) -> Option<T> {
56        match &mut self.inner {
57            WaiterInner::Taken => {
58                // what are you doing go away
59                None
60            }
61
62            #[cfg(not(target_arch = "wasm32"))]
63            WaiterInner::Available(_) => {
64                // entry api when
65                let taken = std::mem::replace(&mut self.inner, WaiterInner::Taken);
66                let taken = match taken {
67                    WaiterInner::Available(it) => it,
68                    _ => unreachable!(),
69                };
70                Some(taken)
71            }
72
73            #[cfg(target_arch = "wasm32")]
74            WaiterInner::Waiting(waiter, _phantom) => {
75                use crate::objecttools::ObjectTools;
76
77                let res: Result<Option<T>, String> = (|| {
78                    let waiting = waiter
79                        .try_get_field("waiting")
80                        .ok_or_else(|| "Couldn't find `waiting` field".to_string())?;
81                    Ok(if waiting.truthy() {
82                        // too bad
83                        None
84                    } else {
85                        // ooh we get our value?
86                        let value = waiter
87                            .try_get_field("value")
88                            .ok_or_else(|| "Couldn't find `value` field".to_string())?;
89                        let value = T::from_js(value).map_err(|e| {
90                            let err: Box<_> = e.into();
91                            err.to_string()
92                        })?;
93                        // nice!
94                        Some(value)
95                    })
96                })();
97                match res {
98                    Ok(it) => {
99                        if let Some(it) = it {
100                            self.inner = WaiterInner::Taken;
101                            Some(it)
102                        } else {
103                            // Oh well, better luck next frame?
104                            None
105                        }
106                    }
107                    Err(oh_no) => {
108                        self.inner = WaiterInner::Error(oh_no);
109                        None
110                    }
111                }
112            }
113
114            #[cfg(target_arch = "wasm32")]
115            WaiterInner::Error(..) => None,
116        }
117    }
118}
119
120enum WaiterInner<T> {
121    /// The value has been taken.
122    Taken,
123
124    /// The value is immediately available.
125    #[cfg(not(target_arch = "wasm32"))]
126    Available(T),
127    /// On the web, we wait.
128    ///
129    /// I hate waiting.
130    ///
131    /// Must have the phantom data here because otherwise the T isn't used anywhere in the type
132    /// on wasm.
133    #[cfg(target_arch = "wasm32")]
134    Waiting(sapp_jsutils::JsObject, std::marker::PhantomData<T>),
135    /// An error occurred somewhere.
136    /// And here's your error!
137    #[cfg(target_arch = "wasm32")]
138    Error(String),
139}
140
141/// JsObject doesn't impl Debug >:(
142impl<T: Debug> Debug for WaiterInner<T> {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        match self {
145            Self::Taken => write!(f, "Taken"),
146            #[cfg(not(target_arch = "wasm32"))]
147            Self::Available(it) => write!(f, "Available({:?})", it),
148            #[cfg(target_arch = "wasm32")]
149            WaiterInner::Waiting(_, _) => write!(f, "Waiting"),
150            #[cfg(target_arch = "wasm32")]
151            WaiterInner::Error(e) => write!(f, "Error({})", e),
152        }
153    }
154}