use std::{future::Future, time::Duration};
#[cfg(not(target_arch = "wasm32"))]
pub trait MaybeSend: Send {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Send + ?Sized> MaybeSend for T {}
#[cfg(target_arch = "wasm32")]
pub trait MaybeSend {}
#[cfg(target_arch = "wasm32")]
impl<T: ?Sized> MaybeSend for T {}
#[cfg(not(target_arch = "wasm32"))]
pub trait MaybeSync: Sync {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Sync + ?Sized> MaybeSync for T {}
#[cfg(target_arch = "wasm32")]
pub trait MaybeSync {}
#[cfg(target_arch = "wasm32")]
impl<T: ?Sized> MaybeSync for T {}
pub fn spawn(future: impl Future<Output = ()> + MaybeSend + 'static) {
#[cfg(not(target_arch = "wasm32"))]
tokio::spawn(future);
#[cfg(target_arch = "wasm32")]
wasm_bindgen_futures::spawn_local(future);
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn sleep(duration: Duration) {
tokio::time::sleep(duration).await;
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen::prelude::wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = setTimeout)]
fn set_timeout(handler: &js_sys::Function, millis: i32);
}
#[cfg(target_arch = "wasm32")]
pub(crate) async fn sleep(duration: Duration) {
let millis = duration.as_millis().min(i32::MAX as u128) as i32;
let promise = js_sys::Promise::new(&mut |resolve, _| set_timeout(&resolve, millis));
let _ = wasm_bindgen_futures::JsFuture::from(promise).await;
}
pub(crate) trait Timeout {
fn timeout(limit: Duration) -> Self;
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) struct Exclusive;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn exclusive(_name: &str) -> Exclusive {
Exclusive
}
#[cfg(target_arch = "wasm32")]
pub(crate) struct Exclusive(Option<js_sys::Function>);
#[cfg(target_arch = "wasm32")]
impl Drop for Exclusive {
fn drop(&mut self) {
if let Some(release) = self.0.take() {
let _ = release.call0(&wasm_bindgen::JsValue::UNDEFINED);
}
}
}
#[cfg(target_arch = "wasm32")]
pub(crate) async fn exclusive(name: &str) -> Exclusive {
use js_sys::{Array, Function, Promise, Reflect};
use wasm_bindgen::{JsCast, JsValue, closure::Closure};
let locks = Reflect::get(&js_sys::global(), &"navigator".into())
.and_then(|navigator| Reflect::get(&navigator, &"locks".into()))
.ok()
.filter(JsValue::is_object);
let Some(locks) = locks else {
return Exclusive(None);
};
let Some(request) = Reflect::get(&locks, &"request".into())
.ok()
.and_then(|request| request.dyn_into::<Function>().ok())
else {
return Exclusive(None);
};
let mut release = None;
let held = Promise::new(&mut |resolve, _| release = Some(resolve));
let guard = Exclusive(release);
let mut grant = None;
let granted = Promise::new(&mut |resolve, _| grant = Some(resolve));
let Some(grant) = grant else {
return guard;
};
let callback = Closure::once_into_js(move |_lock: JsValue| {
let _ = grant.call0(&JsValue::UNDEFINED);
held
});
let Ok(pending) = request.call2(&locks, &JsValue::from_str(name), &callback) else {
return guard;
};
let pending = Promise::resolve(&pending);
let _ =
wasm_bindgen_futures::JsFuture::from(Promise::race(&Array::of2(&granted, &pending))).await;
guard
}
pub(crate) async fn bounded<T, E: Timeout>(
limit: Duration,
call: impl Future<Output = Result<T, E>>,
) -> Result<T, E> {
tokio::select! {
result = call => result,
_ = sleep(limit) => Err(E::timeout(limit)),
}
}