Skip to content

Commit

Permalink
Bump version to 0.2.0:
Browse files Browse the repository at this point in the history
- Remove type erasure using traits in favor of boxing the updater factories
  • Loading branch information
ChrisRega committed Oct 11, 2022
1 parent da42451 commit 7e3cc14
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 96 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lazy_async_promise"
version = "0.1.2"
version = "0.2.0"
edition = "2021"
authors = ["Christopher Regali <[email protected]>"]
license = "MIT"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/immediatevalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl<E: Error + Send + 'static> 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;
Expand Down Expand Up @@ -54,7 +54,7 @@ pub struct ImmediateValuePromise<T: Send + 'static> {
state: ImmediateValueState<T>,
}

/// 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<T> {
/// future is not yet resolved
Updating,
Expand Down
58 changes: 23 additions & 35 deletions src/lazyvalue.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
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};

/// # 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:
Expand All @@ -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<dyn ValuePromise<i32>> = Box::new(LazyValuePromise::new(updater, 6));
///
/// fn main_loop(lazy_promise: &mut Box<dyn ValuePromise<i32>>) {
/// fn main_loop(lazy_promise: &mut LazyValuePromise<i32>) {
/// 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()); }
/// }
/// }
Expand All @@ -40,55 +38,45 @@ use tokio::sync::mpsc::{channel, Receiver, Sender};
///
///

pub struct LazyValuePromise<
T: Debug,
U: Fn(Sender<Message<T>>) -> Fut,
Fut: Future<Output = ()> + Send + 'static,
> {
pub struct LazyValuePromise<T: Debug> {
cache: Option<T>,
updater: U,
updater: BoxedFutureFactory<T>,
state: DataState,
rx: Receiver<Message<T>>,
tx: Sender<Message<T>>,
}
impl<T: Debug, U: Fn(Sender<Message<T>>) -> Fut, Fut: Future<Output = ()> + Send + 'static>
LazyValuePromise<T, U, Fut>
{
impl<T: Debug> LazyValuePromise<T> {
/// 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<Message<T>>) -> Fut + 'static,
Fut: Future<Output = ()> + Send + 'static,
>(
future_factory: U,
buffer_size: usize,
) -> Self {
let (tx, rx) = channel::<Message<T>>(buffer_size);

Self {
cache: None,
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<T: Debug, U: Fn(Sender<Message<T>>) -> Fut, Fut: Future<Output = ()> + Send + 'static> Value<T>
for LazyValuePromise<T, U, Fut>
{
fn value(&self) -> Option<&T> {
self.cache.as_ref()
}
}

impl<T: Debug, U: Fn(Sender<Message<T>>) -> Fut, Fut: Future<Output = ()> + Send + 'static>
ValuePromise<T> for LazyValuePromise<T, U, Fut>
{
}

impl<T: Debug, U: Fn(Sender<Message<T>>) -> Fut, Fut: Future<Output = ()> + Send + 'static> Promise
for LazyValuePromise<T, U, Fut>
{
impl<T: Debug> Promise for LazyValuePromise<T> {
fn poll_state(&mut self) -> &DataState {
if self.state == DataState::Uninitialized {
self.update();
Expand Down
56 changes: 23 additions & 33 deletions src/lazyvec.rs
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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<dyn SlicePromise<i32>> = Box::new(LazyVecPromise::new(updater, 6));
///
/// fn main_loop(lazy_promise: &mut Box<dyn SlicePromise<i32>>) {
/// fn main_loop(lazy_promise: &mut LazyVecPromise<i32>) {
/// loop {
/// match lazy_promise.poll_state() {
/// DataState::Error(er) => { println!("Error {} occurred! Retrying!", er); std::thread::sleep(Duration::from_millis(500)); lazy_promise.update(); },
Expand All @@ -43,55 +41,47 @@ use tokio::sync::mpsc::{channel, Receiver, Sender};
/// ```
///
///
pub struct LazyVecPromise<
T: Debug,
U: Fn(Sender<Message<T>>) -> Fut,
Fut: Future<Output = ()> + Send + 'static,
> {

pub struct LazyVecPromise<T: Debug> {
data: Vec<T>,
state: DataState,
rx: Receiver<Message<T>>,
tx: Sender<Message<T>>,
updater: U,
updater: BoxedFutureFactory<T>,
}

impl<T: Debug, U: Fn(Sender<Message<T>>) -> Fut, Fut: Future<Output = ()> + Send + 'static>
LazyVecPromise<T, U, Fut>
{
impl<T: Debug> LazyVecPromise<T> {
/// 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<Message<T>>) -> Fut + 'static,
Fut: Future<Output = ()> + Send + 'static,
>(
future_factory: U,
buffer_size: usize,
) -> Self {
let (tx, rx) = channel::<Message<T>>(buffer_size);

Self {
data: vec![],
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<T: Debug, U: Fn(Sender<Message<T>>) -> Fut, Fut: Future<Output = ()> + Send + 'static>
Sliceable<T> for LazyVecPromise<T, U, Fut>
{
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<T: Debug, U: Fn(Sender<Message<T>>) -> Fut, Fut: Future<Output = ()> + Send + 'static>
SlicePromise<T> for LazyVecPromise<T, U, Fut>
{
#[cfg(test)]
pub fn is_uninitialized(&self) -> bool {
self.state == DataState::Uninitialized
}
}

impl<T: Debug, U: Fn(Sender<Message<T>>) -> Fut, Fut: Future<Output = ()> + Send + 'static> Promise
for LazyVecPromise<T, U, Fut>
{
impl<T: Debug> Promise for LazyVecPromise<T> {
fn poll_state(&mut self) -> &DataState {
if self.state == DataState::Uninitialized {
self.update();
Expand Down
49 changes: 27 additions & 22 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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 {
Expand Down Expand Up @@ -59,24 +69,6 @@ pub trait Promise {
fn update(&mut self);
}

/// Implementors can be viewed as slice
pub trait Sliceable<T> {
/// get current data slice
fn as_slice(&self) -> &[T];
}

/// Implementors may have a value
pub trait Value<T> {
/// 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<T>: Promise + Sliceable<T> {}

/// Some type that implements lazy updating and provides a single value of the desired type
pub trait ValuePromise<T>: Promise + Value<T> {}

#[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 {
Expand All @@ -93,3 +85,16 @@ macro_rules! unpack_result {
}
};
}

type BoxedFutureFactory<T> =
Box<dyn Fn(Sender<Message<T>>) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>>>;

fn box_future_factory<
T: Debug,
U: Fn(Sender<Message<T>>) -> Fut + 'static,
Fut: Future<Output = ()> + Send + 'static,
>(
future_factory: U,
) -> BoxedFutureFactory<T> {
Box::new(move |tx: Sender<Message<T>>| Box::pin(future_factory(tx)))
}

0 comments on commit 7e3cc14

Please sign in to comment.