use std::time::{Duration,Instant};
pub trait NowSource : Copy {
fn now(&mut self) -> Instant;
#[allow(unused)]
fn sleep(&mut self, how_long: Duration) {}
}
#[derive(Debug,Copy,Clone)]
pub struct RealtimeNowSource {}
impl RealtimeNowSource {
pub fn new() -> RealtimeNowSource { RealtimeNowSource { } }
}
impl NowSource for RealtimeNowSource {
fn now(&mut self) -> Instant { Instant::now() }
fn sleep(&mut self, how_long: Duration) { std::thread::sleep(how_long) }
}
#[derive(Debug,Copy,Clone)]
pub struct Metronome<N: NowSource = RealtimeNowSource> {
now_source: N,
epoch: Instant,
now: Instant,
ticks_per_second: (u32, u32),
max_ticks_behind: u32,
last_tick_no: u64,
rendered_this_tick: bool,
rendered_this_sample: bool,
return_idle: bool,
paused: bool,
}
#[derive(Clone,Copy,Debug,PartialEq)]
pub enum Status {
Tick,
Frame {
phase: f32
},
Idle,
TimeWentBackwards,
TicksLost(u64),
Rollover,
}
#[derive(Clone,Copy,Debug,PartialEq)]
pub enum Mode {
TickOnly,
MaxOneFramePerTick,
UnlimitedFrames,
}
impl Mode {
fn cares_about_subticks(&self) -> bool {
*self != Mode::TickOnly
}
}
impl<N: NowSource> Metronome<N> {
pub fn new(mut now_source: N,
ticks_per_second: (u32, u32),
max_ticks_behind: u32) -> Metronome<N> {
assert_ne!(ticks_per_second.0, 0);
assert_ne!(ticks_per_second.1, 0);
let epoch = now_source.now();
Metronome {
now_source,
epoch,
now: epoch,
ticks_per_second,
max_ticks_behind,
last_tick_no: 0,
rendered_this_tick: false,
rendered_this_sample: false,
return_idle: true,
paused: false,
}
}
pub fn sample(&mut self) {
self.now = self.now_source.now();
self.rendered_this_sample = false;
self.return_idle = true;
}
fn advance_epoch(&mut self) {
self.epoch += Duration::new(self.last_tick_no, 0)
* self.ticks_per_second.1 / self.ticks_per_second.0;
self.last_tick_no = 0;
}
fn rollover(&mut self) {
if self.last_tick_no == 0 {
unreachable!();
}
self.advance_epoch()
}
pub fn status(&mut self, mode: Mode) -> Option<Status> {
if self.now < self.epoch {
self.epoch = self.now;
self.last_tick_no = 0;
return Some(Status::TimeWentBackwards)
}
let time_since_epoch = self.now - self.epoch;
let duration_since_epoch = match time_since_epoch.checked_mul(self.ticks_per_second.0) {
Some(x) => x,
None => {
self.rollover();
return Some(Status::Rollover)
},
} / self.ticks_per_second.1;
let (ticks_since_epoch, subsec) = if duration_since_epoch.subsec_nanos() == 0 && mode.cares_about_subticks() {
(duration_since_epoch.as_secs().saturating_sub(1), 1000000000)
}
else {
(duration_since_epoch.as_secs(),
duration_since_epoch.subsec_nanos())
};
if ticks_since_epoch < self.last_tick_no {
self.last_tick_no = ticks_since_epoch;
return Some(Status::TimeWentBackwards)
}
let ticks_since_last = ticks_since_epoch - self.last_tick_no;
if ticks_since_last > self.max_ticks_behind as u64 {
let lost_ticks = ticks_since_last - 1;
self.last_tick_no += lost_ticks;
return Some(Status::TicksLost(lost_ticks))
}
else if ticks_since_last > 0 {
self.last_tick_no += 1;
self.rendered_this_tick = false;
self.return_idle = false;
if !self.paused {
return Some(Status::Tick)
}
}
match mode {
Mode::TickOnly => {
if self.return_idle {
self.return_idle = false;
Some(Status::Idle)
}
else { None }
},
Mode::MaxOneFramePerTick => {
if self.rendered_this_tick {
if self.return_idle {
self.return_idle = false;
Some(Status::Idle)
}
else { None }
}
else {
self.rendered_this_tick = true;
self.return_idle = false;
Some(Status::Frame { phase: 1.0 })
}
},
Mode::UnlimitedFrames => {
if self.rendered_this_sample { None }
else {
self.rendered_this_sample = true;
self.return_idle = false;
if self.paused {
Some(Status::Frame { phase: 1.0 })
}
else {
Some(Status::Frame { phase: (subsec as f32) / 1.0e9 })
}
}
}
}
}
pub fn amount_to_sleep_until_next_tick(&mut self) -> Option<Duration> {
let duration_from_epoch_until_next_tick
= match Duration::new(self.last_tick_no+1, 0)
.checked_mul(self.ticks_per_second.1) {
Some(x) => x / self.ticks_per_second.0,
None => {
self.rollover();
Duration::new(1, 0) * self.ticks_per_second.1
/ self.ticks_per_second.0
}
};
let moment_of_next_tick = self.epoch + duration_from_epoch_until_next_tick;
if moment_of_next_tick > self.now {
Some(moment_of_next_tick - self.now)
}
else {
None
}
}
pub fn sleep_until_next_tick(&mut self) {
match self.amount_to_sleep_until_next_tick() {
None => (),
Some(x) => self.now_source.sleep(x)
}
}
pub fn set_paused(&mut self, paused: bool) { self.paused = paused }
pub fn set_tickrate(&mut self, ticks_per_second: (u32, u32)) {
if ticks_per_second != self.ticks_per_second {
self.advance_epoch();
debug_assert_eq!(self.last_tick_no, 0);
self.ticks_per_second = ticks_per_second;
}
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use super::*;
#[derive(Debug)]
enum TestCmd {
SetNow(u64, u32),
StatusWithMode(Option<Status>, Mode),
SetTickrate(u32, u32),
SetPaused(bool),
AmountToSleep(u64, u32),
ShouldNotSleep,
}
use TestCmd::*;
#[derive(Debug,Copy,Clone)]
struct TestNowSource {
epoch: Instant,
now: Instant,
}
impl TestNowSource {
pub fn new() -> TestNowSource {
let now = Instant::now();
TestNowSource { epoch: now, now }
}
pub fn set_now(&mut self, delta: Duration) {
self.now = self.epoch.checked_add(delta).unwrap();
}
}
impl NowSource for TestNowSource {
fn now(&mut self) -> Instant { self.now }
}
impl NowSource for &RefCell<TestNowSource> {
fn now(&mut self) -> Instant { self.borrow().now }
}
fn run_test(tps: (u32, u32), max_ticks_behind: u32, cmds: &[TestCmd]) {
let now_source = RefCell::new(TestNowSource::new());
let mut metronome = Metronome::new(&now_source, tps, max_ticks_behind);
let mut bad = None;
for n in 0..cmds.len() {
let cmd = &cmds[n];
match cmd {
SetNow(sec, nsec) => {
now_source.borrow_mut().set_now(Duration::new(*sec,*nsec));
metronome.sample();
},
StatusWithMode(status, mode) => {
let check = metronome.status(*mode);
if status != &check {
let ok = if let Some(Status::Frame{phase}) = *status {
if let Some(Status::Frame{phase:check_phase}) = check {
(phase - check_phase).abs() < 0.0001
}
else { false }
} else { false };
if !ok {
bad = Some((n, format!("expected {:?}, got {:?}", status, check)));
break;
}
}
},
AmountToSleep(sec, nsec) => {
let duration = Some(Duration::new(*sec, *nsec));
let check = metronome.amount_to_sleep_until_next_tick();
if duration != check {
bad = Some((n, format!("expected {:?}, got {:?}", duration, check)));
break;
}
},
ShouldNotSleep => {
let check = metronome.amount_to_sleep_until_next_tick();
if None != check {
bad = Some((n, format!("expected None, got {:?}", check)));
break;
}
},
SetTickrate(num, den) => {
metronome.set_tickrate((*num, *den));
},
SetPaused(paused) => { metronome.set_paused(*paused) },
}
}
if let Some((index, explanation)) = bad {
eprintln!("Test failed!");
for n in index.saturating_sub(10) .. index {
eprintln!("OK\t[{}] = {:?}", n, cmds[n]);
}
eprintln!("BAD\t[{}] = {:?}", index, cmds[index]);
eprintln!("{}", explanation);
panic!("Test failed!");
}
}
#[test]
fn simple() {
run_test((5, 1), 10, &[
AmountToSleep(0, 200000000),
SetNow(1, 0),
ShouldNotSleep,
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::UnlimitedFrames),
StatusWithMode(None, Mode::UnlimitedFrames),
SetNow(2, 0),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(None, Mode::TickOnly),
SetNow(2, 100000000),
StatusWithMode(Some(Status::Frame{phase:0.5}), Mode::UnlimitedFrames),
StatusWithMode(None, Mode::UnlimitedFrames),
SetNow(2, 200000000),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(None, Mode::TickOnly),
StatusWithMode(Some(Status::TimeWentBackwards), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::UnlimitedFrames),
StatusWithMode(None, Mode::UnlimitedFrames),
SetNow(2, 400000000),
StatusWithMode(Some(Status::Tick), Mode::MaxOneFramePerTick),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::MaxOneFramePerTick),
StatusWithMode(None, Mode::MaxOneFramePerTick),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::UnlimitedFrames),
StatusWithMode(None, Mode::MaxOneFramePerTick),
StatusWithMode(None, Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
SetNow(4, 400000000),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
SetNow(6, 600000000),
StatusWithMode(Some(Status::TicksLost(10)), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(None, Mode::TickOnly),
SetNow(6, 0),
StatusWithMode(Some(Status::TimeWentBackwards), Mode::TickOnly),
StatusWithMode(Some(Status::Idle), Mode::TickOnly),
StatusWithMode(None, Mode::TickOnly),
SetNow(6, 200000000),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::MaxOneFramePerTick),
StatusWithMode(None, Mode::MaxOneFramePerTick),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::UnlimitedFrames),
StatusWithMode(None, Mode::MaxOneFramePerTick),
]);
}
#[test]
fn rollover() {
run_test((0x10000, 1), 10, &[
SetNow(0, 0),
StatusWithMode(Some(Status::Idle), Mode::TickOnly),
StatusWithMode(None, Mode::TickOnly),
SetNow(0x100000000, 0),
StatusWithMode(Some(Status::TicksLost(0x1000000000000-1)), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(None, Mode::TickOnly),
SetNow(0x1000000000000, 0),
StatusWithMode(Some(Status::Rollover), Mode::TickOnly),
StatusWithMode(Some(Status::TicksLost((0x1000000000000000-0x100000000000)*16-1)), Mode::TickOnly),
StatusWithMode(Some(Status::Tick), Mode::TickOnly),
StatusWithMode(None, Mode::TickOnly),
]);
}
#[test] #[should_panic]
fn rollover_crash() {
run_test((0x10000, 1), 10, &[
SetNow(0, 0),
StatusWithMode(None, Mode::TickOnly),
SetNow(0x1000000000000, 0),
StatusWithMode(None, Mode::TickOnly),
]);
}
#[test]
fn ntsc() {
run_test((60000, 1001), 120, &[
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::UnlimitedFrames),
SetNow(0, 500000000),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Frame{phase:(30.0/1.001)-29.0}), Mode::UnlimitedFrames),
AmountToSleep(0, 500000),
SetNow(1, 0),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Frame{phase:(60.0/1.001)-59.0}), Mode::UnlimitedFrames),
SetNow(1, 250000000),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Frame{phase:(75.0/1.001)-74.0}), Mode::UnlimitedFrames),
SetNow(1, 375000000),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Frame{phase:(82.5/1.001)-82.0}), Mode::UnlimitedFrames),
]);
}
#[test]
fn vtvf() {
run_test((5, 1), 10, &[
SetNow(0, 400000000),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::UnlimitedFrames),
SetNow(0, 600000000),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::UnlimitedFrames),
SetTickrate(10, 1),
SetNow(0, 800000000),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Tick), Mode::UnlimitedFrames),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::UnlimitedFrames),
]);
}
#[test]
fn pause() {
run_test((5, 1), 10, &[
SetNow(0, 400000000),
StatusWithMode(Some(Status::Tick), Mode::MaxOneFramePerTick),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::MaxOneFramePerTick),
StatusWithMode(None, Mode::MaxOneFramePerTick),
SetNow(0, 600000000),
StatusWithMode(Some(Status::Tick), Mode::MaxOneFramePerTick),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::MaxOneFramePerTick),
StatusWithMode(None, Mode::MaxOneFramePerTick),
SetPaused(true),
SetNow(0, 800000000),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::MaxOneFramePerTick),
StatusWithMode(None, Mode::MaxOneFramePerTick),
SetNow(1, 0),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::MaxOneFramePerTick),
StatusWithMode(None, Mode::MaxOneFramePerTick),
SetPaused(false),
StatusWithMode(None, Mode::MaxOneFramePerTick),
SetNow(1, 200000000),
StatusWithMode(Some(Status::Tick), Mode::MaxOneFramePerTick),
StatusWithMode(Some(Status::Frame{phase:1.0}), Mode::MaxOneFramePerTick),
StatusWithMode(None, Mode::MaxOneFramePerTick),
]);
}
}