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}