use std::time::Duration;
use crate::change_notifier::Listener;
const TIMER_BUDGET: usize = 100_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Timer {
id: u64,
}
impl Timer {
pub fn new(app: &mut crate::App, duration: Duration, callback: Listener) -> Timer {
app.schedule_timer(duration, callback)
}
pub fn cancel(self, app: &mut crate::App) {
app.cancel_timer(self);
}
pub fn is_active(self, app: &crate::App) -> bool {
app.timer_is_active(self)
}
}
struct ScheduledTimer {
id: u64,
due: Duration,
callback: Listener,
}
#[derive(Default)]
pub(crate) struct Timers {
queue: Vec<ScheduledTimer>,
next_id: u64,
now: Duration,
host_timer_wakeup: Option<Duration>,
}
impl Timers {
pub(crate) fn schedule(&mut self, duration: Duration, callback: Listener) -> Timer {
let id = self.next_id;
self.next_id += 1;
let due = self.now + duration;
self.queue.push(ScheduledTimer { id, due, callback });
Timer { id }
}
pub(crate) fn timer_wakeup_if_changed(&mut self) -> Option<Option<Duration>> {
let due = self.earliest_due();
if due == self.host_timer_wakeup {
return None;
}
self.host_timer_wakeup = due;
Some(due.map(|due| due.saturating_sub(self.now)))
}
pub(crate) fn cancel(&mut self, timer: Timer) {
self.queue.retain(|scheduled| scheduled.id != timer.id);
}
pub(crate) fn is_active(&self, timer: Timer) -> bool {
self.queue.iter().any(|scheduled| scheduled.id == timer.id)
}
pub(crate) fn now(&self) -> Duration {
self.now
}
pub(crate) fn pop_next_due(&mut self, target: Duration) -> Option<Listener> {
let index = self
.queue
.iter()
.enumerate()
.filter(|(_, scheduled)| scheduled.due <= target)
.min_by_key(|(_, scheduled)| (scheduled.due, scheduled.id))
.map(|(index, _)| index)?;
let scheduled = self.queue.remove(index);
if scheduled.due > self.now {
self.now = scheduled.due;
}
Some(scheduled.callback)
}
pub(crate) fn advance_to(&mut self, target: Duration) {
self.now = target;
}
pub(crate) fn assert_fire_budget(fired: usize) {
assert!(
fired <= TIMER_BUDGET,
"timers did not converge after {TIMER_BUDGET} callbacks; is a \
callback rescheduling an already-due timer?"
);
}
pub(crate) fn earliest_due(&self) -> Option<Duration> {
self.queue.iter().map(|scheduled| scheduled.due).min()
}
}