mini-rx 0.1.1

bare-bones "reactive programming" (change propogation) using a central data dependency graph
Documentation
use mini_rx::*;
use test_log::test;
use std::cell::{Cell, RefCell};

#[test]
fn test_rx() {
    let side_effect = Cell::new(1);

    let mut g = RxDAG::new();
    let rx = g.new_var(1);
    let crx = g.new_crx(move |g| *rx.get(g) * 2);
    let side_effect_ref = &side_effect;
    g.run_crx(move |g| {
        side_effect_ref.set(side_effect_ref.get() + *crx.get(g));
    });
    assert_eq!(rx.get(g.stale()), &1);
    assert_eq!(crx.get(g.stale()), &2);
    assert_eq!(side_effect.get(), 3);

    rx.set(&g, 2);
    assert_eq!(rx.get(g.stale()), &1);
    assert_eq!(crx.get(g.stale()), &2);
    assert_eq!(side_effect.get(), 3);
    g.recompute();
    assert_eq!(rx.get(g.stale()), &2);
    assert_eq!(crx.get(g.stale()), &4);
    assert_eq!(side_effect.get(), 7);

    rx.set(&g, 4);
    assert_eq!(rx.get(g.stale()), &2);
    assert_eq!(crx.get(g.stale()), &4);
    assert_eq!(side_effect.get(), 7);
    g.recompute();
    assert_eq!(rx.get(g.stale()), &4);
    assert_eq!(crx.get(g.stale()), &8);
    assert_eq!(side_effect.get(), 15);
}

#[test]
fn test_rx_modify() {
    let mut g = RxDAG::new();
    let rx = g.new_var(1);
    let crx = g.new_crx(move |g| *rx.get(g) * 2);
    let side_effect = Cell::new(1);
    let side_effect2 = &side_effect;
    g.run_crx(move |g| {
        side_effect2.set(side_effect2.get() + crx.get(g));
    });
    assert_eq!(rx.get(g.now()), &1);
    assert_eq!(crx.get(g.now()), &2);
    assert_eq!(side_effect.get(), 3);

    rx.modify(&g, |g| g + 3);
    rx.modify(&g, |g| g + 5);
    assert_eq!(rx.get(g.now()), &9);
    assert_eq!(crx.get(g.now()), &18);
    assert_eq!(side_effect.get(), 21);

    // See above
    drop(g);
}

#[test]
fn test_rx_multiple_inputs_outputs() {
    let mut g = RxDAG::new();
    let rx = g.new_var(1);
    let rx2 = g.new_var(2);
    let rx3 = g.new_var(vec![3, 4]);
    {
        let crx = g.new_crx(move |g| vec![*rx.get(g) * 10, *rx2.get(g) * 10]);
        let crx2 = g.new_crx(move |g| {
            let mut vec = Vec::new();
            vec.push(*rx.get(g));
            for elem in rx3.get(g).iter().copied() {
                vec.push(elem)
            }
            for elem in crx.get(g).iter().copied() {
                vec.push(elem)
            }
            vec
        });
        let (crx3_1, crx3_2) = g.new_crx2(move |g| {
            let vec = crx.get(g);
            (vec[0] * 10, vec[1] * 10)
        });
        let (crx4_1, crx4_2, crx4_3) = g.new_crx3(move |g| {
            let v2 = *rx.get(g);
            let v3 = *crx3_2.get(g);
            let v4 = rx3.get(g)[0];
            (v2, v3, v4 * 100)
        });

        assert_eq!(rx.get(g.now()), &1);
        assert_eq!(rx2.get(g.now()), &2);
        assert_eq!(rx3.get(g.now()), &vec![3, 4]);
        assert_eq!(crx.get(g.now()), &vec![10, 20]);
        assert_eq!(crx2.get(g.now()), &vec![1, 3, 4, 10, 20]);
        assert_eq!(crx3_1.get(g.now()), &100);
        assert_eq!(crx3_2.get(g.now()), &200);
        assert_eq!(crx4_1.get(g.now()), &1);
        assert_eq!(crx4_2.get(g.now()), &200);
        assert_eq!(crx4_3.get(g.now()), &300);

        rx.set(&g, 5);
        rx2.set(&g, 6);
        rx3.set(&g, vec![7, 8, 9]);
        g.recompute();

        assert_eq!(rx.get(g.now()), &5);
        assert_eq!(rx2.get(g.now()), &6);
        assert_eq!(rx3.get(g.now()), &vec![7, 8, 9]);
        assert_eq!(crx.get(g.now()), &vec![50, 60]);
        assert_eq!(crx2.get(g.now()), &vec![5, 7, 8, 9, 50, 60]);
        assert_eq!(crx3_1.get(g.now()), &500);
        assert_eq!(crx3_2.get(g.now()), &600);
        assert_eq!(crx4_1.get(g.now()), &5);
        assert_eq!(crx4_2.get(g.now()), &600);
        assert_eq!(crx4_3.get(g.now()), &700);
    }
}

#[test]
fn test_drx_split() {
    let mut g = RxDAG::new();
    let rx = g.new_var(vec![1, 2, 3]);
    {
        let drx0 = rx.derive_using_clone(|x| &x[0], |x, new| {
            x[0] = new;
        });
        let drx1 = rx.derive_using_clone(|x| &x[1], |x, new| {
            x[1] = new;
        });
        let drx2 = rx.derive_using_clone(|x| &x[2], |x, new| {
            x[2] = new;
        });
        assert_eq!(drx0.get(g.now()), &1);
        assert_eq!(drx1.get(g.now()), &2);
        assert_eq!(drx2.get(g.now()), &3);
        drx0.set(&g, 2);
        drx1.set(&g, 3);
        drx2.set(&g, 4);
    }
    assert_eq!(rx.get(g.now()), &vec![2, 3, 4]);
}

