#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TimerId(u64);
enum Repeat {
Once,
Every(f32),
}
struct Timer {
id: TimerId,
fire_at: f32,
repeat: Repeat,
callback: Box<dyn FnMut()>,
}
#[derive(Default)]
pub struct Scheduler {
now: f32,
next_id: u64,
timers: Vec<Timer>,
frame_callbacks: Vec<(TimerId, Box<dyn FnMut()>)>,
}
impl Scheduler {
pub fn new() -> Self {
Self::default()
}
pub fn now(&self) -> f32 {
self.now
}
pub fn pending(&self) -> usize {
self.timers.len()
}
pub fn pending_frames(&self) -> usize {
self.frame_callbacks.len()
}
fn alloc_id(&mut self) -> TimerId {
let id = TimerId(self.next_id);
self.next_id += 1;
id
}
pub fn after(&mut self, delay: f32, callback: impl FnMut() + 'static) -> TimerId {
let id = self.alloc_id();
self.timers.push(Timer {
id,
fire_at: self.now + delay.max(0.0),
repeat: Repeat::Once,
callback: Box::new(callback),
});
id
}
pub fn every(&mut self, interval: f32, callback: impl FnMut() + 'static) -> TimerId {
let interval = interval.max(1e-4);
let id = self.alloc_id();
self.timers.push(Timer {
id,
fire_at: self.now + interval,
repeat: Repeat::Every(interval),
callback: Box::new(callback),
});
id
}
pub fn request_frame(&mut self, callback: impl FnMut() + 'static) -> TimerId {
let id = self.alloc_id();
self.frame_callbacks.push((id, Box::new(callback)));
id
}
pub fn cancel(&mut self, id: TimerId) -> bool {
let before = self.timers.len() + self.frame_callbacks.len();
self.timers.retain(|t| t.id != id);
self.frame_callbacks.retain(|(fid, _)| *fid != id);
self.timers.len() + self.frame_callbacks.len() != before
}
pub fn tick(&mut self, dt: f32) -> usize {
self.now += dt.max(0.0);
let mut fired = 0;
let frames = std::mem::take(&mut self.frame_callbacks);
for (_, mut cb) in frames {
cb();
fired += 1;
}
loop {
let due_idx = self
.timers
.iter()
.enumerate()
.filter(|(_, t)| t.fire_at <= self.now)
.min_by(|(_, a), (_, b)| {
a.fire_at
.partial_cmp(&b.fire_at)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i);
let Some(idx) = due_idx else { break };
match self.timers[idx].repeat {
Repeat::Once => {
let mut timer = self.timers.remove(idx);
(timer.callback)();
fired += 1;
}
Repeat::Every(interval) => {
(self.timers[idx].callback)();
fired += 1;
self.timers[idx].fire_at += interval;
}
}
}
fired
}
}
#[derive(Clone, Copy, Debug)]
pub struct Debounce {
delay: f32,
last_signal: Option<f32>,
}
impl Debounce {
pub fn new(delay: f32) -> Self {
Self {
delay: delay.max(0.0),
last_signal: None,
}
}
pub fn signal(&mut self, now: f32) {
self.last_signal = Some(now);
}
pub fn poll(&mut self, now: f32) -> bool {
match self.last_signal {
Some(t) if now - t >= self.delay => {
self.last_signal = None;
true
}
_ => false,
}
}
pub fn is_pending(&self) -> bool {
self.last_signal.is_some()
}
}
#[derive(Clone, Copy, Debug)]
pub struct Throttle {
interval: f32,
last_fire: Option<f32>,
}
impl Throttle {
pub fn new(interval: f32) -> Self {
Self {
interval: interval.max(0.0),
last_fire: None,
}
}
pub fn try_fire(&mut self, now: f32) -> bool {
match self.last_fire {
Some(t) if now - t < self.interval => false,
_ => {
self.last_fire = Some(now);
true
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use std::rc::Rc;
#[test]
fn after_fires_once_when_due() {
let mut s = Scheduler::new();
let n = Rc::new(Cell::new(0u32));
let n_c = Rc::clone(&n);
s.after(1.0, move || n_c.set(n_c.get() + 1));
assert_eq!(s.tick(0.5), 0);
assert_eq!(n.get(), 0);
assert_eq!(s.tick(0.6), 1);
assert_eq!(n.get(), 1);
assert_eq!(s.tick(5.0), 0);
assert_eq!(n.get(), 1);
assert_eq!(s.pending(), 0);
}
#[test]
fn every_repeats_and_handles_large_dt() {
let mut s = Scheduler::new();
let n = Rc::new(Cell::new(0u32));
let n_c = Rc::clone(&n);
s.every(1.0, move || n_c.set(n_c.get() + 1));
let fired = s.tick(3.5);
assert_eq!(fired, 3);
assert_eq!(n.get(), 3);
s.tick(0.6); assert_eq!(n.get(), 4);
}
#[test]
fn request_frame_fires_next_tick_only() {
let mut s = Scheduler::new();
let n = Rc::new(Cell::new(0u32));
let n_c = Rc::clone(&n);
s.request_frame(move || n_c.set(n_c.get() + 1));
assert_eq!(s.pending_frames(), 1);
assert_eq!(s.tick(0.0), 1);
assert_eq!(n.get(), 1);
assert_eq!(s.tick(0.0), 0);
assert_eq!(n.get(), 1);
}
#[test]
fn cancel_prevents_fire() {
let mut s = Scheduler::new();
let n = Rc::new(Cell::new(0u32));
let n_c = Rc::clone(&n);
let id = s.after(1.0, move || n_c.set(n_c.get() + 1));
assert!(s.cancel(id));
assert!(!s.cancel(id));
s.tick(2.0);
assert_eq!(n.get(), 0);
}
#[test]
fn debounce_fires_only_after_quiet_period() {
let mut d = Debounce::new(0.3);
d.signal(0.0);
assert!(d.is_pending());
assert!(!d.poll(0.2));
d.signal(0.2);
assert!(!d.poll(0.4)); assert!(d.poll(0.5));
assert!(!d.is_pending());
assert!(!d.poll(1.0));
}
#[test]
fn throttle_limits_rate_leading_edge() {
let mut t = Throttle::new(1.0);
assert!(t.try_fire(0.0)); assert!(!t.try_fire(0.5)); assert!(!t.try_fire(0.99));
assert!(t.try_fire(1.0)); assert!(!t.try_fire(1.5));
}
}