lazy_async_promise/
lib.rs

1#![crate_name = "lazy_async_promise"]
2//! # Primitives for combining tokio and immediate mode guis
3//! ## List of primitives
4//! The following primitives are implemented:
5//! - [`ImmediateValuePromise`]: An immediately updating async-enabled single value promise
6//! - [`ProgressTrackedImValProm`]: A progress/status emitting enhanced wrapper for [`ImmediateValuePromise`]
7//! - [`LazyVecPromise`]: A lazily evaluated, partially readable and async-enabled vector-backed promise
8//! - [`LazyValuePromise`]: A lazily evaluated and async-enabled single value promise
9//!
10//! See these items for their respective documentation.
11//! ## What to use
12//! A general usage guide would be:
13//! - You just want one value when ready? Use: [`ImmediateValuePromise`] (for laziness wrap in `Option`)
14//! - If you need status update support for that, use [`ProgressTrackedImValProm`]
15//! - You want several items of the same kind / streamed? Use: [`LazyVecPromise`]
16//! - You want one item when ready and need lazy evaluation or have intermediate results? Use: [`LazyValuePromise`]
17#![deny(missing_docs)]
18#![deny(deprecated)]
19#![deny(absolute_paths_not_starting_with_crate)]
20#![deny(unstable_features)]
21#![deny(unsafe_code)]
22
23extern crate core;
24
25use std::error::Error;
26use std::fmt::Debug;
27use std::future::Future;
28use std::ops::Deref;
29use std::pin::Pin;
30
31use tokio::sync::mpsc::Sender;
32
33#[doc(inline)]
34pub use immediatevalue::ImmediateValuePromise;
35pub use immediatevalue::ImmediateValueState;
36#[doc(inline)]
37pub use immediatevalueprogress::ProgressTrackedImValProm;
38pub use immediatevalueprogress::Status;
39pub use immediatevalueprogress::StringStatus;
40
41#[doc(inline)]
42pub use lazyvalue::LazyValuePromise;
43#[doc(inline)]
44pub use lazyvec::LazyVecPromise;
45
46mod immediatevalue;
47mod immediatevalueprogress;
48mod lazyvalue;
49mod lazyvec;
50
51/// Strong type to keep the boxed error. You can just deref it to get the inside box.
52pub struct BoxedSendError(pub Box<dyn Error + Send>);
53
54/// Type alias for futures with BoxedSendError
55pub type FutureResult<T> = Result<T, BoxedSendError>;
56
57impl<E: Error + Send + 'static> From<E> for BoxedSendError {
58    fn from(e: E) -> Self {
59        BoxedSendError(Box::new(e))
60    }
61}
62
63impl Deref for BoxedSendError {
64    type Target = Box<dyn Error + Send>;
65
66    fn deref(&self) -> &Self::Target {
67        &self.0
68    }
69}
70
71/// Trait for directly accessing the cache underneath any promise
72pub trait DirectCacheAccess<T, E> {
73    /// returns mutable reference to the cache if applicable
74    fn get_value_mut(&mut self) -> Option<&mut T>;
75    /// returns a reference to the cache if applicable
76    fn get_value(&self) -> Option<&T>;
77    /// returns a reference to the cache or to error if applicable
78    fn get_result(&self) -> Option<Result<&T, &E>>;
79    /// takes the value and leaves the promise in a valid state indicating its emptiness
80    fn take_value(&mut self) -> Option<T>;
81    /// takes the value or error and leaves the promise in a valid state indicating its emptiness
82    fn take_result(&mut self) -> Option<Result<T, E>>;
83}
84
85/// Blanket implementation for any `Option<DirectCacheAccess<T>>` allows for better handling of option-laziness
86impl<T: Send + 'static, E: Send + 'static, A: DirectCacheAccess<T, E>> DirectCacheAccess<T, E>
87    for Option<A>
88{
89    fn get_value_mut(&mut self) -> Option<&mut T> {
90        self.as_mut().and_then(|inner| inner.get_value_mut())
91    }
92    fn get_value(&self) -> Option<&T> {
93        self.as_ref().and_then(|inner| inner.get_value())
94    }
95    fn get_result(&self) -> Option<Result<&T, &E>> {
96        self.as_ref().and_then(|inner| inner.get_result())
97    }
98    fn take_value(&mut self) -> Option<T> {
99        self.as_mut().and_then(|inner| inner.take_value())
100    }
101    fn take_result(&mut self) -> Option<Result<T, E>> {
102        self.as_mut().and_then(|inner| inner.take_result())
103    }
104}
105
106/// a f64 type which is constrained to the range of 0.0 and 1.0
107#[derive(Clone, Copy, PartialEq, Debug)]
108pub struct Progress(f64);
109
110impl<T: Into<f64>> From<T> for Progress {
111    fn from(t: T) -> Self {
112        Progress(t.into().clamp(0.0, 1.0))
113    }
114}
115
116/// Use this to get all macros
117pub mod api_macros {
118    pub use crate::send_data;
119    pub use crate::set_error;
120    pub use crate::set_finished;
121    pub use crate::set_progress;
122    pub use crate::unpack_result;
123    pub use crate::Progress;
124}
125
126impl Progress {
127    /// Create a Progress from a percentage
128    /// ```rust, no_run
129    /// use lazy_async_promise::Progress;
130    /// let progress_half = Progress::from_percent(50);
131    /// ```
132    ///
133    pub fn from_percent(percent: impl Into<f64>) -> Progress {
134        Self::from_fraction(percent, 100.)
135    }
136
137    /// Create a Progress from a fraction, useful for handling loops
138    /// ```rust, no_run
139    /// use lazy_async_promise::Progress;
140    /// let num_iterations = 100;
141    /// for i in 0..num_iterations {
142    ///   let progress_current = Progress::from_fraction(i, num_iterations);
143    /// }
144    /// ```
145    ///
146    pub fn from_fraction(numerator: impl Into<f64>, denominator: impl Into<f64>) -> Progress {
147        (numerator.into() / denominator.into()).into()
148    }
149
150    /// return progress as f32
151    pub fn as_f32(&self) -> f32 {
152        self.0 as f32
153    }
154
155    /// return progress as f64
156    pub fn as_f64(&self) -> f64 {
157        self.0
158    }
159}
160
161impl Default for Progress {
162    fn default() -> Self {
163        Progress(0.0)
164    }
165}
166
167impl Deref for Progress {
168    type Target = f64;
169    fn deref(&self) -> &Self::Target {
170        &self.0
171    }
172}
173
174#[derive(Clone, PartialEq, Debug)]
175/// Represents a processing state.
176pub enum DataState {
177    /// You can only receive this after taking ownership of the data
178    Uninitialized,
179    /// Data is complete
180    UpToDate,
181    /// Data is not (completely) ready, depending on your implementation, you may be able to get partial results
182    /// Embedded progress in [0,1)
183    Updating(Progress),
184    /// Some error occurred
185    Error(String),
186}
187
188impl DataState {
189    /// Yields the progress if state is `DataState::Updating` otherwise yields none, even if finished.
190    pub fn get_progress(&self) -> Option<Progress> {
191        match &self {
192            DataState::Updating(progress) => Some(*progress),
193            _ => None,
194        }
195    }
196}
197
198#[derive(Debug)]
199/// The message-type to send from the updater to the main thread. There's only two variants,
200/// `NewData` which allows to send new data or `StateChange` which allows to signal readiness or error.
201pub enum Message<T: Debug> {
202    /// Adding or setting new data to the promise, depending on the implementation
203    NewData(T),
204    /// Modify the state of the promise, including setting an error
205    StateChange(DataState),
206}
207
208/// Maybe this should rather be called "LazyUpdating"?
209/// Implementors can react to polling by queueing an update if needed.
210/// Update should force an update.
211pub trait Promise {
212    /// Polls the promise, triggers update if state is [`DataState::Uninitialized`]
213    fn poll_state(&mut self) -> &DataState;
214    /// Clears the data cache and immediately triggers an update
215    fn update(&mut self);
216}
217
218#[macro_export]
219/// Error checking in async updater functions is tedious - this helps out by resolving results and sending errors on error. Result will be unwrapped if no error occurs.
220macro_rules! unpack_result {
221    ( $result: expr, $sender: expr ) => {
222        match $result {
223            Ok(val) => val,
224            Err(e) => {
225                set_error!(format!("{}", e), $sender);
226                return;
227            }
228        }
229    };
230}
231
232#[macro_export]
233/// Setting the given progress using a given sender.
234macro_rules! set_progress {
235    ($progress: expr, $sender: expr) => {
236        $sender
237            .send(Message::StateChange(DataState::Updating($progress)))
238            .await
239            .unwrap();
240    };
241}
242
243#[macro_export]
244/// Setting the given progress using a given sender.
245macro_rules! set_error {
246    ($error: expr, $sender: expr) => {
247        $sender
248            .send(Message::StateChange(DataState::Error($error)))
249            .await
250            .unwrap();
251    };
252}
253
254#[macro_export]
255/// Send new data via the sender
256macro_rules! send_data {
257    ($data: expr, $sender: expr) => {
258        $sender.send(Message::NewData($data)).await.unwrap();
259    };
260}
261
262#[macro_export]
263/// Set state to `DataState::UpToDate`
264macro_rules! set_finished {
265    ($sender: expr) => {
266        $sender
267            .send(Message::StateChange(DataState::UpToDate))
268            .await
269            .unwrap();
270    };
271}
272
273type BoxedFutureFactory<T> =
274    Box<dyn Fn(Sender<Message<T>>) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>>>;
275
276fn box_future_factory<
277    T: Debug,
278    U: Fn(Sender<Message<T>>) -> Fut + 'static,
279    Fut: Future<Output = ()> + Send + 'static,
280>(
281    future_factory: U,
282) -> BoxedFutureFactory<T> {
283    Box::new(move |tx: Sender<Message<T>>| Box::pin(future_factory(tx)))
284}
285
286#[cfg(test)]
287mod test {
288    use super::*;
289
290    #[test]
291    fn progress_constructors() {
292        let half = Progress::from_percent(50);
293        assert_eq!(half.0, 0.5);
294        let half = Progress::from_fraction(1, 2);
295        assert_eq!(half.0, 0.5);
296    }
297
298    #[test]
299    fn progress_clamps() {
300        let minimum = Progress::from_percent(-50);
301        assert_eq!(minimum.as_f32(), 0.0);
302        let maximum = Progress::from_fraction(2, 1);
303        assert_eq!(maximum.as_f64(), 1.0);
304        let progress: Progress = 2.0.into();
305        assert_eq!(progress.as_f64(), 1.0);
306    }
307
308    #[test]
309    fn default_progress_is_start() {
310        assert_eq!(Progress::default().as_f64(), 0.0);
311    }
312}