Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_time/src/fixed.rs
6598 views
1
use bevy_app::FixedMain;
2
use bevy_ecs::world::World;
3
#[cfg(feature = "bevy_reflect")]
4
use bevy_reflect::Reflect;
5
use core::time::Duration;
6
7
use crate::{time::Time, virt::Virtual};
8
9
/// The fixed timestep game clock following virtual time.
10
///
11
/// A specialization of the [`Time`] structure. **For method documentation, see
12
/// [`Time<Fixed>#impl-Time<Fixed>`].**
13
///
14
/// It is automatically inserted as a resource by
15
/// [`TimePlugin`](crate::TimePlugin) and updated based on
16
/// [`Time<Virtual>`](Virtual). The fixed clock is automatically set as the
17
/// generic [`Time`] resource during [`FixedUpdate`](bevy_app::FixedUpdate)
18
/// schedule processing.
19
///
20
/// The fixed timestep clock advances in fixed-size increments, which is
21
/// extremely useful for writing logic (like physics) that should have
22
/// consistent behavior, regardless of framerate.
23
///
24
/// The default [`timestep()`](Time::timestep) is 64 hertz, or 15625
25
/// microseconds. This value was chosen because using 60 hertz has the potential
26
/// for a pathological interaction with the monitor refresh rate where the game
27
/// alternates between running two fixed timesteps and zero fixed timesteps per
28
/// frame (for example when running two fixed timesteps takes longer than a
29
/// frame). Additionally, the value is a power of two which losslessly converts
30
/// into [`f32`] and [`f64`].
31
///
32
/// To run a system on a fixed timestep, add it to one of the [`FixedMain`]
33
/// schedules, most commonly [`FixedUpdate`](bevy_app::FixedUpdate).
34
///
35
/// This schedule is run a number of times between
36
/// [`PreUpdate`](bevy_app::PreUpdate) and [`Update`](bevy_app::Update)
37
/// according to the accumulated [`overstep()`](Time::overstep) time divided by
38
/// the [`timestep()`](Time::timestep). This means the schedule may run 0, 1 or
39
/// more times during a single update (which typically corresponds to a rendered
40
/// frame).
41
///
42
/// `Time<Fixed>` and the generic [`Time`] resource will report a
43
/// [`delta()`](Time::delta) equal to [`timestep()`](Time::timestep) and always
44
/// grow [`elapsed()`](Time::elapsed) by one [`timestep()`](Time::timestep) per
45
/// iteration.
46
///
47
/// The fixed timestep clock follows the [`Time<Virtual>`](Virtual) clock, which
48
/// means it is affected by [`pause()`](Time::pause),
49
/// [`set_relative_speed()`](Time::set_relative_speed) and
50
/// [`set_max_delta()`](Time::set_max_delta) from virtual time. If the virtual
51
/// clock is paused, the [`FixedUpdate`](bevy_app::FixedUpdate) schedule will
52
/// not run. It is guaranteed that the [`elapsed()`](Time::elapsed) time in
53
/// `Time<Fixed>` is always between the previous `elapsed()` and the current
54
/// `elapsed()` value in `Time<Virtual>`, so the values are compatible.
55
///
56
/// Changing the timestep size while the game is running should not normally be
57
/// done, as having a regular interval is the point of this schedule, but it may
58
/// be necessary for effects like "bullet-time" if the normal granularity of the
59
/// fixed timestep is too big for the slowed down time. In this case,
60
/// [`set_timestep()`](Time::set_timestep) and be called to set a new value. The
61
/// new value will be used immediately for the next run of the
62
/// [`FixedUpdate`](bevy_app::FixedUpdate) schedule, meaning that it will affect
63
/// the [`delta()`](Time::delta) value for the very next
64
/// [`FixedUpdate`](bevy_app::FixedUpdate), even if it is still during the same
65
/// frame. Any [`overstep()`](Time::overstep) present in the accumulator will be
66
/// processed according to the new [`timestep()`](Time::timestep) value.
67
#[derive(Debug, Copy, Clone)]
68
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Clone))]
69
pub struct Fixed {
70
timestep: Duration,
71
overstep: Duration,
72
}
73
74
impl Time<Fixed> {
75
/// Corresponds to 64 Hz.
76
const DEFAULT_TIMESTEP: Duration = Duration::from_micros(15625);
77
78
/// Return new fixed time clock with given timestep as [`Duration`]
79
///
80
/// # Panics
81
///
82
/// Panics if `timestep` is zero.
83
pub fn from_duration(timestep: Duration) -> Self {
84
let mut ret = Self::default();
85
ret.set_timestep(timestep);
86
ret
87
}
88
89
/// Return new fixed time clock with given timestep seconds as `f64`
90
///
91
/// # Panics
92
///
93
/// Panics if `seconds` is zero, negative or not finite.
94
pub fn from_seconds(seconds: f64) -> Self {
95
let mut ret = Self::default();
96
ret.set_timestep_seconds(seconds);
97
ret
98
}
99
100
/// Return new fixed time clock with given timestep frequency in Hertz (1/seconds)
101
///
102
/// # Panics
103
///
104
/// Panics if `hz` is zero, negative or not finite.
105
pub fn from_hz(hz: f64) -> Self {
106
let mut ret = Self::default();
107
ret.set_timestep_hz(hz);
108
ret
109
}
110
111
/// Returns the amount of virtual time that must pass before the fixed
112
/// timestep schedule is run again.
113
#[inline]
114
pub fn timestep(&self) -> Duration {
115
self.context().timestep
116
}
117
118
/// Sets the amount of virtual time that must pass before the fixed timestep
119
/// schedule is run again, as [`Duration`].
120
///
121
/// Takes effect immediately on the next run of the schedule, respecting
122
/// what is currently in [`Self::overstep`].
123
///
124
/// # Panics
125
///
126
/// Panics if `timestep` is zero.
127
#[inline]
128
pub fn set_timestep(&mut self, timestep: Duration) {
129
assert_ne!(
130
timestep,
131
Duration::ZERO,
132
"attempted to set fixed timestep to zero"
133
);
134
self.context_mut().timestep = timestep;
135
}
136
137
/// Sets the amount of virtual time that must pass before the fixed timestep
138
/// schedule is run again, as seconds.
139
///
140
/// Timestep is stored as a [`Duration`], which has fixed nanosecond
141
/// resolution and will be converted from the floating point number.
142
///
143
/// Takes effect immediately on the next run of the schedule, respecting
144
/// what is currently in [`Self::overstep`].
145
///
146
/// # Panics
147
///
148
/// Panics if `seconds` is zero, negative or not finite.
149
#[inline]
150
pub fn set_timestep_seconds(&mut self, seconds: f64) {
151
assert!(
152
seconds.is_sign_positive(),
153
"seconds less than or equal to zero"
154
);
155
assert!(seconds.is_finite(), "seconds is infinite");
156
self.set_timestep(Duration::from_secs_f64(seconds));
157
}
158
159
/// Sets the amount of virtual time that must pass before the fixed timestep
160
/// schedule is run again, as frequency.
161
///
162
/// The timestep value is set to `1 / hz`, converted to a [`Duration`] which
163
/// has fixed nanosecond resolution.
164
///
165
/// Takes effect immediately on the next run of the schedule, respecting
166
/// what is currently in [`Self::overstep`].
167
///
168
/// # Panics
169
///
170
/// Panics if `hz` is zero, negative or not finite.
171
#[inline]
172
pub fn set_timestep_hz(&mut self, hz: f64) {
173
assert!(hz.is_sign_positive(), "Hz less than or equal to zero");
174
assert!(hz.is_finite(), "Hz is infinite");
175
self.set_timestep_seconds(1.0 / hz);
176
}
177
178
/// Returns the amount of overstep time accumulated toward new steps, as
179
/// [`Duration`].
180
#[inline]
181
pub fn overstep(&self) -> Duration {
182
self.context().overstep
183
}
184
185
/// Discard a part of the overstep amount.
186
///
187
/// If `discard` is higher than overstep, the overstep becomes zero.
188
#[inline]
189
pub fn discard_overstep(&mut self, discard: Duration) {
190
let context = self.context_mut();
191
context.overstep = context.overstep.saturating_sub(discard);
192
}
193
194
/// Returns the amount of overstep time accumulated toward new steps, as an
195
/// [`f32`] fraction of the timestep.
196
#[inline]
197
pub fn overstep_fraction(&self) -> f32 {
198
self.context().overstep.as_secs_f32() / self.context().timestep.as_secs_f32()
199
}
200
201
/// Returns the amount of overstep time accumulated toward new steps, as an
202
/// [`f64`] fraction of the timestep.
203
#[inline]
204
pub fn overstep_fraction_f64(&self) -> f64 {
205
self.context().overstep.as_secs_f64() / self.context().timestep.as_secs_f64()
206
}
207
208
fn accumulate(&mut self, delta: Duration) {
209
self.context_mut().overstep += delta;
210
}
211
212
fn expend(&mut self) -> bool {
213
let timestep = self.timestep();
214
if let Some(new_value) = self.context_mut().overstep.checked_sub(timestep) {
215
// reduce accumulated and increase elapsed by period
216
self.context_mut().overstep = new_value;
217
self.advance_by(timestep);
218
true
219
} else {
220
// no more periods left in accumulated
221
false
222
}
223
}
224
}
225
226
impl Default for Fixed {
227
fn default() -> Self {
228
Self {
229
timestep: Time::<Fixed>::DEFAULT_TIMESTEP,
230
overstep: Duration::ZERO,
231
}
232
}
233
}
234
235
/// Runs [`FixedMain`] zero or more times based on delta of
236
/// [`Time<Virtual>`](Virtual) and [`Time::overstep`].
237
/// You can order your systems relative to this by using
238
/// [`RunFixedMainLoopSystems`](bevy_app::prelude::RunFixedMainLoopSystems).
239
pub(super) fn run_fixed_main_schedule(world: &mut World) {
240
let delta = world.resource::<Time<Virtual>>().delta();
241
world.resource_mut::<Time<Fixed>>().accumulate(delta);
242
243
// Run the schedule until we run out of accumulated time
244
let _ = world.try_schedule_scope(FixedMain, |world, schedule| {
245
while world.resource_mut::<Time<Fixed>>().expend() {
246
*world.resource_mut::<Time>() = world.resource::<Time<Fixed>>().as_generic();
247
schedule.run(world);
248
}
249
});
250
251
*world.resource_mut::<Time>() = world.resource::<Time<Virtual>>().as_generic();
252
}
253
254
#[cfg(test)]
255
mod test {
256
use super::*;
257
258
#[test]
259
fn test_set_timestep() {
260
let mut time = Time::<Fixed>::default();
261
262
assert_eq!(time.timestep(), Time::<Fixed>::DEFAULT_TIMESTEP);
263
264
time.set_timestep(Duration::from_millis(500));
265
assert_eq!(time.timestep(), Duration::from_millis(500));
266
267
time.set_timestep_seconds(0.25);
268
assert_eq!(time.timestep(), Duration::from_millis(250));
269
270
time.set_timestep_hz(8.0);
271
assert_eq!(time.timestep(), Duration::from_millis(125));
272
}
273
274
#[test]
275
fn test_expend() {
276
let mut time = Time::<Fixed>::from_seconds(2.0);
277
278
assert_eq!(time.delta(), Duration::ZERO);
279
assert_eq!(time.elapsed(), Duration::ZERO);
280
281
time.accumulate(Duration::from_secs(1));
282
283
assert_eq!(time.delta(), Duration::ZERO);
284
assert_eq!(time.elapsed(), Duration::ZERO);
285
assert_eq!(time.overstep(), Duration::from_secs(1));
286
assert_eq!(time.overstep_fraction(), 0.5);
287
assert_eq!(time.overstep_fraction_f64(), 0.5);
288
289
assert!(!time.expend()); // false
290
291
assert_eq!(time.delta(), Duration::ZERO);
292
assert_eq!(time.elapsed(), Duration::ZERO);
293
assert_eq!(time.overstep(), Duration::from_secs(1));
294
assert_eq!(time.overstep_fraction(), 0.5);
295
assert_eq!(time.overstep_fraction_f64(), 0.5);
296
297
time.accumulate(Duration::from_secs(1));
298
299
assert_eq!(time.delta(), Duration::ZERO);
300
assert_eq!(time.elapsed(), Duration::ZERO);
301
assert_eq!(time.overstep(), Duration::from_secs(2));
302
assert_eq!(time.overstep_fraction(), 1.0);
303
assert_eq!(time.overstep_fraction_f64(), 1.0);
304
305
assert!(time.expend()); // true
306
307
assert_eq!(time.delta(), Duration::from_secs(2));
308
assert_eq!(time.elapsed(), Duration::from_secs(2));
309
assert_eq!(time.overstep(), Duration::ZERO);
310
assert_eq!(time.overstep_fraction(), 0.0);
311
assert_eq!(time.overstep_fraction_f64(), 0.0);
312
313
assert!(!time.expend()); // false
314
315
assert_eq!(time.delta(), Duration::from_secs(2));
316
assert_eq!(time.elapsed(), Duration::from_secs(2));
317
assert_eq!(time.overstep(), Duration::ZERO);
318
assert_eq!(time.overstep_fraction(), 0.0);
319
assert_eq!(time.overstep_fraction_f64(), 0.0);
320
321
time.accumulate(Duration::from_secs(1));
322
323
assert_eq!(time.delta(), Duration::from_secs(2));
324
assert_eq!(time.elapsed(), Duration::from_secs(2));
325
assert_eq!(time.overstep(), Duration::from_secs(1));
326
assert_eq!(time.overstep_fraction(), 0.5);
327
assert_eq!(time.overstep_fraction_f64(), 0.5);
328
329
assert!(!time.expend()); // false
330
331
assert_eq!(time.delta(), Duration::from_secs(2));
332
assert_eq!(time.elapsed(), Duration::from_secs(2));
333
assert_eq!(time.overstep(), Duration::from_secs(1));
334
assert_eq!(time.overstep_fraction(), 0.5);
335
assert_eq!(time.overstep_fraction_f64(), 0.5);
336
}
337
338
#[test]
339
fn test_expend_multiple() {
340
let mut time = Time::<Fixed>::from_seconds(2.0);
341
342
time.accumulate(Duration::from_secs(7));
343
assert_eq!(time.overstep(), Duration::from_secs(7));
344
345
assert!(time.expend()); // true
346
assert_eq!(time.elapsed(), Duration::from_secs(2));
347
assert_eq!(time.overstep(), Duration::from_secs(5));
348
349
assert!(time.expend()); // true
350
assert_eq!(time.elapsed(), Duration::from_secs(4));
351
assert_eq!(time.overstep(), Duration::from_secs(3));
352
353
assert!(time.expend()); // true
354
assert_eq!(time.elapsed(), Duration::from_secs(6));
355
assert_eq!(time.overstep(), Duration::from_secs(1));
356
357
assert!(!time.expend()); // false
358
assert_eq!(time.elapsed(), Duration::from_secs(6));
359
assert_eq!(time.overstep(), Duration::from_secs(1));
360
}
361
}
362
363