reactive_cache/effect.rs
1use std::rc::Rc;
2
3/// A reactive effect that runs a closure whenever its dependencies change.
4///
5/// `Effect<F>` behaves similarly to an "event listener" or a callback,
6/// but it is automatically tied to any signals or memos it reads during execution.
7/// When those dependencies change, the effect will re-run.
8///
9/// Note: The closure runs **immediately upon creation** via `Effect::wrap`,
10/// so the effect is always initialized with an up-to-date value.
11///
12/// In short:
13/// - Like a callback: wraps a closure of type `F` and runs it.
14/// - Adds tracking: automatically re-runs when dependent signals change.
15/// - Runs once immediately at creation.
16///
17/// # Type Parameters
18///
19/// - `F`: The closure type wrapped by this effect. Must implement `Fn()`.
20/// The closure is executed immediately upon creation and tracked for reactive updates.
21pub struct Effect<F>
22where
23 F: Fn(),
24{
25 f: F,
26}
27
28impl<F> Effect<F>
29where
30 F: Fn(),
31{
32 /// Creates a new `Effect`, wrapping the provided closure
33 /// and running it immediately for dependency tracking.
34 ///
35 /// Returns an `Rc<dyn IEffect>` so the effect can be stored and shared
36 /// as a non-generic trait object.
37 ///
38 /// # Examples
39 ///
40 /// ```
41 /// use std::{cell::Cell, rc::Rc};
42 /// use reactive_cache::Effect;
43 ///
44 /// let counter = Rc::new(Cell::new(0));
45 /// let c_clone = counter.clone();
46 ///
47 /// let effect = Effect::new(move || {
48 /// // This closure runs immediately
49 /// c_clone.set(c_clone.get() + 1);
50 /// });
51 ///
52 /// assert_eq!(counter.get(), 1);
53 /// ```
54 #[allow(clippy::new_ret_no_self)]
55 pub fn new(f: F) -> Rc<dyn IEffect>
56 where
57 F: 'static,
58 {
59 let e: Rc<dyn IEffect> = Rc::new(Effect { f });
60
61 crate::creating_effect_push(Rc::downgrade(&e));
62 e.run();
63 crate::creating_effect_pop();
64
65 e
66 }
67}
68
69/// A non-generic trait for reactive effects.
70///
71/// `IEffect` serves as a type-erased trait for `Effect<F>` instances.
72/// By implementing `IEffect`, an `Effect<F>` can be stored as `Rc<dyn IEffect>`
73/// regardless of the specific closure type `F`. This allows the reactive system
74/// to manage multiple effects uniformly without exposing the generic type.
75pub trait IEffect {
76 /// Runs the effect closure.
77 ///
78 /// Typically called by the reactive system when dependencies change.
79 fn run(&self);
80}
81
82impl<F> IEffect for Effect<F>
83where
84 F: Fn(),
85{
86 fn run(&self) {
87 (self.f)()
88 }
89}