1use std::any::Any;
2use std::mem;
3use std::rc::Weak;
4
5use super::*;
6use std::cell::RefCell;
7use std::collections::HashSet;
8use std::hash::{Hash, Hasher};
9use std::ptr;
10use std::rc::Rc;
11
12thread_local! {
13 pub(super) static CONTEXTS: RefCell<Vec<Weak<RefCell<Option<Running>>>>> = RefCell::new(Vec::new());
18 pub(super) static OWNER: RefCell<Option<Owner>> = RefCell::new(None);
19}
20
21pub(super) struct Running {
24 pub(super) execute: Rc<dyn Fn()>,
25 pub(super) dependencies: HashSet<Dependency>,
26 owner: Owner,
28}
29
30impl Running {
31 fn clear_dependencies(&mut self) {
34 for dependency in &self.dependencies {
35 dependency
36 .signal()
37 .unsubscribe(&Callback(Rc::downgrade(&self.execute)));
38 }
39 self.dependencies.clear();
40 }
41}
42
43impl Drop for Running {
44 fn drop(&mut self) {
45 self.clear_dependencies();
46 }
47}
48
49#[derive(Default)]
52pub struct Owner {
53 effects: Vec<Rc<RefCell<Option<Running>>>>,
54 cleanup: Vec<Box<dyn FnOnce()>>,
55}
56
57impl Owner {
58 pub fn new() -> Self {
62 Self::default()
63 }
64
65 pub(super) fn add_effect_state(&mut self, effect: Rc<RefCell<Option<Running>>>) {
67 self.effects.push(effect);
68 }
69
70 pub(super) fn add_cleanup(&mut self, cleanup: Box<dyn FnOnce()>) {
72 self.cleanup.push(cleanup);
73 }
74}
75
76impl Drop for Owner {
77 fn drop(&mut self) {
78 for effect in &self.effects {
79 effect.borrow_mut().as_mut().unwrap().clear_dependencies();
80 }
81
82 for cleanup in mem::take(&mut self.cleanup) {
83 cleanup();
84 }
85 }
86}
87
88#[derive(Clone)]
89pub(super) struct Callback(pub(super) Weak<dyn Fn()>);
90
91impl Callback {
92 #[track_caller]
93 #[must_use = "returned value must be manually called"]
94 pub fn callback(&self) -> Rc<dyn Fn()> {
95 self.try_callback().expect("callback is not valid anymore")
96 }
97
98 #[must_use = "returned value must be manually called"]
99 pub fn try_callback(&self) -> Option<Rc<dyn Fn()>> {
100 self.0.upgrade()
101 }
102}
103
104impl Hash for Callback {
105 fn hash<H: Hasher>(&self, state: &mut H) {
106 Rc::as_ptr(&self.callback()).hash(state);
107 }
108}
109
110impl PartialEq for Callback {
111 fn eq(&self, other: &Self) -> bool {
112 ptr::eq::<()>(
113 Rc::as_ptr(&self.callback()).cast(),
114 Rc::as_ptr(&other.callback()).cast(),
115 )
116 }
117}
118impl Eq for Callback {}
119
120#[derive(Clone)]
122pub(super) struct Dependency(pub(super) Weak<dyn AnySignalInner>);
123
124impl Dependency {
125 fn signal(&self) -> Rc<dyn AnySignalInner> {
126 self.0.upgrade().expect("backlink should always be valid")
127 }
128}
129
130impl Hash for Dependency {
131 fn hash<H: Hasher>(&self, state: &mut H) {
132 Rc::as_ptr(&self.signal()).hash(state);
133 }
134}
135
136impl PartialEq for Dependency {
137 fn eq(&self, other: &Self) -> bool {
138 ptr::eq::<()>(
139 Rc::as_ptr(&self.signal()).cast(),
140 Rc::as_ptr(&other.signal()).cast(),
141 )
142 }
143}
144impl Eq for Dependency {}
145
146pub fn create_effect_initial<R: 'static>(
151 initial: impl FnOnce() -> (Rc<dyn Fn()>, R) + 'static,
152) -> R {
153 type InitialFn = dyn FnOnce() -> (Rc<dyn Fn()>, Box<dyn Any>);
154
155 fn internal(initial: Box<InitialFn>) -> Box<dyn Any> {
157 let running: Rc<RefCell<Option<Running>>> = Rc::new(RefCell::new(None));
158
159 type MutEffect = Rc<RefCell<Option<Rc<dyn Fn()>>>>;
160 let effect: MutEffect = Rc::new(RefCell::new(None));
161 let ret: Rc<RefCell<Option<Box<dyn Any>>>> = Rc::new(RefCell::new(None));
162
163 let initial = RefCell::new(Some(initial));
164
165 let execute: Rc<dyn Fn()> = Rc::new({
166 let running = Rc::downgrade(&running);
167 let ret = Rc::downgrade(&ret);
168 move || {
169 CONTEXTS.with(|contexts| {
170 let initial_context_size = contexts.borrow().len();
171
172 let running = running.upgrade().unwrap();
174
175 running.borrow_mut().as_mut().unwrap().clear_dependencies();
177
178 contexts.borrow_mut().push(Rc::downgrade(&running));
179
180 if let Some(initial) = initial.take() {
181 let effect = Rc::clone(&effect);
182 let ret = Weak::upgrade(&ret).unwrap();
183 let owner = create_root(move || {
184 let (effect_tmp, ret_tmp) = initial(); *effect.borrow_mut() = Some(effect_tmp);
186 *ret.borrow_mut() = Some(ret_tmp);
187 });
188 running.borrow_mut().as_mut().unwrap().owner = owner;
189 } else {
190 let old_owner = mem::replace(
192 &mut running.borrow_mut().as_mut().unwrap().owner,
193 Owner::new(), );
195 drop(old_owner);
196
197 let effect = Rc::clone(&effect);
198 let owner = create_root(move || {
199 effect.borrow().as_ref().unwrap()();
200 });
201 running.borrow_mut().as_mut().unwrap().owner = owner;
202 }
203
204 for dependency in &running.borrow().as_ref().unwrap().dependencies {
206 dependency.signal().subscribe(Callback(Rc::downgrade(
207 &running.borrow().as_ref().unwrap().execute,
208 )));
209 }
210
211 contexts.borrow_mut().pop();
213
214 debug_assert_eq!(
215 initial_context_size,
216 contexts.borrow().len(),
217 "context size should not change before and after create_effect_initial"
218 );
219 });
220 }
221 });
222
223 *running.borrow_mut() = Some(Running {
224 execute: Rc::clone(&execute),
225 dependencies: HashSet::new(),
226 owner: Owner::new(),
227 });
228 debug_assert_eq!(
229 Rc::strong_count(&running),
230 1,
231 "Running should be owned exclusively by owner"
232 );
233
234 OWNER.with(|owner| {
235 if owner.borrow().is_some() {
236 owner
237 .borrow_mut()
238 .as_mut()
239 .unwrap()
240 .add_effect_state(running);
241 } else {
242 #[cfg(all(target_arch = "wasm32", debug_assertions))]
243 web_sys::console::warn_1(
244 &"Effects created outside of a reactive root will never get disposed.".into(),
245 );
246 #[cfg(all(not(target_arch = "wasm32"), debug_assertions))]
247 eprintln!(
248 "WARNING: Effects created outside of a reactive root will never get dropped."
249 );
250 Rc::into_raw(running); }
252 });
253
254 execute();
255
256 let ret = Rc::try_unwrap(ret).expect("ret should only have 1 strong reference");
257 ret.into_inner().unwrap()
258 }
259
260 let ret = internal(Box::new(|| {
261 let (effect, ret) = initial();
262 (effect, Box::new(ret))
263 }));
264
265 *ret.downcast::<R>().unwrap()
266}
267
268pub fn create_effect<F>(effect: F)
283where
284 F: Fn() + 'static,
285{
286 fn internal(effect: Rc<dyn Fn()>) {
288 create_effect_initial(move || {
289 effect();
290 (effect, ())
291 })
292 }
293
294 internal(Rc::new(effect));
295}
296
297pub fn create_memo<F, Out>(derived: F) -> StateHandle<Out>
312where
313 F: Fn() -> Out + 'static,
314 Out: 'static,
315{
316 create_selector_with(derived, |_, _| false)
317}
318
319pub fn create_selector<F, Out>(derived: F) -> StateHandle<Out>
325where
326 F: Fn() -> Out + 'static,
327 Out: PartialEq + 'static,
328{
329 create_selector_with(derived, PartialEq::eq)
330}
331
332pub fn create_selector_with<F, Out, C>(derived: F, comparator: C) -> StateHandle<Out>
341where
342 F: Fn() -> Out + 'static,
343 Out: 'static,
344 C: Fn(&Out, &Out) -> bool + 'static,
345{
346 let derived = Rc::new(derived);
347 let comparator = Rc::new(comparator);
348
349 create_effect_initial(move || {
350 let memo = Signal::new(derived());
351
352 let effect = Rc::new({
353 let memo = memo.clone();
354 let derived = Rc::clone(&derived);
355 move || {
356 let new_value = derived();
357 if !comparator(&memo.get_untracked(), &new_value) {
358 memo.set(new_value);
359 }
360 }
361 });
362
363 (effect, memo.into_handle())
364 })
365}
366
367pub fn untrack<T>(f: impl FnOnce() -> T) -> T {
390 CONTEXTS.with(|contexts| {
391 let tmp = contexts.take();
392
393 let ret = f();
394
395 *contexts.borrow_mut() = tmp;
396
397 ret
398 })
399}
400
401pub fn on_cleanup(f: impl FnOnce() + 'static) {
421 OWNER.with(|owner| {
422 if owner.borrow().is_some() {
423 owner
424 .borrow_mut()
425 .as_mut()
426 .unwrap()
427 .add_cleanup(Box::new(f));
428 } else {
429 #[cfg(all(target_arch = "wasm32", debug_assertions))]
430 web_sys::console::warn_1(
431 &"Cleanup callbacks created outside of a reactive root will never run.".into(),
432 );
433 #[cfg(all(not(target_arch = "wasm32"), debug_assertions))]
434 eprintln!(
435 "WARNING: Cleanup callbacks created outside of a reactive root will never run."
436 );
437 }
438 });
439}
440
441#[cfg(test)]
442mod tests {
443 use super::*;
444 use crate::cloned;
445
446 #[test]
447 fn effects() {
448 let state = Signal::new(0);
449
450 let double = Signal::new(-1);
451
452 create_effect(cloned!((state, double) => move || {
453 double.set(*state.get() * 2);
454 }));
455 assert_eq!(*double.get(), 0); state.set(1);
458 assert_eq!(*double.get(), 2);
459 state.set(2);
460 assert_eq!(*double.get(), 4);
461 }
462
463 #[test]
465 #[ignore]
466 #[should_panic(expected = "cannot create cyclic dependency")]
467 fn cyclic_effects_fail() {
468 let state = Signal::new(0);
469
470 create_effect(cloned!((state) => move || {
471 state.set(*state.get() + 1);
472 }));
473
474 state.set(1);
475 }
476
477 #[test]
478 #[ignore]
479 #[should_panic(expected = "cannot create cyclic dependency")]
480 fn cyclic_effects_fail_2() {
481 let state = Signal::new(0);
482
483 create_effect(cloned!((state) => move || {
484 let value = *state.get();
485 state.set(value + 1);
486 }));
487
488 state.set(1);
489 }
490
491 #[test]
492 fn effect_should_subscribe_once() {
493 let state = Signal::new(0);
494
495 let counter = Signal::new(0);
496 create_effect(cloned!((state, counter) => move || {
497 counter.set(*counter.get_untracked() + 1);
498
499 state.get();
501 state.get();
502 }));
503
504 assert_eq!(*counter.get(), 1);
505
506 state.set(1);
507 assert_eq!(*counter.get(), 2);
508 }
509
510 #[test]
511 fn effect_should_recreate_dependencies() {
512 let condition = Signal::new(true);
513
514 let state1 = Signal::new(0);
515 let state2 = Signal::new(1);
516
517 let counter = Signal::new(0);
518 create_effect(cloned!((condition, state1, state2, counter) => move || {
519 counter.set(*counter.get_untracked() + 1);
520
521 if *condition.get() {
522 state1.get();
523 } else {
524 state2.get();
525 }
526 }));
527
528 assert_eq!(*counter.get(), 1);
529
530 state1.set(1);
531 assert_eq!(*counter.get(), 2);
532
533 state2.set(1);
534 assert_eq!(*counter.get(), 2); condition.set(false);
537 assert_eq!(*counter.get(), 3);
538
539 state1.set(2);
540 assert_eq!(*counter.get(), 3); state2.set(2);
543 assert_eq!(*counter.get(), 4); }
545
546 #[test]
547 fn nested_effects_should_recreate_inner() {
548 let counter = Signal::new(0);
549
550 let trigger = Signal::new(());
551
552 create_effect(cloned!((trigger, counter) => move || {
553 trigger.get(); create_effect(cloned!((counter) => move || {
556 counter.set(*counter.get_untracked() + 1);
557 }));
558 }));
559
560 assert_eq!(*counter.get(), 1);
561
562 trigger.set(());
563 assert_eq!(*counter.get(), 2); }
565
566 #[test]
567 fn destroy_effects_on_owner_drop() {
568 let counter = Signal::new(0);
569
570 let trigger = Signal::new(());
571
572 let owner = create_root(cloned!((trigger, counter) => move || {
573 create_effect(move || {
574 trigger.get(); counter.set(*counter.get_untracked() + 1);
576 });
577 }));
578
579 assert_eq!(*counter.get(), 1);
580
581 trigger.set(());
582 assert_eq!(*counter.get(), 2);
583
584 drop(owner);
585 trigger.set(());
586 assert_eq!(*counter.get(), 2); }
588
589 #[test]
590 fn memo() {
591 let state = Signal::new(0);
592
593 let double = create_memo(cloned!((state) => move || *state.get() * 2));
594 assert_eq!(*double.get(), 0);
595
596 state.set(1);
597 assert_eq!(*double.get(), 2);
598
599 state.set(2);
600 assert_eq!(*double.get(), 4);
601 }
602
603 #[test]
604 fn memo_only_run_once() {
606 let state = Signal::new(0);
607
608 let counter = Signal::new(0);
609 let double = create_memo(cloned!((state, counter) => move || {
610 counter.set(*counter.get_untracked() + 1);
611
612 *state.get() * 2
613 }));
614 assert_eq!(*counter.get(), 1); state.set(2);
617 assert_eq!(*counter.get(), 2);
618 assert_eq!(*double.get(), 4);
619 assert_eq!(*counter.get(), 2); }
621
622 #[test]
623 fn dependency_on_memo() {
624 let state = Signal::new(0);
625
626 let double = create_memo(cloned!((state) => move || *state.get() * 2));
627
628 let quadruple = create_memo(move || *double.get() * 2);
629
630 assert_eq!(*quadruple.get(), 0);
631
632 state.set(1);
633 assert_eq!(*quadruple.get(), 4);
634 }
635
636 #[test]
637 fn untracked_memo() {
638 let state = Signal::new(1);
639
640 let double = create_memo(cloned!((state) => move || *state.get_untracked() * 2));
641
642 assert_eq!(*double.get(), 2);
643
644 state.set(2);
645 assert_eq!(*double.get(), 2); }
647
648 #[test]
649 fn selector() {
650 let state = Signal::new(0);
651
652 let double = create_selector(cloned!((state) => move || *state.get() * 2));
653
654 let counter = Signal::new(0);
655 create_effect(cloned!((counter, double) => move || {
656 counter.set(*counter.get_untracked() + 1);
657
658 double.get();
659 }));
660 assert_eq!(*double.get(), 0);
661 assert_eq!(*counter.get(), 1);
662
663 state.set(0);
664 assert_eq!(*double.get(), 0);
665 assert_eq!(*counter.get(), 1); state.set(2);
668 assert_eq!(*double.get(), 4);
669 assert_eq!(*counter.get(), 2);
670 }
671
672 #[test]
673 fn cleanup() {
674 let cleanup_called = Signal::new(false);
675 let owner = create_root(cloned!((cleanup_called) => move || {
676 on_cleanup(move || {
677 cleanup_called.set(true);
678 })
679 }));
680
681 assert_eq!(*cleanup_called.get(), false);
682
683 drop(owner);
684 assert_eq!(*cleanup_called.get(), true);
685 }
686
687 #[test]
688 fn cleanup_in_effect() {
689 let trigger = Signal::new(());
690
691 let counter = Signal::new(0);
692
693 create_effect(cloned!((trigger, counter) => move || {
694 trigger.get(); on_cleanup(cloned!((counter) => move || {
697 counter.set(*counter.get() + 1);
698 }));
699 }));
700
701 assert_eq!(*counter.get(), 0);
702
703 trigger.set(());
704 assert_eq!(*counter.get(), 1);
705
706 trigger.set(());
707 assert_eq!(*counter.get(), 2);
708 }
709}