use std::time::{Duration, Instant};
pub struct IdleDeadline {
deadline: Instant,
}
impl IdleDeadline {
pub fn new(budget: Duration) -> Self {
Self {
deadline: Instant::now() + budget,
}
}
pub fn from_instant(deadline: Instant) -> Self {
Self { deadline }
}
pub fn time_remaining(&self) -> Duration {
self.deadline.saturating_duration_since(Instant::now())
}
pub fn did_timeout(&self) -> bool {
Instant::now() >= self.deadline
}
}
pub(crate) type IdleCallback = Box<dyn FnOnce(IdleDeadline)>;
#[derive(Default)]
pub(crate) struct IdleQueue {
callbacks: Vec<IdleCallback>,
}
impl IdleQueue {
pub fn new() -> Self {
Self::default()
}
#[allow(dead_code)] pub fn push(&mut self, callback: impl FnOnce(IdleDeadline) + 'static) {
self.callbacks.push(Box::new(callback));
}
pub fn push_boxed(&mut self, callback: IdleCallback) {
self.callbacks.push(callback);
}
pub fn drain(&mut self) -> Vec<IdleCallback> {
std::mem::take(&mut self.callbacks)
}
pub fn is_empty(&self) -> bool {
self.callbacks.is_empty()
}
#[allow(dead_code)] pub fn len(&self) -> usize {
self.callbacks.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn idle_deadline_time_remaining() {
let deadline = IdleDeadline::new(Duration::from_millis(100));
assert!(!deadline.did_timeout());
assert!(deadline.time_remaining() > Duration::ZERO);
}
#[test]
fn idle_deadline_expired() {
let deadline = IdleDeadline::from_instant(Instant::now() - Duration::from_millis(1));
assert!(deadline.did_timeout());
assert_eq!(deadline.time_remaining(), Duration::ZERO);
}
#[test]
fn idle_queue_push_and_drain() {
use std::cell::Cell;
use std::rc::Rc;
let mut queue = IdleQueue::new();
assert!(queue.is_empty());
let called = Rc::new(Cell::new(false));
let c = called.clone();
queue.push(move |_deadline| {
c.set(true);
});
assert_eq!(queue.len(), 1);
let callbacks = queue.drain();
assert!(queue.is_empty());
callbacks.into_iter().for_each(|cb| {
cb(IdleDeadline::new(Duration::from_millis(16)));
});
assert!(called.get());
}
}