use std::time::{Duration, Instant};
pub trait MasterClock: Send + Sync {
fn position(&self) -> Duration;
fn is_running(&self) -> bool {
true
}
}
#[derive(Debug)]
pub struct Clock {
anchor_position: Duration,
anchor_instant: Option<Instant>,
rate: f64,
}
impl Default for Clock {
fn default() -> Self {
Clock::new()
}
}
impl Clock {
pub fn new() -> Self {
Clock {
anchor_position: Duration::ZERO,
anchor_instant: None,
rate: 1.0,
}
}
pub fn play(&mut self) {
if self.anchor_instant.is_none() {
self.anchor_instant = Some(Instant::now());
}
}
pub fn pause(&mut self) {
if self.anchor_instant.is_some() {
self.anchor_position = self.position();
self.anchor_instant = None;
}
}
pub fn is_running(&self) -> bool {
self.anchor_instant.is_some()
}
pub fn seek(&mut self, position: Duration) {
self.anchor_position = position;
if self.anchor_instant.is_some() {
self.anchor_instant = Some(Instant::now());
}
}
pub fn set_rate(&mut self, rate: f64) {
if self.anchor_instant.is_some() {
self.anchor_position = self.position();
self.anchor_instant = Some(Instant::now());
}
self.rate = rate.clamp(0.01, 16.0);
}
pub fn rate(&self) -> f64 {
self.rate
}
}
impl MasterClock for Clock {
fn position(&self) -> Duration {
match self.anchor_instant {
Some(started) => self.anchor_position + started.elapsed().mul_f64(self.rate),
None => self.anchor_position,
}
}
fn is_running(&self) -> bool {
self.anchor_instant.is_some()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Action {
Present,
Wait(Duration),
Drop,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Pacing {
tolerance: Duration,
presented: u64,
dropped: u64,
repeated: u64,
}
impl Pacing {
pub fn for_frame_rate(frames_per_second: f64) -> Self {
let interval = if frames_per_second > 0.0 {
Duration::from_secs_f64(1.0 / frames_per_second)
} else {
Duration::from_millis(40)
};
Pacing {
tolerance: interval / 2,
..Pacing::default()
}
}
pub fn with_tolerance(tolerance: Duration) -> Self {
Pacing {
tolerance,
..Pacing::default()
}
}
pub fn decide(&mut self, frame_pts: Duration, now: Duration) -> Action {
if frame_pts > now {
let wait = frame_pts - now;
if wait <= self.tolerance {
self.presented += 1;
return Action::Present;
}
return Action::Wait(wait - self.tolerance);
}
let late = now - frame_pts;
if late > self.tolerance {
self.dropped += 1;
return Action::Drop;
}
self.presented += 1;
Action::Present
}
pub fn note_repeat(&mut self) {
self.repeated += 1;
}
pub fn counts(&self) -> (u64, u64, u64) {
(self.presented, self.dropped, self.repeated)
}
pub fn drop_rate(&self) -> f64 {
let total = self.presented + self.dropped;
if total == 0 {
return 0.0;
}
self.dropped as f64 / total as f64
}
pub fn reset(&mut self) {
self.presented = 0;
self.dropped = 0;
self.repeated = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Default)]
struct FixedClock(Duration);
impl MasterClock for FixedClock {
fn position(&self) -> Duration {
self.0
}
}
#[test]
fn a_new_clock_is_paused_at_zero() {
let clock = Clock::new();
assert_eq!(clock.position(), Duration::ZERO);
assert!(!clock.is_running());
}
#[test]
fn pausing_keeps_the_position_and_stops_it_advancing() {
let mut clock = Clock::new();
clock.seek(Duration::from_secs(5));
clock.play();
std::thread::sleep(Duration::from_millis(20));
clock.pause();
let paused_at = clock.position();
assert!(paused_at >= Duration::from_secs(5));
std::thread::sleep(Duration::from_millis(20));
assert_eq!(clock.position(), paused_at, "a paused clock moved");
}
#[test]
fn pausing_twice_does_not_rewind() {
let mut clock = Clock::new();
clock.play();
std::thread::sleep(Duration::from_millis(20));
clock.pause();
let first = clock.position();
clock.pause();
assert_eq!(clock.position(), first);
}
#[test]
fn rate_changes_do_not_lose_the_position() {
let mut clock = Clock::new();
clock.seek(Duration::from_secs(10));
clock.play();
std::thread::sleep(Duration::from_millis(10));
clock.set_rate(2.0);
assert!(clock.position() >= Duration::from_secs(10));
assert_eq!(clock.rate(), 2.0);
clock.set_rate(0.0);
assert!(clock.rate() > 0.0, "zero rate is what pause is for");
}
#[test]
fn a_frame_due_now_is_presented() {
let mut pacing = Pacing::for_frame_rate(24.0);
let action = pacing.decide(Duration::from_millis(100), Duration::from_millis(100));
assert_eq!(action, Action::Present);
assert_eq!(pacing.counts(), (1, 0, 0));
}
#[test]
fn a_frame_from_the_future_asks_the_caller_to_wait() {
let mut pacing = Pacing::with_tolerance(Duration::from_millis(5));
let action = pacing.decide(Duration::from_millis(200), Duration::from_millis(100));
assert_eq!(action, Action::Wait(Duration::from_millis(95)));
assert_eq!(pacing.counts(), (0, 0, 0), "waiting is not presenting");
}
#[test]
fn a_frame_too_late_to_matter_is_dropped() {
let mut pacing = Pacing::for_frame_rate(24.0);
let action = pacing.decide(Duration::from_millis(100), Duration::from_millis(500));
assert_eq!(action, Action::Drop);
assert_eq!(pacing.counts(), (0, 1, 0));
assert_eq!(pacing.drop_rate(), 1.0);
}
#[test]
fn a_frame_barely_early_is_presented_rather_than_waited_for() {
let mut pacing = Pacing::for_frame_rate(24.0);
let action = pacing.decide(Duration::from_millis(101), Duration::from_millis(100));
assert_eq!(action, Action::Present);
}
#[test]
fn counters_survive_until_they_are_reset() {
let mut pacing = Pacing::for_frame_rate(24.0);
pacing.decide(Duration::ZERO, Duration::ZERO);
pacing.decide(Duration::ZERO, Duration::from_secs(1));
pacing.note_repeat();
assert_eq!(pacing.counts(), (1, 1, 1));
assert!((pacing.drop_rate() - 0.5).abs() < 1e-9);
pacing.reset();
assert_eq!(pacing.counts(), (0, 0, 0));
assert_eq!(pacing.drop_rate(), 0.0);
}
#[test]
fn pacing_follows_whatever_master_it_is_given() {
let master = FixedClock(Duration::from_millis(500));
let mut pacing = Pacing::for_frame_rate(24.0);
assert_eq!(
pacing.decide(Duration::from_millis(500), master.position()),
Action::Present
);
assert_eq!(
pacing.decide(Duration::from_millis(100), master.position()),
Action::Drop
);
assert!(master.is_running());
}
}