use std::sync::Arc;
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()
}
}
pub type SharedClock = Arc<dyn MasterClock>;
#[derive(Clone, Copy, Debug)]
pub struct Monotonic {
started: Instant,
}
impl Monotonic {
pub fn new() -> Self {
Monotonic {
started: Instant::now(),
}
}
pub fn shared() -> SharedClock {
Arc::new(Monotonic::new())
}
}
impl Default for Monotonic {
fn default() -> Self {
Monotonic::new()
}
}
impl MasterClock for Monotonic {
fn position(&self) -> Duration {
self.started.elapsed()
}
}
#[cfg(feature = "atome")]
impl MasterClock for atome::PlayClock {
fn position(&self) -> Duration {
atome::PlayClock::position(self)
}
fn is_running(&self) -> bool {
atome::PlayClock::is_running(self)
}
}
#[derive(Clone)]
pub struct Follower {
master: SharedClock,
from: Duration,
master_at: Option<Duration>,
}
impl std::fmt::Debug for Follower {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("Follower")
.field("position", &self.position())
.field("running", &self.master_at.is_some())
.finish()
}
}
impl Follower {
pub fn new(master: SharedClock) -> Self {
Follower {
master,
from: Duration::ZERO,
master_at: None,
}
}
pub fn starting_at(master: SharedClock, master_position: Duration) -> Self {
Follower {
master,
from: Duration::ZERO,
master_at: Some(master_position),
}
}
pub fn master(&self) -> &SharedClock {
&self.master
}
pub fn play(&mut self) {
if self.master_at.is_none() {
self.master_at = Some(self.master.position());
}
}
pub fn pause(&mut self) {
if self.master_at.is_some() {
self.from = self.position();
self.master_at = None;
}
}
pub fn seek(&mut self, position: Duration) {
self.from = position;
if self.master_at.is_some() {
self.master_at = Some(self.master.position());
}
}
pub fn is_playing(&self) -> bool {
self.master_at.is_some()
}
pub fn is_due(&self) -> bool {
self.master_at
.is_none_or(|at| self.master.position() >= at)
}
}
impl MasterClock for Follower {
fn position(&self) -> Duration {
match self.master_at {
Some(at) => self.from + self.master.position().saturating_sub(at),
None => self.from,
}
}
fn is_running(&self) -> bool {
self.master_at.is_some() && self.master.is_running()
}
}
#[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);
}
#[derive(Debug, Default)]
struct HandClock {
nanos: std::sync::atomic::AtomicU64,
stopped: std::sync::atomic::AtomicBool,
}
impl HandClock {
fn set(&self, position: Duration) {
self.nanos
.store(position.as_nanos() as u64, std::sync::atomic::Ordering::Relaxed);
}
}
impl MasterClock for HandClock {
fn position(&self) -> Duration {
Duration::from_nanos(self.nanos.load(std::sync::atomic::Ordering::Relaxed))
}
fn is_running(&self) -> bool {
!self.stopped.load(std::sync::atomic::Ordering::Relaxed)
}
}
#[test]
fn a_follower_measures_its_own_play_and_pause_on_the_master() {
let master = Arc::new(HandClock::default());
master.set(Duration::from_secs(100));
let mut clip = Follower::new(master.clone());
assert_eq!(clip.position(), Duration::ZERO, "paused at zero");
clip.play();
master.set(Duration::from_secs(103));
assert_eq!(clip.position(), Duration::from_secs(3));
clip.pause();
master.set(Duration::from_secs(110));
assert_eq!(clip.position(), Duration::from_secs(3), "paused clips hold");
clip.play();
master.set(Duration::from_secs(112));
assert_eq!(clip.position(), Duration::from_secs(5));
clip.seek(Duration::from_secs(60));
master.set(Duration::from_secs(113));
assert_eq!(clip.position(), Duration::from_secs(61));
}
#[test]
fn clips_on_one_master_stay_together() {
let master = Arc::new(HandClock::default());
let mut first = Follower::new(master.clone());
let mut second = Follower::new(master.clone());
first.play();
master.set(Duration::from_millis(500));
second.play();
master.set(Duration::from_millis(10_500));
assert_eq!(first.position() - second.position(), Duration::from_millis(500));
}
#[test]
fn a_stopped_master_stops_its_followers() {
let master = Arc::new(HandClock::default());
let mut clip = Follower::new(master.clone());
clip.play();
assert!(clip.is_running());
master.stopped.store(true, std::sync::atomic::Ordering::Relaxed);
assert!(!clip.is_running());
}
#[test]
fn a_clip_started_at_a_point_on_the_master_waits_for_it() {
let master = Arc::new(HandClock::default());
master.set(Duration::from_secs(5));
let clip = Follower::starting_at(master.clone(), Duration::from_secs(8));
assert!(!clip.is_due());
assert_eq!(clip.position(), Duration::ZERO);
master.set(Duration::from_secs(9));
assert!(clip.is_due());
assert_eq!(clip.position(), Duration::from_secs(1));
}
#[test]
fn the_monotonic_master_runs_from_the_moment_it_is_made() {
let master = Monotonic::shared();
std::thread::sleep(Duration::from_millis(5));
assert!(master.position() >= Duration::from_millis(5));
assert!(master.is_running());
}
#[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());
}
}