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