fgr 0.3.1

Fine-grained reactivity for Rust
Documentation
use super::node::HasNode;
use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};

pub struct Context {
    pub transaction_depth: Cell<usize>,
    pub in_node: RefCell<Option<Rc<dyn HasNode>>>,
    pub to_be_updated: RefCell<Vec<Weak<dyn HasNode>>>,
    pub reset_visited_flags: RefCell<Vec<Weak<dyn HasNode>>>,
    pub clear_dirty_flags: RefCell<Vec<Weak<dyn HasNode>>>,
    pub effects: RefCell<Vec<Rc<RefCell<Box<dyn FnMut()>>>>>,
}

thread_local!(static CTX: Context =
    Context {
        transaction_depth: Cell::new(0),
        in_node: RefCell::new(None),
        to_be_updated: RefCell::new(Vec::new()),
        reset_visited_flags: RefCell::new(Vec::new()),
        clear_dirty_flags: RefCell::new(Vec::new()),
        effects: RefCell::new(Vec::new()),
    }
);

pub fn in_node<R,K:FnOnce()->R>(node: Rc<dyn HasNode>, k: K) -> R {
    let outer = CTX.with(|ctx| {
        let mut in_node = ctx.in_node.borrow_mut();
        let mut outer = Some(node);
        std::mem::swap(&mut *in_node, &mut outer);
        outer
    });
    let r = k();
    CTX.with(|ctx| {
        *ctx.in_node.borrow_mut() = outer;
    });
    r
}

pub fn outer_node() -> Option<Rc<dyn HasNode + 'static>> {
    CTX.with(|ctx|
        ctx.in_node.borrow().clone()
    )
}

pub fn mark_nodes_to_be_updated<I: Iterator<Item=Weak<dyn HasNode>>>(nodes: I) {
    CTX.with(|ctx| {
        let mut to_be_updated = ctx.to_be_updated.borrow_mut();
        let to_be_updated = &mut *to_be_updated;
        nodes.for_each(|node| to_be_updated.push(node));
    });
}

pub fn batch<R,K:FnOnce()->R>(k: K) -> R {
    CTX.with(|ctx| {
        ctx.transaction_depth.set(ctx.transaction_depth.get() + 1);
        let r = k();
        ctx.transaction_depth.set(ctx.transaction_depth.get() - 1);
        if ctx.transaction_depth.get() == 0 {
            propergate();
        }
        r
    })
}

pub fn propergate() {
    let mut effects = Vec::new();
    CTX.with(|ctx| ctx.transaction_depth.set(ctx.transaction_depth.get() + 1));
    loop {
        while let Some(weak_node) = CTX.with(|ctx| ctx.to_be_updated.borrow_mut().pop()) {
            if let Some(node) = weak_node.upgrade() {
                update_node(node);
            }
        }
        CTX.with(|ctx| {
            {
                let mut reset_visited_flags = ctx.reset_visited_flags.borrow_mut();
                for weak_node in &*reset_visited_flags {
                    if let Some(node) = weak_node.upgrade() {
                        node.node().visited.set(false);
                    }
                }
                reset_visited_flags.clear();
            }
            {
                let mut clear_dirty_flags = ctx.clear_dirty_flags.borrow_mut();
                for weak_node in &*clear_dirty_flags {
                    if let Some(node) = weak_node.upgrade() {
                        node.node().dirty.set(false);
                    }
                }
                clear_dirty_flags.clear();
            }
            std::mem::swap(&mut effects, &mut ctx.effects.borrow_mut());
        });
        if effects.is_empty() {
            break;
        }
        for effect in effects.drain(0..) {
            effect.borrow_mut()();
        }
    }
    CTX.with(|ctx| ctx.transaction_depth.set(ctx.transaction_depth.get() - 1));
}

pub fn update_node(node: Rc<dyn HasNode>) {
    if node.node().visited.get() {
        return;
    }
    let mut all_children_done = true;
    for dependency in &*node.node().dependencies.borrow() {
        if !dependency.node().visited.get() {
            all_children_done = false;
            CTX.with(|ctx| ctx.to_be_updated.borrow_mut().push(Rc::downgrade(dependency)));
        }
    }
    if !all_children_done {
        CTX.with(|ctx| ctx.to_be_updated.borrow_mut().insert(0, Rc::downgrade(&node)));
        return;
    }
    let any_dirty_children = node.node().dependencies.borrow().iter().any(|child| child.node().dirty.get());
    if any_dirty_children {
        if let Some(ref mut update) = &mut *node.node().update_op.borrow_mut() {
            let changed = update();
            if changed {
                node.node().dirty.set(true);
                clear_dirty_flag_at_end(Rc::downgrade(&node));
            }
        }
    }
    if node.node().dirty.get() {
        CTX.with(|ctx| {
            for dependent in &*node.node().dependents.borrow() {
                ctx.to_be_updated.borrow_mut().push(dependent.clone());
            }
        });
    }
    node.node().visited.set(true);
    reset_visited_flag_at_end(Rc::downgrade(&node));
}

pub fn reset_visited_flag_at_end(node: Weak<dyn HasNode>) {
    CTX.with(|ctx| ctx.reset_visited_flags.borrow_mut().push(node));
}

pub fn clear_dirty_flag_at_end(node: Weak<dyn HasNode>) {
    CTX.with(|ctx| ctx.clear_dirty_flags.borrow_mut().push(node));
}

pub fn push_effect(effect: Rc<RefCell<Box<dyn FnMut()>>>) {
    CTX.with(|ctx| ctx.effects.borrow_mut().push(effect));
}