use std::time::{Duration, Instant};
use crate::event::Ctx;
use crate::reactive::Runtime;
use crate::window::WindowId;
pub const FRAME_PERIOD: Duration = Duration::from_millis(16);
type TimerCallback = Box<dyn FnMut(&mut Ctx)>;
pub(crate) struct Timer {
pub(crate) id: u64,
pub(crate) window: WindowId,
pub(crate) deadline: Instant,
pub(crate) interval: Option<Duration>,
pub(crate) cb: Option<TimerCallback>,
}
impl Timer {
pub(crate) fn take_cb(&mut self) -> Option<TimerCallback> {
self.cb.take()
}
}
pub struct TimerHandle {
rt: Runtime,
id: u64,
window: WindowId,
}
impl TimerHandle {
pub fn id(&self) -> u64 {
self.id
}
pub fn window(&self) -> WindowId {
self.window
}
pub fn is_active(&self) -> bool {
self.rt
.inner
.timers
.borrow()
.iter()
.any(|t| t.id == self.id)
}
pub fn cancel(&self) {
self.rt
.inner
.timers
.borrow_mut()
.retain(|t| t.id != self.id);
}
}
impl std::fmt::Debug for TimerHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TimerHandle")
.field("id", &self.id)
.field("active", &self.is_active())
.finish_non_exhaustive()
}
}
impl Runtime {
pub fn set_timeout<F: FnMut(&mut Ctx) + 'static>(
&self,
window: WindowId,
dur: Duration,
f: F,
) -> TimerHandle {
self.push_timer(window, dur, None, Box::new(f))
}
pub fn set_interval<F: FnMut(&mut Ctx) + 'static>(
&self,
window: WindowId,
dur: Duration,
f: F,
) -> TimerHandle {
self.push_timer(window, dur, Some(dur), Box::new(f))
}
fn push_timer(
&self,
window: WindowId,
dur: Duration,
interval: Option<Duration>,
cb: TimerCallback,
) -> TimerHandle {
let id = {
let n = self.inner.next_task_id.get() + 1;
self.inner.next_task_id.set(n);
n
};
self.inner.timers.borrow_mut().push(Timer {
id,
window,
deadline: Instant::now() + dur,
interval,
cb: Some(cb),
});
self.wake();
TimerHandle {
rt: self.clone(),
id,
window,
}
}
pub fn request_animation(&self, window: WindowId) {
let mut list = self.inner.animating.borrow_mut();
if !list.contains(&window) {
list.push(window);
}
drop(list);
self.wake();
}
pub fn animation_pending(&self, window: WindowId) -> bool {
self.inner.animating.borrow().contains(&window)
}
pub(crate) fn take_animation_request(&self, window: WindowId) -> bool {
let mut list = self.inner.animating.borrow_mut();
match list.iter().position(|w| *w == window) {
Some(i) => {
list.remove(i);
true
}
None => false,
}
}
pub(crate) fn take_due_timers(&self, window: WindowId, now: Instant) -> Vec<Timer> {
let mut timers = self.inner.timers.borrow_mut();
let mut due = Vec::new();
let mut i = 0;
while i < timers.len() {
if timers[i].window == window && timers[i].deadline <= now {
due.push(timers.remove(i));
} else {
i += 1;
}
}
due
}
pub(crate) fn reschedule_timer(&self, mut timer: Timer, now: Instant) {
let Some(interval) = timer.interval else {
return; };
let Some(cb) = timer.take_cb() else {
return; };
timer.cb = Some(cb);
timer.deadline += interval.max(Duration::from_millis(1));
if timer.deadline <= now {
timer.deadline = now + interval.max(Duration::from_millis(1));
}
self.inner.timers.borrow_mut().push(timer);
}
pub fn next_deadline(&self, window: WindowId) -> Option<Instant> {
let due = self
.inner
.timers
.borrow()
.iter()
.filter(|t| t.window == window)
.map(|t| t.deadline)
.min();
let anim = self
.animation_pending(window)
.then(|| Instant::now() + FRAME_PERIOD);
match (due, anim) {
(Some(a), Some(b)) => Some(a.min(b)),
(a, b) => a.or(b),
}
}
pub(crate) fn cancel_timers_of(&self, window: WindowId) {
self.inner
.timers
.borrow_mut()
.retain(|t| t.window != window);
self.inner.animating.borrow_mut().retain(|w| *w != window);
}
pub fn timer_count(&self) -> usize {
self.inner.timers.borrow().len()
}
}
#[cfg(test)]
pub(crate) fn force_due(rt: &Runtime, window: WindowId) {
let mut timers = rt.inner.timers.borrow_mut();
for t in timers.iter_mut() {
if t.window == window {
t.deadline = Instant::now() - Duration::from_millis(1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn win() -> WindowId {
WindowId::new(1)
}
#[test]
fn timeout_is_due_only_after_its_deadline_and_is_one_shot() {
let rt = Runtime::new();
rt.register_window(win());
let hits = std::rc::Rc::new(std::cell::Cell::new(0));
let h = {
let hits = hits.clone();
rt.set_timeout(win(), Duration::from_secs(60), move |_| hits.set(hits.get() + 1))
};
assert_eq!(rt.timer_count(), 1);
assert!(h.is_active());
assert!(rt.next_deadline(win()).is_some(), "有定时器 ⇒ 有唤醒时刻");
assert!(rt.take_due_timers(win(), Instant::now()).is_empty());
force_due(&rt, win());
let mut due = rt.take_due_timers(win(), Instant::now());
assert_eq!(due.len(), 1);
(due[0].take_cb().unwrap())(&mut Ctx::new(&rt, win(), crate::event::EventView::tick()));
rt.reschedule_timer(due.remove(0), Instant::now());
assert_eq!(hits.get(), 1);
assert_eq!(rt.timer_count(), 0, "一次性定时器触发后消失");
assert!(rt.next_deadline(win()).is_none());
}
#[test]
fn interval_reschedules_itself_until_cancelled() {
let rt = Runtime::new();
rt.register_window(win());
let h = rt.set_interval(win(), Duration::from_millis(10), |_| {});
for _ in 0..3 {
force_due(&rt, win());
let mut due = rt.take_due_timers(win(), Instant::now());
assert_eq!(due.len(), 1);
rt.reschedule_timer(due.remove(0), Instant::now());
}
assert_eq!(rt.timer_count(), 1, "周期定时器一直在表里");
h.cancel();
assert_eq!(rt.timer_count(), 0);
assert!(!h.is_active());
}
#[test]
fn animation_request_is_one_shot_and_drives_the_deadline() {
let rt = Runtime::new();
rt.register_window(win());
assert!(!rt.animation_pending(win()));
assert!(rt.next_deadline(win()).is_none());
rt.request_animation(win());
rt.request_animation(win()); assert!(rt.animation_pending(win()));
assert!(rt.next_deadline(win()).is_some(), "排了动画帧 ⇒ 有唤醒时刻");
assert!(rt.take_animation_request(win()), "被取走后当帧消费掉");
assert!(!rt.take_animation_request(win()), "一次性:不重复触发");
assert!(!rt.animation_pending(win()));
}
#[test]
fn timers_are_per_window_and_cancelled_with_the_window() {
let rt = Runtime::new();
let a = WindowId::new(1);
let b = WindowId::new(2);
rt.register_window(a);
rt.register_window(b);
rt.set_timeout(a, Duration::from_secs(60), |_| {});
rt.set_timeout(b, Duration::from_secs(60), |_| {});
rt.request_animation(b);
assert_eq!(rt.timer_count(), 2);
rt.cancel_timers_of(a);
assert_eq!(rt.timer_count(), 1, "只清 a 的");
assert!(rt.next_deadline(a).is_none());
assert!(rt.next_deadline(b).is_some());
rt.cancel_timers_of(b);
assert_eq!(rt.timer_count(), 0);
assert!(!rt.animation_pending(b));
}
}