firebase_rs_sdk/platform/
runtime.rs

1use std::fmt;
2use std::future::Future;
3use std::time::{Duration, SystemTime};
4
5#[cfg(all(target_arch = "wasm32", feature = "wasm-web"))]
6use js_sys::Date;
7#[cfg(all(target_arch = "wasm32", feature = "wasm-web"))]
8use std::time::UNIX_EPOCH;
9
10/// Platform-independent helper to spawn an async task that runs in the background.
11#[cfg(target_arch = "wasm32")]
12pub fn spawn_detached<F>(future: F)
13where
14    F: Future<Output = ()> + 'static,
15{
16    wasm_bindgen_futures::spawn_local(future);
17}
18
19/// Platform-independent helper to spawn an async task that runs in the background.
20#[cfg(not(target_arch = "wasm32"))]
21pub fn spawn_detached<F>(future: F)
22where
23    F: Future<Output = ()> + Send + 'static,
24{
25    use std::sync::LazyLock;
26    use tokio::runtime::{Builder, Handle, Runtime};
27
28    static BACKGROUND_RUNTIME: LazyLock<Runtime> = LazyLock::new(|| {
29        Builder::new_current_thread()
30            .enable_all()
31            .build()
32            .expect("failed to build background tokio runtime")
33    });
34
35    if let Ok(handle) = Handle::try_current() {
36        handle.spawn(future);
37    } else {
38        let _ = BACKGROUND_RUNTIME.spawn(future);
39    }
40}
41
42/// Returns the current system time in a platform-aware way.
43#[cfg(all(target_arch = "wasm32", feature = "wasm-web"))]
44pub fn now() -> SystemTime {
45    let millis = Date::now() as u64;
46    UNIX_EPOCH + Duration::from_millis(millis)
47}
48
49/// Returns the current system time in a platform-aware way.
50#[cfg(not(all(target_arch = "wasm32", feature = "wasm-web")))]
51pub fn now() -> SystemTime {
52    SystemTime::now()
53}
54
55/// Asynchronously waits for the provided duration in a platform-compatible way.
56pub async fn sleep(duration: Duration) {
57    if duration.is_zero() {
58        return;
59    }
60
61    sleep_impl(duration).await;
62}
63
64/// Cooperatively yields to the scheduler/event-loop in a platform-aware way.
65pub async fn yield_now() {
66    yield_now_impl().await;
67}
68
69/// Timeout error returned when an operation exceeds the allotted duration.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub struct TimeoutError;
72
73impl fmt::Display for TimeoutError {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(f, "operation timed out")
76    }
77}
78
79impl std::error::Error for TimeoutError {}
80
81/// Runs the provided future and resolves with `TimeoutError` if it does not complete
82/// within the specified duration.
83pub async fn with_timeout<F, T>(future: F, duration: Duration) -> Result<T, TimeoutError>
84where
85    F: Future<Output = T>,
86{
87    if duration.is_zero() {
88        return Ok(future.await);
89    }
90
91    with_timeout_impl(future, duration).await
92}
93
94#[cfg(target_arch = "wasm32")]
95async fn sleep_impl(duration: Duration) {
96    use gloo_timers::future::sleep;
97    sleep(duration).await;
98}
99
100#[cfg(not(target_arch = "wasm32"))]
101async fn sleep_impl(duration: Duration) {
102    use tokio::time::sleep;
103    sleep(duration).await;
104}
105
106#[cfg(target_arch = "wasm32")]
107async fn yield_now_impl() {
108    use std::future::Future;
109    use std::pin::Pin;
110    use std::task::{Context, Poll};
111
112    struct YieldOnce {
113        yielded: bool,
114    }
115
116    impl Future for YieldOnce {
117        type Output = ();
118
119        fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
120            if self.yielded {
121                Poll::Ready(())
122            } else {
123                self.yielded = true;
124                cx.waker().wake_by_ref();
125                Poll::Pending
126            }
127        }
128    }
129
130    YieldOnce { yielded: false }.await;
131}
132
133#[cfg(not(target_arch = "wasm32"))]
134async fn yield_now_impl() {
135    tokio::task::yield_now().await;
136}
137
138#[cfg(not(target_arch = "wasm32"))]
139async fn with_timeout_impl<F, T>(future: F, duration: Duration) -> Result<T, TimeoutError>
140where
141    F: Future<Output = T>,
142{
143    use tokio::time::timeout;
144
145    timeout(duration, future).await.map_err(|_| TimeoutError)
146}
147
148#[cfg(target_arch = "wasm32")]
149async fn with_timeout_impl<F, T>(future: F, duration: Duration) -> Result<T, TimeoutError>
150where
151    F: Future<Output = T>,
152{
153    use futures::future::poll_fn;
154    use gloo_timers::future::TimeoutFuture;
155    use std::future::Future;
156
157    let mut future = Box::pin(future);
158    let timeout_ms = duration.as_millis().min(u32::MAX as u128) as u32;
159    let timeout_ms = timeout_ms.max(1);
160    let mut timeout_future = Box::pin(TimeoutFuture::new(timeout_ms));
161
162    poll_fn(|cx| {
163        if let std::task::Poll::Ready(result) = future.as_mut().poll(cx) {
164            return std::task::Poll::Ready(Ok(result));
165        }
166
167        if let std::task::Poll::Ready(_) = timeout_future.as_mut().poll(cx) {
168            return std::task::Poll::Ready(Err(TimeoutError));
169        }
170
171        std::task::Poll::Pending
172    })
173    .await
174}