Skip to main content

pebble/
time.rs

1use std::time::Duration;
2use web_time::Instant;
3
4use crate::{
5    ecs::{plugin::Plugin, resources::Write, system::SystemStage},
6};
7
8/// Per-tick delta time and total elapsed time — inserted as a resource by
9/// [`TimePlugin`], updated once per tick on [`SystemStage::PreUpdate`].
10pub struct Time {
11    start: Instant,
12    last_tick: Instant,
13    delta: Duration,
14    elapsed: Duration,
15}
16
17impl Time {
18    fn new() -> Self {
19        let now = Instant::now();
20        Self { start: now, last_tick: now, delta: Duration::ZERO, elapsed: Duration::ZERO }
21    }
22
23    fn tick(&mut self) {
24        let now = Instant::now();
25        self.delta = now.duration_since(self.last_tick);
26        self.last_tick = now;
27        self.elapsed = now.duration_since(self.start);
28    }
29
30    pub fn delta(&self) -> Duration {
31        self.delta
32    }
33
34    pub fn delta_seconds(&self) -> f32 {
35        self.delta.as_secs_f32()
36    }
37
38    pub fn elapsed(&self) -> Duration {
39        self.elapsed
40    }
41
42    pub fn elapsed_seconds(&self) -> f32 {
43        self.elapsed.as_secs_f32()
44    }
45
46    pub fn fps(&self) -> f32 {
47        let seconds = self.delta_seconds();
48        if seconds > 0.0 { 1.0 / seconds } else { 0.0 }
49    }
50}
51
52fn tick_time(mut time: Write<Time>) {
53    time.tick();
54}
55
56/// Inserts [`Time`] and keeps it ticking every frame.
57pub struct TimePlugin;
58
59impl Plugin for TimePlugin {
60    fn build(self, app: crate::app::App) -> crate::app::App {
61        app.insert_resource(Time::new()).add_system(SystemStage::PreUpdate, tick_time)
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn a_fresh_time_has_zero_delta_and_elapsed() {
71        let time = Time::new();
72        assert_eq!(time.delta(), Duration::ZERO);
73        assert_eq!(time.elapsed(), Duration::ZERO);
74        assert_eq!(time.fps(), 0.0);
75    }
76
77    #[test]
78    fn ticking_advances_delta_and_accumulates_elapsed() {
79        let mut time = Time::new();
80        std::thread::sleep(Duration::from_millis(5));
81        time.tick();
82
83        assert!(time.delta_seconds() > 0.0);
84        assert!(time.elapsed_seconds() >= time.delta_seconds());
85        assert!(time.fps() > 0.0);
86
87        let first_elapsed = time.elapsed();
88        std::thread::sleep(Duration::from_millis(5));
89        time.tick();
90        assert!(time.elapsed() > first_elapsed);
91    }
92}