gameloop_timing/
tickloop.rs

1//! Implementation of a tickloop according to <http://www.koonsolo.com/news/dewitters-gameloop/>
2
3use super::*;
4
5/// Implementation of a tickloop according to <http://www.koonsolo.com/news/dewitters-gameloop/>
6pub struct TickLoopState {
7    /// Start.
8    start: Instant,
9    
10    /// The amount of ticks to attempt per second.
11    ticks_per_second: u32,
12
13    /// The normal duration between individual ticks.
14    tick_duration: Duration,
15
16    /// The normal duration between individual tocks.
17    tock_duration: Duration,
18
19    /// How many frames can be skipped when catching up with lost ticks.
20    max_frameskip: i32,
21
22    /// Counter of catch-up ticks per frame.
23    loops: i32,
24
25    /// The moment when the next tick happens.
26    next_game_tick: Instant,
27
28    /// The moment when the next tock happens.
29    next_game_tock: Instant,
30
31    /// The time when the last tick finished.
32    last_tick_time: Instant,
33
34    /// The time when the last tock occurred.
35    last_tock_time: Instant,
36    
37    /// The x-per-second counter for ticks.
38    tick_count: u32,
39
40    /// Total number of ticks the gameloop went trough.
41    total_ticks: u64,
42
43    /// Total number of tocks the gameloop went trough.
44    total_tocks: u64,
45}
46
47/// Event that is spawned when a tick occurs.
48#[derive(Debug)]
49pub struct TickLoopEvent {
50    /// The targeted tick-rate.
51    pub target_tickrate: u32,
52    
53    /// The current point in time.
54    pub time: Instant,
55    
56    /// The intended tick [`Duration`].
57    pub duration: Duration,
58    
59    /// The total number of ticks the gameloop went trough.
60    pub ticks: u64,
61}
62
63/// Event that is spawned when a tock occurs (eg: once per second).
64#[derive(Debug)]
65pub struct TockLoopEvent {
66    /// The targeted tick-rate.
67    pub target_tickrate: u32,
68    
69    /// The current point in time.
70    pub time: Instant,
71    
72    /// The intended tick [`Duration`].
73    pub duration: Duration,
74    
75    /// The total number of ticks the gameloop went trough.
76    pub ticks: u64,
77    
78    /// The total number of tocks the gameloop went trough.
79    pub tocks: u64,
80    
81    /// The time of the last tock to occur.
82    pub last_tock: Instant,
83    
84    /// The average tick-rate (TPS).
85    pub average_tickrate: f64,
86}
87
88impl TickLoopState {
89    /// Creates a new tickloop with the given target tick-rate.
90    pub fn new(ticks_per_second: u32) -> Self {
91        Self {
92            start: Instant::now(),
93            ticks_per_second,
94            tick_duration: Duration::from_secs(1) / ticks_per_second,
95            tock_duration: Duration::from_secs(1),
96            max_frameskip: 1,
97            loops: 0,
98            next_game_tick: Instant::now(),
99            next_game_tock: Instant::now(),
100            last_tick_time: Instant::now(),
101            last_tock_time: Instant::now(),
102            tick_count: 0,
103            total_ticks: 0,
104            total_tocks: 0,
105        }
106    }
107    
108    /// Resets the tickloop.
109    pub fn pre(&mut self) {
110        self.loops = 0;
111    }
112    
113    /// Attempt to do a tick (at current tickrate per second), returning `true` if one happened.
114    pub fn tick<F: FnOnce(&mut TickLoopEvent)>(&mut self, current_time: Instant, function: F) -> bool {
115        
116        if (current_time > self.next_game_tick) && (self.loops < self.max_frameskip) {
117            function(&mut TickLoopEvent {
118                target_tickrate: self.ticks_per_second,
119                time: current_time,
120                duration: self.tick_duration,
121                ticks: self.total_ticks,
122            });
123            
124            self.last_tick_time = current_time;
125            self.next_game_tick += self.tick_duration;
126            self.loops += 1;
127            self.tick_count += 1;
128            self.total_ticks += 1;
129            
130            return true;
131        }
132        
133        false
134    }
135    
136    /// Attempt to do a tock (once per second), returning `true` if one happened.
137    pub fn tock<F: FnOnce(&mut TockLoopEvent)>(&mut self, current_time: Instant, function: F) -> bool {
138        
139        let time_since_tock = current_time.duration_since(self.last_tock_time).as_secs_f64();
140        
141        if self.next_game_tock <= current_time {
142            let hertz_avg = self.tick_count as f64 / time_since_tock;
143            
144            function(&mut TockLoopEvent {
145                target_tickrate: self.ticks_per_second,
146                time: current_time,
147                duration: self.tick_duration,
148                last_tock: self.last_tock_time,
149                average_tickrate: hertz_avg,
150                ticks: self.total_ticks,
151                tocks: self.total_tocks,
152            });
153            
154            self.tick_count = 0;
155            self.total_tocks += 1;
156            self.last_tock_time = current_time;
157            self.next_game_tock = current_time + self.tock_duration;
158            return true;
159        }
160        
161        false
162    }
163    
164    /// Returns the interpolation factor between the previous and next tick, for smooth rendering.
165    pub fn interpolation(&self, current_time: Instant) -> f64 {
166        let delta = current_time - self.next_game_tick;
167        (delta + self.tick_duration).as_secs_f64() / self.tick_duration.as_secs_f64()
168    }
169    
170    /// Returns the intended minimum duration of a single tick.
171    pub fn get_minimum_tick_duration(&self) -> Duration {
172        self.tick_duration
173    }
174    
175    /// Returns when the tickloop was created.
176    pub fn get_start(&self) -> Instant {
177        self.start
178    }
179}