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}