telar_reactive_core/runtime/
mod.rs1use std::cell::{Cell, Ref, RefCell, RefMut};
2use std::cmp::Reverse;
3use std::collections::BinaryHeap;
4use std::panic::Location;
5use std::rc::Rc;
6
7use rustc_hash::FxHashSet;
8
9mod effects;
10mod flush;
11mod signals;
12mod surface;
13
14pub(crate) use effects::{
15 current_observer, deregister_effect, is_alive, register_effect, register_pure_effect,
16 run_effect, schedule,
17};
18pub use flush::{batch, begin_batch, end_batch, reset_runtime, set_flush_notify};
19pub(crate) use signals::{
20 clone_signal, create_signal_storage, drop_signal, notify_signal, set_signal_value,
21 track_signal, update_signal_value, with_signal_value,
22};
23pub use surface::{
24 SurfaceEnterGuard, SurfaceHandle, current_surface, set_current_surface, set_surface_enter_hook,
25};
26
27pub(crate) type EffectId = usize;
28pub(crate) type SignalId = usize;
29
30pub(crate) struct EffectEntry {
31 pub(crate) callback: Box<dyn Fn()>,
32 pub(crate) surface: SurfaceHandle,
35 pub(crate) is_pure: bool,
36 pub(crate) last_run_epoch: u64,
37 pub(crate) sources: Vec<SignalId>,
38 pub(crate) source_slots: Vec<usize>,
39 pub(crate) source_versions: Vec<u64>,
40 pub(crate) height: u32,
42 pub(crate) memo_dirty: bool,
44}
45
46pub(crate) struct SignalStorage {
47 pub(crate) value: Box<dyn std::any::Any>,
48 pub(crate) version: u64,
49 pub(crate) subscribers: Vec<EffectId>,
50 pub(crate) observer_slots: Vec<usize>,
52 pub(crate) ref_count: usize,
54}
55
56pub(crate) struct Runtime {
57 pub(crate) observer_stack: Vec<EffectId>,
58 pub(crate) effects: slab::Slab<EffectEntry>,
59 pub(crate) signals: slab::Slab<SignalStorage>,
60 pub(crate) batch_depth: usize,
61 pub(crate) pending: Vec<EffectId>,
62 pub(crate) memo_pending: BinaryHeap<(Reverse<u32>, EffectId)>,
63 pub(crate) pending_set: FxHashSet<EffectId>,
64 subscriber_scratch: Vec<EffectId>,
66 flush_callbacks: Vec<(u64, Rc<dyn Fn()>)>,
67 next_flush_callback_id: u64,
68 pub(crate) flushing: bool,
69 flush_epoch: u64,
70}
71
72impl Runtime {
73 fn new() -> Self {
74 Runtime {
75 observer_stack: Vec::new(),
76 effects: slab::Slab::new(),
77 signals: slab::Slab::new(),
78 batch_depth: 0,
79 pending: Vec::new(),
80 memo_pending: BinaryHeap::new(),
81 pending_set: FxHashSet::default(),
82 subscriber_scratch: Vec::new(),
83 flush_callbacks: Vec::new(),
84 next_flush_callback_id: 0,
85 flushing: false,
86 flush_epoch: 0,
87 }
88 }
89}
90
91struct RuntimeCell {
93 ptr: Cell<*mut RefCell<Runtime>>,
94 last_borrow: Cell<Option<&'static Location<'static>>>,
98}
99
100impl RuntimeCell {
101 #[track_caller]
102 fn borrow_mut(&self) -> RefMut<'_, Runtime> {
103 self.enter(unsafe { (*self.ptr.get()).try_borrow_mut() }.ok())
104 }
105
106 #[track_caller]
107 fn borrow(&self) -> Ref<'_, Runtime> {
108 self.enter(unsafe { (*self.ptr.get()).try_borrow() }.ok())
109 }
110
111 fn take_ptr(&self) -> *mut RefCell<Runtime> {
114 self.last_borrow.set(None);
115 self.ptr
116 .replace(Box::into_raw(Box::new(RefCell::new(Runtime::new()))))
117 }
118
119 #[track_caller]
120 fn enter<G>(&self, borrowed: Option<G>) -> G {
121 let here = Location::caller();
122 match borrowed {
123 Some(guard) => {
124 self.last_borrow.set(Some(here));
125 guard
126 }
127 None => crate::reentry::borrow_collision("RUNTIME", self.last_borrow.get(), here),
128 }
129 }
130}
131
132thread_local! {
133 static RUNTIME: RuntimeCell = RuntimeCell {
134 ptr: Cell::new(Box::into_raw(Box::new(RefCell::new(Runtime::new())))),
135 last_borrow: const { Cell::new(None) },
136 };
137}
138
139pub struct FlushNotifyHandle {
140 id: u64,
141}
142
143impl Drop for FlushNotifyHandle {
144 fn drop(&mut self) {
145 deregister_flush_notify(self.id);
146 }
147}
148
149fn deregister_flush_notify(id: u64) {
150 RUNTIME.with(|rt| {
151 rt.borrow_mut()
152 .flush_callbacks
153 .retain(|(entry_id, _)| *entry_id != id);
154 });
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
166 fn a_reentrant_runtime_borrow_names_both_call_sites() {
167 let quiet = std::panic::take_hook();
168 std::panic::set_hook(Box::new(|_| {}));
169 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
170 RUNTIME.with(|rt| {
171 let _held = rt.borrow_mut();
172 let _collides = rt.borrow_mut();
173 });
174 }));
175 std::panic::set_hook(quiet);
176
177 let payload = outcome.expect_err("the second borrow cannot succeed");
178 let message = payload
179 .downcast_ref::<String>()
180 .map(String::as_str)
181 .unwrap_or_default();
182 assert!(
183 message.contains("`RUNTIME` is already borrowed"),
184 "{message}"
185 );
186 assert_eq!(
187 message.matches(file!()).count(),
188 2,
189 "both sites are named, and both are in this file:\n{message}"
190 );
191 }
192}