@@ -4,64 +4,27 @@ use crate::effect_stack::{effect_peak, effect_pop, effect_push};
44
55/// A reactive effect that runs a closure whenever its dependencies change.
66///
7- /// `Effect<F> ` behaves similarly to an "event listener" or a callback,
7+ /// `Effect` behaves similarly to an "event listener" or a callback,
88/// but it is automatically tied to any signals or memos it reads during execution.
99/// When those dependencies change, the effect will re-run.
1010///
11- /// Note: The closure runs **immediately upon creation** via `Effect::wrap` ,
11+ /// Note: The closure runs **immediately upon creation** via [ `Effect::new`] ,
1212/// so the effect is always initialized with an up-to-date value.
1313///
1414/// In short:
15- /// - Like a callback: wraps a closure of type `F` and runs it.
15+ /// - Like a callback: wraps a closure and runs it.
1616/// - Adds tracking: automatically re-runs when dependent signals change.
1717/// - Runs once immediately at creation.
18- ///
19- /// # Type Parameters
20- ///
21- /// - `F`: The closure type wrapped by this effect. Must implement `Fn()`.
22- /// The closure is executed immediately upon creation and tracked for reactive updates.
23- pub struct Effect < F >
24- where
25- F : Fn ( ) + ' static ,
26- {
27- f : F ,
18+ pub struct Effect {
19+ f : Box < dyn Fn ( ) > ,
2820}
2921
30- impl < F > Effect < F >
31- where
32- F : Fn ( ) ,
33- {
34- fn new_inner < D > ( f : F , deps : Option < D > ) -> Rc < dyn IEffect >
35- where
36- D : Fn ( ) + ' static ,
37- {
38- let e: Rc < dyn IEffect > = Rc :: new ( Effect { f } ) ;
39- let w = Rc :: downgrade ( & e) ;
40-
41- // Dependency collection only at creation time
42- effect_push ( w. clone ( ) , true ) ;
43- if let Some ( deps) = & deps {
44- deps ( ) ;
45- } else {
46- e. run ( ) ;
47- }
48- effect_pop ( w. clone ( ) , true ) ;
49-
50- // If there is an additional dependency initializer,
51- // the `Effect` needs to be run immediately
52- // after dependency collection is completed.
53- if deps. is_some ( ) {
54- run_untracked ( & e) ;
55- }
56-
57- e
58- }
59-
22+ impl Effect {
6023 /// Creates a new `Effect`, wrapping the provided closure
6124 /// and running it immediately for dependency tracking.
6225 ///
63- /// Returns an `Rc<dyn IEffect >` so the effect can be stored and shared
64- /// as a non-generic trait object .
26+ /// Returns an `Rc<Effect >` so the effect can be stored and shared
27+ /// as a non-generic type .
6528 ///
6629 /// # Examples
6730 ///
8043 /// assert_eq!(counter.get(), 1);
8144 /// ```
8245 #[ allow( clippy:: new_ret_no_self) ]
83- pub fn new ( f : F ) -> Rc < dyn IEffect > {
84- Self :: new_inner :: < fn ( ) > ( f, None )
46+ pub fn new ( f : impl Fn ( ) + ' static ) -> Rc < Effect > {
47+ let e: Rc < Effect > = Rc :: new ( Effect { f : Box :: new ( f) } ) ;
48+ let w = Rc :: downgrade ( & e) ;
49+
50+ // Dependency collection only at creation time
51+ effect_push ( w. clone ( ) , true ) ;
52+ e. run ( ) ;
53+ effect_pop ( w. clone ( ) , true ) ;
54+
55+ e
8556 }
8657
8758 /// Creates a new `Effect` with an additional dependency initializer.
9768 /// (e.g. `if`/`match`), and you want to ensure that *all possible branches*
9869 /// have their dependencies tracked on the first run.
9970 ///
100- /// Returns an `Rc<dyn IEffect >` so the effect can be stored and shared
101- /// as a non-generic trait object .
71+ /// Returns an `Rc<Effect >` so the effect can be stored and shared
72+ /// as a non-generic type .
10273 ///
10374 /// # Examples
10475 ///
@@ -141,46 +112,42 @@ where
141112 /// COUNTER_set(20);
142113 /// assert_eq!(result.get(), 20);
143114 /// ```
144- pub fn new_with_deps < D > ( f : F , deps : D ) -> Rc < dyn IEffect >
145- where
146- D : Fn ( ) + ' static ,
147- {
148- Self :: new_inner ( f, Some ( deps) )
115+ pub fn new_with_deps ( f : impl Fn ( ) + ' static , deps : impl Fn ( ) ) -> Rc < Effect > {
116+ let e: Rc < Effect > = Rc :: new ( Effect { f : Box :: new ( f) } ) ;
117+ let w = Rc :: downgrade ( & e) ;
118+
119+ // Dependency collection only at creation time
120+ effect_push ( w. clone ( ) , true ) ;
121+ deps ( ) ;
122+ effect_pop ( w. clone ( ) , true ) ;
123+
124+ // If there is an additional dependency initializer,
125+ // the `Effect` needs to be run immediately
126+ // after dependency collection is completed.
127+ run_untracked ( & e) ;
128+
129+ e
149130 }
150- }
151131
152- /// A non-generic trait for reactive effects.
153- ///
154- /// `IEffect` serves as a type-erased trait for `Effect<F>` instances.
155- /// By implementing `IEffect`, an `Effect<F>` can be stored as `Rc<dyn IEffect>`
156- /// regardless of the specific closure type `F`. This allows the reactive system
157- /// to manage multiple effects uniformly without exposing the generic type.
158- pub trait IEffect {
159132 /// Runs the effect closure.
160133 ///
161134 /// Typically called by the reactive system when dependencies change.
162135 ///
163136 /// # Notes
164137 ///
165- /// Any calls to `Effect` must be handled with care.
138+ /// After initialization, any call to an `Effect` must go through `run()`.
139+ /// Since the preconditions for executing `run()` differ depending on context
140+ /// (e.g. dependency collection vs. signal-triggered updates), such calls
141+ /// must be handled with care.
166142 ///
167- /// After initialization, any calls to an `Effect` are completely dependent on
168- /// run() , including Signal-triggered runs or dependency collection. However,
169- /// these calls have different assumptions.
143+ /// Dependency collection for an `Effect` should be limited to its directly
144+ /// connected signals. The intended call chain is:
170145 ///
171- /// Specifically, dependency collection for an `Effect` should be limited to
172- /// its directly connected Signals. In this case, the `Effect`'s call chain
173- /// conforms to the `Effect → Memo(s) → Signal(s)` model, which assumes that
174- /// the `Effect` must be the start of the call chain. Any Signals linked to
175- /// calls to other `Effect`s should not be collected, and runs triggered by
176- /// `Signal`s should not be subject to dependency collection.
177- fn run ( & self ) ;
178- }
179-
180- impl < F > IEffect for Effect < F >
181- where
182- F : Fn ( ) ,
183- {
146+ /// `Effect → Memo(s) → Signal(s)`
147+ ///
148+ /// In this model, the `Effect` must always be the root of the chain.
149+ /// Other `Effect`s should not be tracked as dependencies, and runs triggered
150+ /// by signals should not themselves cause further dependency collection.
184151 fn run ( & self ) {
185152 assert ! (
186153 std:: ptr:: eq( & * effect_peak( ) . unwrap( ) . effect. upgrade( ) . unwrap( ) , self ) ,
@@ -191,7 +158,7 @@ where
191158 }
192159}
193160
194- pub ( crate ) fn run_untracked ( e : & Rc < dyn IEffect > ) {
161+ pub ( crate ) fn run_untracked ( e : & Rc < Effect > ) {
195162 let w = Rc :: downgrade ( e) ;
196163
197164 effect_push ( w. clone ( ) , false ) ;
0 commit comments