euv_core/reactive/effect/impl.rs
1use crate::*;
2
3/// Implementation of RenderEffect reactive operations.
4impl RenderEffect {
5 /// Creates a new RenderEffect that wraps the given closure.
6 ///
7 /// On creation, the closure is executed once immediately. During execution,
8 /// any `Signal::get()` calls will register this effect as a subscriber.
9 /// When any of those signals change, the closure is re-executed.
10 ///
11 /// # Arguments
12 ///
13 /// - `F` - The closure to execute reactively.
14 ///
15 /// # Returns
16 ///
17 /// - `Self` - A new RenderEffect handle.
18 pub fn create<F>(effect_fn: F) -> Self
19 where
20 F: FnMut() + 'static,
21 {
22 let effect_inner: Rc<RefCell<RenderEffectInner>> = Rc::new(RefCell::new(
23 RenderEffectInner::new(Box::new(effect_fn), Vec::new(), false, false),
24 ));
25 let effect: RenderEffect = RenderEffect::new(effect_inner.clone());
26 let addr: usize = effect.get_inner_addr();
27 render_effect_registry_mut().insert(addr, effect_inner);
28 effect.run_once();
29 effect
30 }
31
32 /// Returns the memory address of the inner `Rc` for identity comparison.
33 ///
34 /// # Returns
35 ///
36 /// - `usize` - The memory address of the inner `Rc`.
37 pub(crate) fn get_inner_addr(&self) -> usize {
38 Rc::as_ptr(self.get_inner()) as usize
39 }
40
41 /// Executes the effect closure once with dependency tracking.
42 ///
43 /// Before executing, removes this effect's subscriber entries from
44 /// all previously tracked signals (old dependencies) via the global
45 /// effect subscriber registry. Then sets this effect as the current
46 /// tracking target and executes the closure. During execution,
47 /// `Signal::get()` calls register this effect as a subscriber on
48 /// those signals via `track_signal`. After execution, the previous
49 /// tracking target is restored.
50 pub(crate) fn run_once(&self) {
51 let effect_addr: usize = self.get_inner_addr();
52 {
53 let mut inner: RefMut<RenderEffectInner> = self.get_inner().borrow_mut();
54 if inner.get_disposed() {
55 return;
56 }
57 if inner.get_running() {
58 return;
59 }
60 inner.set_running(true);
61 cleanup_effect_dependencies(effect_addr, inner.get_mut_dependencies());
62 }
63 let prev: Option<usize> = swap_current_effect(Some(effect_addr));
64 {
65 let mut inner: RefMut<RenderEffectInner> = self.get_inner().borrow_mut();
66 (inner.get_mut_effect_fn())();
67 }
68 set_current_effect(prev);
69 {
70 let mut inner: RefMut<RenderEffectInner> = self.get_inner().borrow_mut();
71 inner.set_running(false);
72 }
73 }
74
75 /// Marks this effect as disposed and cleans up all its dependencies.
76 ///
77 /// After disposal, `run_once` becomes a complete no-op. All subscriber
78 /// entries for this effect are removed from the global `EFFECT_SUBSCRIBERS`
79 /// registry, preventing the effect from being scheduled or executed again.
80 /// This is called when the associated DOM node is removed from the tree
81 /// (e.g., during a match arm switch in routing) to prevent stale effects
82 /// from causing infinite loops.
83 pub fn dispose(&self) {
84 let effect_addr: usize = self.get_inner_addr();
85 let mut inner: RefMut<RenderEffectInner> = self.get_inner().borrow_mut();
86 if inner.get_disposed() {
87 return;
88 }
89 inner.set_disposed(true);
90 cleanup_effect_dependencies(effect_addr, inner.get_mut_dependencies());
91 }
92}
93
94/// SAFETY: `EffectSubscribersCell` is only used in single-threaded WASM contexts.
95unsafe impl Sync for EffectSubscribersCell {}
96
97/// SAFETY: `CurrentEffectCell` is only used in single-threaded WASM contexts.
98unsafe impl Sync for CurrentEffectCell {}
99
100/// SAFETY: `RenderEffectRegistryCell` is only used in single-threaded WASM contexts.
101unsafe impl Sync for RenderEffectRegistryCell {}