1use std::sync::atomic::AtomicU64;
2
3use crate::{
4 effect::observer_clean_up,
5 runtime::{RUNTIME, Runtime},
6 signal::SignalState,
7 sync_runtime::SYNC_RUNTIME,
8};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct Id(u64);
13
14impl Id {
15 pub(crate) fn next() -> Id {
17 static COUNTER: AtomicU64 = AtomicU64::new(0);
18 Id(COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
19 }
20
21 pub(crate) fn signal(&self) -> Option<SignalState> {
23 if Runtime::is_ui_thread() {
24 if let Some(sig) = RUNTIME.with(|runtime| runtime.signals.borrow().get(self).cloned()) {
25 return Some(sig);
26 }
27 SYNC_RUNTIME.get_signal(self).map(Into::into)
28 } else {
29 SYNC_RUNTIME.get_signal(self).map(Into::into)
30 }
31 }
32
33 pub(crate) fn add_signal(&self, signal: SignalState) {
35 RUNTIME.with(|runtime| runtime.signals.borrow_mut().insert(*self, signal));
36 }
37
38 pub(crate) fn set_scope(&self) {
40 RUNTIME.with(|runtime| {
41 let scope = *runtime.current_scope.borrow();
42 runtime
43 .children
44 .borrow_mut()
45 .entry(scope)
46 .or_default()
47 .insert(*self);
48 runtime.parents.borrow_mut().insert(*self, scope);
49 });
50 }
51
52 pub(crate) fn dispose_children(&self) {
54 if let Ok(Some(children)) =
55 RUNTIME.try_with(|runtime| runtime.children.borrow_mut().remove(self))
56 {
57 for child in children {
58 child.dispose();
59 }
60 }
61 }
62
63 pub(crate) fn dispose(&self) {
66 if !Runtime::is_ui_thread() {
67 SYNC_RUNTIME.enqueue_disposals([*self]);
69 return;
70 }
71
72 if let Ok((children, signal, effect)) = RUNTIME.try_with(|runtime| {
73 runtime.scope_contexts.borrow_mut().remove(self);
75 runtime.parents.borrow_mut().remove(self);
76
77 (
78 runtime.children.borrow_mut().remove(self),
79 runtime.signals.borrow_mut().remove(self),
80 runtime.effects.borrow_mut().remove(self),
81 )
82 }) {
83 if let Some(children) = children {
84 for child in children {
85 child.dispose();
86 }
87 }
88
89 if let Some(effect) = effect {
90 observer_clean_up(&effect);
91 }
92
93 let mut signal = signal;
94 if signal.is_none() {
95 signal = SYNC_RUNTIME.remove_signal(self).map(Into::into);
96 }
97 Self::cleanup_signal(signal);
98 } else if let Some(signal) = SYNC_RUNTIME.remove_signal(self) {
99 Self::cleanup_signal(Some(signal.into()));
100 }
101 }
102
103 fn cleanup_signal(signal: Option<SignalState>) {
104 if let Some(signal) = signal {
105 for effect_id in signal.subscriber_ids() {
106 effect_id.dispose();
109 }
110 }
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use std::{cell::Cell, rc::Rc};
117
118 use crate::{
119 SignalTrack, SignalUpdate, create_effect, create_rw_signal,
120 runtime::{RUNTIME, Runtime},
121 scope::Scope,
122 };
123
124 #[test]
125 fn effect_disposed_when_dependency_signal_disposed() {
126 let parent = Scope::new();
127 let signal_scope = parent.create_child();
128 let (signal, setter) = signal_scope.create_signal(0);
129
130 let count = Rc::new(Cell::new(0));
131 parent.enter(|| {
132 let count = count.clone();
133 create_effect(move |_| {
134 signal.track();
135 count.set(count.get() + 1);
136 });
137 });
138
139 assert_eq!(count.get(), 1);
140
141 signal_scope.dispose();
143
144 setter.set(1);
146 Runtime::drain_pending_work();
147 assert_eq!(count.get(), 1);
148
149 RUNTIME.with(|runtime| assert!(runtime.effects.borrow().is_empty()));
151 }
152
153 #[test]
154 fn signals_created_by_effect_are_disposed_with_effect() {
155 let parent = Scope::new();
156 let dep_scope = parent.create_child();
157 let (dep_signal, dep_setter) = dep_scope.create_signal(0);
158
159 let created_signal = Rc::new(std::cell::RefCell::new(None));
160 let run_count = Rc::new(Cell::new(0));
161
162 parent.enter(|| {
163 let created_signal = created_signal.clone();
164 let run_count = run_count.clone();
165 create_effect(move |_| {
166 dep_signal.track();
167 run_count.set(run_count.get() + 1);
168 if created_signal.borrow().is_none() {
169 created_signal.replace(Some(create_rw_signal(0)));
170 }
171 });
172 });
173
174 assert_eq!(run_count.get(), 1);
175 let inner_signal = created_signal.borrow().expect("signal created");
176 assert!(inner_signal.id().signal().is_some());
177
178 dep_scope.dispose();
180 Runtime::drain_pending_work();
181
182 dep_setter.set(1);
184 Runtime::drain_pending_work();
185 assert_eq!(run_count.get(), 1);
186
187 assert!(inner_signal.id().signal().is_none());
188 RUNTIME.with(|runtime| assert!(runtime.effects.borrow().is_empty()));
189 }
190
191 #[test]
192 fn disposing_scope_drops_signals_and_effects() {
193 let scope = Scope::new();
194 let (signal, setter) = scope.create_signal(0);
195 let signal_id = signal.id();
196
197 let run_count = Rc::new(Cell::new(0));
198 scope.enter(|| {
199 let run_count = run_count.clone();
200 create_effect(move |_| {
201 signal.track();
202 run_count.set(run_count.get() + 1);
203 });
204 });
205
206 assert_eq!(run_count.get(), 1);
208 RUNTIME.with(|runtime| {
209 assert!(runtime.signals.borrow().contains_key(&signal_id));
210 assert_eq!(runtime.effects.borrow().len(), 1);
211 assert!(runtime.children.borrow().get(&scope.0).is_some());
212 });
213
214 scope.dispose();
216 Runtime::drain_pending_work();
217
218 setter.set(1);
219 Runtime::drain_pending_work();
220 assert_eq!(run_count.get(), 1);
221
222 RUNTIME.with(|runtime| {
223 assert!(runtime.signals.borrow().get(&signal_id).is_none());
224 assert!(runtime.effects.borrow().is_empty());
225 assert!(runtime.children.borrow().get(&scope.0).is_none());
226 });
227 }
228
229 #[test]
230 fn set_parent_reparents_scope() {
231 let parent = Scope::new();
233 let child = Scope::new();
234
235 assert!(child.parent().is_none());
237
238 child.set_parent(parent);
240
241 assert_eq!(child.parent().map(|s| s.0), Some(parent.0));
243
244 RUNTIME.with(|runtime| {
246 let children = runtime.children.borrow();
247 let parent_children = children
248 .get(&parent.0)
249 .expect("parent should have children");
250 assert!(parent_children.contains(&child.0));
251 });
252 }
253
254 #[test]
255 fn set_parent_disposes_with_new_parent() {
256 let parent = Scope::new();
257 let child = Scope::new();
258
259 let signal = child.create_rw_signal(42);
261 let signal_id = signal.id();
262
263 child.set_parent(parent);
265
266 assert!(signal_id.signal().is_some());
268
269 parent.dispose();
271 Runtime::drain_pending_work();
272
273 assert!(signal_id.signal().is_none());
275 }
276
277 #[test]
278 fn set_parent_removes_from_old_parent() {
279 let old_parent = Scope::new();
280 let new_parent = Scope::new();
281 let child = old_parent.create_child();
282
283 assert_eq!(child.parent().map(|s| s.0), Some(old_parent.0));
285
286 child.set_parent(new_parent);
288
289 assert_eq!(child.parent().map(|s| s.0), Some(new_parent.0));
291
292 RUNTIME.with(|runtime| {
294 let children = runtime.children.borrow();
295 if let Some(old_children) = children.get(&old_parent.0) {
296 assert!(!old_children.contains(&child.0));
297 }
298 });
299
300 RUNTIME.with(|runtime| {
302 let children = runtime.children.borrow();
303 let new_children = children
304 .get(&new_parent.0)
305 .expect("new parent should have children");
306 assert!(new_children.contains(&child.0));
307 });
308 }
309}