hyphae 3.0.0

Reactive cells and runtime primitives for rship
Documentation
use std::{
    marker::PhantomData,
    sync::{
        Arc,
        atomic::{AtomicBool, Ordering},
    },
};

use super::CellValue;
use crate::{
    pipeline::{Definite, Pipeline, PipelineInstall, PipelineSeed},
    signal::Signal,
    subscription::SubscriptionGuard,
};

pub struct TakeUntilPipeline<S, N, T, U> {
    source: S,
    notifier: N,
    _types: PhantomData<fn() -> (T, U)>,
}

impl<S, N, T, U> PipelineInstall<T> for TakeUntilPipeline<S, N, T, U>
where
    S: PipelineInstall<T>,
    N: PipelineInstall<U>,
    T: CellValue,
    U: CellValue,
{
    fn install(&self, callback: Arc<dyn Fn(&Signal<T>) + Send + Sync>) -> SubscriptionGuard {
        let stopped = Arc::new(AtomicBool::new(false));
        let notifier_stopped = stopped.clone();
        let notifier_callback = callback.clone();
        let notifier_first = AtomicBool::new(true);
        let notifier = self.notifier.install(Arc::new(move |signal| {
            if matches!(signal, Signal::Value(_)) && !notifier_first.swap(false, Ordering::SeqCst) {
                notifier_stopped.store(true, Ordering::SeqCst);
                notifier_callback(&Signal::Complete);
            }
        }));
        let source_first = AtomicBool::new(true);
        let source = self.source.install(Arc::new(move |signal| match signal {
            Signal::Value(_) if source_first.swap(false, Ordering::SeqCst) => {}
            Signal::Value(_) if stopped.load(Ordering::SeqCst) => {}
            _ => callback(signal),
        }));
        SubscriptionGuard::combine(vec![notifier, source])
    }
}
impl<S, N, T, U> PipelineSeed<T> for TakeUntilPipeline<S, N, T, U>
where
    S: PipelineSeed<T>,
    N: PipelineInstall<U>,
    T: CellValue,
    U: CellValue,
{
    fn seed(&self) -> T {
        self.source.seed()
    }
}
impl<S, N, T, U> Pipeline<T, Definite> for TakeUntilPipeline<S, N, T, U>
where
    S: Pipeline<T, Definite> + PipelineSeed<T>,
    N: Pipeline<U, Definite>,
    T: CellValue,
    U: CellValue,
{
}

pub trait TakeUntilExt<T: CellValue>: Pipeline<T, Definite> + PipelineSeed<T> {
    fn take_until<U, N>(self, notifier: N) -> impl crate::Materialize<T, Definite>
    where
        U: CellValue,
        N: Pipeline<U, Definite>,
    {
        TakeUntilPipeline {
            source: self,
            notifier,
            _types: PhantomData,
        }
    }
}
impl<T: CellValue, P> TakeUntilExt<T> for P where P: Pipeline<T, Definite> + PipelineSeed<T> {}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Cell, Gettable, Materialize, Mutable, Watchable};
    #[test]
    fn test_take_until() {
        let source = Cell::new(1u64);
        let stopper = Cell::new(false);
        let taken = source.clone().take_until(stopper.clone()).materialize();
        source.set(2);
        assert_eq!(taken.get(), 2);
        stopper.set(true);
        source.set(3);
        assert_eq!(taken.get(), 2);
    }
    #[test]
    fn test_take_until_completes_on_notifier() {
        let source = Cell::new(1u64);
        let stopper = Cell::new(false);
        let taken = source.take_until(stopper.clone()).materialize();
        stopper.set(true);
        assert!(taken.is_complete());
    }
}