incremental 0.2.8

incremental computations, based on Jane Street's incremental
Documentation
use std::{cell::RefCell, rc::Rc};
// RUST_LOG_SPAN_EVENTS=enter,exit
use test_log::test;

use incremental::incrsan::NotObserver;
use incremental::{expert::*, Incr, IncrState, Value};

fn join<T: Value>(incr: &Incr<Incr<T>>) -> Incr<T> {
    let prev_rhs: Rc<RefCell<Option<Dependency<T>>>> = Rc::new(None.into());
    let state = incr.state();
    let join = Node::<T>::new(&state, {
        let prev_rhs_ = prev_rhs.clone();
        move || prev_rhs_.borrow().clone().unwrap().value_cloned()
    });
    let join_ = join.weak();
    // In order to schedule the dependency additions/deletions before the expert Node executes,
    // we put this in a Map node (akin to BindLhsChange).
    let lhs_change = incr.map(move |rhs| {
        let dep = join_.add_dependency(rhs);
        let mut prev_rhs_ = prev_rhs.borrow_mut();
        if let Some(prev) = prev_rhs_.take() {
            join_.remove_dependency(prev);
        }
        prev_rhs_.replace(dep);
    });
    join.add_dependency(&lhs_change);
    join.watch()
}

#[test]
fn test_join() {
    let incr = IncrState::new();
    let inner = incr.var(10i32);
    let outer = incr.var(inner.watch());
    let joined = join(&outer);
    let o = joined.observe();
    incr.stabilise();
    assert_eq!(o.try_get_value(), Ok(10));
    inner.set(20);
    incr.stabilise();
    assert_eq!(o.try_get_value(), Ok(20));
}

#[allow(dead_code)]
fn bind<T: Value, R: Value>(
    incr: Incr<T>,
    mut f: impl FnMut(&T) -> Incr<R> + 'static + NotObserver,
) -> Incr<R> {
    let prev_rhs: Rc<RefCell<Option<Dependency<R>>>> = Rc::new(None.into());
    let state = incr.state();
    let join = Node::<R>::new(&state, {
        let prev_rhs_ = prev_rhs.clone();
        move || prev_rhs_.borrow().clone().unwrap().value_cloned()
    });
    let join_ = join.weak();
    let lhs_change = incr.map(move |input| {
        let rhs = f(input);
        let mut prev_rhs_ = prev_rhs.borrow_mut();
        if prev_rhs_.as_ref().map_or(false, |prev| prev.node() == rhs) {
            return;
        }
        let dep = join_.add_dependency(&rhs);
        if let Some(prev) = prev_rhs_.take() {
            join_.remove_dependency(prev);
        }
        prev_rhs_.replace(dep);
    });
    join.add_dependency(&lhs_change);
    join.watch()
}

#[test]
fn map345_expert() {
    let incr = IncrState::new();
    let i1 = incr.var(3);
    let i2 = incr.var(5);
    let i3 = incr.var(7);
    let triple = i1.map3(&i2, &i3, |a, b, c| (a * b, *c));

    // now have an expert node add a dependency on triple
    let outer = incr.var(triple.clone());
    let joined = join(&outer);

    let j = joined.observe();
    incr.stabilise();
    assert_eq!(j.value(), (15, 7));
}

fn manual_zip2<T1: Value, T2: Value>(one: &Incr<T1>, two: &Incr<T2>) -> Incr<(T1, T2)> {
    let state = one.state();
    enum Storage<A, B> {
        None,
        OneOnly(A),
        TwoOnly(B),
        Both(A, B),
    }
    impl<A, B> Storage<A, B> {
        fn take(&mut self) -> Self {
            std::mem::replace(self, Storage::None)
        }
        fn both_cloned(&self) -> (A, B)
        where
            A: Clone,
            B: Clone,
        {
            match self {
                Self::Both(a, b) => (a.clone(), b.clone()),
                _ => panic!("zip2 node has not yet read both inputs"),
            }
        }
    }
    let current = Rc::new(RefCell::new(Storage::None));
    let zip2 = Node::<(T1, T2)>::new(&state, {
        let current_ = current.clone();
        move || current_.borrow().both_cloned()
    });
    let current_1 = current.clone();
    zip2.add_dependency_with(one, move |new_one| {
        let mut tuple = current_1.borrow_mut();
        let storage = tuple.take();
        let new_one = new_one.clone();
        let new = match storage {
            Storage::Both(_, b) | Storage::TwoOnly(b) => Storage::Both(new_one, b),
            Storage::None | Storage::OneOnly(_) => Storage::OneOnly(new_one),
        };
        *tuple = new;
    });
    let current_2 = current;
    zip2.add_dependency_with(two, move |new_two| {
        let mut tuple = current_2.borrow_mut();
        let storage = tuple.take();
        let new_b = new_two.clone();
        let new = match storage {
            Storage::Both(a, _) | Storage::OneOnly(a) => Storage::Both(a, new_b),
            Storage::None | Storage::TwoOnly(_) => Storage::TwoOnly(new_b),
        };
        *tuple = new;
    });
    zip2.watch()
}

#[test]
fn test_zip2() {
    let incr = IncrState::new();
    let i1 = incr.var(3);
    let i2 = incr.var(5);
    let z = manual_zip2(&i1, &i2);
    let o = z.observe();
    incr.stabilise();
    assert_eq!(o.value(), (3, 5));
}

#[test]
fn expert_duplicate_inputs() {
    let incr = IncrState::new();
    let constant = incr.constant(3);
    let z = manual_zip2(&constant, &constant);
    let o = z.observe();
    incr.stabilise();
    assert_eq!(o.value(), (3, 3));
    drop(o);
    incr.stabilise();
}