1use std::{future::Future, time::Duration};
7
8#[cfg(not(target_arch = "wasm32"))]
11pub trait MaybeSend: Send {}
12#[cfg(not(target_arch = "wasm32"))]
13impl<T: Send + ?Sized> MaybeSend for T {}
14#[cfg(target_arch = "wasm32")]
15pub trait MaybeSend {}
16#[cfg(target_arch = "wasm32")]
17impl<T: ?Sized> MaybeSend for T {}
18
19#[cfg(not(target_arch = "wasm32"))]
21pub trait MaybeSync: Sync {}
22#[cfg(not(target_arch = "wasm32"))]
23impl<T: Sync + ?Sized> MaybeSync for T {}
24#[cfg(target_arch = "wasm32")]
25pub trait MaybeSync {}
26#[cfg(target_arch = "wasm32")]
27impl<T: ?Sized> MaybeSync for T {}
28
29pub fn spawn(future: impl Future<Output = ()> + MaybeSend + 'static) {
33 #[cfg(not(target_arch = "wasm32"))]
34 tokio::spawn(future);
35 #[cfg(target_arch = "wasm32")]
36 wasm_bindgen_futures::spawn_local(future);
37}
38
39#[cfg(not(target_arch = "wasm32"))]
40pub(crate) async fn sleep(duration: Duration) {
41 tokio::time::sleep(duration).await;
42}
43
44#[cfg(target_arch = "wasm32")]
45#[wasm_bindgen::prelude::wasm_bindgen]
46extern "C" {
47 #[wasm_bindgen(js_name = setTimeout)]
49 fn set_timeout(handler: &js_sys::Function, millis: i32);
50}
51
52#[cfg(target_arch = "wasm32")]
53pub(crate) async fn sleep(duration: Duration) {
54 let millis = duration.as_millis().min(i32::MAX as u128) as i32;
55 let promise = js_sys::Promise::new(&mut |resolve, _| set_timeout(&resolve, millis));
56 let _ = wasm_bindgen_futures::JsFuture::from(promise).await;
57}
58
59pub(crate) trait Timeout {
61 fn timeout(limit: Duration) -> Self;
62}
63
64#[cfg(not(target_arch = "wasm32"))]
69pub(crate) struct Exclusive;
70
71#[cfg(not(target_arch = "wasm32"))]
72pub(crate) async fn exclusive(_name: &str) -> Exclusive {
73 Exclusive
74}
75
76#[cfg(target_arch = "wasm32")]
77pub(crate) struct Exclusive(Option<js_sys::Function>);
78
79#[cfg(target_arch = "wasm32")]
80impl Drop for Exclusive {
81 fn drop(&mut self) {
82 if let Some(release) = self.0.take() {
83 let _ = release.call0(&wasm_bindgen::JsValue::UNDEFINED);
84 }
85 }
86}
87
88#[cfg(target_arch = "wasm32")]
94pub(crate) async fn exclusive(name: &str) -> Exclusive {
95 use js_sys::{Array, Function, Promise, Reflect};
96 use wasm_bindgen::{JsCast, JsValue, closure::Closure};
97
98 let locks = Reflect::get(&js_sys::global(), &"navigator".into())
99 .and_then(|navigator| Reflect::get(&navigator, &"locks".into()))
100 .ok()
101 .filter(JsValue::is_object);
102 let Some(locks) = locks else {
103 return Exclusive(None);
104 };
105 let Some(request) = Reflect::get(&locks, &"request".into())
106 .ok()
107 .and_then(|request| request.dyn_into::<Function>().ok())
108 else {
109 return Exclusive(None);
110 };
111
112 let mut release = None;
113 let held = Promise::new(&mut |resolve, _| release = Some(resolve));
114 let guard = Exclusive(release);
115 let mut grant = None;
116 let granted = Promise::new(&mut |resolve, _| grant = Some(resolve));
117 let Some(grant) = grant else {
118 return guard;
119 };
120 let callback = Closure::once_into_js(move |_lock: JsValue| {
121 let _ = grant.call0(&JsValue::UNDEFINED);
122 held
123 });
124 let Ok(pending) = request.call2(&locks, &JsValue::from_str(name), &callback) else {
125 return guard;
126 };
127 let pending = Promise::resolve(&pending);
129 let _ =
130 wasm_bindgen_futures::JsFuture::from(Promise::race(&Array::of2(&granted, &pending))).await;
131 guard
132}
133
134pub(crate) async fn bounded<T, E: Timeout>(
137 limit: Duration,
138 call: impl Future<Output = Result<T, E>>,
139) -> Result<T, E> {
140 tokio::select! {
141 result = call => result,
142 _ = sleep(limit) => Err(E::timeout(limit)),
143 }
144}