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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//! Implementation of a tickloop according to <http://www.koonsolo.com/news/dewitters-gameloop/>

use super::*;

/// Implementation of a tickloop according to <http://www.koonsolo.com/news/dewitters-gameloop/>
pub struct TickLoopState {
    /// Start.
    start: Instant,
    
    /// The amount of ticks to attempt per second.
    ticks_per_second: u32,

    /// The normal duration between individual ticks.
    tick_duration: Duration,

    /// The normal duration between individual tocks.
    tock_duration: Duration,

    /// How many frames can be skipped when catching up with lost ticks.
    max_frameskip: i32,

    /// Counter of catch-up ticks per frame.
    loops: i32,

    /// The moment when the next tick happens.
    next_game_tick: Instant,

    /// The moment when the next tock happens.
    next_game_tock: Instant,

    /// The time when the last tick finished.
    last_tick_time: Instant,

    /// The time when the last tock occurred.
    last_tock_time: Instant,
    
    /// The x-per-second counter for ticks.
    tick_count: u32,

    /// Total number of ticks the gameloop went trough.
    total_ticks: u64,

    /// Total number of tocks the gameloop went trough.
    total_tocks: u64,
}

/// Event that is spawned when a tick occurs.
#[derive(Debug)]
pub struct TickLoopEvent {
    /// The targeted tick-rate.
    pub target_tickrate: u32,
    
    /// The current point in time.
    pub time: Instant,
    
    /// The intended tick [`Duration`].
    pub duration: Duration,
    
    /// The total number of ticks the gameloop went trough.
    pub ticks: u64,
}

/// Event that is spawned when a tock occurs (eg: once per second).
#[derive(Debug)]
pub struct TockLoopEvent {
    /// The targeted tick-rate.
    pub target_tickrate: u32,
    
    /// The current point in time.
    pub time: Instant,
    
    /// The intended tick [`Duration`].
    pub duration: Duration,
    
    /// The total number of ticks the gameloop went trough.
    pub ticks: u64,
    
    /// The total number of tocks the gameloop went trough.
    pub tocks: u64,
    
    /// The time of the last tock to occur.
    pub last_tock: Instant,
    
    /// The average tick-rate (TPS).
    pub average_tickrate: f64,
}

impl TickLoopState {
    /// Creates a new tickloop with the given target tick-rate.
    pub fn new(ticks_per_second: u32) -> Self {
        Self {
            start: Instant::now(),
            ticks_per_second,
            tick_duration: Duration::from_secs(1) / ticks_per_second,
            tock_duration: Duration::from_secs(1),
            max_frameskip: 1,
            loops: 0,
            next_game_tick: Instant::now(),
            next_game_tock: Instant::now(),
            last_tick_time: Instant::now(),
            last_tock_time: Instant::now(),
            tick_count: 0,
            total_ticks: 0,
            total_tocks: 0,
        }
    }
    
    /// Resets the tickloop.
    pub fn pre(&mut self) {
        self.loops = 0;
    }
    
    /// Attempt to do a tick (at current tickrate per second), returning `true` if one happened.
    pub fn tick<F: FnOnce(&mut TickLoopEvent)>(&mut self, current_time: Instant, function: F) -> bool {
        
        if (current_time > self.next_game_tick) && (self.loops < self.max_frameskip) {
            function(&mut TickLoopEvent {
                target_tickrate: self.ticks_per_second,
                time: current_time,
                duration: self.tick_duration,
                ticks: self.total_ticks,
            });
            
            self.last_tick_time = current_time;
            self.next_game_tick += self.tick_duration;
            self.loops += 1;
            self.tick_count += 1;
            self.total_ticks += 1;
            
            return true;
        }
        
        false
    }
    
    /// Attempt to do a tock (once per second), returning `true` if one happened.
    pub fn tock<F: FnOnce(&mut TockLoopEvent)>(&mut self, current_time: Instant, function: F) -> bool {
        
        let time_since_tock = current_time.duration_since(self.last_tock_time).as_secs_f64();
        
        if self.next_game_tock <= current_time {
            let hertz_avg = self.tick_count as f64 / time_since_tock;
            
            function(&mut TockLoopEvent {
                target_tickrate: self.ticks_per_second,
                time: current_time,
                duration: self.tick_duration,
                last_tock: self.last_tock_time,
                average_tickrate: hertz_avg,
                ticks: self.total_ticks,
                tocks: self.total_tocks,
            });
            
            self.tick_count = 0;
            self.total_tocks += 1;
            self.last_tock_time = current_time;
            self.next_game_tock = current_time + self.tock_duration;
            return true;
        }
        
        false
    }
    
    /// Returns the interpolation factor between the previous and next tick, for smooth rendering.
    pub fn interpolation(&self, current_time: Instant) -> f64 {
        let delta = current_time - self.next_game_tick;
        (delta + self.tick_duration).as_secs_f64() / self.tick_duration.as_secs_f64()
    }
    
    /// Returns the intended minimum duration of a single tick.
    pub fn get_minimum_tick_duration(&self) -> Duration {
        self.tick_duration
    }
    
    /// Returns when the tickloop was created.
    pub fn get_start(&self) -> Instant {
        self.start
    }
}