use std::cell::RefCell;
use std::collections::HashMap;
use std::time::{Duration, Instant};
struct Entry {
interval: Duration,
next_fire: Instant,
callback: Option<Box<dyn FnMut()>>,
}
struct Registry {
entries: HashMap<u64, Entry>,
next_id: u64,
}
impl Registry {
fn new() -> Self {
Self {
entries: HashMap::new(),
next_id: 1,
}
}
}
thread_local! {
static REGISTRY: RefCell<Registry> = RefCell::new(Registry::new());
}
pub struct Animation {
id: u64,
}
impl Animation {
pub fn new(interval: Duration, callback: impl FnMut() + 'static) -> Self {
let id = REGISTRY.with(|r| {
let mut reg = r.borrow_mut();
let id = reg.next_id;
reg.next_id += 1;
reg.entries.insert(
id,
Entry {
interval,
next_fire: Instant::now() + interval,
callback: Some(Box::new(callback)),
},
);
id
});
Animation { id }
}
}
impl Drop for Animation {
fn drop(&mut self) {
REGISTRY.with(|r| r.borrow_mut().entries.remove(&self.id));
}
}
pub fn tick(now: Instant) -> (bool, Option<Instant>) {
let mut to_fire: Vec<(u64, Box<dyn FnMut()>)> = Vec::new();
let next = REGISTRY.with(|r| {
let mut reg = r.borrow_mut();
let due: Vec<u64> = reg
.entries
.iter()
.filter(|(_, e)| now >= e.next_fire)
.map(|(id, _)| *id)
.collect();
for id in due {
if let Some(e) = reg.entries.get_mut(&id) {
e.next_fire = now + e.interval;
if let Some(cb) = e.callback.take() {
to_fire.push((id, cb));
}
}
}
reg.entries.values().map(|e| e.next_fire).min()
});
let fired = !to_fire.is_empty();
REGISTRY.with(|r| {
let mut reg = r.borrow_mut();
for (id, mut cb) in to_fire {
cb();
if let Some(e) = reg.entries.get_mut(&id) {
e.callback = Some(cb);
}
}
});
(fired, next)
}