Skip to main content

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 graph.
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    ///
90    /// Also calls cleanup callbacks and removes context values.
91    pub fn dispose_children(self) {
92        // If node is already disposed, do nothing.
93        if self.1.nodes.borrow().get(self.0).is_none() {
94            return;
95        }
96        let cleanup = std::mem::take(&mut self.1.nodes.borrow_mut()[self.0].cleanups);
97        let children = std::mem::take(&mut self.1.nodes.borrow_mut()[self.0].children);
98
99        // Run the cleanup functions in an untracked scope so that we don't track dependencies.
100        untrack_in_scope(
101            move || {
102                for cb in cleanup {
103                    cb();
104                }
105            },
106            self.1,
107        );
108        for child in children {
109            Self(child, self.1).dispose();
110        }
111
112        // Clear context values.
113        self.1.nodes.borrow_mut()[self.0].context.clear();
114    }
115
116    /// Run a closure under this reactive node.
117    pub fn run_in<T>(&self, f: impl FnOnce() -> T) -> T {
118        let root = self.1;
119        let prev_root = Root::set_global(Some(root));
120        let prev_node = root.current_node.replace(self.0);
121        let ret = f();
122        root.current_node.set(prev_node);
123        Root::set_global(prev_root);
124        ret
125    }
126}