1use crate::runtime::{ScopeId, RUNTIME};
2
3pub fn create_effect<F>(f: F)
4where
5 F: Fn() + 'static,
6{
7 let effect_id = RUNTIME.with(|rt| rt.borrow_mut().register_effect(f));
8 run_effect(effect_id);
9}
10
11pub fn on_cleanup(f: impl FnOnce() + 'static) {
12 RUNTIME.with(|rt| {
13 rt.borrow_mut().add_cleanup(f);
14 });
15}
16
17pub fn create_scope() -> ScopeId {
18 RUNTIME.with(|rt| rt.borrow_mut().create_scope())
19}
20
21pub fn dispose_scope(scope_id: ScopeId) {
22 RUNTIME.with(|rt| rt.borrow_mut().dispose_scope(scope_id));
23}
24
25pub(crate) fn run_effect(id: usize) {
26 RUNTIME.with(|rt| {
27 if rt.borrow().is_effect_disposed(id) {
28 return;
29 }
30 rt.borrow_mut().run_cleanups(id);
31 let prev = rt.borrow_mut().set_current_effect(Some(id));
32 let effect_fn = rt.borrow().clone_effect(id);
33
34 if let Some(f) = effect_fn {
35 f();
36 }
37
38 rt.borrow_mut().set_current_effect(prev);
39 });
40}
41
42pub(crate) fn flush_effects() {
43 loop {
44 let effect_id = RUNTIME.with(|rt| rt.borrow_mut().pop_pending_effect());
45 match effect_id {
46 Some(id) => run_effect(id),
47 None => break,
48 }
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::signal::create_signal;
56 use std::cell::RefCell;
57 use std::rc::Rc;
58
59 #[test]
60 fn test_effect_runs_immediately() {
61 let ran = Rc::new(RefCell::new(false));
62 let ran_clone = ran.clone();
63
64 create_effect(move || {
65 *ran_clone.borrow_mut() = true;
66 });
67
68 assert!(*ran.borrow());
69 }
70
71 #[test]
72 fn test_effect_auto_run() {
73 let (count, set_count) = create_signal(0);
74 let effect_ran = Rc::new(RefCell::new(0));
75 let effect_ran_clone = effect_ran.clone();
76
77 create_effect(move || {
78 let _ = count.get();
79 *effect_ran_clone.borrow_mut() += 1;
80 });
81
82 assert_eq!(*effect_ran.borrow(), 1);
83 set_count.set(1);
84 assert_eq!(*effect_ran.borrow(), 2);
85 }
86
87 #[test]
88 fn test_effect_only_runs_when_tracked_signal_changes() {
89 let (count, set_count) = create_signal(0);
90 let (other, _set_other) = create_signal(100);
91 let effect_ran = Rc::new(RefCell::new(0));
92 let effect_ran_clone = effect_ran.clone();
93
94 create_effect(move || {
95 let _ = count.get();
96 let _ = other.get_untracked();
97 *effect_ran_clone.borrow_mut() += 1;
98 });
99
100 assert_eq!(*effect_ran.borrow(), 1);
101
102 set_count.set(1);
103 assert_eq!(*effect_ran.borrow(), 2);
104
105 set_count.set(2);
106 assert_eq!(*effect_ran.borrow(), 3);
107 }
108
109 #[test]
110 fn test_effect_disposal() {
111 let (count, set_count) = create_signal(0);
112 let effect_ran = Rc::new(RefCell::new(0));
113 let effect_ran_clone = effect_ran.clone();
114
115 let scope = create_scope();
116 create_effect(move || {
117 let _ = count.get();
118 *effect_ran_clone.borrow_mut() += 1;
119 });
120
121 assert_eq!(*effect_ran.borrow(), 1);
122 set_count.set(1);
123 assert_eq!(*effect_ran.borrow(), 2);
124
125 dispose_scope(scope);
126
127 set_count.set(2);
128 assert_eq!(*effect_ran.borrow(), 2); }
130
131 #[test]
132 fn test_nested_scope_disposal() {
133 let (count, set_count) = create_signal(0);
134 let inner_ran = Rc::new(RefCell::new(0));
135 let inner_ran_clone = inner_ran.clone();
136
137 let outer = create_scope();
138 let _inner = create_scope();
139 create_effect(move || {
140 let _ = count.get();
141 *inner_ran_clone.borrow_mut() += 1;
142 });
143
144 assert_eq!(*inner_ran.borrow(), 1);
145 set_count.set(1);
146 assert_eq!(*inner_ran.borrow(), 2);
147
148 dispose_scope(outer); set_count.set(2);
151 assert_eq!(*inner_ran.borrow(), 2); }
153
154 #[test]
155 fn test_on_cleanup_called_on_dispose() {
156 let cleanup_called = Rc::new(RefCell::new(false));
157 let cleanup_clone = cleanup_called.clone();
158
159 let scope = create_scope();
160 create_effect(move || {
161 let cc = cleanup_clone.clone();
162 on_cleanup(move || {
163 *cc.borrow_mut() = true;
164 });
165 });
166
167 assert!(!*cleanup_called.borrow());
168 dispose_scope(scope);
169 assert!(*cleanup_called.borrow());
170 }
171
172 #[test]
173 fn test_on_cleanup_called_on_rerun() {
174 let (count, set_count) = create_signal(0);
175 let cleanup_count = Rc::new(RefCell::new(0));
176 let cleanup_clone = cleanup_count.clone();
177
178 create_effect(move || {
179 let _ = count.get();
180 let cc = cleanup_clone.clone();
181 on_cleanup(move || {
182 *cc.borrow_mut() += 1;
183 });
184 });
185
186 assert_eq!(*cleanup_count.borrow(), 0);
187 set_count.set(1); assert_eq!(*cleanup_count.borrow(), 1);
189 set_count.set(2);
190 assert_eq!(*cleanup_count.borrow(), 2);
191 }
192
193 #[test]
194 fn test_multiple_effects() {
195 let (count, set_count) = create_signal(0);
196 let effect1_ran = Rc::new(RefCell::new(0));
197 let effect2_ran = Rc::new(RefCell::new(0));
198
199 let e1 = effect1_ran.clone();
200 let c1 = count.clone();
201 create_effect(move || {
202 let _ = c1.get();
203 *e1.borrow_mut() += 1;
204 });
205
206 let e2 = effect2_ran.clone();
207 create_effect(move || {
208 let _ = count.get();
209 *e2.borrow_mut() += 1;
210 });
211
212 assert_eq!(*effect1_ran.borrow(), 1);
213 assert_eq!(*effect2_ran.borrow(), 1);
214
215 set_count.set(1);
216
217 assert_eq!(*effect1_ran.borrow(), 2);
218 assert_eq!(*effect2_ran.borrow(), 2);
219 }
220}