1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
use crate::*;

use riddle_common::define_handles;

use std::sync::Mutex;

/// The Riddle time system core state.
///
/// Manages tracking framerate and timer state.
///
/// It is possible to manage the audio system state independantly - the most important
/// thing to note is that [`ext::TimeSystemExt::process_frame`] must be called once per
/// "frame". The default Riddle integration calls that method whenever the
/// `riddle::Event::ProcessFrame` event is fired.
pub struct TimeSystem {
    weak_self: TimeSystemWeak,
    frame_time: Mutex<FrameTime>,
    timers: TimerSet,
}

define_handles!(<TimeSystem>::weak_self, pub TimeSystemHandle, pub TimeSystemWeak);

impl TimeSystem {
    /// Get the current FPS as calculated based on previous frame durations.
    pub fn fps(&self) -> f32 {
        self.frame_time.lock().unwrap().fps
    }

    /// Get the current delta t as calculated based on previous frame durations.
    pub fn delta_secs(&self) -> f32 {
        1.0 / self.frame_time.lock().unwrap().fps
    }

    /// Get the reference time for this frame. Captured during [`ext::TimeSystemExt::process_frame`].
    pub fn frame_instant(&self) -> std::time::Instant {
        self.frame_time.lock().unwrap().frame_instant
    }

    /// Register a timer with a callback which will be fired when the time elpases.
    ///
    /// The returned handle may be dropped without cancelling the timer.
    ///
    /// # Example
    ///
    /// ```
    /// # use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
    /// # use riddle_time::*; doctest::simple(|time_system| {
    /// let val = Arc::new(AtomicBool::new(false));
    ///
    /// time_system.register_timer(std::time::Duration::from_millis(200), {
    ///     let val = val.clone();
    ///     move || { val.store(true, Ordering::Relaxed); }
    /// });
    ///
    /// // A while later
    /// # doctest::pump_for_secs(time_system, 1);
    /// assert_eq!(true, val.load(Ordering::Relaxed));
    /// # });
    /// ```
    pub fn register_timer<F>(&self, duration: std::time::Duration, callback: F) -> TimerHandle
    where
        F: FnOnce() + Send + 'static,
    {
        self.timers.register_timer(duration, Box::new(callback))
    }
}

impl ext::TimeSystemExt for TimeSystem {
    fn new_shared() -> TimeSystemHandle {
        TimeSystemHandle::new(|weak_self| Self {
            weak_self,
            frame_time: Mutex::new(FrameTime::new()),
            timers: TimerSet::new(),
        })
    }

    fn process_frame(&self) {
        let mut locked_time = self.frame_time.lock().unwrap();
        locked_time.update();
        let delta = locked_time.frame_delta;
        drop(locked_time);

        self.timers.update(delta);
    }
}

struct FrameTime {
    frame_instant: std::time::Instant,
    frame_delta: std::time::Duration,
    fps: f32,
}

impl FrameTime {
    fn new() -> Self {
        Self {
            frame_instant: std::time::Instant::now(),
            frame_delta: Default::default(),
            fps: 0.0,
        }
    }

    fn update(&mut self) {
        let now = std::time::Instant::now();
        self.frame_delta = now.duration_since(self.frame_instant);
        self.fps = 1.0 / self.frame_delta.as_secs_f32().max(0.0001);
        self.frame_instant = now;
    }
}