Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/async/mod.rs
1use std::{
2 future::Future,
3 sync::atomic::{AtomicUsize, Ordering},
4 time::Duration,
5};
6 
7use futures::{pin_mut, FutureExt as _};
8use futures_util::stream::AbortHandle;
9 
10cfg_if::cfg_if! {
11 if #[cfg(target_family = "wasm")] {
12 mod wasm;
13 use wasm as imp;
14 } else {
15 mod native;
16 use native as imp;
17 }
18}
19 
20// Re-export a variety of symbols from the internal implementation modules.
21pub use imp::{block_on, BoxFuture, Spawnable, SpawnableOutput, Stream, Timer, TransportStream};
22 
23pub use futures_util::future::LocalBoxFuture;
24 
25pub mod executor {
26 #[derive(thiserror::Error, Debug)]
27 pub enum Error {
28 #[error("constructed off of the main thread")]
29 NotOnMainThread,
30 }
31 
32 pub use super::imp::executor::*;
33}
34 
35#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
36pub struct FutureId(usize);
37 
38static NEXT_FUTURE_ID: AtomicUsize = AtomicUsize::new(0);
39impl FutureId {
40 /// \return the next view ID. Note the first return is 0.
41 #[allow(clippy::new_without_default)]
42 pub(super) fn new() -> FutureId {
43 let raw = NEXT_FUTURE_ID.fetch_add(1, Ordering::Relaxed);
44 FutureId(raw)
45 }
46}
47 
48/// A handle to a future that was spawned on an executor via `ctx#spawn`.
49/// The handle can be used to abort the future using `#abort`. In tests, the
50/// future ID can be used to await the spawned future.
51#[derive(Debug, Clone)]
52pub struct SpawnedFutureHandle {
53 abort_handle: AbortHandle,
54 future_id: FutureId,
55}
56 
57impl SpawnedFutureHandle {
58 /// Abort the spawned future associated with this handle.
59 pub fn abort(&self) {
60 self.abort_handle.abort()
61 }
62 
63 pub fn abort_handle(&self) -> AbortHandle {
64 self.abort_handle.clone()
65 }
66 
67 /// The `FutureID` associated with this `SpawnedFuture`. In tests, this can be used to
68 /// await the spawned future.
69 pub fn future_id(&self) -> FutureId {
70 self.future_id
71 }
72 
73 pub fn new(abort_handle: AbortHandle, future_id: FutureId) -> Self {
74 Self {
75 abort_handle,
76 future_id,
77 }
78 }
79}
80 
81pub struct SpawnedLocalStream {
82 #[allow(dead_code)]
83 future: LocalBoxFuture<'static, ()>,
84}
85 
86impl SpawnedLocalStream {
87 #[cfg(test)]
88 pub(crate) fn into_future(self) -> LocalBoxFuture<'static, ()> {
89 self.future
90 }
91 
92 pub(crate) fn new(future: LocalBoxFuture<'static, ()>) -> Self {
93 Self { future }
94 }
95}
96 
97/// This trait impl allows us to use `Background` as an executor in some executor-agnostic libraries,
98impl futures_util::task::Spawn for executor::Background {
99 fn spawn_obj(
100 &self,
101 future: futures::task::FutureObj<'static, ()>,
102 ) -> Result<(), futures::task::SpawnError> {
103 self.spawn(future).detach();
104 Ok(())
105 }
106 
107 fn status(&self) -> Result<(), futures::task::SpawnError> {
108 Ok(())
109 }
110}
111 
112#[derive(Debug)]
113pub struct TimeoutError;
114 
115pub trait FutureExt: Future {
116 /// Converts a future into one that will time out with an error after a
117 /// given duration.
118 ///
119 /// Note that this timeout can only occur while the future is at an await
120 /// point, so futures wrapped in this way must periodically yield back to
121 /// the executor.
122 fn with_timeout(
123 self,
124 timeout: Duration,
125 ) -> impl Future<Output = Result<<Self as Future>::Output, TimeoutError>>;
126}
127 
128impl<F: Future> FutureExt for F {
129 async fn with_timeout(
130 self,
131 timeout: Duration,
132 ) -> Result<<Self as Future>::Output, TimeoutError> {
133 let fut = self.fuse();
134 pin_mut!(fut);
135 
136 let mut timeout = Timer::after(timeout).fuse();
137 
138 futures::select! {
139 value = fut => Ok(value),
140 _ = timeout => Err(TimeoutError),
141 }
142 }
143}
144