sycamore_reactive/
node.rs

1//! Reactive nodes.
2
3use std::any::Any;
4
5use slotmap::new_key_type;
6use smallvec::SmallVec;
7
8use crate::{untrack_in_scope, Root};
9
10new_key_type! {
11    pub(crate) struct NodeId;
12}
13
14/// A reactive node inside the reactive grpah.
15pub(crate) struct ReactiveNode {
16    /// Value of the node, if any. If this node is a signal, should have a value.
17    pub value: Option<Box<dyn Any>>,
18    /// Callback when node needs to be updated. Returns a `bool` indicating whether the value has
19    /// changed or not.
20    #[allow(clippy::type_complexity)]
21    pub callback: Option<Box<dyn FnMut(&mut Box<dyn Any>) -> bool>>,
22    /// Nodes that are owned by this node.
23    pub children: Vec<NodeId>,
24    /// The parent of this node (i.e. the node that owns this node). If there is no parent, then
25    /// this field is set to the "null" key.
26    pub parent: NodeId,
27    /// Nodes that depend on this node.
28    pub dependents: Vec<NodeId>,
29    /// Nodes that this node depends on.
30    pub dependencies: SmallVec<[NodeId; 1]>,
31    /// Callbacks called when node is disposed.
32    pub cleanups: Vec<Box<dyn FnOnce()>>,
33    /// Context values stored in this node.
34    pub context: Vec<Box<dyn Any>>,
35    /// Used for keeping track of dirty state of node value.
36    pub state: NodeState,
37    /// Used for DFS traversal of the reactive graph.
38    pub mark: Mark,
39    /// Keep track of where the signal was created for diagnostics.
40    #[cfg(debug_assertions)]
41    #[allow(dead_code)]
42    pub created_at: &'static std::panic::Location<'static>,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub(crate) enum NodeState {
47    Clean,
48    Dirty,
49}
50
51/// A mark used for DFS traversal of the reactive graph.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub(crate) enum Mark {
54    /// Mark when DFS reaches node.
55    Temp,
56    /// Mark when DFS is done with node.
57    Permanent,
58    /// No mark.
59    None,
60}
61
62/// A handle to a reactive node (signal, memo, effect) that lets you run further tasks in it or
63/// manually dispose it.
64#[derive(Clone, Copy)]
65pub struct NodeHandle(pub(crate) NodeId, pub(crate) &'static Root);
66
67impl NodeHandle {
68    /// Disposes the node that is being referenced by this handle. If the node has already been
69    /// disposed, this does nothing.
70    ///
71    /// Automatically calls [`NodeHandle::dispose_children`].
72    pub fn dispose(self) {
73        // Dispose children first since this node could be referenced in a cleanup.
74        self.dispose_children();
75        let mut nodes = self.1.nodes.borrow_mut();
76        // Release memory.
77        if let Some(this) = nodes.remove(self.0) {
78            // Remove self from all dependencies.
79            for dependent in this.dependents {
80                // dependent might have been removed if it is a child node.
81                if let Some(dependent) = nodes.get_mut(dependent) {
82                    dependent.dependencies.retain(|&mut id| id != self.0);
83                }
84            }
85        }
86    }
87
88    /// Dispose all the children of the node but not the node itself.
89    pub fn dispose_children(self) {
90        // If node is already disposed, do nothing.
91        if self.1.nodes.borrow().get(self.0).is_none() {
92            return;
93        }
94        let cleanup = std::mem::take(&mut self.1.nodes.borrow_mut()[self.0].cleanups);
95        let children = std::mem::take(&mut self.1.nodes.borrow_mut()[self.0].children);
96
97        // Run the cleanup functions in an untracked scope so that we don't track dependencies.
98        untrack_in_scope(
99            move || {
100                for cb in cleanup {
101                    cb();
102                }
103            },
104            self.1,
105        );
106        for child in children {
107            Self(child, self.1).dispose();
108        }
109    }
110
111    /// Run a closure under this reactive node.
112    pub fn run_in<T>(&self, f: impl FnOnce() -> T) -> T {
113        let root = self.1;
114        let prev_root = Root::set_global(Some(root));
115        let prev_node = root.current_node.replace(self.0);
116        let ret = f();
117        root.current_node.set(prev_node);
118        Root::set_global(prev_root);
119        ret
120    }
121}