floem_reactive/
effect.rs

1use std::{any::Any, cell::RefCell, collections::HashSet, marker::PhantomData, mem, rc::Rc};
2
3use crate::{
4    id::Id,
5    runtime::RUNTIME,
6    scope::{with_scope, Scope},
7    signal::NotThreadSafe,
8};
9
10pub(crate) trait EffectTrait {
11    fn id(&self) -> Id;
12    fn run(&self) -> bool;
13    fn add_observer(&self, id: Id);
14    fn clear_observers(&self) -> HashSet<Id>;
15}
16
17struct Effect<T, F>
18where
19    T: 'static,
20    F: Fn(Option<T>) -> T,
21{
22    id: Id,
23    f: F,
24    value: RefCell<Option<T>>,
25    observers: RefCell<HashSet<Id>>,
26    ts: PhantomData<NotThreadSafe>,
27}
28
29impl<T, F> Drop for Effect<T, F>
30where
31    T: 'static,
32    F: Fn(Option<T>) -> T,
33{
34    fn drop(&mut self) {
35        self.id.dispose();
36    }
37}
38
39/// Create an Effect that runs the given function whenever the Signals that subscribed
40/// to it in the function.
41///
42/// The given function will be run immediately once, and tracks all the signals that
43/// subscribed in that run. And when these Signals update, it will rerun the function.
44/// And the effect re-tracks the signals in each run, so that it will only be re-run
45/// by the Signals that actually ran in the last effect run.
46pub fn create_effect<T>(f: impl Fn(Option<T>) -> T + 'static)
47where
48    T: Any + 'static,
49{
50    let id = Id::next();
51    let effect = Rc::new(Effect {
52        id,
53        f,
54        value: RefCell::new(None),
55        observers: RefCell::new(HashSet::default()),
56        ts: PhantomData,
57    });
58    id.set_scope();
59
60    run_initial_effect(effect);
61}
62
63struct UpdaterEffect<T, I, C, U>
64where
65    C: Fn(Option<T>) -> (I, T),
66    U: Fn(I, T) -> T,
67{
68    id: Id,
69    compute: C,
70    on_change: U,
71    value: RefCell<Option<T>>,
72    observers: RefCell<HashSet<Id>>,
73}
74
75impl<T, I, C, U> Drop for UpdaterEffect<T, I, C, U>
76where
77    C: Fn(Option<T>) -> (I, T),
78    U: Fn(I, T) -> T,
79{
80    fn drop(&mut self) {
81        self.id.dispose();
82    }
83}
84
85/// Create an effect updater that runs `on_change` when any signals `compute` subscribes to
86/// changes. `compute` is immediately run and its return value is returned from `create_updater`.
87pub fn create_updater<R>(compute: impl Fn() -> R + 'static, on_change: impl Fn(R) + 'static) -> R
88where
89    R: 'static,
90{
91    create_stateful_updater(move |_| (compute(), ()), move |r, _| on_change(r))
92}
93
94/// Create an effect updater that runs `on_change` when any signals `compute` subscribes to
95/// changes. `compute` is immediately run and its return value is returned from `create_updater`.
96pub fn create_stateful_updater<T, R>(
97    compute: impl Fn(Option<T>) -> (R, T) + 'static,
98    on_change: impl Fn(R, T) -> T + 'static,
99) -> R
100where
101    T: Any + 'static,
102    R: 'static,
103{
104    let id = Id::next();
105    let effect = Rc::new(UpdaterEffect {
106        id,
107        compute,
108        on_change,
109        value: RefCell::new(None),
110        observers: RefCell::new(HashSet::default()),
111    });
112    id.set_scope();
113
114    run_initial_updater_effect(effect)
115}
116
117/// Signals that's wrapped this untrack will not subscribe to any effect
118pub fn untrack<T>(f: impl FnOnce() -> T) -> T {
119    let prev_effect = RUNTIME.with(|runtime| runtime.current_effect.borrow_mut().take());
120    let result = f();
121    RUNTIME.with(|runtime| {
122        *runtime.current_effect.borrow_mut() = prev_effect;
123    });
124    result
125}
126
127pub fn batch<T>(f: impl FnOnce() -> T) -> T {
128    let already_batching = RUNTIME.with(|runtime| {
129        let batching = runtime.batching.get();
130        if !batching {
131            runtime.batching.set(true);
132        }
133
134        batching
135    });
136
137    let result = f();
138    if !already_batching {
139        RUNTIME.with(|runtime| {
140            runtime.batching.set(false);
141            runtime.run_pending_effects();
142        });
143    }
144
145    result
146}
147
148pub(crate) fn run_initial_effect(effect: Rc<dyn EffectTrait>) {
149    let effect_id = effect.id();
150
151    RUNTIME.with(|runtime| {
152        *runtime.current_effect.borrow_mut() = Some(effect.clone());
153
154        let effect_scope = Scope(effect_id, PhantomData);
155        with_scope(effect_scope, || {
156            effect_scope.track();
157            effect.run();
158        });
159
160        *runtime.current_effect.borrow_mut() = None;
161    });
162}
163
164pub(crate) fn run_effect(effect: Rc<dyn EffectTrait>) {
165    let effect_id = effect.id();
166    effect_id.dispose();
167
168    observer_clean_up(&effect);
169
170    RUNTIME.with(|runtime| {
171        *runtime.current_effect.borrow_mut() = Some(effect.clone());
172
173        let effect_scope = Scope(effect_id, PhantomData);
174        with_scope(effect_scope, move || {
175            effect_scope.track();
176            effect.run();
177        });
178
179        *runtime.current_effect.borrow_mut() = None;
180    });
181}
182
183fn run_initial_updater_effect<T, I, C, U>(effect: Rc<UpdaterEffect<T, I, C, U>>) -> I
184where
185    T: 'static,
186    I: 'static,
187    C: Fn(Option<T>) -> (I, T) + 'static,
188    U: Fn(I, T) -> T + 'static,
189{
190    let effect_id = effect.id();
191
192    let result = RUNTIME.with(|runtime| {
193        *runtime.current_effect.borrow_mut() = Some(effect.clone());
194
195        let effect_scope = Scope(effect_id, PhantomData);
196        let (result, new_value) = with_scope(effect_scope, || {
197            effect_scope.track();
198            (effect.compute)(None)
199        });
200
201        // set new value
202        *effect.value.borrow_mut() = Some(new_value);
203
204        *runtime.current_effect.borrow_mut() = None;
205
206        result
207    });
208
209    result
210}
211
212/// Do a observer clean up at the beginning of each effect run. It clears the effect
213/// from all the Signals that this effect subscribes to, and clears all the signals
214/// that's stored in this effect, so that the next effect run can re-track signals.
215pub(crate) fn observer_clean_up(effect: &Rc<dyn EffectTrait>) {
216    let effect_id = effect.id();
217    let observers = effect.clear_observers();
218    for observer in observers {
219        if let Some(signal) = observer.signal() {
220            signal.subscribers.borrow_mut().remove(&effect_id);
221        }
222    }
223}
224
225impl<T, F> EffectTrait for Effect<T, F>
226where
227    T: 'static,
228    F: Fn(Option<T>) -> T,
229{
230    fn id(&self) -> Id {
231        self.id
232    }
233
234    fn run(&self) -> bool {
235        let curr_value = self.value.borrow_mut().take();
236
237        // run the effect
238        let new_value = (self.f)(curr_value);
239
240        *self.value.borrow_mut() = Some(new_value);
241
242        true
243    }
244
245    fn add_observer(&self, id: Id) {
246        self.observers.borrow_mut().insert(id);
247    }
248
249    fn clear_observers(&self) -> HashSet<Id> {
250        mem::take(&mut *self.observers.borrow_mut())
251    }
252}
253
254impl<T, I, C, U> EffectTrait for UpdaterEffect<T, I, C, U>
255where
256    T: 'static,
257    C: Fn(Option<T>) -> (I, T),
258    U: Fn(I, T) -> T,
259{
260    fn id(&self) -> Id {
261        self.id
262    }
263
264    fn run(&self) -> bool {
265        let curr_value = self.value.borrow_mut().take();
266
267        // run the effect
268        let (i, t) = (self.compute)(curr_value);
269        let new_value = (self.on_change)(i, t);
270
271        *self.value.borrow_mut() = Some(new_value);
272        true
273    }
274
275    fn add_observer(&self, id: Id) {
276        self.observers.borrow_mut().insert(id);
277    }
278
279    fn clear_observers(&self) -> HashSet<Id> {
280        mem::take(&mut *self.observers.borrow_mut())
281    }
282}
283
284pub struct SignalTracker {
285    id: Id,
286    on_change: Rc<dyn Fn()>,
287}
288
289impl Drop for SignalTracker {
290    fn drop(&mut self) {
291        self.id.dispose();
292    }
293}
294
295pub fn create_tracker(on_change: impl Fn() + 'static) -> SignalTracker {
296    let id = Id::next();
297
298    SignalTracker {
299        id,
300        on_change: Rc::new(on_change),
301    }
302}
303
304impl SignalTracker {
305    pub fn track<T: 'static>(&self, f: impl FnOnce() -> T) -> T {
306        // Clear any previous tracking by disposing the old effect
307        self.id.dispose();
308
309        let prev_effect = RUNTIME.with(|runtime| runtime.current_effect.borrow_mut().take());
310
311        let tracking_effect = Rc::new(TrackingEffect {
312            id: self.id,
313            observers: RefCell::new(HashSet::default()),
314            on_change: self.on_change.clone(),
315        });
316
317        RUNTIME.with(|runtime| {
318            *runtime.current_effect.borrow_mut() = Some(tracking_effect.clone());
319        });
320
321        let effect_scope = Scope(self.id, PhantomData);
322        let result = with_scope(effect_scope, || {
323            effect_scope.track();
324            f()
325        });
326
327        RUNTIME.with(|runtime| {
328            *runtime.current_effect.borrow_mut() = prev_effect;
329        });
330
331        result
332    }
333}
334
335struct TrackingEffect {
336    id: Id,
337    observers: RefCell<HashSet<Id>>,
338    on_change: Rc<dyn Fn()>,
339}
340
341impl EffectTrait for TrackingEffect {
342    fn id(&self) -> Id {
343        self.id
344    }
345
346    fn run(&self) -> bool {
347        (self.on_change)();
348        true
349    }
350
351    fn add_observer(&self, id: Id) {
352        self.observers.borrow_mut().insert(id);
353    }
354
355    fn clear_observers(&self) -> HashSet<Id> {
356        mem::take(&mut *self.observers.borrow_mut())
357    }
358}