Skip to main content

ferroui_view/
reactive.rs

1//! Fine-grained reactivity: signals, memos, effects and the owners that
2//! dispose them.
3//!
4//! A [`Signal`] holds a value. An [`effect`] runs a closure, records the
5//! signals and memos it reads, and runs it again when one of them changes; a
6//! [`Memo`] is a value computed the same way and read like a signal. This is
7//! how a property of a control follows state: the element sets the property
8//! from an effect (`text: move || format!(..)`), so a change updates exactly
9//! that property, with no tree to compare.
10//!
11//! Handles are `Copy`: they are indices into a runtime of the thread, so a
12//! `move` closure takes a signal without a clone. Every signal, memo and
13//! effect belongs to the [`Owner`] that was current when it was made (an
14//! effect is the owner of what is made while it runs), and is disposed with
15//! it: a mounted element tree has an owner that ends with it, and the owner
16//! of an effect disposes what the effect made before it runs again. Using a
17//! handle after its owner was disposed panics.
18//!
19//! Changes propagate when the outermost write ends: a write marks the
20//! effects and memos that read the value as dirty, and the dirty effects run
21//! (reading a dirty memo computes it again first). [`batch`] defers the runs
22//! until the end of a group of writes.
23
24use std::any::Any;
25use std::cell::{Cell, RefCell};
26use std::collections::VecDeque;
27use std::marker::PhantomData;
28use std::rc::Rc;
29
30/// The identity of a node of the runtime: its slot and the generation of the
31/// slot, so that a handle of a disposed node is never taken for the node that
32/// reused its slot.
33#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
34pub(crate) struct NodeId {
35    index: u32,
36    generation: u32,
37}
38
39enum Kind {
40    /// A value that is written from outside.
41    Signal,
42    /// A computed value: the closure computes it into its cell and says
43    /// whether it changed.
44    Memo(Rc<dyn Fn() -> bool>),
45    /// A closure that runs again when what it read changes.
46    Effect(Rc<dyn Fn()>),
47    /// Owns what is made while it is current, and nothing else.
48    Owner,
49}
50
51struct Node {
52    generation: u32,
53    alive: bool,
54    kind: Kind,
55    /// `Rc<RefCell<T>>` of a signal, `Rc<RefCell<Option<T>>>` of a memo.
56    value: Option<Rc<dyn Any>>,
57    /// The memos and effects that read the node.
58    subscribers: Vec<NodeId>,
59    /// The signals and memos the node read when it last ran.
60    sources: Vec<NodeId>,
61    dirty: bool,
62    owner: Option<NodeId>,
63    owned: Vec<NodeId>,
64    cleanups: Vec<Box<dyn FnOnce()>>,
65}
66
67#[derive(Default)]
68struct Runtime {
69    nodes: RefCell<Vec<Node>>,
70    free: RefCell<Vec<u32>>,
71    /// The memo or effect that is running: what is read is its source.
72    observer: Cell<Option<NodeId>>,
73    /// What owns what is made now.
74    owner: Cell<Option<NodeId>>,
75    queue: RefCell<VecDeque<NodeId>>,
76    batch_depth: Cell<u32>,
77    flushing: Cell<bool>,
78}
79
80ferroui_base::ferro_thread_local! {
81    static RUNTIME: Runtime = Runtime::default();
82}
83
84fn with_runtime<R>(f: impl FnOnce(&Runtime) -> R) -> R {
85    RUNTIME.with(f)
86}
87
88impl Runtime {
89    fn create(&self, kind: Kind, value: Option<Rc<dyn Any>>) -> NodeId {
90        let owner = self.owner.get();
91        let node = Node {
92            generation: 0,
93            alive: true,
94            kind,
95            value,
96            subscribers: Vec::new(),
97            sources: Vec::new(),
98            dirty: false,
99            owner,
100            owned: Vec::new(),
101            cleanups: Vec::new(),
102        };
103        let mut nodes = self.nodes.borrow_mut();
104        let id = match self.free.borrow_mut().pop() {
105            Some(index) => {
106                let slot = &mut nodes[index as usize];
107                let generation = slot.generation + 1;
108                *slot = Node { generation, ..node };
109                NodeId { index, generation }
110            }
111            None => {
112                nodes.push(node);
113                NodeId { index: (nodes.len() - 1) as u32, generation: 0 }
114            }
115        };
116        if let Some(owner) = owner {
117            if let Some(owner) = Self::node_mut(&mut nodes, owner) {
118                owner.owned.push(id);
119            }
120        }
121        id
122    }
123
124    fn node_mut(nodes: &mut [Node], id: NodeId) -> Option<&mut Node> {
125        nodes.get_mut(id.index as usize).filter(|node| node.alive && node.generation == id.generation)
126    }
127
128    fn is_alive(&self, id: NodeId) -> bool {
129        let nodes = self.nodes.borrow();
130        nodes.get(id.index as usize).is_some_and(|node| node.alive && node.generation == id.generation)
131    }
132
133    fn value(&self, id: NodeId) -> Rc<dyn Any> {
134        let nodes = self.nodes.borrow();
135        match nodes.get(id.index as usize) {
136            Some(node) if node.alive && node.generation == id.generation => {
137                node.value.clone().expect("the node holds a value")
138            }
139            _ => panic!("a signal or memo was used after its owner was disposed"),
140        }
141    }
142
143    /// Records that the running memo or effect read `source`.
144    fn track(&self, source: NodeId) {
145        let Some(observer) = self.observer.get() else { return };
146        let mut nodes = self.nodes.borrow_mut();
147        let Some(node) = Self::node_mut(&mut nodes, source) else { return };
148        if !node.subscribers.contains(&observer) {
149            node.subscribers.push(observer);
150        }
151        if let Some(observer) = Self::node_mut(&mut nodes, observer) {
152            if !observer.sources.contains(&source) {
153                observer.sources.push(source);
154            }
155        }
156    }
157
158    /// Marks what reads `id` as dirty and runs the effects that are, unless
159    /// a batch or a run is in progress.
160    fn notify(&self, id: NodeId) {
161        let subscribers = {
162            let mut nodes = self.nodes.borrow_mut();
163            Self::node_mut(&mut nodes, id).map(|node| node.subscribers.clone()).unwrap_or_default()
164        };
165        for subscriber in subscribers {
166            self.mark_dirty(subscriber);
167        }
168        self.flush();
169    }
170
171    fn mark_dirty(&self, id: NodeId) {
172        let (is_effect, subscribers) = {
173            let mut nodes = self.nodes.borrow_mut();
174            let Some(node) = Self::node_mut(&mut nodes, id) else { return };
175            if node.dirty {
176                return;
177            }
178            node.dirty = true;
179            match node.kind {
180                Kind::Effect(_) => (true, Vec::new()),
181                Kind::Memo(_) => (false, node.subscribers.clone()),
182                _ => (false, Vec::new()),
183            }
184        };
185        if is_effect {
186            self.queue.borrow_mut().push_back(id);
187        }
188        for subscriber in subscribers {
189            self.mark_dirty(subscriber);
190        }
191    }
192
193    fn flush(&self) {
194        if self.batch_depth.get() > 0 || self.flushing.get() {
195            return;
196        }
197        self.flushing.set(true);
198        loop {
199            let next = self.queue.borrow_mut().pop_front();
200            let Some(id) = next else { break };
201            let dirty = {
202                let mut nodes = self.nodes.borrow_mut();
203                Self::node_mut(&mut nodes, id).is_some_and(|node| node.dirty)
204            };
205            if dirty {
206                self.run(id);
207            }
208        }
209        self.flushing.set(false);
210    }
211
212    /// Runs a memo or an effect: what it made and read before is let go, and
213    /// it is the observer and the owner while it runs.
214    fn run(&self, id: NodeId) -> bool {
215        self.dispose_owned(id);
216        self.clear_sources(id);
217        let kind = {
218            let mut nodes = self.nodes.borrow_mut();
219            let Some(node) = Self::node_mut(&mut nodes, id) else { return false };
220            node.dirty = false;
221            match &node.kind {
222                Kind::Memo(compute) => Some(Ok(compute.clone())),
223                Kind::Effect(run) => Some(Err(run.clone())),
224                _ => None,
225            }
226        };
227        let previous = (self.observer.replace(Some(id)), self.owner.replace(Some(id)));
228        let changed = match kind {
229            Some(Ok(compute)) => compute(),
230            Some(Err(run)) => {
231                run();
232                true
233            }
234            None => false,
235        };
236        self.observer.set(previous.0);
237        self.owner.set(previous.1);
238        changed
239    }
240
241    fn clear_sources(&self, id: NodeId) {
242        let mut nodes = self.nodes.borrow_mut();
243        let sources = match Self::node_mut(&mut nodes, id) {
244            Some(node) => std::mem::take(&mut node.sources),
245            None => return,
246        };
247        for source in sources {
248            if let Some(source) = Self::node_mut(&mut nodes, source) {
249                source.subscribers.retain(|subscriber| *subscriber != id);
250            }
251        }
252    }
253
254    fn dispose_owned(&self, id: NodeId) {
255        let (owned, cleanups) = {
256            let mut nodes = self.nodes.borrow_mut();
257            match Self::node_mut(&mut nodes, id) {
258                Some(node) => (std::mem::take(&mut node.owned), std::mem::take(&mut node.cleanups)),
259                None => return,
260            }
261        };
262        for cleanup in cleanups.into_iter().rev() {
263            cleanup();
264        }
265        for child in owned.into_iter().rev() {
266            self.dispose(child);
267        }
268    }
269
270    fn dispose(&self, id: NodeId) {
271        if !self.is_alive(id) {
272            return;
273        }
274        self.dispose_owned(id);
275        self.clear_sources(id);
276        let (subscribers, value, owner) = {
277            let mut nodes = self.nodes.borrow_mut();
278            let Some(node) = Self::node_mut(&mut nodes, id) else { return };
279            node.alive = false;
280            node.kind = Kind::Owner;
281            (std::mem::take(&mut node.subscribers), node.value.take(), node.owner.take())
282        };
283        {
284            let mut nodes = self.nodes.borrow_mut();
285            // An owner that lives on forgets the node.
286            if let Some(owner) = owner.and_then(|owner| Self::node_mut(&mut nodes, owner)) {
287                owner.owned.retain(|owned| *owned != id);
288            }
289            for subscriber in subscribers {
290                if let Some(subscriber) = Self::node_mut(&mut nodes, subscriber) {
291                    subscriber.sources.retain(|source| *source != id);
292                }
293            }
294        }
295        // The value is dropped outside the borrow: dropping it may run code.
296        drop(value);
297        self.free.borrow_mut().push(id.index);
298    }
299}
300
301/// A value that the reactive closures reading it follow. `Copy`: a `move`
302/// closure takes it without a clone.
303pub struct Signal<T: 'static> {
304    id: NodeId,
305    _marker: PhantomData<fn() -> T>,
306}
307
308impl<T: 'static> Clone for Signal<T> {
309    fn clone(&self) -> Self {
310        *self
311    }
312}
313
314impl<T: 'static> Copy for Signal<T> {}
315
316impl<T: 'static> PartialEq for Signal<T> {
317    fn eq(&self, other: &Self) -> bool {
318        self.id == other.id
319    }
320}
321
322impl<T: 'static> std::fmt::Debug for Signal<T> {
323    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324        write!(f, "Signal({:?})", self.id)
325    }
326}
327
328/// Creates a signal that holds `value`, owned by the current owner.
329pub fn signal<T: 'static>(value: T) -> Signal<T> {
330    let id = with_runtime(|runtime| runtime.create(Kind::Signal, Some(Rc::new(RefCell::new(value)))));
331    Signal { id, _marker: PhantomData }
332}
333
334impl<T: 'static> Signal<T> {
335    fn cell(&self) -> Rc<RefCell<T>> {
336        with_runtime(|runtime| runtime.value(self.id))
337            .downcast::<RefCell<T>>()
338            .unwrap_or_else(|_| panic!("a signal holds a value of its type"))
339    }
340
341    /// Calls `f` with the value, and records the read when a memo or an
342    /// effect is running.
343    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
344        with_runtime(|runtime| runtime.track(self.id));
345        self.with_untracked(f)
346    }
347
348    /// Calls `f` with the value without recording the read.
349    pub fn with_untracked<R>(&self, f: impl FnOnce(&T) -> R) -> R {
350        let cell = self.cell();
351        let value = cell.borrow();
352        f(&value)
353    }
354
355    /// A copy of the value; the read is recorded.
356    pub fn get(&self) -> T
357    where
358        T: Clone,
359    {
360        self.with(T::clone)
361    }
362
363    /// A copy of the value; the read is not recorded.
364    pub fn get_untracked(&self) -> T
365    where
366        T: Clone,
367    {
368        self.with_untracked(T::clone)
369    }
370
371    /// Replaces the value, and runs what reads it.
372    pub fn set(&self, value: T) {
373        *self.cell().borrow_mut() = value;
374        with_runtime(|runtime| runtime.notify(self.id));
375    }
376
377    /// Replaces the value when it differs from the one held; a value that is
378    /// equal changes nothing and runs nothing.
379    pub fn set_if_changed(&self, value: T)
380    where
381        T: PartialEq,
382    {
383        let changed = self.with_untracked(|current| *current != value);
384        if changed {
385            self.set(value);
386        }
387    }
388
389    /// Changes the value in place, and runs what reads it.
390    pub fn update(&self, f: impl FnOnce(&mut T)) {
391        f(&mut self.cell().borrow_mut());
392        with_runtime(|runtime| runtime.notify(self.id));
393    }
394
395    /// Takes the value, leaving the default, and runs what reads it.
396    pub fn take(&self) -> T
397    where
398        T: Default,
399    {
400        let value = std::mem::take(&mut *self.cell().borrow_mut());
401        with_runtime(|runtime| runtime.notify(self.id));
402        value
403    }
404
405    /// The signal as a two-way value of a property: the property follows the
406    /// signal, and a change of the property (the user typed, moved a slider)
407    /// is written into the signal.
408    pub fn two_way(self) -> TwoWay<T> {
409        TwoWay(self)
410    }
411
412    /// Whether the owner of the signal is still alive.
413    pub fn is_alive(&self) -> bool {
414        with_runtime(|runtime| runtime.is_alive(self.id))
415    }
416}
417
418/// A signal used as a two-way value of a property (see [`Signal::two_way`]).
419pub struct TwoWay<T: 'static>(pub(crate) Signal<T>);
420
421/// A value computed from signals and memos, computed again when one of them
422/// changes and read like a signal. `Copy`.
423pub struct Memo<T: 'static> {
424    id: NodeId,
425    _marker: PhantomData<fn() -> T>,
426}
427
428impl<T: 'static> Clone for Memo<T> {
429    fn clone(&self) -> Self {
430        *self
431    }
432}
433
434impl<T: 'static> Copy for Memo<T> {}
435
436/// Creates a memo of `compute`, owned by the current owner. It is computed
437/// when it is first read; `compute` gets the previous value.
438pub fn memo<T: PartialEq + 'static>(compute: impl Fn(Option<&T>) -> T + 'static) -> Memo<T> {
439    let cell: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None));
440    let target = cell.clone();
441    let recompute = move || {
442        let next = {
443            let previous = target.borrow();
444            compute(previous.as_ref())
445        };
446        let changed = target.borrow().as_ref() != Some(&next);
447        if changed {
448            *target.borrow_mut() = Some(next);
449        }
450        changed
451    };
452    let id = with_runtime(|runtime| {
453        let id = runtime.create(Kind::Memo(Rc::new(recompute)), Some(cell));
454        // Dirty until it is first read.
455        let mut nodes = runtime.nodes.borrow_mut();
456        if let Some(node) = Runtime::node_mut(&mut nodes, id) {
457            node.dirty = true;
458        }
459        id
460    });
461    Memo { id, _marker: PhantomData }
462}
463
464impl<T: 'static> Memo<T> {
465    /// Calls `f` with the value, computing it first when it is dirty; the read
466    /// is recorded.
467    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
468        let dirty = with_runtime(|runtime| {
469            runtime.track(self.id);
470            let mut nodes = runtime.nodes.borrow_mut();
471            Runtime::node_mut(&mut nodes, self.id).is_some_and(|node| node.dirty)
472        });
473        if dirty {
474            with_runtime(|runtime| runtime.run(self.id));
475        }
476        let cell = with_runtime(|runtime| runtime.value(self.id))
477            .downcast::<RefCell<Option<T>>>()
478            .unwrap_or_else(|_| panic!("a memo holds a value of its type"));
479        let value = cell.borrow();
480        f(value.as_ref().expect("a memo that was read is computed"))
481    }
482
483    /// A copy of the value; the read is recorded.
484    pub fn get(&self) -> T
485    where
486        T: Clone,
487    {
488        self.with(T::clone)
489    }
490
491    /// A copy of the value; the read is not recorded.
492    pub fn get_untracked(&self) -> T
493    where
494        T: Clone,
495    {
496        untrack(|| self.get())
497    }
498}
499
500/// Runs `f` now and again whenever a signal or memo it read changes, until
501/// its owner is disposed. What `f` makes while it runs is owned by the effect
502/// and disposed before it runs again.
503pub fn effect(f: impl Fn() + 'static) {
504    with_runtime(|runtime| {
505        let id = runtime.create(Kind::Effect(Rc::new(f)), None);
506        runtime.run(id);
507    });
508}
509
510/// Runs `f` once the current owner (or the running effect) is disposed or
511/// runs again.
512pub fn on_cleanup(f: impl FnOnce() + 'static) {
513    with_runtime(|runtime| {
514        let Some(owner) = runtime.owner.get() else { return };
515        let mut nodes = runtime.nodes.borrow_mut();
516        if let Some(node) = Runtime::node_mut(&mut nodes, owner) {
517            node.cleanups.push(Box::new(f));
518        }
519    });
520}
521
522/// Runs `f` without recording what it reads.
523pub fn untrack<R>(f: impl FnOnce() -> R) -> R {
524    let previous = with_runtime(|runtime| runtime.observer.replace(None));
525    let result = f();
526    with_runtime(|runtime| runtime.observer.set(previous));
527    result
528}
529
530/// Runs `f`, and runs what its writes changed once, when it returns.
531pub fn batch<R>(f: impl FnOnce() -> R) -> R {
532    with_runtime(|runtime| runtime.batch_depth.set(runtime.batch_depth.get() + 1));
533    let result = f();
534    with_runtime(|runtime| {
535        runtime.batch_depth.set(runtime.batch_depth.get() - 1);
536        runtime.flush();
537    });
538    result
539}
540
541/// What owns signals, memos and effects: they are disposed with it. A
542/// mounted element tree has one; each item of a dynamic list has its own.
543#[derive(Clone, Copy, PartialEq, Eq, Debug)]
544pub struct Owner {
545    id: NodeId,
546}
547
548impl Owner {
549    /// A new owner, owned by the current one (if any): disposed with it.
550    pub fn new() -> Self {
551        Owner { id: with_runtime(|runtime| runtime.create(Kind::Owner, None)) }
552    }
553
554    /// A new owner that no other owner disposes: the root of a tree.
555    pub fn new_root() -> Self {
556        let previous = with_runtime(|runtime| runtime.owner.replace(None));
557        let owner = Self::new();
558        with_runtime(|runtime| runtime.owner.set(previous));
559        owner
560    }
561
562    /// The owner of what is made now, if any.
563    pub fn current() -> Option<Self> {
564        with_runtime(|runtime| runtime.owner.get()).map(|id| Owner { id })
565    }
566
567    /// Runs `f` with this owner current (and no observer: `f` itself is not
568    /// reactive).
569    pub fn with<R>(&self, f: impl FnOnce() -> R) -> R {
570        let previous = with_runtime(|runtime| (runtime.owner.replace(Some(self.id)), runtime.observer.replace(None)));
571        let result = f();
572        with_runtime(|runtime| {
573            runtime.owner.set(previous.0);
574            runtime.observer.set(previous.1);
575        });
576        result
577    }
578
579    /// Disposes the owner and everything it owns; cleanups run.
580    pub fn dispose(self) {
581        with_runtime(|runtime| runtime.dispose(self.id));
582    }
583
584    /// Whether the owner was not disposed.
585    pub fn is_alive(&self) -> bool {
586        with_runtime(|runtime| runtime.is_alive(self.id))
587    }
588}
589
590impl Default for Owner {
591    fn default() -> Self {
592        Self::new()
593    }
594}