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 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
//! Timing and measurement functions. //! //! ggez does not try to do any framerate limitation by default. If //! you want to run at anything other than full-bore max speed all the //! time, call [`thread::yield_now()`](https://doc.rust-lang.org/std/thread/fn.yield_now.html) //! (or [`timer::yield_now()`](fn.yield_now.html) which does the same //! thing) to yield to the OS so it has a chance to breathe before continuing //! with your game. This should prevent it from using 100% CPU as much unless it //! really needs to. Enabling vsync by setting //! [`conf.window_setup.vsync`](../conf/struct.WindowSetup.html#structfield.vsync) //! in your [`Conf`](../conf/struct.Conf.html) object is generally the best //! way to cap your displayed framerate. //! //! For a more detailed tutorial in how to handle frame timings in games, //! see <http://gafferongames.com/game-physics/fix-your-timestep/> use crate::context::Context; use std::cmp; use std::f64; use std::thread; use std::time; /// A simple buffer that fills /// up to a limit and then holds the last /// N items that have been inserted into it, /// overwriting old ones in a round-robin fashion. /// /// It's not quite a ring buffer 'cause you can't /// remove items from it, it just holds the last N /// things. #[derive(Debug, Clone)] struct LogBuffer<T> where T: Clone, { head: usize, size: usize, /// The number of actual samples inserted, used for /// smarter averaging. samples: usize, contents: Vec<T>, } impl<T> LogBuffer<T> where T: Clone + Copy, { fn new(size: usize, init_val: T) -> LogBuffer<T> { LogBuffer { head: 0, size, contents: vec![init_val; size], // Never divide by 0 samples: 1, } } /// Pushes a new item into the `LogBuffer`, overwriting /// the oldest item in it. fn push(&mut self, item: T) { self.head = (self.head + 1) % self.contents.len(); self.contents[self.head] = item; self.size = cmp::min(self.size + 1, self.contents.len()); self.samples += 1; } /// Returns a slice pointing at the contents of the buffer. /// They are in *no particular order*, and if not all the /// slots are filled, the empty slots will be present but /// contain the initial value given to [`new()`](#method.new). /// /// We're only using this to log FPS for a short time, /// so we don't care for the second or so when it's inaccurate. fn contents(&self) -> &[T] { if self.samples > self.size { &self.contents } else { &self.contents[..self.samples] } } /// Returns the most recent value in the buffer. fn latest(&self) -> T { self.contents[self.head] } } /// A structure that contains our time-tracking state. #[derive(Debug)] pub struct TimeContext { init_instant: time::Instant, last_instant: time::Instant, frame_durations: LogBuffer<time::Duration>, residual_update_dt: time::Duration, frame_count: usize, } // How many frames we log update times for. const TIME_LOG_FRAMES: usize = 200; impl TimeContext { /// Creates a new `TimeContext` and initializes the start to this instant. pub fn new() -> TimeContext { let initial_dt = time::Duration::from_millis(16); TimeContext { init_instant: time::Instant::now(), last_instant: time::Instant::now(), frame_durations: LogBuffer::new(TIME_LOG_FRAMES, initial_dt), residual_update_dt: time::Duration::from_secs(0), frame_count: 0, } } /// Update the state of the `TimeContext` to record that /// another frame has taken place. Necessary for the FPS /// tracking and [`check_update_time()`](fn.check_update_time.html) /// functions to work. /// /// It's usually not necessary to call this function yourself, /// [`event::run()`](../event/fn.run.html) will do it for you. pub fn tick(&mut self) { let now = time::Instant::now(); let time_since_last = now - self.last_instant; self.frame_durations.push(time_since_last); self.last_instant = now; self.frame_count += 1; self.residual_update_dt += time_since_last; } } impl Default for TimeContext { fn default() -> Self { Self::new() } } /// Get the time between the start of the last frame and the current one; /// in other words, the length of the last frame. pub fn delta(ctx: &Context) -> time::Duration { let tc = &ctx.timer_context; tc.frame_durations.latest() } /// Gets the average time of a frame, averaged /// over the last 200 frames. pub fn average_delta(ctx: &Context) -> time::Duration { let tc = &ctx.timer_context; let sum: time::Duration = tc.frame_durations.contents().iter().sum(); // If our buffer is actually full, divide by its size. // Otherwise divide by the number of samples we've added if tc.frame_durations.samples > tc.frame_durations.size { sum / (tc.frame_durations.size as u32) } else { sum / (tc.frame_durations.samples as u32) } } /// A convenience function to convert a Rust `Duration` type /// to a (less precise but more useful) `f64`. /// /// Does not make sure that the `Duration` is within the bounds /// of the `f64`. pub fn duration_to_f64(d: time::Duration) -> f64 { let seconds = d.as_secs() as f64; let nanos = f64::from(d.subsec_nanos()); seconds + (nanos * 1e-9) } /// A convenience function to create a Rust `Duration` type /// from a (less precise but more useful) `f64`. /// /// Only handles positive numbers correctly. pub fn f64_to_duration(t: f64) -> time::Duration { debug_assert!(t >= 0.0, "f64_to_duration passed a negative number!"); let seconds = t.trunc(); let nanos = t.fract() * 1e9; time::Duration::new(seconds as u64, nanos as u32) } /// Returns a `Duration` representing how long each /// frame should be to match the given fps. /// /// Approximately. fn fps_as_duration(fps: u32) -> time::Duration { let target_dt_seconds = 1.0 / f64::from(fps); f64_to_duration(target_dt_seconds) } /// Gets the FPS of the game, averaged over the last /// 200 frames. pub fn fps(ctx: &Context) -> f64 { let duration_per_frame = average_delta(ctx); let seconds_per_frame = duration_to_f64(duration_per_frame); 1.0 / seconds_per_frame } /// Returns the time since the game was initialized, /// as reported by the system clock. pub fn time_since_start(ctx: &Context) -> time::Duration { let tc = &ctx.timer_context; time::Instant::now() - tc.init_instant } /// This function will return true if the time since the /// last [`update()`](../event/trait.EventHandler.html#tymethod.update) /// call has been equal to or greater to /// the update FPS indicated by the `target_fps`. /// It keeps track of fractional frames, so if you want /// 60 fps (16.67 ms/frame) and the game stutters so that /// there is 40 ms between `update()` calls, this will return /// `true` twice, and take the remaining 6.67 ms into account /// in the next frame. /// /// The intention is to for it to be called in a while loop /// in your `update()` callback: /// /// ```rust /// # use ggez::*; /// # fn update_game_physics() -> GameResult { Ok(()) } /// # struct State; /// # impl ggez::event::EventHandler for State { /// fn update(&mut self, ctx: &mut Context) -> GameResult { /// while(timer::check_update_time(ctx, 60)) { /// update_game_physics()?; /// } /// Ok(()) /// } /// # fn draw(&mut self, _ctx: &mut Context) -> GameResult { Ok(()) } /// # } /// ``` pub fn check_update_time(ctx: &mut Context, target_fps: u32) -> bool { let timedata = &mut ctx.timer_context; let target_dt = fps_as_duration(target_fps); if timedata.residual_update_dt > target_dt { timedata.residual_update_dt -= target_dt; true } else { false } } /// Returns the fractional amount of a frame not consumed /// by [`check_update_time()`](fn.check_update_time.html). /// For example, if the desired /// update frame time is 40 ms (25 fps), and 45 ms have /// passed since the last frame, [`check_update_time()`](fn.check_update_time.html) /// will return `true` and `remaining_update_time()` will /// return 5 ms -- the amount of time "overflowing" from one /// frame to the next. /// /// The intention is for it to be called in your /// [`draw()`](../event/trait.EventHandler.html#tymethod.draw) callback /// to interpolate physics states for smooth rendering. /// (see <http://gafferongames.com/game-physics/fix-your-timestep/>) pub fn remaining_update_time(ctx: &mut Context) -> time::Duration { ctx.timer_context.residual_update_dt } /// Pauses the current thread for the target duration. /// Just calls [`std::thread::sleep()`](https://doc.rust-lang.org/std/thread/fn.sleep.html) /// so it's as accurate as that is (which is usually not very). pub fn sleep(duration: time::Duration) { thread::sleep(duration); } /// Yields the current timeslice to the OS. /// /// This just calls [`std::thread::yield_now()`](https://doc.rust-lang.org/std/thread/fn.yield_now.html) /// but it's handy to have here. pub fn yield_now() { thread::yield_now(); } /// Gets the number of times the game has gone through its event loop. /// /// Specifically, the number of times that [`TimeContext::tick()`](struct.TimeContext.html#method.tick) /// has been called by it. pub fn ticks(ctx: &Context) -> usize { ctx.timer_context.frame_count }