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