use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct Time {
start: Instant,
last_frame: Option<Instant>,
delta: f32,
raw_delta: f32,
elapsed: f64,
frame: u64,
scale: f32,
max_delta: f32,
fps: f32,
schedule: Vec<Scheduled>,
fired: Vec<String>,
}
#[derive(Debug)]
struct Scheduled {
name: String,
at: f64,
repeat: Option<f64>,
}
impl Default for Time {
fn default() -> Self {
Self::new()
}
}
impl Time {
pub(crate) fn new() -> Self {
Self {
start: Instant::now(),
last_frame: None,
delta: 0.0,
raw_delta: 0.0,
elapsed: 0.0,
frame: 0,
scale: 1.0,
max_delta: 0.25,
fps: 0.0,
schedule: Vec::new(),
fired: Vec::new(),
}
}
pub(crate) fn advance(&mut self, now: Instant) {
let raw = match self.last_frame {
Some(last) => now.duration_since(last).as_secs_f32(),
None => 0.0,
};
self.last_frame = Some(now);
self.raw_delta = raw.min(self.max_delta);
self.delta = self.raw_delta * self.scale;
self.elapsed += self.delta as f64;
self.frame += 1;
if raw > 0.0 {
let current = 1.0 / raw;
self.fps = if self.fps == 0.0 { current } else { self.fps * 0.9 + current * 0.1 };
}
self.fired.clear();
let now = self.elapsed;
let mut i = 0;
while i < self.schedule.len() {
if self.schedule[i].at <= now {
self.fired.push(self.schedule[i].name.clone());
match self.schedule[i].repeat {
Some(every) => self.schedule[i].at += every,
None => {
self.schedule.swap_remove(i);
continue;
}
}
}
i += 1;
}
}
pub fn delta(&self) -> f32 {
self.delta
}
pub fn real_delta(&self) -> f32 {
self.raw_delta
}
pub fn elapsed(&self) -> f64 {
self.elapsed
}
pub fn real_elapsed(&self) -> Duration {
self.start.elapsed()
}
pub fn frame(&self) -> u64 {
self.frame
}
pub fn fps(&self) -> f32 {
self.fps
}
pub fn scale(&self) -> f32 {
self.scale
}
pub fn set_scale(&mut self, scale: f32) {
self.scale = scale.max(0.0);
}
pub fn pause(&mut self) {
self.scale = 0.0;
}
pub fn resume(&mut self) {
self.scale = 1.0;
}
pub fn is_paused(&self) -> bool {
self.scale == 0.0
}
pub fn after(&mut self, name: impl Into<String>, seconds: f32) {
let name = name.into();
self.cancel(&name);
self.schedule.push(Scheduled { at: self.elapsed + seconds as f64, name, repeat: None });
}
pub fn every(&mut self, name: impl Into<String>, seconds: f32) {
let name = name.into();
let seconds = (seconds as f64).max(0.001);
self.cancel(&name);
self.schedule.push(Scheduled { at: self.elapsed + seconds, name, repeat: Some(seconds) });
}
pub fn cancel(&mut self, name: &str) {
self.schedule.retain(|s| s.name != name);
}
pub fn is_scheduled(&self, name: &str) -> bool {
self.schedule.iter().any(|s| s.name == name)
}
pub fn remaining(&self, name: &str) -> Option<f32> {
self.schedule.iter().find(|s| s.name == name).map(|s| (s.at - self.elapsed).max(0.0) as f32)
}
pub fn fired(&self, name: &str) -> bool {
self.fired.iter().any(|n| n == name)
}
pub fn fired_events(&self) -> impl Iterator<Item = &str> {
self.fired.iter().map(String::as_str)
}
}
#[derive(Debug, Clone)]
pub struct Timer {
started: Instant,
offset: Duration,
paused_at: Option<Instant>,
}
impl Default for Timer {
fn default() -> Self {
Self::new()
}
}
impl Timer {
pub fn new() -> Self {
Self { started: Instant::now(), offset: Duration::ZERO, paused_at: None }
}
pub fn elapsed(&self) -> Duration {
let end = self.paused_at.unwrap_or_else(Instant::now);
end.duration_since(self.started) + self.offset
}
pub fn elapsed_secs(&self) -> f32 {
self.elapsed().as_secs_f32()
}
pub fn elapsed_ms(&self) -> u64 {
self.elapsed().as_millis() as u64
}
pub fn has_elapsed(&self, seconds: f32) -> bool {
self.elapsed_secs() >= seconds
}
pub fn restart(&mut self) {
*self = Self::new();
}
pub fn set(&mut self, seconds: f32) {
let now = Instant::now();
self.started = now;
self.offset = Duration::from_secs_f32(seconds.max(0.0));
if self.paused_at.is_some() {
self.paused_at = Some(now);
}
}
pub fn pause(&mut self) {
if self.paused_at.is_none() {
self.paused_at = Some(Instant::now());
}
}
pub fn resume(&mut self) {
if let Some(paused) = self.paused_at.take() {
self.started += Instant::now().duration_since(paused);
}
}
pub fn is_paused(&self) -> bool {
self.paused_at.is_some()
}
}
#[derive(Debug, Clone)]
pub struct Cooldown {
length: f32,
timer: Timer,
used: bool,
}
impl Cooldown {
pub fn new(seconds: f32) -> Self {
Self { length: seconds, timer: Timer::new(), used: false }
}
pub fn ready(&self) -> bool {
!self.used || self.timer.has_elapsed(self.length)
}
pub fn try_use(&mut self) -> bool {
if self.ready() {
self.trigger();
true
} else {
false
}
}
pub fn trigger(&mut self) {
self.used = true;
self.timer.restart();
}
pub fn reset(&mut self) {
self.used = false;
}
pub fn remaining(&self) -> f32 {
if self.ready() { 0.0 } else { self.length - self.timer.elapsed_secs() }
}
pub fn set_length(&mut self, seconds: f32) {
self.length = seconds;
}
}
#[derive(Debug, Clone)]
pub struct Interval {
every: f32,
timer: Timer,
}
impl Interval {
pub fn new(seconds: f32) -> Self {
Self { every: seconds.max(0.001), timer: Timer::new() }
}
pub fn tick(&mut self) -> bool {
let elapsed = self.timer.elapsed_secs();
if elapsed >= self.every {
self.timer.set((elapsed - self.every).min(self.every));
true
} else {
false
}
}
pub fn reset(&mut self) {
self.timer.restart();
}
pub fn set_interval(&mut self, seconds: f32) {
self.every = seconds.max(0.001);
}
pub fn interval(&self) -> f32 {
self.every
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scheduler_fires_once_and_repeats() {
let mut t = Time::new();
let base = Instant::now();
t.advance(base);
t.after("once", 0.1);
t.every("rep", 0.05);
t.advance(base + Duration::from_millis(60));
assert!(!t.fired("once"));
assert!(t.fired("rep"));
t.advance(base + Duration::from_millis(120));
assert!(t.fired("once"));
assert!(t.fired("rep"));
assert!(!t.is_scheduled("once"));
assert!(t.is_scheduled("rep"));
}
#[test]
fn scale_pauses_game_time() {
let mut t = Time::new();
let base = Instant::now();
t.advance(base);
t.pause();
t.advance(base + Duration::from_millis(100));
assert_eq!(t.delta(), 0.0);
assert!(t.real_delta() > 0.0);
}
#[test]
fn cooldown_blocks() {
let mut c = Cooldown::new(10.0);
assert!(c.try_use());
assert!(!c.try_use());
c.reset();
assert!(c.ready());
}
}