use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use std::time::Duration;
use web_time::Instant;
use crate::reactive::runtime::{self, CurrentOwnerGuard, OwnerData};
#[derive(Clone, Debug)]
pub struct Clock(ClockKind);
#[derive(Clone, Debug)]
enum ClockKind {
System,
Virtual(VirtualClock),
}
impl Clock {
pub fn system() -> Clock {
Clock(ClockKind::System)
}
pub fn now(&self) -> Instant {
match &self.0 {
ClockKind::System => Instant::now(),
ClockKind::Virtual(clock) => clock.now(),
}
}
}
impl From<VirtualClock> for Clock {
fn from(clock: VirtualClock) -> Clock {
Clock(ClockKind::Virtual(clock))
}
}
#[derive(Clone, Debug)]
pub struct VirtualClock(Rc<Cell<Instant>>);
impl VirtualClock {
pub fn new(start: Instant) -> VirtualClock {
VirtualClock(Rc::new(Cell::new(start)))
}
pub fn now(&self) -> Instant {
self.0.get()
}
pub fn advance(&self, by: Duration) {
self.0.set(self.0.get() + by);
}
}
struct TimerEntry {
period: Option<Duration>,
next: Cell<Instant>,
run: RefCell<Option<Box<dyn FnMut()>>>,
active: Cell<bool>,
owner: Weak<OwnerData>,
}
impl TimerEntry {
fn is_live(&self) -> bool {
self.active.get()
&& self
.owner
.upgrade()
.is_some_and(|owner| !owner.cleaned.get())
}
}
#[must_use = "a timer runs until its handle is dropped, so an unused handle \
cancels it at once"]
pub struct TimerHandle {
entry: Rc<TimerEntry>,
}
impl Drop for TimerHandle {
fn drop(&mut self) {
self.entry.active.set(false);
}
}
pub(crate) struct TimerBook {
pub(crate) clock: Clock,
timers: Vec<Rc<TimerEntry>>,
}
impl TimerBook {
pub(crate) fn new(clock: Clock) -> TimerBook {
TimerBook {
clock,
timers: Vec::new(),
}
}
pub(crate) fn next_deadline(&self) -> Option<Instant> {
self.timers
.iter()
.filter(|entry| entry.is_live())
.map(|entry| entry.next.get())
.min()
}
}
fn register(
period: Option<Duration>,
delay: Duration,
run: Box<dyn FnMut()>,
) -> Option<Rc<TimerEntry>> {
let owner = runtime::with_runtime(|runtime| runtime.current_owner())?;
if owner.cleaned.get() {
return None;
}
let book = book_of(&owner)?;
let now = book.borrow().clock.now();
let entry = Rc::new(TimerEntry {
period,
next: Cell::new(now + delay),
run: RefCell::new(Some(run)),
active: Cell::new(true),
owner: Rc::downgrade(&owner),
});
book.borrow_mut().timers.push(Rc::clone(&entry));
Some(entry)
}
pub(crate) fn fire_due(book: &Rc<RefCell<TimerBook>>) {
let round: Vec<_> = book.borrow().timers.clone();
let mut fired: Vec<Rc<TimerEntry>> = Vec::new();
loop {
let now = book.borrow().clock.now();
let Some(entry) = due_entry(&round, now, &fired) else {
break;
};
fired.push(Rc::clone(&entry));
if !run_entry(&entry) {
entry.active.set(false);
} else if let Some(period) = entry.period {
entry.next.set(entry.next.get().max(now) + period);
}
}
book.borrow_mut().timers.retain(|entry| entry.is_live());
}
fn due_entry(
timers: &[Rc<TimerEntry>],
now: Instant,
skip: &[Rc<TimerEntry>],
) -> Option<Rc<TimerEntry>> {
let mut best: Option<Rc<TimerEntry>> = None;
for entry in timers {
if !entry.active.get() || skip.iter().any(|seen| Rc::ptr_eq(seen, entry)) {
continue;
}
let next = entry.next.get();
if next <= now && best.as_ref().is_none_or(|best| next < best.next.get()) {
best = Some(Rc::clone(entry));
}
}
best
}
fn run_entry(entry: &Rc<TimerEntry>) -> bool {
let Some(owner) = entry.owner.upgrade().filter(|owner| !owner.cleaned.get()) else {
return false;
};
let Ok(mut run) = entry.run.try_borrow_mut() else {
return false;
};
let Some(mut callback) = run.take() else {
return false;
};
{
let _guard = CurrentOwnerGuard::enter(&owner);
callback();
}
*run = Some(callback);
entry.period.is_some() && entry.active.get()
}
fn book_of(owner: &Rc<OwnerData>) -> Option<Rc<RefCell<TimerBook>>> {
let mut current = Rc::clone(owner);
loop {
let book = current.timer_book.borrow().as_ref().and_then(Weak::upgrade);
if book.is_some() {
return book;
}
let parent = current.parent.as_ref()?.upgrade()?;
current = parent;
}
}
pub fn now() -> Instant {
let owner = runtime::with_runtime(|runtime| runtime.current_owner());
match owner.as_ref().and_then(book_of) {
Some(book) => book.borrow().clock.now(),
None => Instant::now(),
}
}
pub fn after(duration: Duration, fun: impl FnMut() + 'static) {
register(None, duration, Box::new(fun));
}
pub fn every(period: Duration, fun: impl FnMut() + 'static) -> TimerHandle {
let entry = register(Some(period), period, Box::new(fun));
TimerHandle {
entry: entry.unwrap_or_else(|| inactive_entry(period)),
}
}
fn inactive_entry(period: Duration) -> Rc<TimerEntry> {
Rc::new(TimerEntry {
period: Some(period),
next: Cell::new(Instant::now()),
run: RefCell::new(None),
active: Cell::new(false),
owner: Weak::default(),
})
}
#[cfg(test)]
#[path = "timer_tests.rs"]
mod tests;