incular_core/
reactivity.rs1use std::{
7 any::Any,
8 cell::{Cell, Ref, RefCell, RefMut},
9 collections::BTreeMap,
10 marker::PhantomData,
11 rc::{Rc, Weak},
12 sync::atomic::{AtomicU64, Ordering},
13};
14
15static NEXT_ID: AtomicU64 = AtomicU64::new(1);
16fn next_id() -> u64 {
17 NEXT_ID
18 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
19 .expect("reactive identity exhausted")
20}
21
22struct Observer {
23 active: Cell<bool>,
24 metadata: Box<dyn Any>,
25 invalidate: Box<dyn Fn()>,
26}
27struct SourceInner {
28 id: u64,
29 observers: RefCell<BTreeMap<u64, Weak<Observer>>>,
30}
31
32#[derive(Clone)]
34pub struct DependencySource(Rc<SourceInner>);
35impl Default for DependencySource {
36 fn default() -> Self {
37 Self(Rc::new(SourceInner {
38 id: next_id(),
39 observers: RefCell::new(BTreeMap::new()),
40 }))
41 }
42}
43impl DependencySource {
44 #[must_use]
45 pub fn id(&self) -> u64 {
46 self.0.id
47 }
48
49 pub fn subscribe(&self, metadata: impl Any, invalidate: impl Fn() + 'static) -> Subscription {
51 let id = next_id();
52 let observer = Rc::new(Observer {
53 active: Cell::new(true),
54 metadata: Box::new(metadata),
55 invalidate: Box::new(invalidate),
56 });
57 self.0
58 .observers
59 .borrow_mut()
60 .insert(id, Rc::downgrade(&observer));
61 Subscription {
62 id,
63 source: Rc::downgrade(&self.0),
64 observer,
65 }
66 }
67
68 pub fn notify(&self) {
71 let observers: Vec<_> = self
72 .0
73 .observers
74 .borrow()
75 .values()
76 .filter_map(Weak::upgrade)
77 .collect();
78 for observer in observers {
79 if observer.active.get() {
80 (observer.invalidate)();
81 }
82 }
83 }
84
85 pub fn subscriber_count(&self) -> usize {
86 self.0.observers.borrow().len()
87 }
88
89 pub fn subscribers<T: Any + Clone>(&self) -> Vec<T> {
91 let observers: Vec<_> = self
92 .0
93 .observers
94 .borrow()
95 .values()
96 .filter_map(Weak::upgrade)
97 .collect();
98 observers
99 .into_iter()
100 .filter(|observer| observer.active.get())
101 .filter_map(|observer| observer.metadata.downcast_ref::<T>().cloned())
102 .collect()
103 }
104
105 pub fn track(&self) -> bool {
107 let callback = TRACKING.with(|stack| stack.borrow().last().cloned());
108 if let Some(callback) = callback {
109 callback(self.clone());
110 true
111 } else {
112 false
113 }
114 }
115}
116
117#[must_use = "keep the subscription alive to observe changes"]
119pub struct Subscription {
120 id: u64,
121 source: Weak<SourceInner>,
122 observer: Rc<Observer>,
123}
124impl Drop for Subscription {
125 fn drop(&mut self) {
126 self.observer.active.set(false);
127 if let Some(source) = self.source.upgrade() {
128 source.observers.borrow_mut().remove(&self.id);
129 }
130 }
131}
132
133type Collector = Rc<dyn Fn(DependencySource)>;
134thread_local! { static TRACKING: RefCell<Vec<Collector>> = const { RefCell::new(Vec::new()) }; }
135thread_local! { static READ_ONLY: Cell<usize> = const { Cell::new(0) }; }
136
137pub struct ReadOnlyGuard(PhantomData<Rc<()>>);
139impl ReadOnlyGuard {
140 #[must_use]
141 pub fn enter() -> Self {
142 READ_ONLY.with(|depth| depth.set(depth.get() + 1));
143 Self(PhantomData)
144 }
145}
146impl Drop for ReadOnlyGuard {
147 fn drop(&mut self) {
148 READ_ONLY.with(|depth| depth.set(depth.get() - 1));
149 }
150}
151pub fn assert_mutation_allowed() {
153 assert!(
154 READ_ONLY.with(Cell::get) == 0,
155 "reactive mutation during a pure build or memo evaluation"
156 );
157}
158
159pub struct TrackingGuard(PhantomData<Rc<()>>);
161impl TrackingGuard {
162 #[must_use]
163 pub fn enter(collect: impl Fn(DependencySource) + 'static) -> Self {
164 TRACKING.with(|stack| stack.borrow_mut().push(Rc::new(collect)));
165 Self(PhantomData)
166 }
167}
168impl Drop for TrackingGuard {
169 fn drop(&mut self) {
170 TRACKING.with(|stack| {
171 stack.borrow_mut().pop();
172 });
173 }
174}
175
176pub struct ReactiveCell<T> {
180 value: RefCell<T>,
181 source: DependencySource,
182}
183impl<T> ReactiveCell<T> {
184 pub fn new(value: T) -> Self {
185 Self {
186 value: RefCell::new(value),
187 source: DependencySource::default(),
188 }
189 }
190 pub fn borrow(&self) -> Ref<'_, T> {
191 self.value.borrow()
192 }
193 pub fn borrow_mut(&self) -> RefMut<'_, T> {
194 self.value.borrow_mut()
195 }
196 pub fn source(&self) -> &DependencySource {
197 &self.source
198 }
199}