Skip to main content

repose_core/
reactive.rs

1use std::cell::{Cell, RefCell};
2use std::collections::{HashSet, VecDeque};
3use std::rc::Rc;
4
5use rustc_hash::{FxHashMap, FxHashSet};
6
7pub type SignalId = usize;
8pub type ObserverId = usize;
9
10thread_local! {
11    static CURRENT_OBSERVER: RefCell<Option<ObserverId>> = const { RefCell::new(None) };
12    static GRAPH: RefCell<DepGraph> = RefCell::new(DepGraph::default());
13    static SIGNAL_DEPTH: Cell<u32> = const { Cell::new(0) };
14    static PENDING_OBSERVERS: RefCell<VecDeque<ObserverId>> = const { RefCell::new(VecDeque::new()) };
15}
16
17#[derive(Default)]
18struct DepGraph {
19    next_observer: ObserverId,
20    // signal_id -> observers that depend on it
21    edges: FxHashMap<SignalId, FxHashSet<ObserverId>>,
22    // observer_id -> signals it depends on
23    back: FxHashMap<ObserverId, FxHashSet<SignalId>>,
24    // recompute closures
25    observers: FxHashMap<ObserverId, Rc<dyn Fn()>>,
26    running: FxHashSet<ObserverId>,
27}
28
29impl DepGraph {
30    fn remove_all_edges_for(&mut self, obs: ObserverId) {
31        if let Some(signals) = self.back.remove(&obs) {
32            for s in signals {
33                if let Some(set) = self.edges.get_mut(&s) {
34                    set.remove(&obs);
35                }
36            }
37        }
38    }
39    fn remove_observer(&mut self, obs: ObserverId) {
40        self.observers.remove(&obs);
41        self.remove_all_edges_for(obs);
42        // scrub forward maps just in case
43        for set in self.edges.values_mut() {
44            set.remove(&obs);
45        }
46        self.running.remove(&obs);
47    }
48}
49
50pub fn register_signal_read(sig: SignalId) {
51    CURRENT_OBSERVER.with(|co| {
52        if let Some(obs) = *co.borrow() {
53            GRAPH.with(|g| {
54                let mut g = g.borrow_mut();
55                g.edges.entry(sig).or_default().insert(obs);
56                g.back.entry(obs).or_default().insert(sig);
57            });
58        }
59    });
60    // track also against the current composition scope (if in a `scope!` body)
61    crate::scope_cache::record_scope_signal_dep(sig);
62}
63
64pub fn signal_changed(sig: SignalId) {
65    // Mark composition scopes that depend on this signal as dirty
66    crate::scope_cache::mark_scope_deps_dirty(sig);
67
68    let is_outer = SIGNAL_DEPTH.with(|depth| {
69        let prev = depth.get();
70        depth.set(prev + 1);
71        if prev > 0 {
72            // Re-entrant: defer affected observers for later draining.
73            GRAPH.with(|gcell| {
74                let g = gcell.borrow();
75                if let Some(obs_set) = g.edges.get(&sig) {
76                    PENDING_OBSERVERS.with(|q| {
77                        let mut queue = q.borrow_mut();
78                        for &obs in obs_set {
79                            if !g.running.contains(&obs) && !queue.contains(&obs) {
80                                queue.push_back(obs);
81                            }
82                        }
83                    });
84                }
85            });
86            false
87        } else {
88            true
89        }
90    });
91
92    if !is_outer {
93        SIGNAL_DEPTH.with(|d| d.set(d.get() - 1));
94        return;
95    }
96
97    GRAPH.with(|gcell| {
98        let mut g = gcell.borrow_mut();
99        let mut queue: VecDeque<ObserverId> = g
100            .edges
101            .get(&sig)
102            .cloned()
103            .unwrap_or_default()
104            .into_iter()
105            .collect();
106        while let Some(obs) = queue.pop_front() {
107            if g.running.contains(&obs) {
108                continue;
109            }
110            g.running.insert(obs);
111            g.remove_all_edges_for(obs);
112            let f = g.observers.get(&obs).cloned();
113            drop(g);
114            if let Some(f) = f {
115                CURRENT_OBSERVER.with(|co| {
116                    let prev = *co.borrow();
117                    *co.borrow_mut() = Some(obs);
118                    f();
119                    *co.borrow_mut() = prev;
120                });
121            }
122            g = gcell.borrow_mut();
123            g.running.remove(&obs);
124        }
125    });
126
127    // Drain any observers that were deferred during re-entrant notifications.
128    SIGNAL_DEPTH.with(|depth| depth.set(0));
129    loop {
130        let obs = PENDING_OBSERVERS.with(|q| q.borrow_mut().pop_front());
131        match obs {
132            None => break,
133            Some(obs) => {
134                GRAPH.with(|gcell| {
135                    let mut g = gcell.borrow_mut();
136                    if g.running.contains(&obs) {
137                        return;
138                    }
139                    g.running.insert(obs);
140                    g.remove_all_edges_for(obs);
141                    let f = g.observers.get(&obs).cloned();
142                    drop(g);
143                    if let Some(f) = f {
144                        CURRENT_OBSERVER.with(|co| {
145                            let prev = *co.borrow();
146                            *co.borrow_mut() = Some(obs);
147                            f();
148                            *co.borrow_mut() = prev;
149                        });
150                    }
151                    g = gcell.borrow_mut();
152                    g.running.remove(&obs);
153                });
154            }
155        }
156    }
157}
158
159pub fn new_observer(f: impl Fn() + 'static) -> ObserverId {
160    GRAPH.with(|g| {
161        let mut g = g.borrow_mut();
162        let id = g.next_observer;
163        g.next_observer += 1;
164        g.observers.insert(id, Rc::new(f));
165        id
166    })
167}
168
169/// Remove an observer and all of its dependency edges.
170pub fn remove_observer(id: ObserverId) {
171    let _ = GRAPH.try_with(|g| {
172        let mut g = g.borrow_mut();
173        g.remove_observer(id);
174    });
175}
176
177/// Run a closure with `CURRENT_OBSERVER` cleared
178pub fn without_observer<R>(f: impl FnOnce() -> R) -> R {
179    CURRENT_OBSERVER.with(|co| {
180        let prev = *co.borrow();
181        *co.borrow_mut() = None;
182        let result = f();
183        *co.borrow_mut() = prev;
184        result
185    })
186}
187
188pub fn run_observer_now(id: ObserverId) {
189    let f = GRAPH.with(|gcell| {
190        let mut g = gcell.borrow_mut();
191        if !g.running.insert(id) {
192            return None;
193        }
194        g.remove_all_edges_for(id);
195        let f = g.observers.get(&id).cloned();
196        drop(g);
197        if let Some(f) = f {
198            CURRENT_OBSERVER.with(|co| {
199                let prev = *co.borrow();
200                *co.borrow_mut() = Some(id);
201                f();
202                *co.borrow_mut() = prev;
203            });
204        }
205        GRAPH.with(|gcell| {
206            let mut g = gcell.borrow_mut();
207            g.running.remove(&id);
208        });
209        Some(())
210    });
211    let _ = f;
212}