bevy_time/fixed.rs
1use bevy_app::FixedMain;
2use bevy_ecs::world::World;
3#[cfg(feature = "bevy_reflect")]
4use bevy_reflect::Reflect;
5use core::time::Duration;
6
7use 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))]
69pub struct Fixed {
70 timestep: Duration,
71 overstep: Duration,
72}
73
74impl 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 /// Increase the overstep time accumulated towards new steps.
186 ///
187 /// This method is provided for use in tests. Ordinarily, the [`run_fixed_main_schedule`] system is responsible for calculating the overstep.
188 #[inline]
189 pub fn accumulate_overstep(&mut self, delta: Duration) {
190 self.context_mut().overstep += delta;
191 }
192
193 /// Discard a part of the overstep amount.
194 ///
195 /// If `discard` is higher than overstep, the overstep becomes zero.
196 #[inline]
197 pub fn discard_overstep(&mut self, discard: Duration) {
198 let context = self.context_mut();
199 context.overstep = context.overstep.saturating_sub(discard);
200 }
201
202 /// Returns the amount of overstep time accumulated toward new steps, as an
203 /// [`f32`] fraction of the timestep.
204 #[inline]
205 pub fn overstep_fraction(&self) -> f32 {
206 self.context().overstep.as_secs_f32() / self.context().timestep.as_secs_f32()
207 }
208
209 /// Returns the amount of overstep time accumulated toward new steps, as an
210 /// [`f64`] fraction of the timestep.
211 #[inline]
212 pub fn overstep_fraction_f64(&self) -> f64 {
213 self.context().overstep.as_secs_f64() / self.context().timestep.as_secs_f64()
214 }
215
216 fn expend(&mut self) -> bool {
217 let timestep = self.timestep();
218 if let Some(new_value) = self.context_mut().overstep.checked_sub(timestep) {
219 // reduce accumulated and increase elapsed by period
220 self.context_mut().overstep = new_value;
221 self.advance_by(timestep);
222 true
223 } else {
224 // no more periods left in accumulated
225 false
226 }
227 }
228}
229
230impl Default for Fixed {
231 fn default() -> Self {
232 Self {
233 timestep: Time::<Fixed>::DEFAULT_TIMESTEP,
234 overstep: Duration::ZERO,
235 }
236 }
237}
238
239/// Runs [`FixedMain`] zero or more times based on delta of
240/// [`Time<Virtual>`](Virtual) and [`Time::overstep`].
241/// You can order your systems relative to this by using
242/// [`RunFixedMainLoopSystems`](bevy_app::prelude::RunFixedMainLoopSystems).
243pub fn run_fixed_main_schedule(world: &mut World) {
244 let delta = world.resource::<Time<Virtual>>().delta();
245 world
246 .resource_mut::<Time<Fixed>>()
247 .accumulate_overstep(delta);
248
249 // Run the schedule until we run out of accumulated time
250 let _ = world.try_schedule_scope(FixedMain, |world, schedule| {
251 while world.resource_mut::<Time<Fixed>>().expend() {
252 *world.resource_mut::<Time>() = world.resource::<Time<Fixed>>().as_generic();
253 schedule.run(world);
254 }
255 });
256
257 *world.resource_mut::<Time>() = world.resource::<Time<Virtual>>().as_generic();
258}
259
260#[cfg(test)]
261mod test {
262 use super::*;
263
264 #[test]
265 fn test_set_timestep() {
266 let mut time = Time::<Fixed>::default();
267
268 assert_eq!(time.timestep(), Time::<Fixed>::DEFAULT_TIMESTEP);
269
270 time.set_timestep(Duration::from_millis(500));
271 assert_eq!(time.timestep(), Duration::from_millis(500));
272
273 time.set_timestep_seconds(0.25);
274 assert_eq!(time.timestep(), Duration::from_millis(250));
275
276 time.set_timestep_hz(8.0);
277 assert_eq!(time.timestep(), Duration::from_millis(125));
278 }
279
280 #[test]
281 fn test_expend() {
282 let mut time = Time::<Fixed>::from_seconds(2.0);
283
284 assert_eq!(time.delta(), Duration::ZERO);
285 assert_eq!(time.elapsed(), Duration::ZERO);
286
287 time.accumulate_overstep(Duration::from_secs(1));
288
289 assert_eq!(time.delta(), Duration::ZERO);
290 assert_eq!(time.elapsed(), Duration::ZERO);
291 assert_eq!(time.overstep(), Duration::from_secs(1));
292 assert_eq!(time.overstep_fraction(), 0.5);
293 assert_eq!(time.overstep_fraction_f64(), 0.5);
294
295 assert!(!time.expend()); // false
296
297 assert_eq!(time.delta(), Duration::ZERO);
298 assert_eq!(time.elapsed(), Duration::ZERO);
299 assert_eq!(time.overstep(), Duration::from_secs(1));
300 assert_eq!(time.overstep_fraction(), 0.5);
301 assert_eq!(time.overstep_fraction_f64(), 0.5);
302
303 time.accumulate_overstep(Duration::from_secs(1));
304
305 assert_eq!(time.delta(), Duration::ZERO);
306 assert_eq!(time.elapsed(), Duration::ZERO);
307 assert_eq!(time.overstep(), Duration::from_secs(2));
308 assert_eq!(time.overstep_fraction(), 1.0);
309 assert_eq!(time.overstep_fraction_f64(), 1.0);
310
311 assert!(time.expend()); // true
312
313 assert_eq!(time.delta(), Duration::from_secs(2));
314 assert_eq!(time.elapsed(), Duration::from_secs(2));
315 assert_eq!(time.overstep(), Duration::ZERO);
316 assert_eq!(time.overstep_fraction(), 0.0);
317 assert_eq!(time.overstep_fraction_f64(), 0.0);
318
319 assert!(!time.expend()); // false
320
321 assert_eq!(time.delta(), Duration::from_secs(2));
322 assert_eq!(time.elapsed(), Duration::from_secs(2));
323 assert_eq!(time.overstep(), Duration::ZERO);
324 assert_eq!(time.overstep_fraction(), 0.0);
325 assert_eq!(time.overstep_fraction_f64(), 0.0);
326
327 time.accumulate_overstep(Duration::from_secs(1));
328
329 assert_eq!(time.delta(), Duration::from_secs(2));
330 assert_eq!(time.elapsed(), Duration::from_secs(2));
331 assert_eq!(time.overstep(), Duration::from_secs(1));
332 assert_eq!(time.overstep_fraction(), 0.5);
333 assert_eq!(time.overstep_fraction_f64(), 0.5);
334
335 assert!(!time.expend()); // false
336
337 assert_eq!(time.delta(), Duration::from_secs(2));
338 assert_eq!(time.elapsed(), Duration::from_secs(2));
339 assert_eq!(time.overstep(), Duration::from_secs(1));
340 assert_eq!(time.overstep_fraction(), 0.5);
341 assert_eq!(time.overstep_fraction_f64(), 0.5);
342 }
343
344 #[test]
345 fn test_expend_multiple() {
346 let mut time = Time::<Fixed>::from_seconds(2.0);
347
348 time.accumulate_overstep(Duration::from_secs(7));
349 assert_eq!(time.overstep(), Duration::from_secs(7));
350
351 assert!(time.expend()); // true
352 assert_eq!(time.elapsed(), Duration::from_secs(2));
353 assert_eq!(time.overstep(), Duration::from_secs(5));
354
355 assert!(time.expend()); // true
356 assert_eq!(time.elapsed(), Duration::from_secs(4));
357 assert_eq!(time.overstep(), Duration::from_secs(3));
358
359 assert!(time.expend()); // true
360 assert_eq!(time.elapsed(), Duration::from_secs(6));
361 assert_eq!(time.overstep(), Duration::from_secs(1));
362
363 assert!(!time.expend()); // false
364 assert_eq!(time.elapsed(), Duration::from_secs(6));
365 assert_eq!(time.overstep(), Duration::from_secs(1));
366 }
367}