use std::time::Instant;
pub struct BlinkTimer {
rng: u32,
next: Option<Instant>,
}
impl BlinkTimer {
pub fn new() -> Self {
Self {
rng: 0xDEAD_BEEF,
next: None,
}
}
pub fn start(&mut self, now: Instant) {
let interval = self.advance();
self.next = Some(now + interval);
}
pub fn stop(&mut self) {
self.next = None;
}
pub fn poll(&mut self, now: Instant) -> bool {
match self.next {
Some(when) if now >= when => {
let interval = self.advance();
self.next = Some(now + interval);
true
}
_ => false,
}
}
pub fn next_tick(&self) -> Option<Instant> {
self.next
}
fn advance(&mut self) -> std::time::Duration {
self.rng ^= self.rng << 13;
self.rng ^= self.rng >> 17;
self.rng ^= self.rng << 5;
std::time::Duration::from_millis((self.rng % 301) as u64)
}
}
impl Default for BlinkTimer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn new_is_stopped() {
let t = BlinkTimer::new();
assert!(t.next_tick().is_none());
}
#[test]
fn poll_returns_false_when_stopped() {
let mut t = BlinkTimer::new();
assert!(!t.poll(Instant::now()));
}
#[test]
fn start_schedules_within_300ms() {
let mut t = BlinkTimer::new();
let now = Instant::now();
t.start(now);
let scheduled = t.next_tick().unwrap();
let delta = scheduled - now;
assert!(delta <= Duration::from_millis(300));
}
#[test]
fn poll_fires_at_or_past_scheduled() {
let mut t = BlinkTimer::new();
let now = Instant::now();
t.start(now);
let scheduled = t.next_tick().unwrap();
assert!(!t.poll(scheduled - Duration::from_millis(1)));
assert!(t.poll(scheduled));
let new_scheduled = t.next_tick().unwrap();
assert!(new_scheduled >= scheduled);
}
#[test]
fn stop_clears_schedule() {
let mut t = BlinkTimer::new();
t.start(Instant::now());
assert!(t.next_tick().is_some());
t.stop();
assert!(t.next_tick().is_none());
}
#[test]
fn intervals_are_not_constant() {
let mut t = BlinkTimer::new();
let mut intervals = std::collections::HashSet::new();
for _ in 0..20 {
intervals.insert(t.advance().as_millis());
}
assert!(
intervals.len() > 5,
"xorshift32 should produce >5 distinct values in 20 draws"
);
}
}