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