1use std::cell::RefCell;
9use std::collections::HashMap;
10use std::marker::PhantomData;
11use std::ops::ControlFlow;
12use std::rc::Rc;
13use std::time::Duration;
14
15use crate::tree::RNode;
16use day_spec::{CancelFrame, FrameCallback, FrameStamp};
17
18#[derive(Clone, Copy, Debug, PartialEq)]
22pub struct Frame {
23 pub timestamp: Duration,
24 pub delta: Duration,
25 pub target_timestamp: Option<Duration>,
26}
27
28type Callback = Rc<RefCell<Box<dyn FnMut(Frame) -> ControlFlow<()>>>>;
29type Requester = Rc<dyn Fn(RNode, FrameCallback) -> CancelFrame>;
30struct Consumer {
31 root: RNode,
32 cb: Callback,
33 active: bool,
34 generation: u64,
35 last: Option<f64>,
36}
37#[derive(Default)]
38struct Source {
39 ticket: u64,
40 armed: bool,
41 dispatching: bool,
42 cancel: Option<CancelFrame>,
43}
44#[derive(Default)]
45struct Driver {
46 requester: Option<Requester>,
47 consumers: HashMap<u64, Consumer>,
48 sources: HashMap<RNode, Source>,
49 next_id: u64,
50 suspended: bool,
51}
52
53day_reactive::tls_slots! {
54 frame;
55 static DRIVER: RefCell<Driver> = RefCell::new(Driver::default());
56 static NATIVE_CALLBACKS: RefCell<(u64, HashMap<u64, FrameCallback>)> = RefCell::new((0, HashMap::new()));
57 static STAMPS: std::cell::Cell<Option<(f64, f64)>> = std::cell::Cell::new(None);
60}
61
62pub fn uptime_secs() -> Option<u64> {
66 STAMPS.with(|s| {
67 s.get()
68 .map(|(first, last)| ((last - first).max(0.0) / 1000.0) as u64)
69 })
70}
71
72pub fn install_frame_requester(f: impl Fn(RNode, FrameCallback) -> CancelFrame + 'static) {
75 DRIVER.with(|d| d.borrow_mut().requester = Some(Rc::new(f)));
76}
77
78#[derive(Clone, Copy, Debug)]
81pub struct FrameClock {
82 root: RNode,
83 ui_thread: PhantomData<Rc<()>>,
84}
85impl FrameClock {
86 pub fn current() -> Self {
88 Self::for_root(crate::toolbar::current_page_window())
89 }
90 pub(crate) fn for_root(root: RNode) -> Self {
91 Self {
92 root,
93 ui_thread: PhantomData,
94 }
95 }
96 pub fn request(self, cb: impl FnOnce(Frame) + 'static) -> FrameHandle {
99 let mut cb = Some(cb);
100 self.subscribe(move |frame| {
101 if let Some(cb) = cb.take() {
102 cb(frame);
103 }
104 ControlFlow::Break(())
105 })
106 }
107 pub fn subscribe(self, cb: impl FnMut(Frame) -> ControlFlow<()> + 'static) -> FrameHandle {
110 let id = DRIVER.with(|d| {
111 let mut d = d.borrow_mut();
112 d.next_id += 1;
113 let id = d.next_id;
114 d.consumers.insert(
115 id,
116 Consumer {
117 root: self.root,
118 cb: Rc::new(RefCell::new(Box::new(cb))),
119 active: true,
120 generation: 0,
121 last: None,
122 },
123 );
124 id
125 });
126 arm(self.root);
127 FrameHandle {
128 id,
129 ui_thread: PhantomData,
130 }
131 }
132}
133
134pub fn request(cb: impl FnOnce(Frame) + 'static) -> FrameHandle {
136 FrameClock::current().request(cb)
137}
138pub fn subscribe(cb: impl FnMut(Frame) -> ControlFlow<()> + 'static) -> FrameHandle {
140 FrameClock::current().subscribe(cb)
141}
142
143#[must_use = "dropping the frame handle cancels the callback"]
145pub struct FrameHandle {
146 id: u64,
147 ui_thread: PhantomData<Rc<()>>,
148}
149impl FrameHandle {
150 pub fn pause(&self) {
151 set_active(self.id, false);
152 }
153 pub fn resume(&self) {
154 set_active(self.id, true);
155 }
156 pub fn is_active(&self) -> bool {
157 DRIVER.with(|d| d.borrow().consumers.get(&self.id).is_some_and(|c| c.active))
158 }
159 pub fn cancel(&self) {
160 remove(self.id);
161 }
162 pub fn in_scope(self) {
165 day_reactive::Scope::current().on_cleanup(move || drop(self));
166 }
167}
168impl Drop for FrameHandle {
169 fn drop(&mut self) {
170 self.cancel();
171 }
172}
173
174fn set_active(id: u64, active: bool) {
175 let root = DRIVER.with(|d| {
176 let mut d = d.borrow_mut();
177 let c = d.consumers.get_mut(&id)?;
178 if c.active != active {
179 c.active = active;
180 c.generation += 1;
181 c.last = None;
182 }
183 Some(c.root)
184 });
185 if let Some(root) = root {
186 reconcile(root);
187 }
188}
189fn remove(id: u64) {
190 let Ok(removed) = DRIVER.try_with(|d| d.borrow_mut().consumers.remove(&id)) else {
195 return;
196 };
197 let root = removed.as_ref().map(|c| c.root);
198 drop(removed);
199 if let Some(root) = root {
200 reconcile(root);
201 }
202}
203fn reconcile(root: RNode) {
204 let cancel = DRIVER.with(|d| {
205 let mut d = d.borrow_mut();
206 let active = !d.suspended && d.consumers.values().any(|c| c.root == root && c.active);
207 if active {
208 return None;
209 }
210 let source = d.sources.get_mut(&root)?;
211 source.ticket += 1;
212 source.armed = false;
213 source.cancel.take()
214 });
215 if let Some(cancel) = cancel {
216 cancel();
217 }
218 arm(root);
219}
220fn arm(root: RNode) {
221 let request = DRIVER.with(|d| {
222 let mut d = d.borrow_mut();
223 if d.suspended || !d.consumers.values().any(|c| c.root == root && c.active) {
224 return None;
225 }
226 let requester = d.requester.clone()?;
227 let source = d.sources.entry(root).or_default();
228 if source.armed || source.dispatching {
229 return None;
230 }
231 source.ticket += 1;
232 source.armed = true;
233 Some((requester, source.ticket))
234 });
235 if let Some((requester, ticket)) = request {
236 let cancel = requester(root, Box::new(move |stamp| tick(root, ticket, stamp)));
237 DRIVER.with(|d| {
238 if let Some(s) = d.borrow_mut().sources.get_mut(&root) {
239 s.cancel = Some(cancel);
240 }
241 });
242 }
243}
244fn tick(root: RNode, ticket: u64, stamp: FrameStamp) {
245 STAMPS.with(|s| {
246 let first = s.get().map_or(stamp.timestamp, |(first, _)| first);
247 s.set(Some((first, stamp.timestamp)));
248 });
249 let callbacks = DRIVER.with(|d| {
250 let mut d = d.borrow_mut();
251 let Some(source) = d.sources.get_mut(&root) else {
252 return Vec::new();
253 };
254 if !source.armed || source.ticket != ticket {
255 return Vec::new();
256 }
257 source.armed = false;
258 source.cancel.take();
259 if Duration::try_from_secs_f64(stamp.timestamp).is_err() {
260 return Vec::new();
261 }
262 source.dispatching = true;
263 let mut callbacks: Vec<_> = d
264 .consumers
265 .iter()
266 .filter(|(_, c)| c.root == root && c.active)
267 .map(|(&id, c)| (id, c.generation, c.cb.clone()))
268 .collect();
269 callbacks.sort_by_key(|c| c.0);
270 callbacks
271 });
272 day_reactive::batch(|| {
273 for (id, generation, cb) in callbacks {
274 let frame = DRIVER.with(|d| {
275 let mut d = d.borrow_mut();
276 if d.suspended {
277 return None;
278 }
279 let c = d.consumers.get_mut(&id)?;
280 if !c.active || c.generation != generation {
281 return None;
282 }
283 let ts = stamp.timestamp.max(c.last.unwrap_or(stamp.timestamp));
285 let delta = c.last.map_or(0.0, |last| ts - last);
286 c.last = Some(ts);
287 Some(Frame {
288 timestamp: Duration::from_secs_f64(ts),
289 delta: Duration::from_secs_f64(delta),
290 target_timestamp: stamp
291 .target_timestamp
292 .filter(|t| t.is_finite() && *t >= ts)
293 .and_then(|t| Duration::try_from_secs_f64(t).ok()),
294 })
295 });
296 let Some(frame) = frame else {
297 continue;
298 };
299 let flow = day_spec::ffi_guard::contain(ControlFlow::Break(()), || {
300 cb.try_borrow_mut()
301 .map_or(ControlFlow::Break(()), |mut f| f(frame))
302 });
303 if flow.is_break() {
304 DRIVER.with(|d| {
305 if let Some(c) = d.borrow_mut().consumers.get_mut(&id)
306 && c.generation == generation
307 {
308 c.active = false;
309 c.last = None;
310 }
311 });
312 }
313 }
314 });
315 day_reactive::flush_now();
316 DRIVER.with(|d| {
317 if let Some(source) = d.borrow_mut().sources.get_mut(&root) {
318 source.dispatching = false;
319 }
320 });
321 reconcile(root);
322}
323
324pub(crate) fn lifecycle(phase: day_spec::Lifecycle) {
327 match phase {
328 day_spec::Lifecycle::DidEnterBackground => suspend(true),
329 day_spec::Lifecycle::WillEnterForeground | day_spec::Lifecycle::DidBecomeActive => {
330 suspend(false)
331 }
332 _ => {}
333 }
334}
335fn suspend(suspended: bool) {
336 let roots = DRIVER.with(|d| {
337 let mut d = d.borrow_mut();
338 if d.suspended == suspended {
339 return Vec::new();
340 }
341 d.suspended = suspended;
342 for c in d.consumers.values_mut() {
343 c.last = None;
344 c.generation += 1;
345 }
346 d.sources
349 .keys()
350 .copied()
351 .chain(d.consumers.values().map(|c| c.root))
352 .collect::<std::collections::HashSet<_>>()
353 .into_iter()
354 .collect::<Vec<_>>()
355 });
356 for root in roots {
357 reconcile(root);
358 }
359}
360pub(crate) fn forget_window(root: RNode) {
361 let (cancel, removed) = DRIVER.with(|d| {
362 let mut d = d.borrow_mut();
363 let ids: Vec<_> = d
364 .consumers
365 .iter()
366 .filter(|(_, c)| c.root == root)
367 .map(|(&id, _)| id)
368 .collect();
369 let removed: Vec<_> = ids
370 .into_iter()
371 .filter_map(|id| d.consumers.remove(&id))
372 .collect();
373 (d.sources.remove(&root).and_then(|s| s.cancel), removed)
374 });
375 drop(removed);
376 if let Some(cancel) = cancel {
377 cancel();
378 }
379}
380
381#[derive(Clone, Copy, Debug, PartialEq, Eq)]
383pub struct FrameConsumer(u64);
384pub fn add_frame_consumer(mut cb: impl FnMut(Duration) + 'static) -> FrameConsumer {
385 let mut first = true;
386 let handle = subscribe(move |frame| {
387 cb(if std::mem::take(&mut first) {
389 Duration::from_secs_f64(1.0 / 60.0)
390 } else {
391 frame.delta.min(Duration::from_millis(100))
392 });
393 ControlFlow::Continue(())
394 });
395 let id = handle.id;
396 std::mem::forget(handle);
397 FrameConsumer(id)
398}
399pub fn remove_frame_consumer(c: FrameConsumer) {
400 remove(c.0);
401}
402pub fn frame_consumer_count() -> usize {
404 DRIVER.with(|d| d.borrow().consumers.values().filter(|c| c.active).count())
405}
406
407#[doc(hidden)]
410pub mod native {
411 use super::*;
412 pub fn register(cb: FrameCallback) -> u64 {
413 NATIVE_CALLBACKS.with(|c| {
414 let mut c = c.borrow_mut();
415 c.0 += 1;
416 let id = c.0;
417 c.1.insert(id, cb);
418 id
419 })
420 }
421 pub fn cancel(id: u64) {
422 NATIVE_CALLBACKS.with(|c| c.borrow_mut().1.remove(&id));
423 }
424 pub fn deliver(id: u64, stamp: FrameStamp) {
425 let cb = NATIVE_CALLBACKS.with(|c| c.borrow_mut().1.remove(&id));
426 if let Some(cb) = cb {
427 day_spec::ffi_guard::contain((), || cb(stamp));
428 }
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435 use std::cell::Cell;
436 use std::collections::VecDeque;
437
438 struct Manual {
439 pending: Rc<RefCell<VecDeque<(RNode, FrameCallback)>>>,
440 requests: Rc<Cell<usize>>,
441 cancellations: Rc<Cell<usize>>,
442 }
443 impl Manual {
444 fn new() -> Self {
445 DRIVER.with(|d| *d.borrow_mut() = Driver::default());
446 let pending = Rc::new(RefCell::new(VecDeque::new()));
447 let requests = Rc::new(Cell::new(0));
448 let cancellations = Rc::new(Cell::new(0));
449 let (q, r, c) = (pending.clone(), requests.clone(), cancellations.clone());
450 install_frame_requester(move |root, cb| {
451 r.set(r.get() + 1);
452 q.borrow_mut().push_back((root, cb));
453 let c = c.clone();
454 Box::new(move || c.set(c.get() + 1))
455 });
456 Self {
457 pending,
458 requests,
459 cancellations,
460 }
461 }
462 fn fire(&self, ts: f64) {
463 let (_, cb) = self
464 .pending
465 .borrow_mut()
466 .pop_front()
467 .expect("pending frame");
468 cb(FrameStamp {
469 timestamp: ts,
470 target_timestamp: Some(ts + 1.0 / 120.0),
471 });
472 }
473 fn clock(&self) -> FrameClock {
474 FrameClock::for_root(RNode::default())
475 }
476 }
477 #[test]
478 fn clients_share_one_request_and_idle_after_delivery() {
479 let m = Manual::new();
480 let seen = Rc::new(RefCell::new(Vec::new()));
481 let s = seen.clone();
482 let a = m.clock().request(move |f| s.borrow_mut().push(f));
483 let s = seen.clone();
484 let b = m.clock().request(move |f| s.borrow_mut().push(f));
485 assert_eq!(m.requests.get(), 1);
486 m.fire(10.0);
487 assert_eq!(seen.borrow().len(), 2);
488 assert_eq!(seen.borrow()[0], seen.borrow()[1]);
489 assert_eq!(seen.borrow()[0].delta, Duration::ZERO);
490 assert!(seen.borrow()[0].target_timestamp.is_some());
491 assert!(!a.is_active() && !b.is_active());
492 assert!(m.pending.borrow().is_empty());
493 }
494 #[test]
495 fn dropping_last_client_cancels_and_late_delivery_is_harmless() {
496 let m = Manual::new();
497 let a = m.clock().request(|_| panic!("cancelled callback ran"));
498 drop(a);
499 assert_eq!(m.cancellations.get(), 1);
500 m.fire(10.0); assert_eq!(frame_consumer_count(), 0);
502 assert_eq!(m.requests.get(), 1);
503 }
504 #[test]
505 fn cancellation_during_dispatch_skips_the_later_callback() {
506 let m = Manual::new();
507 let later = Rc::new(RefCell::new(None::<FrameHandle>));
508 let l = later.clone();
509 let _first = m.clock().request(move |_| {
510 l.borrow_mut().take();
511 });
512 *later.borrow_mut() = Some(m.clock().request(|_| panic!("cancelled during dispatch")));
513 m.fire(1.0);
514 assert!(m.pending.borrow().is_empty());
515 }
516 #[test]
517 fn requests_from_a_callback_run_on_the_next_frame() {
518 let m = Manual::new();
519 let later = Rc::new(RefCell::new(None::<FrameHandle>));
520 let l = later.clone();
521 let seen = Rc::new(Cell::new(false));
522 let s = seen.clone();
523 let clock = m.clock();
524 let _first = clock.request(move |_| {
525 *l.borrow_mut() = Some(clock.request(move |_| s.set(true)));
526 });
527 m.fire(1.0);
528 assert!(!seen.get());
529 assert_eq!(m.requests.get(), 2);
530 m.fire(2.0);
531 assert!(seen.get());
532 }
533 #[test]
534 fn timestamps_are_unclamped_and_resume_starts_a_new_baseline() {
535 let m = Manual::new();
536 let seen = Rc::new(RefCell::new(Vec::new()));
537 let s = seen.clone();
538 let handle = m.clock().subscribe(move |f| {
539 s.borrow_mut().push(f.delta);
540 ControlFlow::Continue(())
541 });
542 m.fire(1.0);
543 m.fire(1.75);
544 assert_eq!(seen.borrow()[1], Duration::from_millis(750));
545 handle.pause();
546 m.fire(2.0); handle.resume();
548 m.fire(50.0);
549 assert_eq!(seen.borrow()[2], Duration::ZERO);
550 }
551 #[test]
552 fn stopping_then_waking_a_subscription_does_not_poll() {
553 let m = Manual::new();
554 let calls = Rc::new(Cell::new(0));
555 let c = calls.clone();
556 let handle = m.clock().subscribe(move |_| {
557 c.set(c.get() + 1);
558 ControlFlow::Break(())
559 });
560 m.fire(1.0);
561 assert!(!handle.is_active());
562 assert!(m.pending.borrow().is_empty());
563 handle.resume();
564 handle.resume();
565 assert_eq!(m.requests.get(), 2);
566 m.fire(5.0);
567 assert_eq!(calls.get(), 2);
568 }
569 #[test]
570 fn windows_have_independent_native_sources() {
571 let m = Manual::new();
572 let mut roots = slotmap::SlotMap::<RNode, ()>::with_key();
573 let one = roots.insert(());
574 let two = roots.insert(());
575 let _a = FrameClock::for_root(one).request(|_| {});
576 let b = FrameClock::for_root(two).subscribe(|_| ControlFlow::Continue(()));
577 assert_eq!(m.requests.get(), 2);
578 assert_eq!(m.pending.borrow()[0].0, one);
579 assert_eq!(m.pending.borrow()[1].0, two);
580 forget_window(two);
581 assert!(!b.is_active());
582 m.fire(1.0);
583 m.fire(1.0);
584 assert!(m.pending.borrow().is_empty());
585 }
586 #[test]
587 fn background_suspends_and_foreground_resets_delta() {
588 let m = Manual::new();
589 let seen = Rc::new(RefCell::new(Vec::new()));
590 let s = seen.clone();
591 let _handle = m.clock().subscribe(move |f| {
592 s.borrow_mut().push(f.delta);
593 ControlFlow::Continue(())
594 });
595 m.fire(1.0);
596 lifecycle(day_spec::Lifecycle::DidEnterBackground);
597 m.fire(2.0);
598 assert_eq!(seen.borrow().len(), 1);
599 lifecycle(day_spec::Lifecycle::WillEnterForeground);
600 m.fire(80.0);
601 assert_eq!(seen.borrow()[1], Duration::ZERO);
602 }
603 #[test]
604 fn malformed_and_backward_stamps_do_not_poison_the_clock() {
605 let m = Manual::new();
606 let seen = Rc::new(RefCell::new(Vec::new()));
607 let s = seen.clone();
608 let _handle = m.clock().subscribe(move |f| {
609 s.borrow_mut().push(f.delta);
610 ControlFlow::Continue(())
611 });
612 m.fire(f64::NAN);
613 m.fire(5.0);
614 m.fire(4.0);
615 m.fire(5.25);
616 assert_eq!(
617 *seen.borrow(),
618 vec![Duration::ZERO, Duration::ZERO, Duration::from_millis(250)]
619 );
620 }
621
622 #[test]
623 fn first_subscription_created_in_background_starts_on_foreground() {
624 let m = Manual::new();
625 lifecycle(day_spec::Lifecycle::DidEnterBackground);
626 let seen = Rc::new(Cell::new(false));
627 let s = seen.clone();
628 let _handle = m.clock().request(move |frame| {
629 assert_eq!(frame.delta, Duration::ZERO);
630 s.set(true);
631 });
632 assert_eq!(m.requests.get(), 0);
633 lifecycle(day_spec::Lifecycle::WillEnterForeground);
634 assert_eq!(m.requests.get(), 1);
635 m.fire(20.0);
636 assert!(seen.get());
637 }
638 #[test]
639 fn cancelled_native_tickets_can_never_run_a_new_callback() {
640 let calls = Rc::new(Cell::new(0));
641 let first = native::register(Box::new(|_| panic!("stale native ticket")));
642 native::cancel(first);
643 let c = calls.clone();
644 let second = native::register(Box::new(move |_| c.set(c.get() + 1)));
645 native::deliver(first, FrameStamp::new(1.0));
646 native::deliver(second, FrameStamp::new(1.0));
647 native::deliver(second, FrameStamp::new(2.0));
648 assert_eq!(calls.get(), 1);
649 }
650
651 #[test]
652 fn dropping_callbacks_can_cancel_other_handles() {
653 let m = Manual::new();
654 let inner = m.clock().request(|_| panic!("cancelled inner callback"));
655 let outer = m.clock().subscribe(move |_| {
656 let _ = &inner;
657 ControlFlow::Continue(())
658 });
659 drop(outer);
660 assert_eq!(frame_consumer_count(), 0);
661 m.fire(1.0);
662 }
663
664 #[test]
665 fn closing_a_window_drops_nested_handles_outside_the_driver_borrow() {
666 let m = Manual::new();
667 let inner = m.clock().request(|_| panic!("closed window"));
668 let _outer = m.clock().subscribe(move |_| {
669 let _ = &inner;
670 ControlFlow::Continue(())
671 });
672 forget_window(m.clock().root);
673 assert_eq!(frame_consumer_count(), 0);
674 m.fire(1.0);
675 }
676
677 #[test]
678 fn handles_drop_during_core_tls_teardown() {
679 const CHILD: &str = "DAY_CORE_FRAME_TEARDOWN_CHILD";
680 if std::env::var_os(CHILD).is_some() {
681 struct TeardownHandle {
682 handle: Option<FrameHandle>,
683 dropped: std::sync::mpsc::Sender<()>,
684 }
685 impl Drop for TeardownHandle {
686 fn drop(&mut self) {
687 assert!(crate::try_tls_root(|_| ()).is_err());
688 drop(self.handle.take());
689 self.dropped.send(()).unwrap();
690 }
691 }
692 let (dropped, observed) = std::sync::mpsc::channel();
693 std::thread::spawn(move || {
694 let m = Manual::new();
695 let paused = m.clock().subscribe(|_| ControlFlow::Continue(()));
696 paused.pause();
697 let held = TeardownHandle {
698 handle: Some(paused),
699 dropped: dropped.clone(),
700 };
701 let active = m.clock().subscribe(move |_| {
702 let _ = &held;
703 ControlFlow::Continue(())
704 });
705 let held = TeardownHandle {
706 handle: Some(active),
707 dropped,
708 };
709 native::register(Box::new(move |_| drop(held)));
713 })
714 .join()
715 .unwrap();
716 for _ in 0..2 {
717 observed
718 .recv_timeout(Duration::from_secs(5))
719 .expect("frame handle was not dropped during thread shutdown");
720 }
721 return;
722 }
723 let output = std::process::Command::new(std::env::current_exe().unwrap())
725 .args([
726 "--exact",
727 "frame::tests::handles_drop_during_core_tls_teardown",
728 "--nocapture",
729 ])
730 .env(CHILD, "1")
731 .output()
732 .unwrap();
733 assert!(
734 output.status.success(),
735 "{}",
736 String::from_utf8_lossy(&output.stderr)
737 );
738 }
739}