bevy_time/
fixed.rs

1use bevy_app::FixedMain;
2use bevy_ecs::world::World;
3#[cfg(feature = "bevy_reflect")]
4use bevy_reflect::Reflect;
5use bevy_utils::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))]
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    /// 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
226impl 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/// [`RunFixedMainLoopSystem`](bevy_app::prelude::RunFixedMainLoopSystem).
239pub(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)]
255mod 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}