diff --git a/Cargo.lock b/Cargo.lock index b9fd3c5..0fc668f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ dependencies = [ [[package]] name = "lazy_async_promise" -version = "0.1.2" +version = "0.2.0" dependencies = [ "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 6a4533e..b0fa6d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lazy_async_promise" -version = "0.1.2" +version = "0.2.0" edition = "2021" authors = ["Christopher Regali "] license = "MIT" diff --git a/README.md b/README.md index 8cc0174..eac857c 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ [![Documentation](https://docs.rs/lazy_async_promise/badge.svg)](https://docs.rs/lazy_async_promise) ![CI](https://github.com/ChrisRega/lazy_async_promise/actions/workflows/rust.yml/badge.svg?branch=main "CI") -This crate currently only features two simple primitives for getting computation time off the main thread using tokio: +This crate currently only features simple primitives for getting computation time off the main thread using tokio: - `LazyVecPromise` for a vector-backed storage which can be displayed while the task in progress. - `LazyValuePromise` for a single value future that can be updated during task-progress. My usage was for iterative algorithms where the intermediate results were interesting for display. -As the name suggests both of them are lazily evaluated and nothing happens until they are polled for the first time. +As the name suggests the two of them are lazily evaluated and nothing happens until they are polled for the first time. For single values which are either available or not there's `ImmediateValuePromise` which triggers computation immediately. There's not in-calculation value read out, so either it's finished or not. diff --git a/src/immediatevalue.rs b/src/immediatevalue.rs index d264f2f..2b4c80c 100644 --- a/src/immediatevalue.rs +++ b/src/immediatevalue.rs @@ -21,7 +21,7 @@ impl ToDynSendBox for E { } /// # A promise which can be easily created and stored. -/// Will spawn a task to resolve the future immediately. +/// Will spawn a task to resolve the future immediately. No possibility to read out interim values. /// ```rust, no_run /// use std::fs::File; /// use std::thread; @@ -54,7 +54,7 @@ pub struct ImmediateValuePromise { state: ImmediateValueState, } -/// The return state of a [`OneShotValue`], contains the error, the value or that it is still updating +/// The return state of a [`ImmediateValuePromise`], contains the error, the value or that it is still updating pub enum ImmediateValueState { /// future is not yet resolved Updating, diff --git a/src/lazyvalue.rs b/src/lazyvalue.rs index 45af4b6..84ebdb4 100644 --- a/src/lazyvalue.rs +++ b/src/lazyvalue.rs @@ -1,4 +1,4 @@ -use crate::{DataState, Message, Promise, Value, ValuePromise}; +use crate::{box_future_factory, BoxedFutureFactory, DataState, Message, Promise}; use std::fmt::Debug; use std::future::Future; use tokio::sync::mpsc::{channel, Receiver, Sender}; @@ -6,12 +6,12 @@ use tokio::sync::mpsc::{channel, Receiver, Sender}; /// # A single lazy-async updated value /// Create one with the [`LazyValuePromise::new`] method and supply an updater. /// It's Updated only on first try to poll it making it scale nicely on more complex UIs. -/// Type erasure can be done using a Box with dyn [`ValuePromise`] +/// While still updating, the value can be read out already, this can make sense for iterative algorithms where an intermediate result can be used as a preview. /// Examples: /// ```rust, no_run /// use std::time::Duration; /// use tokio::sync::mpsc::Sender; -/// use lazy_async_promise::{DataState, Message, ValuePromise}; +/// use lazy_async_promise::{DataState, Message, Promise}; /// use lazy_async_promise::LazyValuePromise; /// use lazy_async_promise::unpack_result; /// // updater-future: @@ -24,14 +24,12 @@ use tokio::sync::mpsc::{channel, Receiver, Sender}; /// }; /// // direct usage: /// let promise = LazyValuePromise::new(updater, 10); -/// // or storing it with type-erasure for easier usage in application state structs: -/// let boxed: Box> = Box::new(LazyValuePromise::new(updater, 6)); /// -/// fn main_loop(lazy_promise: &mut Box>) { +/// fn main_loop(lazy_promise: &mut LazyValuePromise) { /// loop { /// match lazy_promise.poll_state() { -/// DataState::Error(er) => { println!("Error {} occurred! Retrying!", er); std::thread::sleep(Duration::from_millis(500)); lazy_promise.update(); }, -/// DataState::UpToDate => { println!("Value up2date: {}", lazy_promise.value().unwrap()); }, +/// DataState::Error(er) => { println!("Error {} occurred! Retrying!", er); std::thread::sleep(Duration::from_millis(500)); lazy_promise.update(); } +/// DataState::UpToDate => { println!("Value up2date: {}", lazy_promise.value().unwrap()); } /// _ => { println!("Still updating... might be in strange state! (current state: {:?}", lazy_promise.value()); } /// } /// } @@ -40,22 +38,22 @@ use tokio::sync::mpsc::{channel, Receiver, Sender}; /// /// -pub struct LazyValuePromise< - T: Debug, - U: Fn(Sender>) -> Fut, - Fut: Future + Send + 'static, -> { +pub struct LazyValuePromise { cache: Option, - updater: U, + updater: BoxedFutureFactory, state: DataState, rx: Receiver>, tx: Sender>, } -impl>) -> Fut, Fut: Future + Send + 'static> - LazyValuePromise -{ +impl LazyValuePromise { /// Creates a new LazyValuePromise given an Updater and a tokio buffer size - pub fn new(updater: U, buffer_size: usize) -> Self { + pub fn new< + U: Fn(Sender>) -> Fut + 'static, + Fut: Future + Send + 'static, + >( + future_factory: U, + buffer_size: usize, + ) -> Self { let (tx, rx) = channel::>(buffer_size); Self { @@ -63,32 +61,22 @@ impl>) -> Fut, Fut: Future + Send state: DataState::Uninitialized, rx, tx, - updater, + updater: box_future_factory(future_factory), } } + /// get current value, may be incomplete depending on status + pub fn value(&self) -> Option<&T> { + self.cache.as_ref() + } + #[cfg(test)] pub fn is_uninitialized(&self) -> bool { self.state == DataState::Uninitialized } } -impl>) -> Fut, Fut: Future + Send + 'static> Value - for LazyValuePromise -{ - fn value(&self) -> Option<&T> { - self.cache.as_ref() - } -} - -impl>) -> Fut, Fut: Future + Send + 'static> - ValuePromise for LazyValuePromise -{ -} - -impl>) -> Fut, Fut: Future + Send + 'static> Promise - for LazyValuePromise -{ +impl Promise for LazyValuePromise { fn poll_state(&mut self) -> &DataState { if self.state == DataState::Uninitialized { self.update(); diff --git a/src/lazyvec.rs b/src/lazyvec.rs index 394112b..0b49beb 100644 --- a/src/lazyvec.rs +++ b/src/lazyvec.rs @@ -1,16 +1,16 @@ -use crate::{DataState, Message, Promise, SlicePromise, Sliceable}; +use crate::{box_future_factory, BoxedFutureFactory, DataState, Message, Promise}; use std::fmt::Debug; use std::future::Future; use tokio::sync::mpsc::{channel, Receiver, Sender}; /// # A lazy, async and partially readable vector promise /// This promise is the right one for async acquiring of lists which should be partially readable on each frame. -/// It's lazy, meaning that no spawning is done, until the first poll is emitted. Type erasure can be done using a Box with dyn [`SlicePromise`] +/// Imagine slowly streaming data and wanting to read them out as far as they are available each frame. /// Examples: /// ```rust, no_run /// use std::time::Duration; /// use tokio::sync::mpsc::Sender; -/// use lazy_async_promise::{DataState, Message, SlicePromise, ValuePromise}; +/// use lazy_async_promise::{DataState, Message, Promise}; /// use lazy_async_promise::LazyVecPromise; /// use lazy_async_promise::unpack_result; /// // updater-future: @@ -28,10 +28,8 @@ use tokio::sync::mpsc::{channel, Receiver, Sender}; /// }; /// // direct usage: /// let promise = LazyVecPromise::new(updater, 10); -/// // or storing it with type-erasure for easier usage in application state structs: -/// let boxed: Box> = Box::new(LazyVecPromise::new(updater, 6)); /// -/// fn main_loop(lazy_promise: &mut Box>) { +/// fn main_loop(lazy_promise: &mut LazyVecPromise) { /// loop { /// match lazy_promise.poll_state() { /// DataState::Error(er) => { println!("Error {} occurred! Retrying!", er); std::thread::sleep(Duration::from_millis(500)); lazy_promise.update(); }, @@ -43,23 +41,24 @@ use tokio::sync::mpsc::{channel, Receiver, Sender}; /// ``` /// /// -pub struct LazyVecPromise< - T: Debug, - U: Fn(Sender>) -> Fut, - Fut: Future + Send + 'static, -> { + +pub struct LazyVecPromise { data: Vec, state: DataState, rx: Receiver>, tx: Sender>, - updater: U, + updater: BoxedFutureFactory, } -impl>) -> Fut, Fut: Future + Send + 'static> - LazyVecPromise -{ +impl LazyVecPromise { /// creates a new LazyVecPromise given an updater functor and a tokio buffer size - pub fn new(updater: U, buffer_size: usize) -> Self { + pub fn new< + U: Fn(Sender>) -> Fut + 'static, + Fut: Future + Send + 'static, + >( + future_factory: U, + buffer_size: usize, + ) -> Self { let (tx, rx) = channel::>(buffer_size); Self { @@ -67,31 +66,22 @@ impl>) -> Fut, Fut: Future + Send state: DataState::Uninitialized, rx, tx, - updater, + updater: box_future_factory(future_factory), } } - #[cfg(test)] - pub fn is_uninitialized(&self) -> bool { - self.state == DataState::Uninitialized - } -} -impl>) -> Fut, Fut: Future + Send + 'static> - Sliceable for LazyVecPromise -{ - fn as_slice(&self) -> &[T] { + /// get current data as slice, may be incomplete depending on status + pub fn as_slice(&self) -> &[T] { self.data.as_slice() } -} -impl>) -> Fut, Fut: Future + Send + 'static> - SlicePromise for LazyVecPromise -{ + #[cfg(test)] + pub fn is_uninitialized(&self) -> bool { + self.state == DataState::Uninitialized + } } -impl>) -> Fut, Fut: Future + Send + 'static> Promise - for LazyVecPromise -{ +impl Promise for LazyVecPromise { fn poll_state(&mut self) -> &DataState { if self.state == DataState::Uninitialized { self.update(); diff --git a/src/lib.rs b/src/lib.rs index 62c6046..8f38692 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,18 @@ #![crate_name = "lazy_async_promise"] //! # Primitives for combining tokio and immediate mode guis -//! Currently, only two primitives are implemented: +//! Currently, only three primitives are implemented: //! - [`LazyVecPromise`]: A lazily evaluated, partially readable and async-enabled vector-backed promise //! - [`LazyValuePromise`]: A lazily evaluated and async-enabled single value promise -//! -#![warn(missing_docs)] -#![warn(unused_qualifications)] +//! - [`ImmediateValuePromise`]: An immediately updating async-enabled single value promise +//! See these items for their respective documentation. A general usage guide would be: +//! - You want several items of the same kind displayed / streamed? Use: [`LazyVecPromise`] +//! - You want one item displayed when ready and need lazy evaluation or have intermediate results? Use: [`LazyVecPromise`] +//! - You want one item displayed when ready and can afford spawning it directly? Use: [`ImmediateValuePromise`] +#![deny(missing_docs)] +#![deny(unused_qualifications)] #![deny(deprecated)] +#![deny(absolute_paths_not_starting_with_crate)] +#![deny(unstable_features)] extern crate core; @@ -26,6 +32,10 @@ pub use immediatevalue::ToDynSendBox; pub use lazyvalue::LazyValuePromise; use std::fmt::Debug; +use std::future::Future; +use std::pin::Pin; +use tokio::sync::mpsc::Sender; + #[derive(Clone, PartialEq, Eq, Debug)] /// Represents a processing state. pub enum DataState { @@ -59,24 +69,6 @@ pub trait Promise { fn update(&mut self); } -/// Implementors can be viewed as slice -pub trait Sliceable { - /// get current data slice - fn as_slice(&self) -> &[T]; -} - -/// Implementors may have a value -pub trait Value { - /// Maybe returns the value, depending on the object's state - fn value(&self) -> Option<&T>; -} - -/// Some type that implements lazy updating and provides a slice of the desired type -pub trait SlicePromise: Promise + Sliceable {} - -/// Some type that implements lazy updating and provides a single value of the desired type -pub trait ValuePromise: Promise + Value {} - #[macro_export] /// 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. macro_rules! unpack_result { @@ -93,3 +85,16 @@ macro_rules! unpack_result { } }; } + +type BoxedFutureFactory = + Box>) -> Pin + Send + 'static>>>; + +fn box_future_factory< + T: Debug, + U: Fn(Sender>) -> Fut + 'static, + Fut: Future + Send + 'static, +>( + future_factory: U, +) -> BoxedFutureFactory { + Box::new(move |tx: Sender>| Box::pin(future_factory(tx))) +}