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 let mut children = runtime.children.borrow_mut();
43 let children = children.entry(*scope).or_default();
44 children.insert(*self);
45 });
46 }
47
48 pub(crate) fn dispose_children(&self) {
50 if let Ok(Some(children)) =
51 RUNTIME.try_with(|runtime| runtime.children.borrow_mut().remove(self))
52 {
53 for child in children {
54 child.dispose();
55 }
56 }
57 }
58
59 pub(crate) fn dispose(&self) {
62 if !Runtime::is_ui_thread() {
63 SYNC_RUNTIME.enqueue_disposals([*self]);
65 return;
66 }
67
68 if let Ok((children, signal, effect)) = RUNTIME.try_with(|runtime| {
69 (
70 runtime.children.borrow_mut().remove(self),
71 runtime.signals.borrow_mut().remove(self),
72 runtime.effects.borrow_mut().remove(self),
73 )
74 }) {
75 if let Some(children) = children {
76 for child in children {
77 child.dispose();
78 }
79 }
80
81 if let Some(effect) = effect {
82 observer_clean_up(&effect);
83 }
84
85 let mut signal = signal;
86 if signal.is_none() {
87 signal = SYNC_RUNTIME.remove_signal(self).map(Into::into);
88 }
89 Self::cleanup_signal(signal);
90 } else if let Some(signal) = SYNC_RUNTIME.remove_signal(self) {
91 Self::cleanup_signal(Some(signal.into()));
92 }
93 }
94
95 fn cleanup_signal(signal: Option<SignalState>) {
96 if let Some(signal) = signal {
97 for effect_id in signal.subscriber_ids() {
98 effect_id.dispose();
101 }
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use std::{cell::Cell, rc::Rc};
109
110 use crate::{
111 create_effect, create_rw_signal,
112 runtime::{Runtime, RUNTIME},
113 scope::Scope,
114 SignalTrack, SignalUpdate,
115 };
116
117 #[test]
118 fn effect_disposed_when_dependency_signal_disposed() {
119 let parent = Scope::new();
120 let signal_scope = parent.create_child();
121 let (signal, setter) = signal_scope.create_signal(0);
122
123 let count = Rc::new(Cell::new(0));
124 parent.enter(|| {
125 let count = count.clone();
126 create_effect(move |_| {
127 signal.track();
128 count.set(count.get() + 1);
129 });
130 });
131
132 assert_eq!(count.get(), 1);
133
134 signal_scope.dispose();
136
137 setter.set(1);
139 Runtime::drain_pending_work();
140 assert_eq!(count.get(), 1);
141
142 RUNTIME.with(|runtime| assert!(runtime.effects.borrow().is_empty()));
144 }
145
146 #[test]
147 fn signals_created_by_effect_are_disposed_with_effect() {
148 let parent = Scope::new();
149 let dep_scope = parent.create_child();
150 let (dep_signal, dep_setter) = dep_scope.create_signal(0);
151
152 let created_signal = Rc::new(std::cell::RefCell::new(None));
153 let run_count = Rc::new(Cell::new(0));
154
155 parent.enter(|| {
156 let created_signal = created_signal.clone();
157 let run_count = run_count.clone();
158 create_effect(move |_| {
159 dep_signal.track();
160 run_count.set(run_count.get() + 1);
161 if created_signal.borrow().is_none() {
162 created_signal.replace(Some(create_rw_signal(0)));
163 }
164 });
165 });
166
167 assert_eq!(run_count.get(), 1);
168 let inner_signal = created_signal.borrow().clone().expect("signal created");
169 assert!(inner_signal.id().signal().is_some());
170
171 dep_scope.dispose();
173 Runtime::drain_pending_work();
174
175 dep_setter.set(1);
177 Runtime::drain_pending_work();
178 assert_eq!(run_count.get(), 1);
179
180 assert!(inner_signal.id().signal().is_none());
181 RUNTIME.with(|runtime| assert!(runtime.effects.borrow().is_empty()));
182 }
183
184 #[test]
185 fn disposing_scope_drops_signals_and_effects() {
186 let scope = Scope::new();
187 let (signal, setter) = scope.create_signal(0);
188 let signal_id = signal.id();
189
190 let run_count = Rc::new(Cell::new(0));
191 scope.enter(|| {
192 let run_count = run_count.clone();
193 create_effect(move |_| {
194 signal.track();
195 run_count.set(run_count.get() + 1);
196 });
197 });
198
199 assert_eq!(run_count.get(), 1);
201 RUNTIME.with(|runtime| {
202 assert!(runtime.signals.borrow().contains_key(&signal_id));
203 assert_eq!(runtime.effects.borrow().len(), 1);
204 assert!(runtime.children.borrow().get(&scope.0).is_some());
205 });
206
207 scope.dispose();
209 Runtime::drain_pending_work();
210
211 setter.set(1);
212 Runtime::drain_pending_work();
213 assert_eq!(run_count.get(), 1);
214
215 RUNTIME.with(|runtime| {
216 assert!(runtime.signals.borrow().get(&signal_id).is_none());
217 assert!(runtime.effects.borrow().is_empty());
218 assert!(runtime.children.borrow().get(&scope.0).is_none());
219 });
220 }
221}