1use std::cell::RefCell;
5use std::collections::BTreeMap;
6use std::marker::PhantomData;
7use std::panic::{self, AssertUnwindSafe, Location};
8use std::rc::Rc;
9use std::time::{Duration, Instant};
10
11use crabshell_reactive::{Owner, Signal, untrack};
12
13use crate::cx::Cx;
14use crate::input::panic_message;
15
16const MIN_PERIOD: Duration = Duration::from_millis(1);
18const MAX_PERIOD: Duration = Duration::from_secs(u32::MAX as u64);
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25pub struct Timer {
26 id: u64,
27 _not_send: PhantomData<*const ()>,
29}
30
31struct Entry {
32 owner: Owner,
33 label: Rc<str>,
35 period: Duration,
36 repeats: bool,
37 deadline: Option<Instant>,
39 running: Signal<bool>,
41 callback: Option<Box<dyn FnMut()>>,
43}
44
45thread_local! {
46 static TIMERS: RefCell<(u64, BTreeMap<u64, Entry>)> = const { RefCell::new((0, BTreeMap::new())) };
48}
49
50impl Cx {
51 #[track_caller]
66 pub fn after(&self, delay: Duration, f: impl FnMut() + 'static) -> Timer {
67 let label = format!("cx.after at {}", Location::caller());
68 add(self, label, delay.min(MAX_PERIOD), false, Box::new(f))
69 }
70
71 #[track_caller]
76 pub fn interval(&self, period: Duration, f: impl FnMut() + 'static) -> Timer {
77 let label = format!("cx.interval at {}", Location::caller());
78 let period = period.clamp(MIN_PERIOD, MAX_PERIOD);
79 add(self, label, period, true, Box::new(f))
80 }
81}
82
83fn add(cx: &Cx, label: String, period: Duration, repeats: bool, f: Box<dyn FnMut()>) -> Timer {
84 let running = cx.signal(true);
85 let entry = Entry {
86 owner: cx.owner(),
87 label: label.into(),
88 period,
89 repeats,
90 deadline: Some(Instant::now() + period),
91 running,
92 callback: Some(f),
93 };
94 let id = TIMERS.with(|timers| {
95 let (next, table) = &mut *timers.borrow_mut();
96 *next += 1;
97 table.insert(*next, entry);
98 *next
99 });
100 Timer {
101 id,
102 _not_send: PhantomData,
103 }
104}
105
106impl Timer {
107 pub fn stop(&self) {
109 self.set_deadline(|_| None);
110 }
111
112 pub fn restart(&self) {
116 self.set_deadline(|period| Some(Instant::now() + period));
117 }
118
119 pub fn running(&self) -> bool {
122 let running = TIMERS.with(|timers| timers.borrow().1.get(&self.id).map(|entry| entry.running));
123 running
124 .and_then(|running| running.try_get())
125 .unwrap_or(false)
126 }
127
128 fn set_deadline(&self, deadline: impl FnOnce(Duration) -> Option<Instant>) {
129 let changed = TIMERS.with(|timers| {
130 let mut timers = timers.borrow_mut();
131 let entry = timers.1.get_mut(&self.id)?;
132 if entry.owner.is_disposed() {
133 return None;
134 }
135 entry.deadline = deadline(entry.period);
136 Some((entry.running, entry.deadline.is_some()))
137 });
138 if let Some((signal, running)) = changed {
139 set_running(signal, running);
140 }
141 }
142}
143
144fn set_running(signal: Signal<bool>, running: bool) {
147 if untrack(|| signal.try_with(|&was| was != running)) == Some(true) {
148 signal.try_set(running);
149 }
150}
151
152pub fn next_timer() -> Option<Instant> {
156 sweep();
157 TIMERS.with(|timers| {
158 let timers = timers.borrow();
159 timers.1.values().filter_map(|entry| entry.deadline).min()
160 })
161}
162
163pub fn fire_timers(now: Instant) {
167 sweep();
168 let mut due: Vec<(Instant, u64)> = TIMERS.with(|timers| {
169 let timers = timers.borrow();
170 let due = timers.1.iter().filter_map(|(&id, entry)| {
171 let deadline = entry.deadline.filter(|&deadline| deadline <= now)?;
172 Some((deadline, id))
173 });
174 due.collect()
175 });
176 due.sort_unstable();
177 for (_, id) in due {
178 fire(id, now);
179 }
180}
181
182fn fire(id: u64, now: Instant) {
183 let taken = TIMERS.with(|timers| {
186 let mut timers = timers.borrow_mut();
187 let entry = timers.1.get_mut(&id)?;
188 let deadline = entry.deadline.filter(|&deadline| deadline <= now)?;
191 if entry.owner.is_disposed() {
192 return None;
193 }
194 let callback = entry.callback.take()?;
195 entry.deadline = entry.repeats.then(|| {
196 let next = deadline + entry.period;
197 if next > now { next } else { now + entry.period }
198 });
199 let entry = &*entry;
200 Some((
201 callback,
202 Rc::clone(&entry.label),
203 entry.owner,
204 entry.running,
205 entry.deadline.is_some(),
206 ))
207 });
208 let Some((mut callback, label, owner, running, again)) = taken else {
209 return;
210 };
211 if !again {
212 set_running(running, false);
213 }
214 let panicked = panic::catch_unwind(AssertUnwindSafe(&mut callback)).err();
215 if let Some(payload) = &panicked {
216 let place = owner
217 .name()
218 .map(|name| format!(" in {name}"))
219 .unwrap_or_default();
220 tracing::error!(
221 "timer {label}{place} panicked and stops: {}",
222 panic_message(payload.as_ref())
223 );
224 }
225 let orphan = TIMERS.with(|timers| {
226 let mut timers = timers.borrow_mut();
227 let Some(entry) = timers.1.get_mut(&id) else {
228 return Some(callback);
229 };
230 if panicked.is_some() {
231 entry.deadline = None;
232 }
233 entry.callback = Some(callback);
234 None
235 });
236 if panicked.is_some() {
237 set_running(running, false);
238 }
239 if let Some(callback) = orphan {
241 drop_isolated(&label, callback);
242 }
243}
244
245fn sweep() {
248 let gone: Vec<Entry> = TIMERS.with(|timers| {
249 let mut timers = timers.borrow_mut();
250 let ids: Vec<u64> = timers
251 .1
252 .iter()
253 .filter(|(_, entry)| entry.owner.is_disposed())
254 .map(|(&id, _)| id)
255 .collect();
256 ids.iter().filter_map(|id| timers.1.remove(id)).collect()
257 });
258 for entry in gone {
259 if let Some(callback) = entry.callback {
260 drop_isolated(&entry.label, callback);
261 }
262 }
263}
264
265fn drop_isolated(label: &str, callback: Box<dyn FnMut()>) {
267 if let Err(payload) = panic::catch_unwind(AssertUnwindSafe(move || drop(callback))) {
268 tracing::error!(
269 "dropping timer {label} panicked: {}",
270 panic_message(payload.as_ref())
271 );
272 }
273}