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}