hyphae 3.0.0

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

use parking_lot::Mutex;

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

pub struct ZipPipeline<L, R, T, U> {
    left: L,
    right: R,
    _types: PhantomData<fn() -> (T, U)>,
}

impl<L, R, T, U> PipelineInstall<(T, U)> for ZipPipeline<L, R, T, U>
where
    L: PipelineInstall<T>,
    R: PipelineInstall<U>,
    T: CellValue,
    U: CellValue,
{
    fn install(&self, callback: Arc<dyn Fn(&Signal<(T, U)>) + Send + Sync>) -> SubscriptionGuard {
        let buffers = Arc::new(Mutex::new((VecDeque::<T>::new(), VecDeque::<U>::new())));
        let left_buffers = buffers.clone();
        let left_callback = callback.clone();
        let left_first = AtomicBool::new(true);
        let left = self.left.install(Arc::new(move |signal| match signal {
            Signal::Value(_) if left_first.swap(false, Ordering::SeqCst) => {}
            Signal::Value(value) => {
                let mut buffers = left_buffers.lock();
                if let Some(right) = buffers.1.pop_front() {
                    left_callback(&Signal::value((value.as_ref().clone(), right)));
                } else {
                    buffers.0.push_back(value.as_ref().clone());
                }
            }
            Signal::Complete => left_callback(&Signal::Complete),
            Signal::Error(error) => left_callback(&Signal::Error(error.clone())),
        }));
        let right_first = AtomicBool::new(true);
        let right = self.right.install(Arc::new(move |signal| match signal {
            Signal::Value(_) if right_first.swap(false, Ordering::SeqCst) => {}
            Signal::Value(value) => {
                let mut buffers = buffers.lock();
                if let Some(left) = buffers.0.pop_front() {
                    callback(&Signal::value((left, value.as_ref().clone())));
                } else {
                    buffers.1.push_back(value.as_ref().clone());
                }
            }
            Signal::Complete => callback(&Signal::Complete),
            Signal::Error(error) => callback(&Signal::Error(error.clone())),
        }));
        SubscriptionGuard::combine(vec![left, right])
    }
}

impl<L, R, T, U> PipelineSeed<(T, U)> for ZipPipeline<L, R, T, U>
where
    L: PipelineSeed<T>,
    R: PipelineSeed<U>,
    T: CellValue,
    U: CellValue,
{
    fn seed(&self) -> (T, U) {
        (self.left.seed(), self.right.seed())
    }
}
impl<L, R, T, U> Pipeline<(T, U), Definite> for ZipPipeline<L, R, T, U>
where
    L: Pipeline<T, Definite> + PipelineSeed<T>,
    R: Pipeline<U, Definite> + PipelineSeed<U>,
    T: CellValue,
    U: CellValue,
{
}

pub trait ZipExt<T: CellValue>: Pipeline<T, Definite> + PipelineSeed<T> {
    fn zip<U, R>(self, other: R) -> impl crate::Materialize<(T, U), Definite>
    where
        U: CellValue,
        R: Pipeline<U, Definite> + PipelineSeed<U>,
    {
        ZipPipeline {
            left: self,
            right: other,
            _types: PhantomData,
        }
    }
}
impl<T: CellValue, P> ZipExt<T> for P where P: Pipeline<T, Definite> + PipelineSeed<T> {}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Cell, Gettable, Materialize, Mutable};
    #[test]
    fn test_zip() {
        let a = Cell::new(1);
        let b = Cell::new("a");
        let zipped = a.clone().zip(b.clone()).materialize();
        assert_eq!(zipped.get(), (1, "a"));
        a.set(2);
        a.set(3);
        assert_eq!(zipped.get(), (1, "a"));
        b.set("b");
        assert_eq!(zipped.get(), (2, "b"));
        b.set("c");
        assert_eq!(zipped.get(), (3, "c"));
    }
}