use bevy_app::FixedMain;1use bevy_ecs::world::World;2#[cfg(feature = "bevy_reflect")]3use bevy_reflect::Reflect;4use core::time::Duration;56use crate::{time::Time, virt::Virtual};78/// The fixed timestep game clock following virtual time.9///10/// A specialization of the [`Time`] structure. **For method documentation, see11/// [`Time<Fixed>#impl-Time<Fixed>`].**12///13/// It is automatically inserted as a resource by14/// [`TimePlugin`](crate::TimePlugin) and updated based on15/// [`Time<Virtual>`](Virtual). The fixed clock is automatically set as the16/// generic [`Time`] resource during [`FixedUpdate`](bevy_app::FixedUpdate)17/// schedule processing.18///19/// The fixed timestep clock advances in fixed-size increments, which is20/// extremely useful for writing logic (like physics) that should have21/// consistent behavior, regardless of framerate.22///23/// The default [`timestep()`](Time::timestep) is 64 hertz, or 1562524/// microseconds. This value was chosen because using 60 hertz has the potential25/// for a pathological interaction with the monitor refresh rate where the game26/// alternates between running two fixed timesteps and zero fixed timesteps per27/// frame (for example when running two fixed timesteps takes longer than a28/// frame). Additionally, the value is a power of two which losslessly converts29/// into [`f32`] and [`f64`].30///31/// To run a system on a fixed timestep, add it to one of the [`FixedMain`]32/// schedules, most commonly [`FixedUpdate`](bevy_app::FixedUpdate).33///34/// This schedule is run a number of times between35/// [`PreUpdate`](bevy_app::PreUpdate) and [`Update`](bevy_app::Update)36/// according to the accumulated [`overstep()`](Time::overstep) time divided by37/// the [`timestep()`](Time::timestep). This means the schedule may run 0, 1 or38/// more times during a single update (which typically corresponds to a rendered39/// frame).40///41/// `Time<Fixed>` and the generic [`Time`] resource will report a42/// [`delta()`](Time::delta) equal to [`timestep()`](Time::timestep) and always43/// grow [`elapsed()`](Time::elapsed) by one [`timestep()`](Time::timestep) per44/// iteration.45///46/// The fixed timestep clock follows the [`Time<Virtual>`](Virtual) clock, which47/// means it is affected by [`pause()`](Time::pause),48/// [`set_relative_speed()`](Time::set_relative_speed) and49/// [`set_max_delta()`](Time::set_max_delta) from virtual time. If the virtual50/// clock is paused, the [`FixedUpdate`](bevy_app::FixedUpdate) schedule will51/// not run. It is guaranteed that the [`elapsed()`](Time::elapsed) time in52/// `Time<Fixed>` is always between the previous `elapsed()` and the current53/// `elapsed()` value in `Time<Virtual>`, so the values are compatible.54///55/// Changing the timestep size while the game is running should not normally be56/// done, as having a regular interval is the point of this schedule, but it may57/// be necessary for effects like "bullet-time" if the normal granularity of the58/// fixed timestep is too big for the slowed down time. In this case,59/// [`set_timestep()`](Time::set_timestep) and be called to set a new value. The60/// new value will be used immediately for the next run of the61/// [`FixedUpdate`](bevy_app::FixedUpdate) schedule, meaning that it will affect62/// the [`delta()`](Time::delta) value for the very next63/// [`FixedUpdate`](bevy_app::FixedUpdate), even if it is still during the same64/// frame. Any [`overstep()`](Time::overstep) present in the accumulator will be65/// processed according to the new [`timestep()`](Time::timestep) value.66#[derive(Debug, Copy, Clone)]67#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Clone))]68pub struct Fixed {69timestep: Duration,70overstep: Duration,71}7273impl Time<Fixed> {74/// Corresponds to 64 Hz.75const DEFAULT_TIMESTEP: Duration = Duration::from_micros(15625);7677/// Return new fixed time clock with given timestep as [`Duration`]78///79/// # Panics80///81/// Panics if `timestep` is zero.82pub fn from_duration(timestep: Duration) -> Self {83let mut ret = Self::default();84ret.set_timestep(timestep);85ret86}8788/// Return new fixed time clock with given timestep seconds as `f64`89///90/// # Panics91///92/// Panics if `seconds` is zero, negative or not finite.93pub fn from_seconds(seconds: f64) -> Self {94let mut ret = Self::default();95ret.set_timestep_seconds(seconds);96ret97}9899/// Return new fixed time clock with given timestep frequency in Hertz (1/seconds)100///101/// # Panics102///103/// Panics if `hz` is zero, negative or not finite.104pub fn from_hz(hz: f64) -> Self {105let mut ret = Self::default();106ret.set_timestep_hz(hz);107ret108}109110/// Returns the amount of virtual time that must pass before the fixed111/// timestep schedule is run again.112#[inline]113pub fn timestep(&self) -> Duration {114self.context().timestep115}116117/// Sets the amount of virtual time that must pass before the fixed timestep118/// schedule is run again, as [`Duration`].119///120/// Takes effect immediately on the next run of the schedule, respecting121/// what is currently in [`Self::overstep`].122///123/// # Panics124///125/// Panics if `timestep` is zero.126#[inline]127pub fn set_timestep(&mut self, timestep: Duration) {128assert_ne!(129timestep,130Duration::ZERO,131"attempted to set fixed timestep to zero"132);133self.context_mut().timestep = timestep;134}135136/// Sets the amount of virtual time that must pass before the fixed timestep137/// schedule is run again, as seconds.138///139/// Timestep is stored as a [`Duration`], which has fixed nanosecond140/// resolution and will be converted from the floating point number.141///142/// Takes effect immediately on the next run of the schedule, respecting143/// what is currently in [`Self::overstep`].144///145/// # Panics146///147/// Panics if `seconds` is zero, negative or not finite.148#[inline]149pub fn set_timestep_seconds(&mut self, seconds: f64) {150assert!(151seconds.is_sign_positive(),152"seconds less than or equal to zero"153);154assert!(seconds.is_finite(), "seconds is infinite");155self.set_timestep(Duration::from_secs_f64(seconds));156}157158/// Sets the amount of virtual time that must pass before the fixed timestep159/// schedule is run again, as frequency.160///161/// The timestep value is set to `1 / hz`, converted to a [`Duration`] which162/// has fixed nanosecond resolution.163///164/// Takes effect immediately on the next run of the schedule, respecting165/// what is currently in [`Self::overstep`].166///167/// # Panics168///169/// Panics if `hz` is zero, negative or not finite.170#[inline]171pub fn set_timestep_hz(&mut self, hz: f64) {172assert!(hz.is_sign_positive(), "Hz less than or equal to zero");173assert!(hz.is_finite(), "Hz is infinite");174self.set_timestep_seconds(1.0 / hz);175}176177/// Returns the amount of overstep time accumulated toward new steps, as178/// [`Duration`].179#[inline]180pub fn overstep(&self) -> Duration {181self.context().overstep182}183184/// Increase the overstep time accumulated towards new steps.185///186/// This method is provided for use in tests. Ordinarily, the [`run_fixed_main_schedule`] system is responsible for calculating the overstep.187#[inline]188pub fn accumulate_overstep(&mut self, delta: Duration) {189self.context_mut().overstep += delta;190}191192/// Discard a part of the overstep amount.193///194/// If `discard` is higher than overstep, the overstep becomes zero.195#[inline]196pub fn discard_overstep(&mut self, discard: Duration) {197let context = self.context_mut();198context.overstep = context.overstep.saturating_sub(discard);199}200201/// Returns the amount of overstep time accumulated toward new steps, as an202/// [`f32`] fraction of the timestep.203#[inline]204pub fn overstep_fraction(&self) -> f32 {205self.context().overstep.as_secs_f32() / self.context().timestep.as_secs_f32()206}207208/// Returns the amount of overstep time accumulated toward new steps, as an209/// [`f64`] fraction of the timestep.210#[inline]211pub fn overstep_fraction_f64(&self) -> f64 {212self.context().overstep.as_secs_f64() / self.context().timestep.as_secs_f64()213}214215fn expend(&mut self) -> bool {216let timestep = self.timestep();217if let Some(new_value) = self.context_mut().overstep.checked_sub(timestep) {218// reduce accumulated and increase elapsed by period219self.context_mut().overstep = new_value;220self.advance_by(timestep);221true222} else {223// no more periods left in accumulated224false225}226}227}228229impl Default for Fixed {230fn default() -> Self {231Self {232timestep: Time::<Fixed>::DEFAULT_TIMESTEP,233overstep: Duration::ZERO,234}235}236}237238/// Runs [`FixedMain`] zero or more times based on delta of239/// [`Time<Virtual>`](Virtual) and [`Time::overstep`].240/// You can order your systems relative to this by using241/// [`RunFixedMainLoopSystems`](bevy_app::prelude::RunFixedMainLoopSystems).242pub fn run_fixed_main_schedule(world: &mut World) {243let delta = world.resource::<Time<Virtual>>().delta();244world245.resource_mut::<Time<Fixed>>()246.accumulate_overstep(delta);247248// Run the schedule until we run out of accumulated time249let _ = world.try_schedule_scope(FixedMain, |world, schedule| {250while world.resource_mut::<Time<Fixed>>().expend() {251*world.resource_mut::<Time>() = world.resource::<Time<Fixed>>().as_generic();252schedule.run(world);253}254});255256*world.resource_mut::<Time>() = world.resource::<Time<Virtual>>().as_generic();257}258259#[cfg(test)]260mod test {261use super::*;262263#[test]264fn test_set_timestep() {265let mut time = Time::<Fixed>::default();266267assert_eq!(time.timestep(), Time::<Fixed>::DEFAULT_TIMESTEP);268269time.set_timestep(Duration::from_millis(500));270assert_eq!(time.timestep(), Duration::from_millis(500));271272time.set_timestep_seconds(0.25);273assert_eq!(time.timestep(), Duration::from_millis(250));274275time.set_timestep_hz(8.0);276assert_eq!(time.timestep(), Duration::from_millis(125));277}278279#[test]280fn test_expend() {281let mut time = Time::<Fixed>::from_seconds(2.0);282283assert_eq!(time.delta(), Duration::ZERO);284assert_eq!(time.elapsed(), Duration::ZERO);285286time.accumulate_overstep(Duration::from_secs(1));287288assert_eq!(time.delta(), Duration::ZERO);289assert_eq!(time.elapsed(), Duration::ZERO);290assert_eq!(time.overstep(), Duration::from_secs(1));291assert_eq!(time.overstep_fraction(), 0.5);292assert_eq!(time.overstep_fraction_f64(), 0.5);293294assert!(!time.expend()); // false295296assert_eq!(time.delta(), Duration::ZERO);297assert_eq!(time.elapsed(), Duration::ZERO);298assert_eq!(time.overstep(), Duration::from_secs(1));299assert_eq!(time.overstep_fraction(), 0.5);300assert_eq!(time.overstep_fraction_f64(), 0.5);301302time.accumulate_overstep(Duration::from_secs(1));303304assert_eq!(time.delta(), Duration::ZERO);305assert_eq!(time.elapsed(), Duration::ZERO);306assert_eq!(time.overstep(), Duration::from_secs(2));307assert_eq!(time.overstep_fraction(), 1.0);308assert_eq!(time.overstep_fraction_f64(), 1.0);309310assert!(time.expend()); // true311312assert_eq!(time.delta(), Duration::from_secs(2));313assert_eq!(time.elapsed(), Duration::from_secs(2));314assert_eq!(time.overstep(), Duration::ZERO);315assert_eq!(time.overstep_fraction(), 0.0);316assert_eq!(time.overstep_fraction_f64(), 0.0);317318assert!(!time.expend()); // false319320assert_eq!(time.delta(), Duration::from_secs(2));321assert_eq!(time.elapsed(), Duration::from_secs(2));322assert_eq!(time.overstep(), Duration::ZERO);323assert_eq!(time.overstep_fraction(), 0.0);324assert_eq!(time.overstep_fraction_f64(), 0.0);325326time.accumulate_overstep(Duration::from_secs(1));327328assert_eq!(time.delta(), Duration::from_secs(2));329assert_eq!(time.elapsed(), Duration::from_secs(2));330assert_eq!(time.overstep(), Duration::from_secs(1));331assert_eq!(time.overstep_fraction(), 0.5);332assert_eq!(time.overstep_fraction_f64(), 0.5);333334assert!(!time.expend()); // false335336assert_eq!(time.delta(), Duration::from_secs(2));337assert_eq!(time.elapsed(), Duration::from_secs(2));338assert_eq!(time.overstep(), Duration::from_secs(1));339assert_eq!(time.overstep_fraction(), 0.5);340assert_eq!(time.overstep_fraction_f64(), 0.5);341}342343#[test]344fn test_expend_multiple() {345let mut time = Time::<Fixed>::from_seconds(2.0);346347time.accumulate_overstep(Duration::from_secs(7));348assert_eq!(time.overstep(), Duration::from_secs(7));349350assert!(time.expend()); // true351assert_eq!(time.elapsed(), Duration::from_secs(2));352assert_eq!(time.overstep(), Duration::from_secs(5));353354assert!(time.expend()); // true355assert_eq!(time.elapsed(), Duration::from_secs(4));356assert_eq!(time.overstep(), Duration::from_secs(3));357358assert!(time.expend()); // true359assert_eq!(time.elapsed(), Duration::from_secs(6));360assert_eq!(time.overstep(), Duration::from_secs(1));361362assert!(!time.expend()); // false363assert_eq!(time.elapsed(), Duration::from_secs(6));364assert_eq!(time.overstep(), Duration::from_secs(1));365}366}367368369