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 subscribed Signals in that
40/// function are updated.
41///
42/// The given function will be run immediately once and will track all signals that are
43/// subscribed in that run. On each subsequent run the list is cleared and then
44/// reconstructed based on the Signals that are subscribed during that run.
45pub fn create_effect<T>(f: impl Fn(Option<T>) -> T + 'static)
46where
47    T: Any + 'static,
48{
49    let id = Id::next();
50    let effect = Rc::new(Effect {
51        id,
52        f,
53        value: RefCell::new(None),
54        observers: RefCell::new(HashSet::default()),
55        ts: PhantomData,
56    });
57    id.set_scope();
58
59    run_initial_effect(effect);
60}
61
62struct UpdaterEffect<T, I, C, U>
63where
64    C: Fn(Option<T>) -> (I, T),
65    U: Fn(I, T) -> T,
66{
67    id: Id,
68    compute: C,
69    on_change: U,
70    value: RefCell<Option<T>>,
71    observers: RefCell<HashSet<Id>>,
72}
73
74impl<T, I, C, U> Drop for UpdaterEffect<T, I, C, U>
75where
76    C: Fn(Option<T>) -> (I, T),
77    U: Fn(I, T) -> T,
78{
79    fn drop(&mut self) {
80        self.id.dispose();
81    }
82}
83
84/// Create an effect updater that runs `on_change` when any signals that subscribe during the
85/// run of `compute` are updated. `compute` is immediately run only once, and its value is returned
86/// from the call to `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 within `compute` subscribe 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 are wrapped with `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
295/// Creates a [SignalTracker] that subscribes to any changes in signals used within `on_change`.
296pub fn create_tracker(on_change: impl Fn() + 'static) -> SignalTracker {
297    let id = Id::next();
298
299    SignalTracker {
300        id,
301        on_change: Rc::new(on_change),
302    }
303}
304
305impl SignalTracker {
306    /// Updates the tracking function used for [SignalTracker].
307    pub fn track<T: 'static>(&self, f: impl FnOnce() -> T) -> T {
308        // Clear any previous tracking by disposing the old effect
309        self.id.dispose();
310
311        let prev_effect = RUNTIME.with(|runtime| runtime.current_effect.borrow_mut().take());
312
313        let tracking_effect = Rc::new(TrackingEffect {
314            id: self.id,
315            observers: RefCell::new(HashSet::default()),
316            on_change: self.on_change.clone(),
317        });
318
319        RUNTIME.with(|runtime| {
320            *runtime.current_effect.borrow_mut() = Some(tracking_effect.clone());
321        });
322
323        let effect_scope = Scope(self.id, PhantomData);
324        let result = with_scope(effect_scope, || {
325            effect_scope.track();
326            f()
327        });
328
329        RUNTIME.with(|runtime| {
330            *runtime.current_effect.borrow_mut() = prev_effect;
331        });
332
333        result
334    }
335}
336
337struct TrackingEffect {
338    id: Id,
339    observers: RefCell<HashSet<Id>>,
340    on_change: Rc<dyn Fn()>,
341}
342
343impl EffectTrait for TrackingEffect {
344    fn id(&self) -> Id {
345        self.id
346    }
347
348    fn run(&self) -> bool {
349        (self.on_change)();
350        true
351    }
352
353    fn add_observer(&self, id: Id) {
354        self.observers.borrow_mut().insert(id);
355    }
356
357    fn clear_observers(&self) -> HashSet<Id> {
358        mem::take(&mut *self.observers.borrow_mut())
359    }
360}