1use std::any::Any;
2use std::cell::RefCell;
3use std::collections::{BTreeSet, HashMap, VecDeque};
4use std::marker::PhantomData;
5use std::rc::Rc;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
8pub struct SignalId(usize);
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct ScopeId(usize);
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14enum DependencyId {
15 Signal(SignalId),
16 Computed(ScopeId),
17}
18
19#[derive(Clone)]
20pub struct Signal<T> {
21 id: SignalId,
22 marker: PhantomData<(T, Rc<()>)>,
23}
24
25#[derive(Clone)]
26pub struct Computed<T> {
27 scope: ScopeId,
28 marker: PhantomData<(T, Rc<()>)>,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub struct EffectHandle {
33 scope: ScopeId,
34 marker: PhantomData<Rc<()>>,
35}
36
37pub fn signal<T: Clone + 'static>(value: T) -> Signal<T> {
38 RUNTIME.with(|runtime| runtime.borrow_mut().create_signal(value))
39}
40
41pub fn computed<T, F>(compute: F) -> Computed<T>
42where
43 T: Clone + 'static,
44 F: Fn() -> T + 'static,
45{
46 RUNTIME.with(|runtime| runtime.borrow_mut().create_computed(compute))
47}
48
49pub fn effect<F>(callback: F) -> EffectHandle
50where
51 F: FnMut() + 'static,
52{
53 let scope = RUNTIME.with(|runtime| runtime.borrow_mut().create_effect(callback));
54 run_effect(scope);
55 EffectHandle {
56 scope,
57 marker: PhantomData,
58 }
59}
60
61pub fn flush_reactivity() {
62 while let Some(scope) = RUNTIME.with(|runtime| runtime.borrow_mut().next_dirty_effect()) {
63 run_effect(scope);
64 }
65}
66
67pub fn reset_reactivity_for_testing() {
68 RUNTIME.with(|runtime| *runtime.borrow_mut() = ReactiveRuntime::default());
69}
70
71pub fn render_in_component_scope<T, F>(existing: Option<ScopeId>, render: F) -> (ScopeId, T)
72where
73 F: FnOnce(ScopeId) -> T,
74{
75 let scope = RUNTIME.with(|runtime| runtime.borrow_mut().prepare_component_scope(existing));
76 let value = render(scope);
77 RUNTIME.with(|runtime| runtime.borrow_mut().finish_component_scope(scope));
78 (scope, value)
79}
80
81pub fn take_dirty_component_scopes() -> Vec<ScopeId> {
82 RUNTIME.with(|runtime| runtime.borrow_mut().take_dirty_components())
83}
84
85pub fn dispose_component_scope(scope: ScopeId) {
86 RUNTIME.with(|runtime| runtime.borrow_mut().dispose_component(scope));
87}
88
89pub(crate) fn dispose_computed_scope(scope: ScopeId) {
90 RUNTIME.with(|runtime| runtime.borrow_mut().dispose_scope(scope));
91}
92
93pub fn current_reactive_scope() -> Option<ScopeId> {
94 RUNTIME.with(|runtime| runtime.borrow().active_scopes.last().copied())
95}
96
97impl<T: Clone + 'static> Signal<T> {
98 pub fn get(&self) -> T {
99 RUNTIME.with(|runtime| runtime.borrow_mut().read_signal(self.id))
100 }
101
102 pub fn set(&self, value: T) {
103 RUNTIME.with(|runtime| runtime.borrow_mut().write_signal(self.id, value));
104 }
105
106 pub fn set_if_changed(&self, value: T)
107 where
108 T: PartialEq,
109 {
110 RUNTIME.with(|runtime| {
111 runtime.borrow_mut().write_signal_if_changed(self.id, value);
112 });
113 }
114
115 pub fn update<F>(&self, update: F)
116 where
117 F: FnOnce(&mut T),
118 {
119 RUNTIME.with(|runtime| runtime.borrow_mut().update_signal(self.id, update));
120 }
121}
122
123impl<T: Clone + 'static> Computed<T> {
124 pub fn get(&self) -> T {
125 RUNTIME.with(|runtime| {
126 runtime
127 .borrow_mut()
128 .record_dependency(DependencyId::Computed(self.scope));
129 });
130
131 let evaluator =
132 RUNTIME.with(|runtime| runtime.borrow_mut().prepare_computed_evaluation(self.scope));
133
134 if let Some(evaluator) = evaluator {
135 let value = evaluator();
138 RUNTIME.with(|runtime| {
139 runtime
140 .borrow_mut()
141 .finish_computed_evaluation(self.scope, value);
142 });
143 }
144
145 RUNTIME.with(|runtime| runtime.borrow().read_cached_computed(self.scope))
146 }
147
148 pub(crate) fn scope_id(&self) -> ScopeId {
149 self.scope
150 }
151}
152
153impl EffectHandle {
154 pub fn stop(&self) {
155 RUNTIME.with(|runtime| runtime.borrow_mut().dispose_effect(self.scope));
156 }
157}
158
159thread_local! {
160 static RUNTIME: RefCell<ReactiveRuntime> = RefCell::new(ReactiveRuntime::default());
161}
162
163fn run_effect(scope: ScopeId) {
164 let effect = RUNTIME.with(|runtime| runtime.borrow_mut().prepare_effect_run(scope));
165 if let Some(effect) = effect {
166 (effect.borrow_mut())();
169 RUNTIME.with(|runtime| runtime.borrow_mut().finish_effect_run(scope));
170 }
171}
172
173#[derive(Default)]
174struct ReactiveRuntime {
175 next_signal_id: usize,
176 next_scope_id: usize,
177 active_scopes: Vec<ScopeId>,
178 signals: HashMap<SignalId, Box<dyn Any>>,
179 signal_dependents: HashMap<SignalId, BTreeSet<ScopeId>>,
180 computed_dependents: HashMap<ScopeId, BTreeSet<ScopeId>>,
181 scope_dependencies: HashMap<ScopeId, BTreeSet<DependencyId>>,
182 scopes: HashMap<ScopeId, ScopeState>,
183 dirty_effects: VecDeque<ScopeId>,
184 dirty_components: VecDeque<ScopeId>,
185}
186
187enum ScopeState {
188 Computed(ComputedState),
189 Effect(EffectState),
190 Component(ComponentState),
191}
192
193struct ComputedState {
194 evaluator: Rc<dyn Fn() -> Box<dyn Any>>,
195 cached: Option<Box<dyn Any>>,
196 dirty: bool,
197 evaluating: bool,
198}
199
200struct EffectState {
201 effect: Rc<RefCell<Box<dyn FnMut()>>>,
202 dirty: bool,
203 queued: bool,
204 disposed: bool,
205}
206
207struct ComponentState {
208 dirty: bool,
209 queued: bool,
210}
211
212impl ReactiveRuntime {
213 fn create_signal<T: Clone + 'static>(&mut self, value: T) -> Signal<T> {
214 let id = SignalId(self.next_signal_id);
215 self.next_signal_id += 1;
216 self.signals.insert(id, Box::new(value));
217 Signal {
218 id,
219 marker: PhantomData,
220 }
221 }
222
223 fn create_computed<T, F>(&mut self, compute: F) -> Computed<T>
224 where
225 T: Clone + 'static,
226 F: Fn() -> T + 'static,
227 {
228 let scope = ScopeId(self.next_scope_id);
229 self.next_scope_id += 1;
230 let evaluator: Rc<dyn Fn() -> Box<dyn Any>> = Rc::new(move || Box::new(compute()));
231 self.scopes.insert(
232 scope,
233 ScopeState::Computed(ComputedState {
234 evaluator,
235 cached: None,
236 dirty: true,
237 evaluating: false,
238 }),
239 );
240 Computed {
241 scope,
242 marker: PhantomData,
243 }
244 }
245
246 fn create_effect<F>(&mut self, effect: F) -> ScopeId
247 where
248 F: FnMut() + 'static,
249 {
250 let scope = ScopeId(self.next_scope_id);
251 self.next_scope_id += 1;
252 self.scopes.insert(
253 scope,
254 ScopeState::Effect(EffectState {
255 effect: Rc::new(RefCell::new(Box::new(effect))),
256 dirty: true,
257 queued: false,
258 disposed: false,
259 }),
260 );
261 scope
262 }
263
264 fn prepare_component_scope(&mut self, existing: Option<ScopeId>) -> ScopeId {
265 let scope = existing.unwrap_or_else(|| {
266 let scope = ScopeId(self.next_scope_id);
267 self.next_scope_id += 1;
268 self.scopes.insert(
269 scope,
270 ScopeState::Component(ComponentState {
271 dirty: false,
272 queued: false,
273 }),
274 );
275 scope
276 });
277
278 self.clear_scope_dependencies(scope);
279 if let Some(ScopeState::Component(state)) = self.scopes.get_mut(&scope) {
280 state.dirty = false;
281 state.queued = false;
282 }
283 self.active_scopes.push(scope);
284 scope
285 }
286
287 fn finish_component_scope(&mut self, scope: ScopeId) {
288 let popped = self.active_scopes.pop();
289 assert_eq!(popped, Some(scope), "component scope stack out of sync");
290 }
291
292 fn read_signal<T: Clone + 'static>(&mut self, id: SignalId) -> T {
293 self.record_dependency(DependencyId::Signal(id));
294 self.signals
295 .get(&id)
296 .expect("signal should exist")
297 .downcast_ref::<T>()
298 .expect("signal type mismatch")
299 .clone()
300 }
301
302 fn write_signal<T: Clone + 'static>(&mut self, id: SignalId, value: T) {
303 let signal = self
304 .signals
305 .get_mut(&id)
306 .expect("signal should exist")
307 .downcast_mut::<T>()
308 .expect("signal type mismatch");
309 *signal = value;
310 self.propagate_dirty(DependencyId::Signal(id));
311 }
312
313 fn write_signal_if_changed<T>(&mut self, id: SignalId, value: T)
314 where
315 T: Clone + PartialEq + 'static,
316 {
317 let signal = self
318 .signals
319 .get_mut(&id)
320 .expect("signal should exist")
321 .downcast_mut::<T>()
322 .expect("signal type mismatch");
323
324 if *signal == value {
325 return;
326 }
327
328 *signal = value;
329 self.propagate_dirty(DependencyId::Signal(id));
330 }
331
332 fn update_signal<T: Clone + 'static, F>(&mut self, id: SignalId, update: F)
333 where
334 F: FnOnce(&mut T),
335 {
336 let signal = self
337 .signals
338 .get_mut(&id)
339 .expect("signal should exist")
340 .downcast_mut::<T>()
341 .expect("signal type mismatch");
342 update(signal);
343 self.propagate_dirty(DependencyId::Signal(id));
344 }
345
346 fn record_dependency(&mut self, dependency: DependencyId) {
347 let Some(&scope) = self.active_scopes.last() else {
348 return;
349 };
350
351 if matches!(dependency, DependencyId::Computed(source) if source == scope) {
352 return;
353 }
354
355 self.scope_dependencies
356 .entry(scope)
357 .or_default()
358 .insert(dependency);
359
360 match dependency {
361 DependencyId::Signal(signal) => {
362 self.signal_dependents
363 .entry(signal)
364 .or_default()
365 .insert(scope);
366 }
367 DependencyId::Computed(computed) => {
368 self.computed_dependents
369 .entry(computed)
370 .or_default()
371 .insert(scope);
372 }
373 }
374 }
375
376 fn prepare_computed_evaluation(
377 &mut self,
378 scope: ScopeId,
379 ) -> Option<Rc<dyn Fn() -> Box<dyn Any>>> {
380 let should_evaluate = match self.scopes.get(&scope) {
381 Some(ScopeState::Computed(state)) => state.dirty || state.cached.is_none(),
382 _ => panic!("scope should be a computed"),
383 };
384
385 if !should_evaluate {
386 return None;
387 }
388
389 let evaluating = match self.scopes.get(&scope) {
390 Some(ScopeState::Computed(state)) => state.evaluating,
391 _ => unreachable!(),
392 };
393 assert!(!evaluating, "computed cycle detected");
394
395 self.clear_scope_dependencies(scope);
396
397 let evaluator = match self.scopes.get_mut(&scope) {
398 Some(ScopeState::Computed(state)) => {
399 state.evaluating = true;
400 state.evaluator.clone()
401 }
402 _ => unreachable!(),
403 };
404
405 self.active_scopes.push(scope);
406 Some(evaluator)
407 }
408
409 fn finish_computed_evaluation(&mut self, scope: ScopeId, value: Box<dyn Any>) {
410 match self.scopes.get_mut(&scope) {
411 Some(ScopeState::Computed(state)) => {
412 state.cached = Some(value);
413 state.dirty = false;
414 state.evaluating = false;
415 }
416 _ => panic!("scope should be a computed"),
417 }
418
419 let popped = self.active_scopes.pop();
420 assert_eq!(popped, Some(scope), "computed scope stack out of sync");
421 }
422
423 fn read_cached_computed<T: Clone + 'static>(&self, scope: ScopeId) -> T {
424 match self.scopes.get(&scope) {
425 Some(ScopeState::Computed(state)) => state
426 .cached
427 .as_ref()
428 .expect("computed should have a cached value")
429 .downcast_ref::<T>()
430 .expect("computed type mismatch")
431 .clone(),
432 _ => panic!("scope should be a computed"),
433 }
434 }
435
436 fn prepare_effect_run(&mut self, scope: ScopeId) -> Option<Rc<RefCell<Box<dyn FnMut()>>>> {
437 let should_run = match self.scopes.get(&scope) {
438 Some(ScopeState::Effect(state)) => state.dirty && !state.disposed,
439 _ => false,
440 };
441
442 if !should_run {
443 return None;
444 }
445
446 self.clear_scope_dependencies(scope);
447
448 let effect = match self.scopes.get_mut(&scope) {
449 Some(ScopeState::Effect(state)) => {
450 state.dirty = false;
451 state.queued = false;
452 state.effect.clone()
453 }
454 _ => unreachable!(),
455 };
456
457 self.active_scopes.push(scope);
458 Some(effect)
459 }
460
461 fn finish_effect_run(&mut self, scope: ScopeId) {
462 let popped = self.active_scopes.pop();
463 assert_eq!(popped, Some(scope), "effect scope stack out of sync");
464 }
465
466 fn next_dirty_effect(&mut self) -> Option<ScopeId> {
467 while let Some(scope) = self.dirty_effects.pop_front() {
468 let ready = match self.scopes.get_mut(&scope) {
469 Some(ScopeState::Effect(state)) => {
470 state.queued = false;
471 state.dirty && !state.disposed
472 }
473 _ => false,
474 };
475 if ready {
476 return Some(scope);
477 }
478 }
479 None
480 }
481
482 fn dispose_effect(&mut self, scope: ScopeId) {
483 self.clear_scope_dependencies(scope);
484 if let Some(ScopeState::Effect(state)) = self.scopes.get_mut(&scope) {
485 state.disposed = true;
486 state.dirty = false;
487 state.queued = false;
488 }
489 }
490
491 fn dispose_component(&mut self, scope: ScopeId) {
492 if matches!(self.scopes.get(&scope), Some(ScopeState::Component(_))) {
493 self.dispose_scope(scope);
494 }
495 }
496
497 fn dispose_scope(&mut self, scope: ScopeId) {
498 self.clear_scope_dependencies(scope);
499 self.computed_dependents.remove(&scope);
500 self.scope_dependencies.remove(&scope);
501 self.scopes.remove(&scope);
502 }
503
504 fn clear_scope_dependencies(&mut self, scope: ScopeId) {
505 let Some(dependencies) = self.scope_dependencies.remove(&scope) else {
506 return;
507 };
508
509 for dependency in dependencies {
510 match dependency {
511 DependencyId::Signal(signal) => {
512 if let Some(dependents) = self.signal_dependents.get_mut(&signal) {
513 dependents.remove(&scope);
514 }
515 }
516 DependencyId::Computed(computed) => {
517 if let Some(dependents) = self.computed_dependents.get_mut(&computed) {
518 dependents.remove(&scope);
519 }
520 }
521 }
522 }
523 }
524
525 fn take_dirty_components(&mut self) -> Vec<ScopeId> {
526 let mut dirty = Vec::new();
527 while let Some(scope) = self.dirty_components.pop_front() {
528 let ready = match self.scopes.get_mut(&scope) {
529 Some(ScopeState::Component(state)) => {
530 state.queued = false;
531 if state.dirty {
532 state.dirty = false;
533 true
534 } else {
535 false
536 }
537 }
538 _ => false,
539 };
540
541 if ready {
542 dirty.push(scope);
543 }
544 }
545 dirty
546 }
547
548 fn propagate_dirty(&mut self, dependency: DependencyId) {
549 let mut queue = VecDeque::from([dependency]);
550 let mut visited_scopes = BTreeSet::new();
551
552 while let Some(current) = queue.pop_front() {
553 let dependents = match current {
557 DependencyId::Signal(signal) => self
558 .signal_dependents
559 .get(&signal)
560 .cloned()
561 .unwrap_or_default(),
562 DependencyId::Computed(computed) => self
563 .computed_dependents
564 .get(&computed)
565 .cloned()
566 .unwrap_or_default(),
567 };
568
569 for scope in dependents {
570 if !visited_scopes.insert(scope) {
571 continue;
572 }
573
574 match self.scopes.get_mut(&scope) {
575 Some(ScopeState::Computed(state)) => {
576 state.dirty = true;
577 queue.push_back(DependencyId::Computed(scope));
578 }
579 Some(ScopeState::Effect(state)) => {
580 if state.disposed {
581 continue;
582 }
583 state.dirty = true;
584 if !state.queued {
585 state.queued = true;
586 self.dirty_effects.push_back(scope);
587 }
588 }
589 Some(ScopeState::Component(state)) => {
590 state.dirty = true;
591 if !state.queued {
592 state.queued = true;
593 self.dirty_components.push_back(scope);
594 }
595 }
596 None => {}
597 }
598 }
599 }
600 }
601}