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 create_effect, create_rw_signal,
120 runtime::{Runtime, RUNTIME},
121 scope::Scope,
122 SignalTrack, SignalUpdate,
123 };
124
125 #[test]
126 fn effect_disposed_when_dependency_signal_disposed() {
127 let parent = Scope::new();
128 let signal_scope = parent.create_child();
129 let (signal, setter) = signal_scope.create_signal(0);
130
131 let count = Rc::new(Cell::new(0));
132 parent.enter(|| {
133 let count = count.clone();
134 create_effect(move |_| {
135 signal.track();
136 count.set(count.get() + 1);
137 });
138 });
139
140 assert_eq!(count.get(), 1);
141
142 signal_scope.dispose();
144
145 setter.set(1);
147 Runtime::drain_pending_work();
148 assert_eq!(count.get(), 1);
149
150 RUNTIME.with(|runtime| assert!(runtime.effects.borrow().is_empty()));
152 }
153
154 #[test]
155 fn signals_created_by_effect_are_disposed_with_effect() {
156 let parent = Scope::new();
157 let dep_scope = parent.create_child();
158 let (dep_signal, dep_setter) = dep_scope.create_signal(0);
159
160 let created_signal = Rc::new(std::cell::RefCell::new(None));
161 let run_count = Rc::new(Cell::new(0));
162
163 parent.enter(|| {
164 let created_signal = created_signal.clone();
165 let run_count = run_count.clone();
166 create_effect(move |_| {
167 dep_signal.track();
168 run_count.set(run_count.get() + 1);
169 if created_signal.borrow().is_none() {
170 created_signal.replace(Some(create_rw_signal(0)));
171 }
172 });
173 });
174
175 assert_eq!(run_count.get(), 1);
176 let inner_signal = created_signal.borrow().expect("signal created");
177 assert!(inner_signal.id().signal().is_some());
178
179 dep_scope.dispose();
181 Runtime::drain_pending_work();
182
183 dep_setter.set(1);
185 Runtime::drain_pending_work();
186 assert_eq!(run_count.get(), 1);
187
188 assert!(inner_signal.id().signal().is_none());
189 RUNTIME.with(|runtime| assert!(runtime.effects.borrow().is_empty()));
190 }
191
192 #[test]
193 fn disposing_scope_drops_signals_and_effects() {
194 let scope = Scope::new();
195 let (signal, setter) = scope.create_signal(0);
196 let signal_id = signal.id();
197
198 let run_count = Rc::new(Cell::new(0));
199 scope.enter(|| {
200 let run_count = run_count.clone();
201 create_effect(move |_| {
202 signal.track();
203 run_count.set(run_count.get() + 1);
204 });
205 });
206
207 assert_eq!(run_count.get(), 1);
209 RUNTIME.with(|runtime| {
210 assert!(runtime.signals.borrow().contains_key(&signal_id));
211 assert_eq!(runtime.effects.borrow().len(), 1);
212 assert!(runtime.children.borrow().get(&scope.0).is_some());
213 });
214
215 scope.dispose();
217 Runtime::drain_pending_work();
218
219 setter.set(1);
220 Runtime::drain_pending_work();
221 assert_eq!(run_count.get(), 1);
222
223 RUNTIME.with(|runtime| {
224 assert!(runtime.signals.borrow().get(&signal_id).is_none());
225 assert!(runtime.effects.borrow().is_empty());
226 assert!(runtime.children.borrow().get(&scope.0).is_none());
227 });
228 }
229}