bevy_time/virt.rs
1#[cfg(feature = "bevy_reflect")]
2use bevy_reflect::Reflect;
3use core::time::Duration;
4use log::debug;
5
6use crate::{real::Real, time::Time};
7
8/// The virtual game clock representing game time.
9///
10/// A specialization of the [`Time`] structure. **For method documentation, see
11/// [`Time<Virtual>#impl-Time<Virtual>`].**
12///
13/// Normally used as `Time<Virtual>`. It is automatically inserted as a resource
14/// by [`TimePlugin`](crate::TimePlugin) and updated based on
15/// [`Time<Real>`](Real). The virtual clock is automatically set as the default
16/// generic [`Time`] resource for the update.
17///
18/// The virtual clock differs from real time clock in that it can be paused, sped up
19/// and slowed down. It also limits how much it can advance in a single update
20/// in order to prevent unexpected behavior in cases where updates do not happen
21/// at regular intervals (e.g. coming back after the program was suspended a long time).
22///
23/// The virtual clock can be paused by calling [`pause()`](Time::pause),
24/// unpaused by calling [`unpause()`](Time::unpause), or toggled by calling
25/// [`toggle()`](Time::toggle). When the game clock is
26/// paused [`delta()`](Time::delta) will be zero on each update, and
27/// [`elapsed()`](Time::elapsed) will not grow.
28/// [`effective_speed()`](Time::effective_speed) will return `0.0`. Calling
29/// [`pause()`](Time::pause) will not affect value the [`delta()`](Time::delta)
30/// value for the update currently being processed.
31///
32/// The speed of the virtual clock can be changed by calling
33/// [`set_relative_speed()`](Time::set_relative_speed). A value of `2.0` means
34/// that virtual clock should advance twice as fast as real time, meaning that
35/// [`delta()`](Time::delta) values will be double of what
36/// [`Time<Real>::delta()`](Time::delta) reports and
37/// [`elapsed()`](Time::elapsed) will go twice as fast as
38/// [`Time<Real>::elapsed()`](Time::elapsed). Calling
39/// [`set_relative_speed()`](Time::set_relative_speed) will not affect the
40/// [`delta()`](Time::delta) value for the update currently being processed.
41///
42/// The maximum amount of delta time that can be added by a single update can be
43/// set by [`set_max_delta()`](Time::set_max_delta). This value serves a dual
44/// purpose in the virtual clock.
45///
46/// If the game temporarily freezes due to any reason, such as disk access, a
47/// blocking system call, or operating system level suspend, reporting the full
48/// elapsed delta time is likely to cause bugs in game logic. Usually if a
49/// laptop is suspended for an hour, it doesn't make sense to try to simulate
50/// the game logic for the elapsed hour when resuming. Instead it is better to
51/// lose the extra time and pretend a shorter duration of time passed. Setting
52/// [`max_delta()`](Time::max_delta) to a relatively short time means that the
53/// impact on game logic will be minimal.
54///
55/// If the game lags for some reason, meaning that it will take a longer time to
56/// compute a frame than the real time that passes during the computation, then
57/// we would fall behind in processing virtual time. If this situation persists,
58/// and computing a frame takes longer depending on how much virtual time has
59/// passed, the game would enter a "death spiral" where computing each frame
60/// takes longer and longer and the game will appear to freeze. By limiting the
61/// maximum time that can be added at once, we also limit the amount of virtual
62/// time the game needs to compute for each frame. This means that the game will
63/// run slow, and it will run slower than real time, but it will not freeze and
64/// it will recover as soon as computation becomes fast again.
65///
66/// You should set [`max_delta()`](Time::max_delta) to a value that is
67/// approximately the minimum FPS your game should have even if heavily lagged
68/// for a moment. The actual FPS when lagged will be somewhat lower than this,
69/// depending on how much more time it takes to compute a frame compared to real
70/// time. You should also consider how stable your FPS is, as the limit will
71/// also dictate how big of an FPS drop you can accept without losing time and
72/// falling behind real time.
73#[derive(Debug, Copy, Clone)]
74#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Clone))]
75pub struct Virtual {
76 max_delta: Duration,
77 paused: bool,
78 relative_speed: f64,
79 effective_speed: f64,
80}
81
82impl Time<Virtual> {
83 /// The default amount of time that can added in a single update.
84 ///
85 /// Equal to 250 milliseconds.
86 const DEFAULT_MAX_DELTA: Duration = Duration::from_millis(250);
87
88 /// Create new virtual clock with given maximum delta step [`Duration`]
89 ///
90 /// # Panics
91 ///
92 /// Panics if `max_delta` is zero.
93 pub fn from_max_delta(max_delta: Duration) -> Self {
94 let mut ret = Self::default();
95 ret.set_max_delta(max_delta);
96 ret
97 }
98
99 /// Returns the maximum amount of time that can be added to this clock by a
100 /// single update, as [`Duration`].
101 ///
102 /// This is the maximum value [`Self::delta()`] will return and also to
103 /// maximum time [`Self::elapsed()`] will be increased by in a single
104 /// update.
105 ///
106 /// This ensures that even if no updates happen for an extended amount of time,
107 /// the clock will not have a sudden, huge advance all at once. This also indirectly
108 /// limits the maximum number of fixed update steps that can run in a single update.
109 ///
110 /// The default value is 250 milliseconds.
111 #[inline]
112 pub fn max_delta(&self) -> Duration {
113 self.context().max_delta
114 }
115
116 /// Sets the maximum amount of time that can be added to this clock by a
117 /// single update, as [`Duration`].
118 ///
119 /// This is the maximum value [`Self::delta()`] will return and also to
120 /// maximum time [`Self::elapsed()`] will be increased by in a single
121 /// update.
122 ///
123 /// This is used to ensure that even if the game freezes for a few seconds,
124 /// or is suspended for hours or even days, the virtual clock doesn't
125 /// suddenly jump forward for that full amount, which would likely cause
126 /// gameplay bugs or having to suddenly simulate all the intervening time.
127 ///
128 /// If no updates happen for an extended amount of time, this limit prevents
129 /// having a sudden, huge advance all at once. This also indirectly limits
130 /// the maximum number of fixed update steps that can run in a single
131 /// update.
132 ///
133 /// The default value is 250 milliseconds. If you want to disable this
134 /// feature, set the value to [`Duration::MAX`].
135 ///
136 /// # Panics
137 ///
138 /// Panics if `max_delta` is zero.
139 #[inline]
140 pub fn set_max_delta(&mut self, max_delta: Duration) {
141 assert_ne!(max_delta, Duration::ZERO, "tried to set max delta to zero");
142 self.context_mut().max_delta = max_delta;
143 }
144
145 /// Returns the speed the clock advances relative to your system clock, as [`f32`].
146 /// This is known as "time scaling" or "time dilation" in other engines.
147 #[inline]
148 pub fn relative_speed(&self) -> f32 {
149 self.relative_speed_f64() as f32
150 }
151
152 /// Returns the speed the clock advances relative to your system clock, as [`f64`].
153 /// This is known as "time scaling" or "time dilation" in other engines.
154 #[inline]
155 pub fn relative_speed_f64(&self) -> f64 {
156 self.context().relative_speed
157 }
158
159 /// Returns the speed the clock advanced relative to your system clock in
160 /// this update, as [`f32`].
161 ///
162 /// Returns `0.0` if the game was paused or what the `relative_speed` value
163 /// was at the start of this update.
164 #[inline]
165 pub fn effective_speed(&self) -> f32 {
166 self.context().effective_speed as f32
167 }
168
169 /// Returns the speed the clock advanced relative to your system clock in
170 /// this update, as [`f64`].
171 ///
172 /// Returns `0.0` if the game was paused or what the `relative_speed` value
173 /// was at the start of this update.
174 #[inline]
175 pub fn effective_speed_f64(&self) -> f64 {
176 self.context().effective_speed
177 }
178
179 /// Sets the speed the clock advances relative to your system clock, given as an [`f32`].
180 ///
181 /// For example, setting this to `2.0` will make the clock advance twice as fast as your system
182 /// clock.
183 ///
184 /// # Panics
185 ///
186 /// Panics if `ratio` is negative or not finite.
187 #[inline]
188 pub fn set_relative_speed(&mut self, ratio: f32) {
189 self.set_relative_speed_f64(ratio as f64);
190 }
191
192 /// Sets the speed the clock advances relative to your system clock, given as an [`f64`].
193 ///
194 /// For example, setting this to `2.0` will make the clock advance twice as fast as your system
195 /// clock.
196 ///
197 /// # Panics
198 ///
199 /// Panics if `ratio` is negative or not finite.
200 #[inline]
201 pub fn set_relative_speed_f64(&mut self, ratio: f64) {
202 assert!(ratio.is_finite(), "tried to go infinitely fast");
203 assert!(ratio >= 0.0, "tried to go back in time");
204 self.context_mut().relative_speed = ratio;
205 }
206
207 /// Stops the clock if it is running, otherwise resumes the clock.
208 #[inline]
209 pub fn toggle(&mut self) {
210 self.context_mut().paused ^= true;
211 }
212
213 /// Stops the clock, preventing it from advancing until resumed.
214 #[inline]
215 pub fn pause(&mut self) {
216 self.context_mut().paused = true;
217 }
218
219 /// Resumes the clock.
220 #[inline]
221 pub fn unpause(&mut self) {
222 self.context_mut().paused = false;
223 }
224
225 /// Returns `true` if the clock is currently paused.
226 #[inline]
227 pub fn is_paused(&self) -> bool {
228 self.context().paused
229 }
230
231 /// Returns `true` if the clock was paused at the start of this update.
232 #[inline]
233 pub fn was_paused(&self) -> bool {
234 self.context().effective_speed == 0.0
235 }
236
237 /// Updates the elapsed duration of `self` by `raw_delta`, up to the `max_delta`.
238 fn advance_with_raw_delta(&mut self, raw_delta: Duration) {
239 let max_delta = self.context().max_delta;
240 let clamped_delta = if raw_delta > max_delta {
241 debug!(
242 "delta time larger than maximum delta, clamping delta to {:?} and skipping {:?}",
243 max_delta,
244 raw_delta - max_delta
245 );
246 max_delta
247 } else {
248 raw_delta
249 };
250 let effective_speed = if self.context().paused {
251 0.0
252 } else {
253 self.context().relative_speed
254 };
255 let delta = if effective_speed != 1.0 {
256 clamped_delta.mul_f64(effective_speed)
257 } else {
258 // avoid rounding when at normal speed
259 clamped_delta
260 };
261 self.context_mut().effective_speed = effective_speed;
262 self.advance_by(delta);
263 }
264}
265
266impl Default for Virtual {
267 fn default() -> Self {
268 Self {
269 max_delta: Time::<Virtual>::DEFAULT_MAX_DELTA,
270 paused: false,
271 relative_speed: 1.0,
272 effective_speed: 1.0,
273 }
274 }
275}
276
277/// Advances [`Time<Virtual>`] and [`Time`] based on the elapsed [`Time<Real>`].
278///
279/// The virtual time will be advanced up to the provided [`Time::max_delta`].
280pub fn update_virtual_time(current: &mut Time, virt: &mut Time<Virtual>, real: &Time<Real>) {
281 let raw_delta = real.delta();
282 virt.advance_with_raw_delta(raw_delta);
283 *current = virt.as_generic();
284}
285
286#[cfg(test)]
287mod test {
288 use super::*;
289
290 #[test]
291 fn test_default() {
292 let time = Time::<Virtual>::default();
293
294 assert!(!time.is_paused()); // false
295 assert_eq!(time.relative_speed(), 1.0);
296 assert_eq!(time.max_delta(), Time::<Virtual>::DEFAULT_MAX_DELTA);
297 assert_eq!(time.delta(), Duration::ZERO);
298 assert_eq!(time.elapsed(), Duration::ZERO);
299 }
300
301 #[test]
302 fn test_advance() {
303 let mut time = Time::<Virtual>::default();
304
305 time.advance_with_raw_delta(Duration::from_millis(125));
306
307 assert_eq!(time.delta(), Duration::from_millis(125));
308 assert_eq!(time.elapsed(), Duration::from_millis(125));
309
310 time.advance_with_raw_delta(Duration::from_millis(125));
311
312 assert_eq!(time.delta(), Duration::from_millis(125));
313 assert_eq!(time.elapsed(), Duration::from_millis(250));
314
315 time.advance_with_raw_delta(Duration::from_millis(125));
316
317 assert_eq!(time.delta(), Duration::from_millis(125));
318 assert_eq!(time.elapsed(), Duration::from_millis(375));
319
320 time.advance_with_raw_delta(Duration::from_millis(125));
321
322 assert_eq!(time.delta(), Duration::from_millis(125));
323 assert_eq!(time.elapsed(), Duration::from_millis(500));
324 }
325
326 #[test]
327 fn test_relative_speed() {
328 let mut time = Time::<Virtual>::default();
329
330 time.advance_with_raw_delta(Duration::from_millis(250));
331
332 assert_eq!(time.relative_speed(), 1.0);
333 assert_eq!(time.effective_speed(), 1.0);
334 assert_eq!(time.delta(), Duration::from_millis(250));
335 assert_eq!(time.elapsed(), Duration::from_millis(250));
336
337 time.set_relative_speed_f64(2.0);
338
339 assert_eq!(time.relative_speed(), 2.0);
340 assert_eq!(time.effective_speed(), 1.0);
341
342 time.advance_with_raw_delta(Duration::from_millis(250));
343
344 assert_eq!(time.relative_speed(), 2.0);
345 assert_eq!(time.effective_speed(), 2.0);
346 assert_eq!(time.delta(), Duration::from_millis(500));
347 assert_eq!(time.elapsed(), Duration::from_millis(750));
348
349 time.set_relative_speed_f64(0.5);
350
351 assert_eq!(time.relative_speed(), 0.5);
352 assert_eq!(time.effective_speed(), 2.0);
353
354 time.advance_with_raw_delta(Duration::from_millis(250));
355
356 assert_eq!(time.relative_speed(), 0.5);
357 assert_eq!(time.effective_speed(), 0.5);
358 assert_eq!(time.delta(), Duration::from_millis(125));
359 assert_eq!(time.elapsed(), Duration::from_millis(875));
360 }
361
362 #[test]
363 fn test_pause() {
364 let mut time = Time::<Virtual>::default();
365
366 time.advance_with_raw_delta(Duration::from_millis(250));
367
368 assert!(!time.is_paused()); // false
369 assert!(!time.was_paused()); // false
370 assert_eq!(time.relative_speed(), 1.0);
371 assert_eq!(time.effective_speed(), 1.0);
372 assert_eq!(time.delta(), Duration::from_millis(250));
373 assert_eq!(time.elapsed(), Duration::from_millis(250));
374
375 time.pause();
376
377 assert!(time.is_paused()); // true
378 assert!(!time.was_paused()); // false
379 assert_eq!(time.relative_speed(), 1.0);
380 assert_eq!(time.effective_speed(), 1.0);
381
382 time.advance_with_raw_delta(Duration::from_millis(250));
383
384 assert!(time.is_paused()); // true
385 assert!(time.was_paused()); // true
386 assert_eq!(time.relative_speed(), 1.0);
387 assert_eq!(time.effective_speed(), 0.0);
388 assert_eq!(time.delta(), Duration::ZERO);
389 assert_eq!(time.elapsed(), Duration::from_millis(250));
390
391 time.unpause();
392
393 assert!(!time.is_paused()); // false
394 assert!(time.was_paused()); // true
395 assert_eq!(time.relative_speed(), 1.0);
396 assert_eq!(time.effective_speed(), 0.0);
397
398 time.advance_with_raw_delta(Duration::from_millis(250));
399
400 assert!(!time.is_paused()); // false
401 assert!(!time.was_paused()); // false
402 assert_eq!(time.relative_speed(), 1.0);
403 assert_eq!(time.effective_speed(), 1.0);
404 assert_eq!(time.delta(), Duration::from_millis(250));
405 assert_eq!(time.elapsed(), Duration::from_millis(500));
406 }
407
408 #[test]
409 fn test_max_delta() {
410 let mut time = Time::<Virtual>::default();
411 time.set_max_delta(Duration::from_millis(500));
412
413 time.advance_with_raw_delta(Duration::from_millis(250));
414
415 assert_eq!(time.delta(), Duration::from_millis(250));
416 assert_eq!(time.elapsed(), Duration::from_millis(250));
417
418 time.advance_with_raw_delta(Duration::from_millis(500));
419
420 assert_eq!(time.delta(), Duration::from_millis(500));
421 assert_eq!(time.elapsed(), Duration::from_millis(750));
422
423 time.advance_with_raw_delta(Duration::from_millis(750));
424
425 assert_eq!(time.delta(), Duration::from_millis(500));
426 assert_eq!(time.elapsed(), Duration::from_millis(1250));
427
428 time.set_max_delta(Duration::from_secs(1));
429
430 assert_eq!(time.max_delta(), Duration::from_secs(1));
431
432 time.advance_with_raw_delta(Duration::from_millis(750));
433
434 assert_eq!(time.delta(), Duration::from_millis(750));
435 assert_eq!(time.elapsed(), Duration::from_millis(2000));
436
437 time.advance_with_raw_delta(Duration::from_millis(1250));
438
439 assert_eq!(time.delta(), Duration::from_millis(1000));
440 assert_eq!(time.elapsed(), Duration::from_millis(3000));
441 }
442}