windjammer_ui/
reactivity_optimized.rs1use smallvec::SmallVec;
10use std::cell::RefCell;
11use std::collections::{HashMap, HashSet};
12use std::rc::Rc;
13use std::sync::atomic::{AtomicUsize, Ordering};
14
15pub type SignalId = usize;
16pub type EffectId = usize;
17
18thread_local! {
19 static REACTIVE_CONTEXT: RefCell<ReactiveContext> = RefCell::new(ReactiveContext::default());
20 static EFFECT_REGISTRY: RefCell<HashMap<EffectId, Effect>> = RefCell::new(HashMap::new());
21 static UPDATE_BATCH: RefCell<UpdateBatch> = RefCell::new(UpdateBatch::default());
22}
23
24#[derive(Default)]
25struct ReactiveContext {
26 current_effect: Option<EffectId>,
27}
28
29struct Effect {
30 f: Box<dyn Fn()>,
31}
32
33#[derive(Default)]
35struct UpdateBatch {
36 pending_effects: HashSet<EffectId>,
37 is_batching: bool,
38}
39
40impl UpdateBatch {
41 fn start_batch() {
42 UPDATE_BATCH.with(|batch| {
43 batch.borrow_mut().is_batching = true;
44 });
45 }
46
47 fn end_batch() {
48 UPDATE_BATCH.with(|batch| {
49 let mut batch = batch.borrow_mut();
50 batch.is_batching = false;
51
52 let pending: Vec<_> = batch.pending_effects.drain().collect();
54 drop(batch); for effect_id in pending {
57 EFFECT_REGISTRY.with(|registry| {
58 if let Some(effect) = registry.borrow().get(&effect_id) {
59 (effect.f)();
60 }
61 });
62 }
63 });
64 }
65
66 fn add_effect(effect_id: EffectId) {
67 UPDATE_BATCH.with(|batch| {
68 let mut batch = batch.borrow_mut();
69 if batch.is_batching {
70 batch.pending_effects.insert(effect_id);
71 } else {
72 drop(batch); EFFECT_REGISTRY.with(|registry| {
75 if let Some(effect) = registry.borrow().get(&effect_id) {
76 (effect.f)();
77 }
78 });
79 }
80 });
81 }
82}
83
84#[derive(Clone)]
86pub struct Signal<T: Clone> {
87 id: SignalId,
88 value: Rc<RefCell<T>>,
89 subscribers: Rc<RefCell<SmallVec<[EffectId; 4]>>>,
91}
92
93impl<T: Clone> Signal<T> {
94 pub fn new(value: T) -> Self {
95 static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
96 Self {
97 id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
98 value: Rc::new(RefCell::new(value)),
99 subscribers: Rc::new(RefCell::new(SmallVec::new())),
100 }
101 }
102
103 pub fn get(&self) -> T {
104 REACTIVE_CONTEXT.with(|ctx| {
106 let ctx = ctx.borrow();
107 if let Some(effect_id) = ctx.current_effect {
108 let mut subs = self.subscribers.borrow_mut();
109 if !subs.contains(&effect_id) {
110 subs.push(effect_id);
111 }
112 }
113 });
114
115 self.value.borrow().clone()
116 }
117
118 pub fn get_untracked(&self) -> T {
119 self.value.borrow().clone()
120 }
121
122 pub fn set(&self, value: T) {
123 *self.value.borrow_mut() = value;
124 self.notify();
125 }
126
127 pub fn update<F>(&self, f: F)
128 where
129 F: FnOnce(&mut T),
130 {
131 f(&mut self.value.borrow_mut());
132 self.notify();
133 }
134
135 fn notify(&self) {
136 let subscribers = self.subscribers.borrow().clone();
137 for effect_id in subscribers.iter() {
138 UpdateBatch::add_effect(*effect_id);
139 }
140 }
141
142 pub fn id(&self) -> SignalId {
143 self.id
144 }
145}
146
147#[derive(Clone)]
149pub struct CopySignal<T: Copy> {
150 id: SignalId,
151 value: Rc<RefCell<T>>,
152 subscribers: Rc<RefCell<SmallVec<[EffectId; 4]>>>,
153}
154
155impl<T: Copy> CopySignal<T> {
156 pub fn new(value: T) -> Self {
157 static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
158 Self {
159 id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
160 value: Rc::new(RefCell::new(value)),
161 subscribers: Rc::new(RefCell::new(SmallVec::new())),
162 }
163 }
164
165 pub fn get(&self) -> T {
166 REACTIVE_CONTEXT.with(|ctx| {
167 let ctx = ctx.borrow();
168 if let Some(effect_id) = ctx.current_effect {
169 let mut subs = self.subscribers.borrow_mut();
170 if !subs.contains(&effect_id) {
171 subs.push(effect_id);
172 }
173 }
174 });
175
176 *self.value.borrow()
177 }
178
179 pub fn get_untracked(&self) -> T {
180 *self.value.borrow()
181 }
182
183 pub fn set(&self, value: T) {
184 *self.value.borrow_mut() = value;
185 self.notify();
186 }
187
188 pub fn update<F>(&self, f: F)
189 where
190 F: FnOnce(&mut T),
191 {
192 f(&mut self.value.borrow_mut());
193 self.notify();
194 }
195
196 fn notify(&self) {
197 let subscribers = self.subscribers.borrow().clone();
198 for effect_id in subscribers.iter() {
199 UpdateBatch::add_effect(*effect_id);
200 }
201 }
202
203 pub fn id(&self) -> SignalId {
204 self.id
205 }
206}
207
208pub fn create_effect<F>(f: F) -> EffectId
210where
211 F: Fn() + 'static,
212{
213 static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
214 let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
215
216 let effect = Effect { f: Box::new(f) };
217 EFFECT_REGISTRY.with(|registry| {
218 registry.borrow_mut().insert(id, effect);
219 });
220
221 REACTIVE_CONTEXT.with(|ctx| {
223 ctx.borrow_mut().current_effect = Some(id);
224 });
225
226 EFFECT_REGISTRY.with(|registry| {
227 if let Some(effect) = registry.borrow().get(&id) {
228 (effect.f)();
229 }
230 });
231
232 REACTIVE_CONTEXT.with(|ctx| {
233 ctx.borrow_mut().current_effect = None;
234 });
235
236 id
237}
238
239pub fn batch<F, R>(f: F) -> R
241where
242 F: FnOnce() -> R,
243{
244 UpdateBatch::start_batch();
245 let result = f();
246 UpdateBatch::end_batch();
247 result
248}
249
250pub fn dispose_effect(id: EffectId) {
252 EFFECT_REGISTRY.with(|registry| {
253 registry.borrow_mut().remove(&id);
254 });
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_signal_basic() {
263 let signal = Signal::new(42);
264 assert_eq!(signal.get(), 42);
265
266 signal.set(100);
267 assert_eq!(signal.get(), 100);
268 }
269
270 #[test]
271 fn test_copy_signal() {
272 let signal = CopySignal::new(42);
273 assert_eq!(signal.get(), 42);
274
275 signal.set(100);
276 assert_eq!(signal.get(), 100);
277 }
278
279 #[test]
280 fn test_effect() {
281 let signal = Signal::new(0);
282 let result = Rc::new(RefCell::new(0));
283 let result_clone = result.clone();
284 let signal_clone = signal.clone();
285
286 let _effect_id = create_effect(move || {
287 *result_clone.borrow_mut() = signal_clone.get();
288 });
289
290 assert_eq!(*result.borrow(), 0);
291
292 signal.set(42);
293 assert_eq!(*result.borrow(), 42);
294 }
295
296 #[test]
297 fn test_batched_updates() {
298 let signal1 = Signal::new(0);
299 let signal2 = Signal::new(0);
300 let count = Rc::new(RefCell::new(0));
301 let count_clone = count.clone();
302 let signal1_clone = signal1.clone();
303 let signal2_clone = signal2.clone();
304
305 let _effect_id = create_effect(move || {
306 let _ = signal1_clone.get();
307 let _ = signal2_clone.get();
308 *count_clone.borrow_mut() += 1;
309 });
310
311 *count.borrow_mut() = 0;
313
314 batch(|| {
316 signal1.set(1);
317 signal2.set(2);
318 });
319
320 assert_eq!(*count.borrow(), 1);
322 }
323
324 #[test]
325 fn test_smallvec_optimization() {
326 let signal = Signal::new(0);
327 let s1 = signal.clone();
328 let s2 = signal.clone();
329 let s3 = signal.clone();
330
331 let _e1 = create_effect(move || {
333 let _ = s1.get();
334 });
335 let _e2 = create_effect(move || {
336 let _ = s2.get();
337 });
338 let _e3 = create_effect(move || {
339 let _ = s3.get();
340 });
341
342 assert_eq!(signal.subscribers.borrow().len(), 3);
344 }
345}