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) -> instant::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: instant::Instant,
	frame_delta: instant::Duration,
	fps: f32,
}

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

	fn update(&mut self) {
		let now = instant::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;
	}
}