hyphae 3.0.0

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

use parking_lot::Mutex;

use super::{CellValue, Watchable};
use crate::{
    cell::{Cell, CellMutable},
    pipeline::{Definite, Pipeline, PipelineInstall, PipelineSeed, prepare_install},
    signal::Signal,
    subscription::SubscriptionGuard,
};

pub struct JoinVecPipeline<P, T> {
    sources: Vec<P>,
    _type: PhantomData<fn() -> T>,
}

impl<P, T> PipelineInstall<Vec<T>> for JoinVecPipeline<P, T>
where
    P: PipelineInstall<T> + PipelineSeed<T>,
    T: CellValue,
{
    fn install(&self, callback: Arc<dyn Fn(&Signal<Vec<T>>) + Send + Sync>) -> SubscriptionGuard {
        if self.sources.is_empty() {
            callback(&Signal::Complete);
            return SubscriptionGuard::combine(vec![]);
        }
        let prepared = self.sources.iter().map(prepare_install).collect::<Vec<_>>();
        let initial = prepared
            .iter()
            .map(|source| source.initial().clone())
            .collect::<Vec<_>>();
        let latest = Arc::new(Mutex::new(initial.clone()));
        let derived = Cell::<Vec<T>, CellMutable>::new(initial);
        let completed = Arc::new(AtomicUsize::new(0));
        let count = self.sources.len();
        for (index, source) in prepared.into_iter().enumerate() {
            let latest = latest.clone();
            let completed = completed.clone();
            let weak = derived.downgrade();
            let source_callback: Arc<dyn Fn(&Signal<T>) + Send + Sync> =
                Arc::new(move |signal| match signal {
                    Signal::Value(value) => {
                        let mut latest = latest.lock();
                        if let Some(slot) = latest.get_mut(index) {
                            *slot = value.as_ref().clone();
                        }
                        if let Some(derived) = weak.upgrade() {
                            derived.notify(Signal::value(latest.clone()));
                        }
                    }
                    Signal::Complete
                        if completed.fetch_add(1, Ordering::SeqCst).saturating_add(1) == count =>
                    {
                        if let Some(derived) = weak.upgrade() {
                            derived.notify(Signal::Complete);
                        }
                    }
                    Signal::Complete => {}
                    Signal::Error(error) => {
                        if let Some(derived) = weak.upgrade() {
                            derived.notify(Signal::Error(error.clone()));
                        }
                    }
                });
            let guard = source.activate(&source_callback);
            derived.own(guard);
        }
        derived.subscribe(move |signal| callback(signal))
    }
}
impl<P, T> PipelineSeed<Vec<T>> for JoinVecPipeline<P, T>
where
    P: PipelineSeed<T>,
    T: CellValue,
{
    fn seed(&self) -> Vec<T> {
        self.sources.iter().map(PipelineSeed::seed).collect()
    }
}
impl<P, T> Pipeline<Vec<T>, Definite> for JoinVecPipeline<P, T>
where
    P: Pipeline<T, Definite> + PipelineSeed<T>,
    T: CellValue,
{
}

#[must_use]
pub fn join_vec<T, P>(sources: Vec<P>) -> impl crate::Materialize<Vec<T>, Definite>
where
    T: CellValue,
    P: Pipeline<T, Definite> + PipelineSeed<T>,
{
    JoinVecPipeline {
        sources,
        _type: PhantomData,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Cell, Gettable, Materialize, Mutable, Watchable};
    #[test]
    fn test_join_vec_empty() {
        let combined = join_vec::<i32, Cell<i32, crate::CellImmutable>>(vec![]).materialize();
        assert_eq!(combined.get(), Vec::<i32>::new());
        assert!(combined.is_complete());
    }
    #[test]
    fn test_join_vec_multiple() {
        let a = Cell::new(1);
        let b = Cell::new(2);
        let c = Cell::new(3);
        let combined = join_vec(vec![a.clone(), b.clone(), c.clone()]).materialize();
        a.set(10);
        b.set(20);
        c.set(30);
        assert_eq!(combined.get(), vec![10, 20, 30]);
    }
}