dbuff 0.1.0

Double-buffered state with async command chains, streaming, and keyed task pools for ratatui applications
Documentation
use dbuff::*;
use error_stack::Report;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;

#[derive(Debug, Clone, Default)]
struct AppData {
    total: i32,
    error_log: Vec<String>,
}

struct Add(i32);

#[derive(Debug)]
struct ChainError;

impl std::fmt::Display for ChainError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "chain error")
    }
}

impl std::error::Error for ChainError {}

#[derive(Debug, Clone, wherror::Error)]
#[error(debug)]
struct AddError;

#[async_trait::async_trait]
impl Command<()> for Add {
    type Output = i32;
    type Error = Report<AddError>;

    async fn execute(self, _: ()) -> Result<Self::Output, Self::Error> {
        Ok(self.0)
    }
}

#[derive(Debug, Clone, wherror::Error)]
#[error(debug)]
struct FailError;

struct Fail;

#[async_trait::async_trait]
impl Command<()> for Fail {
    type Output = ();
    type Error = Report<FailError>;

    async fn execute(self, _: ()) -> Result<Self::Output, Self::Error> {
        Err(Report::from(FailError))
    }
}

#[tokio::main]
async fn main() {
    let rt = tokio::runtime::Handle::current();

    let (domain, write_handle) =
        SharedDomainData::with_coalesce(AppData::default(), Duration::from_millis(1));
    tokio::spawn(write_handle.run());

    let error_called = Arc::new(AtomicBool::new(false));
    let flag = error_called.clone();

    let handle = domain
        .bind((), rt.clone())
        .on_error(move |report, d| {
            flag.store(true, Ordering::SeqCst);
            let report = report.change_context(ChainError);
            d.error_log.push(format!("{report:?}"));
        })
        .exec(Add(10), |d, v: &i32| d.total += *v)
        .exec(Add(20), |d, v: &i32| d.total += *v)
        .exec_discard(Fail)
        .exec(Add(5), |d, v: &i32| d.total += *v)
        .go();

    let flow = handle.await.unwrap();
    assert_eq!(flow, ControlFlow::Break);

    tokio::time::sleep(Duration::from_millis(10)).await;

    assert!(error_called.load(Ordering::SeqCst));

    let guard = domain.read();
    assert_eq!(guard.total, 30);
    assert_eq!(guard.error_log.len(), 1);
    println!("total = {}", guard.total);
    println!("error_log = {:#?}", guard.error_log);
}