#[test]
fn test_crx() {
    let mut g = RxDAG::new();
    let rx = g.new_var(vec![1, 2, 3]);
    {
        let crx = g.new_crx(move |g| rx.get(g)[0] * 2);
        let crx2 = g.new_crx(move |g| *crx.get(g) + rx.get(g)[1] * 10);
        let crx3 = g.new_crx(move |g| crx2.get(g).to_string());
        assert_eq!(*crx.get(g.now()), 2);
        assert_eq!(*crx2.get(g.now()), 22);
        assert_eq!(&*crx3.get(g.now()), "22");
        rx.set(&g, vec![2, 3, 4]);
        assert_eq!(*crx.get(g.now()), 4);
        assert_eq!(*crx2.get(g.now()), 34);
        assert_eq!(&*crx3.get(g.now()), "34");
        rx.set(&g, vec![3, 4, 5]);
        assert_eq!(*crx.get(g.now()), 6);
        assert_eq!(*crx2.get(g.now()), 46);
        assert_eq!(&*crx3.get(g.now()), "46");
    }
}

#[test]
fn test_readme() {
    // setup
    let side_effect = Cell::new(0);
    let side_effect2 = RefCell::new(String::new());

    // The centralized data dependency graph
    let mut g = RxDAG::new();

    // Create variables which you can set
    let var1 = g.new_var(1);
    let var2 = g.new_var("hello");
    assert_eq!(var1.get(g.now()), &1);
    assert_eq!(var2.get(g.now()), &"hello");
    var1.set(&g, 2);
    var2.set(&g, "world");
    assert_eq!(var1.get(g.now()), &2);
    assert_eq!(var2.get(g.now()), &"world");

    // Create computed values which depend on these variables...
    let crx1 = g.new_crx(move |g| var1.get(g) * 2);
    // ...and other Rxs
    let crx2 = g.new_crx(move |g| format!("{}-{}", var2.get(g), crx1.get(g) * 2));
    // ...and create multiple values which are computed from a single function
    let (crx3, crx4) = g.new_crx2(move |g| var2.get(g).split_at(3));
    assert_eq!(crx1.get(g.now()), &4);
    assert_eq!(crx2.get(g.now()), &"world-8");
    assert_eq!(crx3.get(g.now()), &"wor");
    assert_eq!(crx4.get(g.now()), &"ld");
    var1.set(&g, 3);
    var2.set(&g, &"rust");
    assert_eq!(crx1.get(g.now()), &6);
    assert_eq!(crx2.get(g.now()), &"rust-12");
    assert_eq!(crx3.get(g.now()), &"rus");
    assert_eq!(crx4.get(g.now()), &"t");

    // Run side effects when a value is recomputed
    let var3 = g.new_var(Vec::from("abc"));
    let side_effect_ref = &side_effect;
    let side_effect_ref2 = &side_effect2;
    // borrowed values must outlive g but don't have to be static
    g.run_crx(move |g| {
        side_effect_ref.set(side_effect_ref.get() + var1.get(g));
        side_effect_ref2.borrow_mut().push_str(&String::from_utf8_lossy(var3.get(g)));
    });
    assert_eq!(side_effect.get(), 3);
    assert_eq!(&*side_effect2.borrow(), &"abc");
    var1.set(&g, 4);
    g.recompute();

    assert_eq!(side_effect.get(), 7);
    assert_eq!(&*side_effect2.borrow(), &"abcabc");

    // Note that the dependencies aren't updated until .recompute or .now is called...
    var3.set(&g, Vec::from("xyz"));
    assert_eq!(side_effect.get(), 7);
    assert_eq!(&*side_effect2.borrow(), &"abcabc");
    g.recompute();
    assert_eq!(side_effect.get(), 11);
    assert_eq!(&*side_effect2.borrow(), &"abcabcxyz");

    // the side-effect also doesn't trigger when none of its dependencies change
    var2.set(&g, "rust-lang");
    g.recompute();
    assert_eq!(side_effect.get(), 11);
    assert_eq!(&*side_effect2.borrow(), &"abcabcxyz");
    assert_eq!(crx2.get(g.now()), &"rust-lang-16");

    // lastly we can create derived values which will access or mutate part of the base value
    // which are useful to pass to children
    let dvar = var3.derive_using_clone(|x| &x[0], |x, char| {
        x[0] = char;
    });
    assert_eq!(dvar.get(g.now()), &b'x');
    dvar.set(&g, b'b');
    assert_eq!(dvar.get(g.now()), &b'b');
    assert_eq!(var3.get(g.now()), &b"byz");
    dvar.set(&g, b'f');
    assert_eq!(dvar.get(g.now()), &b'f');
    assert_eq!(var3.get(g.now()), &b"fyz");
    assert_eq!(&*side_effect2.borrow(), &"abcabcxyzbyzfyz");
}

#[test]
fn stream_like() {
    let stream = RefCell::new(Vec::new());
    let stream_ref = &stream;
    let input1 = vec![1, 2, 3];
    let input2 = vec![0.5, 0.4, 0.8];

    let mut g = RxDAG::new();
    let var1 = g.new_var(0);
    let var2 = g.new_var(0.0);
    let crx = g.new_crx(move |g| *var1.get(g) as f64 + *var2.get(g));

    g.run_crx(move |g| {
        stream_ref.borrow_mut().push(*crx.get(g));
    });

    assert_eq!(&*stream.borrow(), &vec![0.0]);
    for (a, b) in input1.iter().zip(input2.iter()) {
        var1.set(&g, *a);
        var2.set(&g, *b);
        g.recompute();
    }
    assert_eq!(&*stream.borrow(), &vec![0.0, 1.5, 2.4, 3.8]);
}