use std::time::{Duration, Instant};
pub struct TickSchedule {
interval: Option<Duration>,
due: Deadline,
in_flight: bool,
respawn: bool,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Deadline {
Now,
At(Instant),
Never,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Due {
Spawn,
Wait,
Running,
}
impl TickSchedule {
pub fn new(interval: Option<Duration>) -> TickSchedule {
TickSchedule {
interval,
due: Deadline::Now,
in_flight: false,
respawn: false,
}
}
pub fn poll(&mut self, now: Instant) -> Due {
if self.in_flight {
return Due::Running;
}
match self.due {
Deadline::At(due) if now < due => Due::Wait,
Deadline::Never => Due::Wait,
Deadline::Now | Deadline::At(_) => {
self.in_flight = true;
Due::Spawn
}
}
}
pub fn completed(&mut self, now: Instant) {
self.due = if self.respawn {
Deadline::Now
} else {
self.interval
.map_or(Deadline::Never, |interval| Deadline::At(now + interval))
};
self.respawn = false;
self.in_flight = false;
}
pub fn request_now(&mut self) {
self.due = Deadline::Now;
}
pub fn request_respawn(&mut self) {
self.due = Deadline::Now;
self.respawn = self.in_flight;
}
pub fn nap(&self, now: Instant, cap: Duration) -> Duration {
if self.in_flight {
return cap;
}
match self.due {
Deadline::Now => Duration::ZERO,
Deadline::At(due) => due.saturating_duration_since(now).min(cap),
Deadline::Never => cap,
}
}
}
#[cfg(test)]
mod tests {
use std::time::{Duration, Instant};
use super::*;
const IVL: Duration = Duration::from_secs(2);
const CAP: Duration = Duration::from_millis(50);
fn base() -> Instant {
Instant::now()
}
#[test]
fn a_fresh_schedule_spawns_at_once() {
let mut s = TickSchedule::new(Some(IVL));
assert_eq!(s.poll(base()), Due::Spawn);
}
#[test]
fn only_one_child_runs_at_a_time() {
let t = base();
let mut s = TickSchedule::new(Some(IVL));
assert_eq!(s.poll(t), Due::Spawn);
assert_eq!(s.poll(t + IVL * 10), Due::Running);
}
#[test]
fn a_finished_tick_waits_one_interval() {
let t = base();
let mut s = TickSchedule::new(Some(IVL));
s.poll(t);
s.completed(t + Duration::from_secs(30)); assert_eq!(s.poll(t + Duration::from_secs(31)), Due::Wait);
assert_eq!(s.poll(t + Duration::from_secs(32)), Due::Spawn);
}
#[test]
fn the_nap_never_outruns_the_deadline_or_the_cap() {
let t = base();
let mut s = TickSchedule::new(Some(IVL));
s.poll(t);
assert_eq!(s.nap(t, CAP), CAP);
s.completed(t);
assert_eq!(s.nap(t, CAP), CAP);
assert_eq!(
s.nap(t + IVL - Duration::from_millis(10), CAP),
Duration::from_millis(10)
);
assert_eq!(s.nap(t + IVL, CAP), Duration::ZERO);
}
#[test]
fn an_immediate_request_collapses_the_deadline() {
let t = base();
let mut s = TickSchedule::new(Some(IVL));
s.poll(t);
s.completed(t);
assert_eq!(s.poll(t), Due::Wait);
s.request_now();
assert_eq!(s.poll(t), Due::Spawn);
}
#[test]
fn a_request_while_a_child_runs_is_satisfied_by_its_completion() {
let t = base();
let mut s = TickSchedule::new(Some(IVL));
s.poll(t);
s.request_now(); s.completed(t);
assert_eq!(s.poll(t + Duration::from_millis(1)), Due::Wait);
assert_eq!(s.poll(t + IVL), Due::Spawn);
}
#[test]
fn a_respawn_request_outlives_the_child_it_interrupted() {
let t = base();
let mut s = TickSchedule::new(Some(IVL));
s.poll(t);
s.request_respawn(); s.completed(t);
assert_eq!(s.poll(t), Due::Spawn);
}
#[test]
fn a_respawn_request_is_spent_once() {
let t = base();
let mut s = TickSchedule::new(Some(IVL));
s.poll(t);
s.request_respawn();
s.completed(t);
assert_eq!(s.poll(t), Due::Spawn); s.completed(t);
assert_eq!(s.poll(t), Due::Wait);
assert_eq!(s.poll(t + IVL), Due::Spawn);
}
#[test]
fn a_respawn_request_with_nothing_running_spawns_at_once() {
let t = base();
let mut s = TickSchedule::new(Some(IVL));
s.poll(t);
s.completed(t);
s.request_respawn();
assert_eq!(s.poll(t), Due::Spawn);
s.completed(t);
assert_eq!(s.poll(t), Due::Wait);
}
#[test]
fn a_trigger_only_schedule_spawns_once_then_waits_forever() {
let t = base();
let mut s = TickSchedule::new(None);
assert_eq!(s.poll(t), Due::Spawn);
s.completed(t);
assert_eq!(s.poll(t + Duration::from_secs(3600)), Due::Wait);
}
#[test]
fn a_request_still_collapses_a_never_deadline() {
let t = base();
let mut s = TickSchedule::new(None);
s.poll(t);
s.completed(t);
s.request_now();
assert_eq!(s.poll(t), Due::Spawn);
}
#[test]
fn a_respawn_request_survives_completion_without_an_interval() {
let t = base();
let mut s = TickSchedule::new(None);
s.poll(t);
s.request_respawn();
s.completed(t);
assert_eq!(s.poll(t), Due::Spawn);
s.completed(t);
assert_eq!(s.poll(t), Due::Wait);
}
#[test]
fn the_nap_is_the_cap_when_no_deadline_exists() {
let t = base();
let mut s = TickSchedule::new(None);
s.poll(t);
s.completed(t);
assert_eq!(s.nap(t, CAP), CAP);
}
}