telar_reactive_core/runtime/
flush.rs1use std::rc::Rc;
2
3use super::effects::run_effect;
4use super::{EffectId, FlushNotifyHandle, RUNTIME};
5
6const MAX_FLUSH_ITERATIONS: usize = 1_000;
7
8pub(crate) fn flush() {
9 RUNTIME.with(|rt| rt.borrow_mut().flushing = true);
10
11 struct FlushGuard;
15 impl Drop for FlushGuard {
16 fn drop(&mut self) {
17 RUNTIME.with(|rt| rt.borrow_mut().flushing = false);
18 }
19 }
20
21 let (did_work, overflowed) = {
22 let _flush_guard = FlushGuard;
23 let mut did_work = false;
24 let mut overflowed = true;
25 for _ in 0..MAX_FLUSH_ITERATIONS {
26 RUNTIME.with(|rt| rt.borrow_mut().flush_epoch += 1);
33 let memo_batch: Vec<EffectId> = RUNTIME.with(|rt| {
35 let mut rt = rt.borrow_mut();
36 let mut batch = Vec::new();
37 while let Some((_, id)) = rt.memo_pending.pop() {
38 batch.push(id);
39 }
40 batch
41 });
42 let pending_batch = if memo_batch.is_empty() {
43 RUNTIME.with(|rt| {
44 let mut rt = rt.borrow_mut();
45 rt.pending_set.clear();
46 std::mem::take(&mut rt.pending)
47 })
48 } else {
49 Vec::new()
50 };
51
52 if memo_batch.is_empty() && pending_batch.is_empty() {
53 overflowed = false;
54 break;
55 }
56 did_work = true;
57 for id in memo_batch {
58 run_effect(id);
59 }
60 for id in pending_batch {
61 run_effect(id);
62 }
63 }
64 (did_work, overflowed)
65 };
66
67 if overflowed {
68 panic!(
69 "reactive flush exceeded {MAX_FLUSH_ITERATIONS} iterations — \
70 likely an effect is writing to a signal it depends on"
71 );
72 }
73
74 if did_work {
77 let cbs: smallvec::SmallVec<[Rc<dyn Fn()>; 2]> = RUNTIME.with(|rt| {
78 rt.borrow()
79 .flush_callbacks
80 .iter()
81 .map(|(_, cb)| Rc::clone(cb))
82 .collect()
83 });
84 for cb in cbs {
85 cb();
86 }
87 }
88}
89
90pub fn batch<R>(f: impl FnOnce() -> R) -> R {
91 struct DepthGuard;
94 impl Drop for DepthGuard {
95 fn drop(&mut self) {
96 RUNTIME.with(|rt| {
97 let mut rt = rt.borrow_mut();
98 rt.batch_depth = rt.batch_depth.saturating_sub(1);
99 });
100 }
101 }
102 RUNTIME.with(|rt| rt.borrow_mut().batch_depth += 1);
103 let result = {
104 let _depth_guard = DepthGuard;
105 f()
106 };
107 let should_flush = RUNTIME.with(|rt| {
108 let rt = rt.borrow();
109 rt.batch_depth == 0 && (!rt.pending.is_empty() || !rt.memo_pending.is_empty())
110 });
111 if should_flush {
112 flush();
113 }
114 result
115}
116
117pub fn begin_batch() {
118 RUNTIME.with(|rt| rt.borrow_mut().batch_depth += 1);
119}
120
121pub fn end_batch() {
122 let should_flush = RUNTIME.with(|rt| {
123 let mut rt = rt.borrow_mut();
124 debug_assert!(rt.batch_depth > 0, "end_batch without begin_batch");
125 rt.batch_depth = rt.batch_depth.saturating_sub(1);
126 rt.batch_depth == 0 && (!rt.pending.is_empty() || !rt.memo_pending.is_empty())
127 });
128 if should_flush {
129 flush();
130 }
131}
132
133pub fn reset_runtime() {
134 RUNTIME.with(|cell| {
135 let old_ptr = cell.take_ptr();
136 if !old_ptr.is_null() {
138 unsafe { drop(Box::from_raw(old_ptr)) };
139 }
140 });
141}
142
143pub fn set_flush_notify(f: impl Fn() + 'static) -> FlushNotifyHandle {
144 RUNTIME.with(|rt| {
145 let mut rt = rt.borrow_mut();
146 let id = rt.next_flush_callback_id;
147 rt.next_flush_callback_id += 1;
148 rt.flush_callbacks.push((id, Rc::new(f)));
149 FlushNotifyHandle { id }
150 })
151}