From 51684293062b22aedb079ec93666e2a3c7175b49 Mon Sep 17 00:00:00 2001 From: Christopher Regali Date: Sat, 22 Apr 2023 20:43:37 +0200 Subject: [PATCH 01/10] Bump version Add idea for progress modified immediatevalue wrapper --- Cargo.toml | 2 +- src/immediatevalue.rs | 8 ++--- src/immediatevalueprogress.rs | 65 +++++++++++++++++++++++++++++++++++ src/lib.rs | 9 +++-- 4 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 src/immediatevalueprogress.rs diff --git a/Cargo.toml b/Cargo.toml index 67da8d3..2eb601b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lazy_async_promise" -version = "0.4.0" +version = "0.5.0" edition = "2021" authors = ["Christopher Regali "] license = "MIT" diff --git a/src/immediatevalue.rs b/src/immediatevalue.rs index 51688da..a742602 100644 --- a/src/immediatevalue.rs +++ b/src/immediatevalue.rs @@ -100,7 +100,7 @@ use crate::{BoxedSendError, DirectCacheAccess, FutureResult}; /// assert_eq!(*value_opt.unwrap(), 34); /// ``` /// -pub struct ImmediateValuePromise { +pub struct ImmediateValuePromise { value_arc: Arc>>>, state: ImmediateValueState, } @@ -160,9 +160,9 @@ impl DirectCacheAccess for ImmediateValuePromise { } } -impl ImmediateValuePromise { +impl ImmediateValuePromise { /// Creator, supply a future which returns `Result`. Will be immediately spawned via tokio. - pub fn new> + Send + 'static>(updater: U) -> Self { + pub fn new> + Send + 'static>(updater: U) -> Self { let arc = Arc::new(Mutex::new(None)); let arc_clone = arc.clone(); tokio::spawn(async move { @@ -210,8 +210,8 @@ mod test { use tokio::runtime::Runtime; - use crate::DirectCacheAccess; use crate::immediatevalue::{ImmediateValuePromise, ImmediateValueState}; + use crate::DirectCacheAccess; #[test] fn default() { diff --git a/src/immediatevalueprogress.rs b/src/immediatevalueprogress.rs new file mode 100644 index 0000000..8ddb8c1 --- /dev/null +++ b/src/immediatevalueprogress.rs @@ -0,0 +1,65 @@ +use crate::{BoxedSendError, ImmediateValuePromise, ImmediateValueState}; +use crate::{DirectCacheAccess, Progress}; +use std::borrow::Cow; +use std::future::Future; +use tokio::sync::mpsc::Receiver; +use tokio::sync::mpsc::Sender; +use tokio::time::Instant; + +pub struct Status { + time: Instant, + progress: Progress, + message: M, +} + +pub type StringStatus = Status>; + +pub struct ProgressTrackedImValProm { + promise: ImmediateValuePromise, + status: Vec>, + receiver: Receiver>, +} + +impl ProgressTrackedImValProm { + pub fn new( + creator: impl FnOnce(Sender>) -> ImmediateValuePromise, + buffer: usize, + ) -> Self { + let (sender, receiver) = tokio::sync::mpsc::channel(buffer); + ProgressTrackedImValProm { + receiver, + status: Vec::new(), + promise: creator(sender), + } + } + + pub fn finished(&self) -> bool { + self.promise.get_value().is_some() + } + + pub fn poll_state(&mut self) -> &ImmediateValueState { + while let Ok(msg) = self.receiver.try_recv() { + self.status.push(msg); + } + self.promise.poll_state() + } + + pub fn progress(&self) -> Progress { + self.status + .last() + .map(|p| p.progress) + .unwrap_or(Progress::default()) + } +} + +impl DirectCacheAccess for ProgressTrackedImValProm { + fn get_value_mut(&mut self) -> Option<&mut T> { + self.promise.get_value_mut() + } + fn get_value(&self) -> Option<&T> { + self.promise.get_value() + } + fn take_value(&mut self) -> Option { + self.promise.take_value() + } +} diff --git a/src/lib.rs b/src/lib.rs index 94a0dea..ba3b2f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,13 +34,13 @@ pub use lazyvalue::LazyValuePromise; pub use lazyvec::LazyVecPromise; mod immediatevalue; +mod immediatevalueprogress; mod lazyvalue; mod lazyvec; /// Strong type to keep the boxed error. You can just deref it to get the inside box. pub struct BoxedSendError(pub Box); - /// Type alias for futures with BoxedSendError pub type FutureResult = Result; @@ -58,7 +58,6 @@ impl Deref for BoxedSendError { } } - /// Trait for directly accessing the cache underneath any promise pub trait DirectCacheAccess { /// returns mutable reference to the cache if applicable @@ -94,12 +93,12 @@ impl> From for Progress { /// Use this to get all macros pub mod api_macros { - pub use crate::Progress; pub use crate::send_data; pub use crate::set_error; pub use crate::set_finished; pub use crate::set_progress; pub use crate::unpack_result; + pub use crate::Progress; } impl Progress { @@ -243,12 +242,12 @@ macro_rules! set_finished { } type BoxedFutureFactory = -Box>) -> Pin + Send + 'static>>>; + Box>) -> Pin + Send + 'static>>>; fn box_future_factory< T: Debug, U: Fn(Sender>) -> Fut + 'static, - Fut: Future + Send + 'static, + Fut: Future + Send + 'static, >( future_factory: U, ) -> BoxedFutureFactory { From 2147615fad26e499154ed63a3c562d038c7affb3 Mon Sep 17 00:00:00 2001 From: Christopher Regali Date: Sat, 22 Apr 2023 21:02:20 +0200 Subject: [PATCH 02/10] Added some more convenience functionality for the progress-wrapper and the messages --- src/immediatevalueprogress.rs | 89 +++++++++++++++++++++++++++++++++-- src/lib.rs | 12 +++++ 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/src/immediatevalueprogress.rs b/src/immediatevalueprogress.rs index 8ddb8c1..3359913 100644 --- a/src/immediatevalueprogress.rs +++ b/src/immediatevalueprogress.rs @@ -1,19 +1,49 @@ -use crate::{BoxedSendError, ImmediateValuePromise, ImmediateValueState}; use crate::{DirectCacheAccess, Progress}; +use crate::{ImmediateValuePromise, ImmediateValueState}; use std::borrow::Cow; -use std::future::Future; use tokio::sync::mpsc::Receiver; use tokio::sync::mpsc::Sender; use tokio::time::Instant; +#[derive(Debug)] pub struct Status { - time: Instant, - progress: Progress, - message: M, + pub time: Instant, + pub progress: Progress, + pub message: M, +} + +impl Status { + pub fn new(progress: Progress, message: M) -> Self { + Self { + progress, + message, + time: Instant::now(), + } + } } pub type StringStatus = Status>; +impl StringStatus { + pub fn from_str(progress: Progress, static_message: &'static str) -> Self { + StringStatus { + message: Cow::Borrowed(static_message), + time: Instant::now(), + progress, + } + } + pub fn from_string(progress: Progress, message: String) -> Self { + StringStatus { + message: Cow::Owned(message), + time: Instant::now(), + progress, + } + } +} + +/// # A progress and status enabling wrapper for [`ImmediateValuePromise`] +/// This struct allows to use the [`Progress`] type and any kind of status message +/// pub struct ProgressTrackedImValProm { promise: ImmediateValuePromise, status: Vec>, @@ -63,3 +93,52 @@ impl DirectCacheAccess for ProgressTrackedImValProm &Self::Target { + &self.0 + } +} + #[derive(Clone, PartialEq, Debug)] /// Represents a processing state. pub enum DataState { From 3ad8d7d2bcf14f22c54c7c9a619027b8752751b9 Mon Sep 17 00:00:00 2001 From: Christopher Regali Date: Sun, 23 Apr 2023 19:57:14 +0200 Subject: [PATCH 03/10] Naming convention --- src/immediatevalueprogress.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/immediatevalueprogress.rs b/src/immediatevalueprogress.rs index 3359913..92aac88 100644 --- a/src/immediatevalueprogress.rs +++ b/src/immediatevalueprogress.rs @@ -74,7 +74,7 @@ impl ProgressTrackedImValProm { self.promise.poll_state() } - pub fn progress(&self) -> Progress { + pub fn get_progress(&self) -> Progress { self.status .last() .map(|p| p.progress) @@ -127,10 +127,10 @@ mod test { oneshot_progress.poll_state(), ImmediateValueState::Updating )); - assert_eq!(*oneshot_progress.progress(), 0.0); + assert_eq!(*oneshot_progress.get_progress(), 0.0); tokio::time::sleep(Duration::from_millis(100)).await; let _ = oneshot_progress.poll_state(); - assert_eq!(*oneshot_progress.progress(), 1.0); + assert_eq!(*oneshot_progress.get_progress(), 1.0); let result = oneshot_progress.poll_state(); if let ImmediateValueState::Success(val) = result { From 77757c52e28e5bfcee187c367e506ff7d6c1ddb5 Mon Sep 17 00:00:00 2001 From: Christopher Regali Date: Mon, 24 Apr 2023 11:16:28 +0200 Subject: [PATCH 04/10] Add docs and some missing API functions --- src/immediatevalueprogress.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/immediatevalueprogress.rs b/src/immediatevalueprogress.rs index 92aac88..5af9523 100644 --- a/src/immediatevalueprogress.rs +++ b/src/immediatevalueprogress.rs @@ -5,14 +5,19 @@ use tokio::sync::mpsc::Receiver; use tokio::sync::mpsc::Sender; use tokio::time::Instant; +/// A status update struct containing the issue-date, progress and a message #[derive(Debug)] pub struct Status { + /// Time when this status was created pub time: Instant, + /// Current progress pub progress: Progress, + /// Message pub message: M, } impl Status { + /// Create a new status message with now as timestamp pub fn new(progress: Progress, message: M) -> Self { Self { progress, @@ -22,9 +27,11 @@ impl Status { } } +/// This pub type allows to use static str and string in a message pub type StringStatus = Status>; impl StringStatus { + /// create a string status from a static str pub fn from_str(progress: Progress, static_message: &'static str) -> Self { StringStatus { message: Cow::Borrowed(static_message), @@ -32,6 +39,7 @@ impl StringStatus { progress, } } + /// create a string status from a real String pub fn from_string(progress: Progress, message: String) -> Self { StringStatus { message: Cow::Owned(message), @@ -51,6 +59,7 @@ pub struct ProgressTrackedImValProm { } impl ProgressTrackedImValProm { + /// create a new Progress tracked immediate value promise. pub fn new( creator: impl FnOnce(Sender>) -> ImmediateValuePromise, buffer: usize, @@ -63,10 +72,22 @@ impl ProgressTrackedImValProm { } } + /// Slice of all recorded status changes + pub fn status_history(&self) -> &[Status] { + &self.status + } + + /// Get the last status if there is any + pub fn last_status(&self) -> Option<&Status> { + self.status.last() + } + + /// Is the future finished pub fn finished(&self) -> bool { self.promise.get_value().is_some() } + /// Poll the state and process the messages pub fn poll_state(&mut self) -> &ImmediateValueState { while let Ok(msg) = self.receiver.try_recv() { self.status.push(msg); @@ -74,6 +95,7 @@ impl ProgressTrackedImValProm { self.promise.poll_state() } + /// Get the current progress pub fn get_progress(&self) -> Progress { self.status .last() From c86fa095822d0b3e9ee844eb40a89c247e998d54 Mon Sep 17 00:00:00 2001 From: Christopher Regali Date: Wed, 26 Apr 2023 20:40:24 +0200 Subject: [PATCH 05/10] Add some commentary to immediatevalueprogress.rs --- src/immediatevalueprogress.rs | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/immediatevalueprogress.rs b/src/immediatevalueprogress.rs index 5af9523..a810701 100644 --- a/src/immediatevalueprogress.rs +++ b/src/immediatevalueprogress.rs @@ -6,6 +6,7 @@ use tokio::sync::mpsc::Sender; use tokio::time::Instant; /// A status update struct containing the issue-date, progress and a message +/// You can use any struct that can be transferred via tokio mpsc channels. #[derive(Debug)] pub struct Status { /// Time when this status was created @@ -17,7 +18,7 @@ pub struct Status { } impl Status { - /// Create a new status message with now as timestamp + /// Create a new status message with `now` as timestamp pub fn new(progress: Progress, message: M) -> Self { Self { progress, @@ -27,7 +28,7 @@ impl Status { } } -/// This pub type allows to use static str and string in a message +/// This [`Status`] typedef allows to use both: `&'static str` and `String` in a message pub type StringStatus = Status>; impl StringStatus { @@ -39,7 +40,7 @@ impl StringStatus { progress, } } - /// create a string status from a real String + /// create a string status from a `String` pub fn from_string(progress: Progress, message: String) -> Self { StringStatus { message: Cow::Owned(message), @@ -51,6 +52,33 @@ impl StringStatus { /// # A progress and status enabling wrapper for [`ImmediateValuePromise`] /// This struct allows to use the [`Progress`] type and any kind of status message +/// You can use this to set a computation progress and optionally attach any kind of status message. +/// Assume your action runs for an extended period of time and you want to inform the user about the state: +///```rust, no_run +///use std::borrow::Cow; +///use std::time::Duration; +///use lazy_async_promise::{ImmediateValueState, ImmediateValuePromise, Progress, ProgressTrackedImValProm, StringStatus}; +///let mut oneshot_progress = ProgressTrackedImValProm::new( |s| { ImmediateValuePromise::new( +/// async move { +/// //send some initial status +/// s.send(StringStatus::new( +/// Progress::from_percent(0.0), +/// "Initializing".into(), +/// )).await.unwrap(); +/// // do some long running operation +/// for i in 0..100 { +/// tokio::time::sleep(Duration::from_millis(50)).await; +/// s.send(StringStatus::new( +/// Progress::from_percent(i as f64), +/// Cow::Borrowed("In progress"))).await.unwrap(); +/// } +/// Ok(34) +/// })}, 2000); +/// assert!(matches!( +/// oneshot_progress.poll_state(), +/// ImmediateValueState::Updating)); +/// //waiting and polling will yield "In progress" now :) +/// ``` /// pub struct ProgressTrackedImValProm { promise: ImmediateValuePromise, From 23e01c207e87f2b12a811db742788ad6bd2b947e Mon Sep 17 00:00:00 2001 From: Christopher Regali Date: Sat, 29 Apr 2023 21:32:15 +0200 Subject: [PATCH 06/10] Commentary and docu improvements. Readme changelog --- README.md | 5 +++++ src/immediatevalueprogress.rs | 12 ++++++------ src/lib.rs | 12 +++++++----- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e10e151..5a2ad5e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,11 @@ Another example usage of this crate with a small egui/eframe blog-reader can be Changelog: +0.5.0 +- Fixed visibility issues with BoxSendError (thanks @aspcartman) +- Added progress tracked wrapper for immediate value promise (made lazyvaluepromise obsolete at least for me) +- Updated documentation + 0.4.0: - Added more flexible API to lazy and immediate structures, allowing to take the values - Added DirectCacheAccess trait to make option-based usage of the immediate value promise more convenient diff --git a/src/immediatevalueprogress.rs b/src/immediatevalueprogress.rs index a810701..c93f70e 100644 --- a/src/immediatevalueprogress.rs +++ b/src/immediatevalueprogress.rs @@ -1,9 +1,9 @@ use crate::{DirectCacheAccess, Progress}; use crate::{ImmediateValuePromise, ImmediateValueState}; use std::borrow::Cow; +use std::time::Instant; use tokio::sync::mpsc::Receiver; use tokio::sync::mpsc::Sender; -use tokio::time::Instant; /// A status update struct containing the issue-date, progress and a message /// You can use any struct that can be transferred via tokio mpsc channels. @@ -32,7 +32,7 @@ impl Status { pub type StringStatus = Status>; impl StringStatus { - /// create a string status from a static str + /// create a [`StringStatus`] from a `&'static str` pub fn from_str(progress: Progress, static_message: &'static str) -> Self { StringStatus { message: Cow::Borrowed(static_message), @@ -40,7 +40,7 @@ impl StringStatus { progress, } } - /// create a string status from a `String` + /// create a [`StringStatus`] from a `String` pub fn from_string(progress: Progress, message: String) -> Self { StringStatus { message: Cow::Owned(message), @@ -100,17 +100,17 @@ impl ProgressTrackedImValProm { } } - /// Slice of all recorded status changes + /// Slice of all recorded [`Status`] changes pub fn status_history(&self) -> &[Status] { &self.status } - /// Get the last status if there is any + /// Get the last [`Status`] if there is any pub fn last_status(&self) -> Option<&Status> { self.status.last() } - /// Is the future finished + /// Is our future already finished? pub fn finished(&self) -> bool { self.promise.get_value().is_some() } diff --git a/src/lib.rs b/src/lib.rs index 7ac4f64..e787c3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,15 @@ #![crate_name = "lazy_async_promise"] //! # Primitives for combining tokio and immediate mode guis -//! Currently, only three primitives are implemented: +//! The following primitives are implemented: +//! - [`ImmediateValuePromise`]: An immediately updating async-enabled single value promise +//! - [`ProgressTrackedImValProm`]: A progress/status emitting enhanced wrapper for [`ImmediateValuePromise`] //! - [`LazyVecPromise`]: A lazily evaluated, partially readable and async-enabled vector-backed promise //! - [`LazyValuePromise`]: A lazily evaluated and async-enabled single value promise -//! - [`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: [`LazyValuePromise`] -//! - You just want one item displayed when ready? Use: [`ImmediateValuePromise`] (for laziness wrap in `Option`) +//! - You just want one value when ready? Use: [`ImmediateValuePromise`] (for laziness wrap in `Option`) +//! - If you need status update support for that, use [`ProgressTrackedImValProm`] +//! - You want several items of the same kind / streamed? Use: [`LazyVecPromise`] +//! - You want one item when ready and need lazy evaluation or have intermediate results? Use: [`LazyValuePromise`] #![deny(missing_docs)] #![deny(unused_qualifications)] #![deny(deprecated)] From 2dae8acfffd75795b042128e961ee081d8d629e1 Mon Sep 17 00:00:00 2001 From: Christopher Regali Date: Sat, 29 Apr 2023 21:38:06 +0200 Subject: [PATCH 07/10] Bump version to 0.5.0-RC1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2eb601b..252acde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lazy_async_promise" -version = "0.5.0" +version = "0.5.0-RC1" edition = "2021" authors = ["Christopher Regali "] license = "MIT" From bd96c2924abf54a2ed65ce582b3e239b91980950 Mon Sep 17 00:00:00 2001 From: Christopher Regali Date: Sat, 29 Apr 2023 21:47:55 +0200 Subject: [PATCH 08/10] Use tokio macros for most of the tests --- Cargo.toml | 2 +- src/immediatevalue.rs | 184 ++++++++++++++++------------------ src/immediatevalueprogress.rs | 77 +++++++------- src/lazyvalue.rs | 48 ++++----- src/lazyvec.rs | 50 +++++---- 5 files changed, 169 insertions(+), 192 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 252acde..de86f1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,5 +15,5 @@ description = "Primitives for lazily getting data from futures with tokio for im tokio = {version="1", features=["rt-multi-thread", "sync"]} [dev-dependencies] -tokio = {version="1", features=["rt-multi-thread", "sync", "time", "fs"]} +tokio = {version="1", features=["rt-multi-thread", "sync", "time", "fs", "macros"]} diff --git a/src/immediatevalue.rs b/src/immediatevalue.rs index a742602..9e2a29c 100644 --- a/src/immediatevalue.rs +++ b/src/immediatevalue.rs @@ -208,119 +208,107 @@ mod test { use std::fs::File; use std::time::Duration; - use tokio::runtime::Runtime; - use crate::immediatevalue::{ImmediateValuePromise, ImmediateValueState}; use crate::DirectCacheAccess; - #[test] - fn default() { - Runtime::new().unwrap().block_on(async { - let mut oneshot_val = ImmediateValuePromise::new(async { - tokio::time::sleep(Duration::from_millis(50)).await; - Ok(34) - }); - assert!(matches!( - oneshot_val.poll_state(), - ImmediateValueState::Updating - )); - tokio::time::sleep(Duration::from_millis(100)).await; - let result = oneshot_val.poll_state(); - if let ImmediateValueState::Success(val) = result { - assert_eq!(*val, 34); - return; - } - unreachable!(); + #[tokio::test] + async fn default() { + let mut oneshot_val = ImmediateValuePromise::new(async { + tokio::time::sleep(Duration::from_millis(50)).await; + Ok(34) }); + assert!(matches!( + oneshot_val.poll_state(), + ImmediateValueState::Updating + )); + tokio::time::sleep(Duration::from_millis(100)).await; + let result = oneshot_val.poll_state(); + if let ImmediateValueState::Success(val) = result { + assert_eq!(*val, 34); + return; + } + unreachable!(); } - #[test] - fn error() { - Runtime::new().unwrap().block_on(async { - let mut oneshot_val = ImmediateValuePromise::new(async { - let some_result = File::open("DOES_NOT_EXIST"); - some_result?; - Ok("bla".to_string()) - }); - assert!(matches!( - oneshot_val.poll_state(), - ImmediateValueState::Updating - )); - tokio::time::sleep(Duration::from_millis(50)).await; - let result = oneshot_val.poll_state(); - if let ImmediateValueState::Error(e) = result { - let _ = format!("{}", **e); - return; - } - unreachable!(); + #[tokio::test] + async fn error() { + let mut oneshot_val = ImmediateValuePromise::new(async { + let some_result = File::open("DOES_NOT_EXIST"); + some_result?; + Ok("bla".to_string()) }); + assert!(matches!( + oneshot_val.poll_state(), + ImmediateValueState::Updating + )); + tokio::time::sleep(Duration::from_millis(50)).await; + let result = oneshot_val.poll_state(); + if let ImmediateValueState::Error(e) = result { + let _ = format!("{}", **e); + return; + } + unreachable!(); } - #[test] - fn get_state() { - Runtime::new().unwrap().block_on(async { - let mut oneshot_val = ImmediateValuePromise::new(async { Ok("bla".to_string()) }); - // get value does not trigger any polling - let state = oneshot_val.get_state(); - assert!(matches!(state, ImmediateValueState::Updating)); - tokio::time::sleep(Duration::from_millis(50)).await; - let state = oneshot_val.get_state(); - assert!(matches!(state, ImmediateValueState::Updating)); + #[tokio::test] + async fn get_state() { + let mut oneshot_val = ImmediateValuePromise::new(async { Ok("bla".to_string()) }); + // get value does not trigger any polling + let state = oneshot_val.get_state(); + assert!(matches!(state, ImmediateValueState::Updating)); + tokio::time::sleep(Duration::from_millis(50)).await; + let state = oneshot_val.get_state(); + assert!(matches!(state, ImmediateValueState::Updating)); - let polled = oneshot_val.poll_state(); - assert_eq!(polled.get_value().unwrap(), "bla"); - }); + let polled = oneshot_val.poll_state(); + assert_eq!(polled.get_value().unwrap(), "bla"); } - #[test] - fn get_mut_take_value() { - Runtime::new().unwrap().block_on(async { - let mut oneshot_val = ImmediateValuePromise::new(async { Ok("bla".to_string()) }); - tokio::time::sleep(Duration::from_millis(50)).await; - { - // get value does not trigger any polling - let result = oneshot_val.poll_state_mut(); - // we got the value - if let Some(inner) = result.get_value_mut() { - assert_eq!(inner, "bla"); - // write back - *inner = "changed".to_string(); - } else { - unreachable!(); - } - let result = oneshot_val.poll_state_mut(); - // take it out, should be changed and owned - let value = result.take_value(); - assert_eq!(value.unwrap().as_str(), "changed"); - assert!(matches!(result, ImmediateValueState::Empty)); + #[tokio::test] + async fn get_mut_take_value() { + let mut oneshot_val = ImmediateValuePromise::new(async { Ok("bla".to_string()) }); + tokio::time::sleep(Duration::from_millis(50)).await; + { + // get value does not trigger any polling + let result = oneshot_val.poll_state_mut(); + // we got the value + if let Some(inner) = result.get_value_mut() { + assert_eq!(inner, "bla"); + // write back + *inner = "changed".to_string(); + } else { + unreachable!(); } - // afterwards we are empty on get and poll - assert!(matches!( - oneshot_val.get_state(), - ImmediateValueState::Empty - )); - assert!(matches!( - oneshot_val.poll_state(), - ImmediateValueState::Empty - )); - }); + let result = oneshot_val.poll_state_mut(); + // take it out, should be changed and owned + let value = result.take_value(); + assert_eq!(value.unwrap().as_str(), "changed"); + assert!(matches!(result, ImmediateValueState::Empty)); + } + // afterwards we are empty on get and poll + assert!(matches!( + oneshot_val.get_state(), + ImmediateValueState::Empty + )); + assert!(matches!( + oneshot_val.poll_state(), + ImmediateValueState::Empty + )); } - #[test] - fn option_laziness() { + #[tokio::test] + async fn option_laziness() { use crate::*; - Runtime::new().unwrap().block_on(async { - let mut option = Some(ImmediateValuePromise::new(async { Ok("bla".to_string()) })); - tokio::time::sleep(Duration::from_millis(50)).await; - option.as_mut().unwrap().poll_state(); - let _inner = option.get_value(); - let _inner_mut = option.get_value_mut(); - let inner_owned = option.take_value().unwrap(); - assert_eq!(inner_owned, "bla"); - // after value is taken, we can't borrow it again - assert!(option.get_value().is_none()); - assert!(option.get_value_mut().is_none()); - assert!(option.take_value().is_none()); - }); + let mut option = Some(ImmediateValuePromise::new(async { Ok("bla".to_string()) })); + tokio::time::sleep(Duration::from_millis(50)).await; + option.as_mut().unwrap().poll_state(); + let _inner = option.get_value(); + let _inner_mut = option.get_value_mut(); + let inner_owned = option.take_value().unwrap(); + assert_eq!(inner_owned, "bla"); + // after value is taken, we can't borrow it again + assert!(option.get_value().is_none()); + assert!(option.get_value_mut().is_none()); + assert!(option.take_value().is_none()); } } diff --git a/src/immediatevalueprogress.rs b/src/immediatevalueprogress.rs index c93f70e..40fbd5b 100644 --- a/src/immediatevalueprogress.rs +++ b/src/immediatevalueprogress.rs @@ -148,47 +148,44 @@ mod test { use super::*; use crate::ImmediateValuePromise; use std::time::Duration; - use tokio::runtime::Runtime; - #[test] - fn basic_usage_cycle() { - Runtime::new().unwrap().block_on(async { - let mut oneshot_progress = ProgressTrackedImValProm::new( - |s| { - ImmediateValuePromise::new(async move { - s.send(StringStatus::new( - Progress::from_percent(0.0), - Cow::Borrowed("Initializing"), - )) - .await - .unwrap(); - tokio::time::sleep(Duration::from_millis(50)).await; - s.send(StringStatus::new( - Progress::from_percent(100.0), - Cow::Borrowed("Done"), - )) - .await - .unwrap(); - Ok(34) - }) - }, - 2000, - ); - assert!(matches!( - oneshot_progress.poll_state(), - ImmediateValueState::Updating - )); - assert_eq!(*oneshot_progress.get_progress(), 0.0); - tokio::time::sleep(Duration::from_millis(100)).await; - let _ = oneshot_progress.poll_state(); - assert_eq!(*oneshot_progress.get_progress(), 1.0); - let result = oneshot_progress.poll_state(); + #[tokio::test] + async fn basic_usage_cycle() { + let mut oneshot_progress = ProgressTrackedImValProm::new( + |s| { + ImmediateValuePromise::new(async move { + s.send(StringStatus::new( + Progress::from_percent(0.0), + Cow::Borrowed("Initializing"), + )) + .await + .unwrap(); + tokio::time::sleep(Duration::from_millis(50)).await; + s.send(StringStatus::new( + Progress::from_percent(100.0), + Cow::Borrowed("Done"), + )) + .await + .unwrap(); + Ok(34) + }) + }, + 2000, + ); + assert!(matches!( + oneshot_progress.poll_state(), + ImmediateValueState::Updating + )); + assert_eq!(*oneshot_progress.get_progress(), 0.0); + tokio::time::sleep(Duration::from_millis(100)).await; + let _ = oneshot_progress.poll_state(); + assert_eq!(*oneshot_progress.get_progress(), 1.0); + let result = oneshot_progress.poll_state(); - if let ImmediateValueState::Success(val) = result { - assert_eq!(*val, 34); - return; - } + if let ImmediateValueState::Success(val) = result { + assert_eq!(*val, 34); + return; + } - unreachable!(); - }); + unreachable!(); } } diff --git a/src/lazyvalue.rs b/src/lazyvalue.rs index 7b3bf24..0ae0778 100644 --- a/src/lazyvalue.rs +++ b/src/lazyvalue.rs @@ -136,7 +136,7 @@ mod test { use super::*; use crate::api_macros::*; use std::time::Duration; - use tokio::runtime::{Builder, Runtime}; + use tokio::runtime::Builder; use tokio::sync::mpsc::Sender; #[test] @@ -185,42 +185,38 @@ mod test { }); } - #[test] - fn error_propagation() { + #[tokio::test] + async fn error_propagation() { let error_maker = |tx: Sender>| async move { let _ = unpack_result!(std::fs::read_to_string("FILE_NOT_EXISTING"), tx); unreachable!(); }; - Runtime::new().unwrap().block_on(async { - let mut delayed_vec = LazyValuePromise::new(error_maker, 1); - assert_eq!(*delayed_vec.poll_state(), DataState::Updating(0.0.into())); - assert!(delayed_vec.get_value().is_none()); - tokio::time::sleep(Duration::from_millis(150)).await; - assert!(matches!(*delayed_vec.poll_state(), DataState::Error(_))); - assert!(delayed_vec.get_value().is_none()); - }); + let mut delayed_vec = LazyValuePromise::new(error_maker, 1); + assert_eq!(*delayed_vec.poll_state(), DataState::Updating(0.0.into())); + assert!(delayed_vec.get_value().is_none()); + tokio::time::sleep(Duration::from_millis(150)).await; + assert!(matches!(*delayed_vec.poll_state(), DataState::Error(_))); + assert!(delayed_vec.get_value().is_none()); } - #[test] - fn test_direct_cache_access() { + #[tokio::test] + async fn test_direct_cache_access() { let int_maker = |tx: Sender>| async move { send_data!(42, tx); set_finished!(tx); }; - Runtime::new().unwrap().block_on(async { - let mut delayed_value = LazyValuePromise::new(int_maker, 6); - let _ = delayed_value.poll_state(); - tokio::time::sleep(Duration::from_millis(50)).await; - let poll_result = delayed_value.poll_state(); - assert!(matches!(poll_result, DataState::UpToDate)); - let val = delayed_value.get_value(); - assert_eq!(*val.unwrap(), 42); - let _val_mut = delayed_value.get_value_mut(); - let value_owned = delayed_value.take_value().unwrap(); - assert_eq!(value_owned, 42); - assert!(delayed_value.is_uninitialized()); - }); + let mut delayed_value = LazyValuePromise::new(int_maker, 6); + let _ = delayed_value.poll_state(); + tokio::time::sleep(Duration::from_millis(50)).await; + let poll_result = delayed_value.poll_state(); + assert!(matches!(poll_result, DataState::UpToDate)); + let val = delayed_value.get_value(); + assert_eq!(*val.unwrap(), 42); + let _val_mut = delayed_value.get_value_mut(); + let value_owned = delayed_value.take_value().unwrap(); + assert_eq!(value_owned, 42); + assert!(delayed_value.is_uninitialized()); } } diff --git a/src/lazyvec.rs b/src/lazyvec.rs index d17455e..e3e36e5 100644 --- a/src/lazyvec.rs +++ b/src/lazyvec.rs @@ -153,7 +153,7 @@ mod test { use super::*; use crate::api_macros::*; use std::time::Duration; - use tokio::runtime::{Builder, Runtime}; + use tokio::runtime::Builder; use tokio::sync::mpsc::Sender; #[test] @@ -211,43 +211,39 @@ mod test { }); } - #[test] - fn error_propagation_returns_early() { + #[tokio::test] + async fn error_propagation_returns_early() { let error_maker = |tx: Sender>| async move { let _ = unpack_result!(std::fs::read_to_string("NOT_EXISTING"), tx); unreachable!(); }; - Runtime::new().unwrap().block_on(async { - let mut delayed_vec = LazyVecPromise::new(error_maker, 1); - assert_eq!(*delayed_vec.poll_state(), DataState::Updating(0.0.into())); - assert!(delayed_vec.as_slice().is_empty()); - tokio::time::sleep(Duration::from_millis(200)).await; - assert!(matches!(*delayed_vec.poll_state(), DataState::Error(_))); - assert!(delayed_vec.as_slice().is_empty()); - }); + let mut delayed_vec = LazyVecPromise::new(error_maker, 1); + assert_eq!(*delayed_vec.poll_state(), DataState::Updating(0.0.into())); + assert!(delayed_vec.as_slice().is_empty()); + tokio::time::sleep(Duration::from_millis(200)).await; + assert!(matches!(*delayed_vec.poll_state(), DataState::Error(_))); + assert!(delayed_vec.as_slice().is_empty()); } - #[test] - fn test_direct_cache_access() { + #[tokio::test] + async fn test_direct_cache_access() { let int_maker = |tx: Sender>| async move { send_data!(42, tx); set_finished!(tx); }; - Runtime::new().unwrap().block_on(async { - let mut delayed_vec = LazyVecPromise::new(int_maker, 6); - let _ = delayed_vec.poll_state(); - tokio::time::sleep(Duration::from_millis(50)).await; - let poll_result = delayed_vec.poll_state(); - assert!(matches!(poll_result, DataState::UpToDate)); - let val = delayed_vec.get_value(); - assert_eq!(val.unwrap().len(), 1); - assert_eq!(*val.unwrap().first().unwrap(), 42); - let _val_mut = delayed_vec.get_value_mut(); - let value_owned = delayed_vec.take_value().unwrap(); - assert_eq!(*value_owned.first().unwrap(), 42); - assert!(delayed_vec.is_uninitialized()); - }); + let mut delayed_vec = LazyVecPromise::new(int_maker, 6); + let _ = delayed_vec.poll_state(); + tokio::time::sleep(Duration::from_millis(50)).await; + let poll_result = delayed_vec.poll_state(); + assert!(matches!(poll_result, DataState::UpToDate)); + let val = delayed_vec.get_value(); + assert_eq!(val.unwrap().len(), 1); + assert_eq!(*val.unwrap().first().unwrap(), 42); + let _val_mut = delayed_vec.get_value_mut(); + let value_owned = delayed_vec.take_value().unwrap(); + assert_eq!(*value_owned.first().unwrap(), 42); + assert!(delayed_vec.is_uninitialized()); } } From 02f4a87d299dc2f1f4606b7d09b09f7cca068e1d Mon Sep 17 00:00:00 2001 From: Christopher Regali Date: Sat, 29 Apr 2023 21:55:36 +0200 Subject: [PATCH 09/10] Add better testing to the immediatevalueprogress.rs --- src/immediatevalueprogress.rs | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/immediatevalueprogress.rs b/src/immediatevalueprogress.rs index 40fbd5b..5c4a76c 100644 --- a/src/immediatevalueprogress.rs +++ b/src/immediatevalueprogress.rs @@ -153,16 +153,24 @@ mod test { let mut oneshot_progress = ProgressTrackedImValProm::new( |s| { ImmediateValuePromise::new(async move { - s.send(StringStatus::new( + s.send(StringStatus::from_str( Progress::from_percent(0.0), - Cow::Borrowed("Initializing"), + "Initializing", )) .await .unwrap(); - tokio::time::sleep(Duration::from_millis(50)).await; + tokio::time::sleep(Duration::from_millis(25)).await; s.send(StringStatus::new( + Progress::from_percent(50.0), + "processing".into(), + )) + .await + .unwrap(); + tokio::time::sleep(Duration::from_millis(25)).await; + + s.send(StringStatus::from_string( Progress::from_percent(100.0), - Cow::Borrowed("Done"), + format!("Done"), )) .await .unwrap(); @@ -175,6 +183,8 @@ mod test { oneshot_progress.poll_state(), ImmediateValueState::Updating )); + assert!(!oneshot_progress.finished()); + assert_eq!(*oneshot_progress.get_progress(), 0.0); tokio::time::sleep(Duration::from_millis(100)).await; let _ = oneshot_progress.poll_state(); @@ -183,9 +193,22 @@ mod test { if let ImmediateValueState::Success(val) = result { assert_eq!(*val, 34); - return; + } else { + unreachable!(); } + // check finished + assert!(oneshot_progress.finished()); + let history = oneshot_progress.status_history(); + assert_eq!(history.len(), 3); - unreachable!(); + // check direct cache access trait + let val = oneshot_progress.get_value().unwrap(); + assert_eq!(*val, 34); + let val = oneshot_progress.get_value_mut().unwrap(); + *val = 33; + assert_eq!(*oneshot_progress.get_value().unwrap(), 33); + let val = oneshot_progress.take_value().unwrap(); + assert_eq!(val, 33); + assert!(oneshot_progress.get_value().is_none()); } } From b33706040b959d7fac348a65c6bd4fdf7d371347 Mon Sep 17 00:00:00 2001 From: Christopher Regali Date: Sun, 28 May 2023 21:01:44 +0200 Subject: [PATCH 10/10] Add better documentation --- Cargo.toml | 2 +- src/lib.rs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index de86f1e..3232ec8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lazy_async_promise" -version = "0.5.0-RC1" +version = "0.5.0" edition = "2021" authors = ["Christopher Regali "] license = "MIT" diff --git a/src/lib.rs b/src/lib.rs index e787c3e..419b93c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,15 @@ #![crate_name = "lazy_async_promise"] //! # Primitives for combining tokio and immediate mode guis +//! ## List of primitives //! The following primitives are implemented: //! - [`ImmediateValuePromise`]: An immediately updating async-enabled single value promise //! - [`ProgressTrackedImValProm`]: A progress/status emitting enhanced wrapper for [`ImmediateValuePromise`] //! - [`LazyVecPromise`]: A lazily evaluated, partially readable and async-enabled vector-backed promise //! - [`LazyValuePromise`]: A lazily evaluated and async-enabled single value promise -//! See these items for their respective documentation. A general usage guide would be: +//! +//! See these items for their respective documentation. +//! ## What to use +//! A general usage guide would be: //! - You just want one value when ready? Use: [`ImmediateValuePromise`] (for laziness wrap in `Option`) //! - If you need status update support for that, use [`ProgressTrackedImValProm`] //! - You want several items of the same kind / streamed? Use: [`LazyVecPromise`]