2024-05-12 23:19:07 +00:00
|
|
|
#[cfg(feature = "bevy_reflect")]
|
Unify `FixedTime` and `Time` while fixing several problems (#8964)
# Objective
Current `FixedTime` and `Time` have several problems. This pull aims to
fix many of them at once.
- If there is a longer pause between app updates, time will jump forward
a lot at once and fixed time will iterate on `FixedUpdate` for a large
number of steps. If the pause is merely seconds, then this will just
mean jerkiness and possible unexpected behaviour in gameplay. If the
pause is hours/days as with OS suspend, the game will appear to freeze
until it has caught up with real time.
- If calculating a fixed step takes longer than specified fixed step
period, the game will enter a death spiral where rendering each frame
takes longer and longer due to more and more fixed step updates being
run per frame and the game appears to freeze.
- There is no way to see current fixed step elapsed time inside fixed
steps. In order to track this, the game designer needs to add a custom
system inside `FixedUpdate` that calculates elapsed or step count in a
resource.
- Access to delta time inside fixed step is `FixedStep::period` rather
than `Time::delta`. This, coupled with the issue that `Time::elapsed`
isn't available at all for fixed steps, makes it that time requiring
systems are either implemented to be run in `FixedUpdate` or `Update`,
but rarely work in both.
- Fixes #8800
- Fixes #8543
- Fixes #7439
- Fixes #5692
## Solution
- Create a generic `Time<T>` clock that has no processing logic but
which can be instantiated for multiple usages. This is also exposed for
users to add custom clocks.
- Create three standard clocks, `Time<Real>`, `Time<Virtual>` and
`Time<Fixed>`, all of which contain their individual logic.
- Create one "default" clock, which is just `Time` (or `Time<()>`),
which will be overwritten from `Time<Virtual>` on each update, and
`Time<Fixed>` inside `FixedUpdate` schedule. This way systems that do
not care specifically which time they track can work both in `Update`
and `FixedUpdate` without changes and the behaviour is intuitive.
- Add `max_delta` to virtual time update, which limits how much can be
added to virtual time by a single update. This fixes both the behaviour
after a long freeze, and also the death spiral by limiting how many
fixed timestep iterations there can be per update. Possible future work
could be adding `max_accumulator` to add a sort of "leaky bucket" time
processing to possibly smooth out jumps in time while keeping frame rate
stable.
- Many minor tweaks and clarifications to the time functions and their
documentation.
## Changelog
- `Time::raw_delta()`, `Time::raw_elapsed()` and related methods are
moved to `Time<Real>::delta()` and `Time<Real>::elapsed()` and now match
`Time` API
- `FixedTime` is now `Time<Fixed>` and matches `Time` API.
- `Time<Fixed>` default timestep is now 64 Hz, or 15625 microseconds.
- `Time` inside `FixedUpdate` now reflects fixed timestep time, making
systems portable between `Update ` and `FixedUpdate`.
- `Time::pause()`, `Time::set_relative_speed()` and related methods must
now be called as `Time<Virtual>::pause()` etc.
- There is a new `max_delta` setting in `Time<Virtual>` that limits how
much the clock can jump by a single update. The default value is 0.25
seconds.
- Removed `on_fixed_timer()` condition as `on_timer()` does the right
thing inside `FixedUpdate` now.
## Migration Guide
- Change all `Res<Time>` instances that access `raw_delta()`,
`raw_elapsed()` and related methods to `Res<Time<Real>>` and `delta()`,
`elapsed()`, etc.
- Change access to `period` from `Res<FixedTime>` to `Res<Time<Fixed>>`
and use `delta()`.
- The default timestep has been changed from 60 Hz to 64 Hz. If you wish
to restore the old behaviour, use
`app.insert_resource(Time::<Fixed>::from_hz(60.0))`.
- Change `app.insert_resource(FixedTime::new(duration))` to
`app.insert_resource(Time::<Fixed>::from_duration(duration))`
- Change `app.insert_resource(FixedTime::new_from_secs(secs))` to
`app.insert_resource(Time::<Fixed>::from_seconds(secs))`
- Change `system.on_fixed_timer(duration)` to
`system.on_timer(duration)`. Timers in systems placed in `FixedUpdate`
schedule automatically use the fixed time clock.
- Change `ResMut<Time>` calls to `pause()`, `is_paused()`,
`set_relative_speed()` and related methods to `ResMut<Time<Virtual>>`
calls. The API is the same, with the exception that `relative_speed()`
will return the actual last ste relative speed, while
`effective_relative_speed()` returns 0.0 if the time is paused and
corresponds to the speed that was set when the update for the current
frame started.
## Todo
- [x] Update pull name and description
- [x] Top level documentation on usage
- [x] Fix examples
- [x] Decide on default `max_delta` value
- [x] Decide naming of the three clocks: is `Real`, `Virtual`, `Fixed`
good?
- [x] Decide if the three clock inner structures should be in prelude
- [x] Decide on best way to configure values at startup: is manually
inserting a new clock instance okay, or should there be config struct
separately?
- [x] Fix links in docs
- [x] Decide what should be public and what not
- [x] Decide how `wrap_period` should be handled when it is changed
- [x] ~~Add toggles to disable setting the clock as default?~~ No,
separate pull if needed.
- [x] Add tests
- [x] Reformat, ensure adheres to conventions etc.
- [x] Build documentation and see that it looks correct
## Contributors
Huge thanks to @alice-i-cecile and @maniwani while building this pull.
It was a shared effort!
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Cameron <51241057+maniwani@users.noreply.github.com>
Co-authored-by: Jerome Humbert <djeedai@gmail.com>
2023-10-16 01:57:55 +00:00
|
|
|
use bevy_reflect::Reflect;
|
2023-10-16 19:18:39 +00:00
|
|
|
use bevy_utils::{tracing::debug, Duration};
|
Unify `FixedTime` and `Time` while fixing several problems (#8964)
# Objective
Current `FixedTime` and `Time` have several problems. This pull aims to
fix many of them at once.
- If there is a longer pause between app updates, time will jump forward
a lot at once and fixed time will iterate on `FixedUpdate` for a large
number of steps. If the pause is merely seconds, then this will just
mean jerkiness and possible unexpected behaviour in gameplay. If the
pause is hours/days as with OS suspend, the game will appear to freeze
until it has caught up with real time.
- If calculating a fixed step takes longer than specified fixed step
period, the game will enter a death spiral where rendering each frame
takes longer and longer due to more and more fixed step updates being
run per frame and the game appears to freeze.
- There is no way to see current fixed step elapsed time inside fixed
steps. In order to track this, the game designer needs to add a custom
system inside `FixedUpdate` that calculates elapsed or step count in a
resource.
- Access to delta time inside fixed step is `FixedStep::period` rather
than `Time::delta`. This, coupled with the issue that `Time::elapsed`
isn't available at all for fixed steps, makes it that time requiring
systems are either implemented to be run in `FixedUpdate` or `Update`,
but rarely work in both.
- Fixes #8800
- Fixes #8543
- Fixes #7439
- Fixes #5692
## Solution
- Create a generic `Time<T>` clock that has no processing logic but
which can be instantiated for multiple usages. This is also exposed for
users to add custom clocks.
- Create three standard clocks, `Time<Real>`, `Time<Virtual>` and
`Time<Fixed>`, all of which contain their individual logic.
- Create one "default" clock, which is just `Time` (or `Time<()>`),
which will be overwritten from `Time<Virtual>` on each update, and
`Time<Fixed>` inside `FixedUpdate` schedule. This way systems that do
not care specifically which time they track can work both in `Update`
and `FixedUpdate` without changes and the behaviour is intuitive.
- Add `max_delta` to virtual time update, which limits how much can be
added to virtual time by a single update. This fixes both the behaviour
after a long freeze, and also the death spiral by limiting how many
fixed timestep iterations there can be per update. Possible future work
could be adding `max_accumulator` to add a sort of "leaky bucket" time
processing to possibly smooth out jumps in time while keeping frame rate
stable.
- Many minor tweaks and clarifications to the time functions and their
documentation.
## Changelog
- `Time::raw_delta()`, `Time::raw_elapsed()` and related methods are
moved to `Time<Real>::delta()` and `Time<Real>::elapsed()` and now match
`Time` API
- `FixedTime` is now `Time<Fixed>` and matches `Time` API.
- `Time<Fixed>` default timestep is now 64 Hz, or 15625 microseconds.
- `Time` inside `FixedUpdate` now reflects fixed timestep time, making
systems portable between `Update ` and `FixedUpdate`.
- `Time::pause()`, `Time::set_relative_speed()` and related methods must
now be called as `Time<Virtual>::pause()` etc.
- There is a new `max_delta` setting in `Time<Virtual>` that limits how
much the clock can jump by a single update. The default value is 0.25
seconds.
- Removed `on_fixed_timer()` condition as `on_timer()` does the right
thing inside `FixedUpdate` now.
## Migration Guide
- Change all `Res<Time>` instances that access `raw_delta()`,
`raw_elapsed()` and related methods to `Res<Time<Real>>` and `delta()`,
`elapsed()`, etc.
- Change access to `period` from `Res<FixedTime>` to `Res<Time<Fixed>>`
and use `delta()`.
- The default timestep has been changed from 60 Hz to 64 Hz. If you wish
to restore the old behaviour, use
`app.insert_resource(Time::<Fixed>::from_hz(60.0))`.
- Change `app.insert_resource(FixedTime::new(duration))` to
`app.insert_resource(Time::<Fixed>::from_duration(duration))`
- Change `app.insert_resource(FixedTime::new_from_secs(secs))` to
`app.insert_resource(Time::<Fixed>::from_seconds(secs))`
- Change `system.on_fixed_timer(duration)` to
`system.on_timer(duration)`. Timers in systems placed in `FixedUpdate`
schedule automatically use the fixed time clock.
- Change `ResMut<Time>` calls to `pause()`, `is_paused()`,
`set_relative_speed()` and related methods to `ResMut<Time<Virtual>>`
calls. The API is the same, with the exception that `relative_speed()`
will return the actual last ste relative speed, while
`effective_relative_speed()` returns 0.0 if the time is paused and
corresponds to the speed that was set when the update for the current
frame started.
## Todo
- [x] Update pull name and description
- [x] Top level documentation on usage
- [x] Fix examples
- [x] Decide on default `max_delta` value
- [x] Decide naming of the three clocks: is `Real`, `Virtual`, `Fixed`
good?
- [x] Decide if the three clock inner structures should be in prelude
- [x] Decide on best way to configure values at startup: is manually
inserting a new clock instance okay, or should there be config struct
separately?
- [x] Fix links in docs
- [x] Decide what should be public and what not
- [x] Decide how `wrap_period` should be handled when it is changed
- [x] ~~Add toggles to disable setting the clock as default?~~ No,
separate pull if needed.
- [x] Add tests
- [x] Reformat, ensure adheres to conventions etc.
- [x] Build documentation and see that it looks correct
## Contributors
Huge thanks to @alice-i-cecile and @maniwani while building this pull.
It was a shared effort!
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Cameron <51241057+maniwani@users.noreply.github.com>
Co-authored-by: Jerome Humbert <djeedai@gmail.com>
2023-10-16 01:57:55 +00:00
|
|
|
|
|
|
|
use crate::{real::Real, time::Time};
|
|
|
|
|
|
|
|
/// The virtual game clock representing game time.
|
|
|
|
///
|
|
|
|
/// A specialization of the [`Time`] structure. **For method documentation, see
|
|
|
|
/// [`Time<Virtual>#impl-Time<Virtual>`].**
|
|
|
|
///
|
|
|
|
/// Normally used as `Time<Virtual>`. It is automatically inserted as a resource
|
|
|
|
/// by [`TimePlugin`](crate::TimePlugin) and updated based on
|
|
|
|
/// [`Time<Real>`](Real). The virtual clock is automatically set as the default
|
|
|
|
/// generic [`Time`] resource for the update.
|
|
|
|
///
|
|
|
|
/// The virtual clock differs from real time clock in that it can be paused, sped up
|
|
|
|
/// and slowed down. It also limits how much it can advance in a single update
|
|
|
|
/// in order to prevent unexpected behavior in cases where updates do not happen
|
|
|
|
/// at regular intervals (e.g. coming back after the program was suspended a long time).
|
|
|
|
///
|
|
|
|
/// The virtual clock can be paused by calling [`pause()`](Time::pause) and
|
|
|
|
/// unpaused by calling [`unpause()`](Time::unpause). When the game clock is
|
|
|
|
/// paused [`delta()`](Time::delta) will be zero on each update, and
|
|
|
|
/// [`elapsed()`](Time::elapsed) will not grow.
|
|
|
|
/// [`effective_speed()`](Time::effective_speed) will return `0.0`. Calling
|
|
|
|
/// [`pause()`](Time::pause) will not affect value the [`delta()`](Time::delta)
|
|
|
|
/// value for the update currently being processed.
|
|
|
|
///
|
|
|
|
/// The speed of the virtual clock can be changed by calling
|
|
|
|
/// [`set_relative_speed()`](Time::set_relative_speed). A value of `2.0` means
|
|
|
|
/// that virtual clock should advance twice as fast as real time, meaning that
|
|
|
|
/// [`delta()`](Time::delta) values will be double of what
|
|
|
|
/// [`Time<Real>::delta()`](Time::delta) reports and
|
|
|
|
/// [`elapsed()`](Time::elapsed) will go twice as fast as
|
|
|
|
/// [`Time<Real>::elapsed()`](Time::elapsed). Calling
|
|
|
|
/// [`set_relative_speed()`](Time::set_relative_speed) will not affect the
|
|
|
|
/// [`delta()`](Time::delta) value for the update currently being processed.
|
|
|
|
///
|
|
|
|
/// The maximum amount of delta time that can be added by a single update can be
|
|
|
|
/// set by [`set_max_delta()`](Time::set_max_delta). This value serves a dual
|
|
|
|
/// purpose in the virtual clock.
|
|
|
|
///
|
|
|
|
/// If the game temporarily freezes due to any reason, such as disk access, a
|
|
|
|
/// blocking system call, or operating system level suspend, reporting the full
|
|
|
|
/// elapsed delta time is likely to cause bugs in game logic. Usually if a
|
|
|
|
/// laptop is suspended for an hour, it doesn't make sense to try to simulate
|
|
|
|
/// the game logic for the elapsed hour when resuming. Instead it is better to
|
|
|
|
/// lose the extra time and pretend a shorter duration of time passed. Setting
|
|
|
|
/// [`max_delta()`](Time::max_delta) to a relatively short time means that the
|
|
|
|
/// impact on game logic will be minimal.
|
|
|
|
///
|
|
|
|
/// If the game lags for some reason, meaning that it will take a longer time to
|
|
|
|
/// compute a frame than the real time that passes during the computation, then
|
|
|
|
/// we would fall behind in processing virtual time. If this situation persists,
|
|
|
|
/// and computing a frame takes longer depending on how much virtual time has
|
|
|
|
/// passed, the game would enter a "death spiral" where computing each frame
|
|
|
|
/// takes longer and longer and the game will appear to freeze. By limiting the
|
|
|
|
/// maximum time that can be added at once, we also limit the amount of virtual
|
|
|
|
/// time the game needs to compute for each frame. This means that the game will
|
|
|
|
/// run slow, and it will run slower than real time, but it will not freeze and
|
|
|
|
/// it will recover as soon as computation becomes fast again.
|
|
|
|
///
|
|
|
|
/// You should set [`max_delta()`](Time::max_delta) to a value that is
|
|
|
|
/// approximately the minimum FPS your game should have even if heavily lagged
|
|
|
|
/// for a moment. The actual FPS when lagged will be somewhat lower than this,
|
|
|
|
/// depending on how much more time it takes to compute a frame compared to real
|
|
|
|
/// time. You should also consider how stable your FPS is, as the limit will
|
|
|
|
/// also dictate how big of an FPS drop you can accept without losing time and
|
|
|
|
/// falling behind real time.
|
2024-05-12 23:19:07 +00:00
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
|
|
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
Unify `FixedTime` and `Time` while fixing several problems (#8964)
# Objective
Current `FixedTime` and `Time` have several problems. This pull aims to
fix many of them at once.
- If there is a longer pause between app updates, time will jump forward
a lot at once and fixed time will iterate on `FixedUpdate` for a large
number of steps. If the pause is merely seconds, then this will just
mean jerkiness and possible unexpected behaviour in gameplay. If the
pause is hours/days as with OS suspend, the game will appear to freeze
until it has caught up with real time.
- If calculating a fixed step takes longer than specified fixed step
period, the game will enter a death spiral where rendering each frame
takes longer and longer due to more and more fixed step updates being
run per frame and the game appears to freeze.
- There is no way to see current fixed step elapsed time inside fixed
steps. In order to track this, the game designer needs to add a custom
system inside `FixedUpdate` that calculates elapsed or step count in a
resource.
- Access to delta time inside fixed step is `FixedStep::period` rather
than `Time::delta`. This, coupled with the issue that `Time::elapsed`
isn't available at all for fixed steps, makes it that time requiring
systems are either implemented to be run in `FixedUpdate` or `Update`,
but rarely work in both.
- Fixes #8800
- Fixes #8543
- Fixes #7439
- Fixes #5692
## Solution
- Create a generic `Time<T>` clock that has no processing logic but
which can be instantiated for multiple usages. This is also exposed for
users to add custom clocks.
- Create three standard clocks, `Time<Real>`, `Time<Virtual>` and
`Time<Fixed>`, all of which contain their individual logic.
- Create one "default" clock, which is just `Time` (or `Time<()>`),
which will be overwritten from `Time<Virtual>` on each update, and
`Time<Fixed>` inside `FixedUpdate` schedule. This way systems that do
not care specifically which time they track can work both in `Update`
and `FixedUpdate` without changes and the behaviour is intuitive.
- Add `max_delta` to virtual time update, which limits how much can be
added to virtual time by a single update. This fixes both the behaviour
after a long freeze, and also the death spiral by limiting how many
fixed timestep iterations there can be per update. Possible future work
could be adding `max_accumulator` to add a sort of "leaky bucket" time
processing to possibly smooth out jumps in time while keeping frame rate
stable.
- Many minor tweaks and clarifications to the time functions and their
documentation.
## Changelog
- `Time::raw_delta()`, `Time::raw_elapsed()` and related methods are
moved to `Time<Real>::delta()` and `Time<Real>::elapsed()` and now match
`Time` API
- `FixedTime` is now `Time<Fixed>` and matches `Time` API.
- `Time<Fixed>` default timestep is now 64 Hz, or 15625 microseconds.
- `Time` inside `FixedUpdate` now reflects fixed timestep time, making
systems portable between `Update ` and `FixedUpdate`.
- `Time::pause()`, `Time::set_relative_speed()` and related methods must
now be called as `Time<Virtual>::pause()` etc.
- There is a new `max_delta` setting in `Time<Virtual>` that limits how
much the clock can jump by a single update. The default value is 0.25
seconds.
- Removed `on_fixed_timer()` condition as `on_timer()` does the right
thing inside `FixedUpdate` now.
## Migration Guide
- Change all `Res<Time>` instances that access `raw_delta()`,
`raw_elapsed()` and related methods to `Res<Time<Real>>` and `delta()`,
`elapsed()`, etc.
- Change access to `period` from `Res<FixedTime>` to `Res<Time<Fixed>>`
and use `delta()`.
- The default timestep has been changed from 60 Hz to 64 Hz. If you wish
to restore the old behaviour, use
`app.insert_resource(Time::<Fixed>::from_hz(60.0))`.
- Change `app.insert_resource(FixedTime::new(duration))` to
`app.insert_resource(Time::<Fixed>::from_duration(duration))`
- Change `app.insert_resource(FixedTime::new_from_secs(secs))` to
`app.insert_resource(Time::<Fixed>::from_seconds(secs))`
- Change `system.on_fixed_timer(duration)` to
`system.on_timer(duration)`. Timers in systems placed in `FixedUpdate`
schedule automatically use the fixed time clock.
- Change `ResMut<Time>` calls to `pause()`, `is_paused()`,
`set_relative_speed()` and related methods to `ResMut<Time<Virtual>>`
calls. The API is the same, with the exception that `relative_speed()`
will return the actual last ste relative speed, while
`effective_relative_speed()` returns 0.0 if the time is paused and
corresponds to the speed that was set when the update for the current
frame started.
## Todo
- [x] Update pull name and description
- [x] Top level documentation on usage
- [x] Fix examples
- [x] Decide on default `max_delta` value
- [x] Decide naming of the three clocks: is `Real`, `Virtual`, `Fixed`
good?
- [x] Decide if the three clock inner structures should be in prelude
- [x] Decide on best way to configure values at startup: is manually
inserting a new clock instance okay, or should there be config struct
separately?
- [x] Fix links in docs
- [x] Decide what should be public and what not
- [x] Decide how `wrap_period` should be handled when it is changed
- [x] ~~Add toggles to disable setting the clock as default?~~ No,
separate pull if needed.
- [x] Add tests
- [x] Reformat, ensure adheres to conventions etc.
- [x] Build documentation and see that it looks correct
## Contributors
Huge thanks to @alice-i-cecile and @maniwani while building this pull.
It was a shared effort!
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Cameron <51241057+maniwani@users.noreply.github.com>
Co-authored-by: Jerome Humbert <djeedai@gmail.com>
2023-10-16 01:57:55 +00:00
|
|
|
pub struct Virtual {
|
|
|
|
max_delta: Duration,
|
|
|
|
paused: bool,
|
|
|
|
relative_speed: f64,
|
|
|
|
effective_speed: f64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Time<Virtual> {
|
|
|
|
/// The default amount of time that can added in a single update.
|
|
|
|
///
|
|
|
|
/// Equal to 250 milliseconds.
|
|
|
|
const DEFAULT_MAX_DELTA: Duration = Duration::from_millis(250);
|
|
|
|
|
|
|
|
/// Create new virtual clock with given maximum delta step [`Duration`]
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics if `max_delta` is zero.
|
|
|
|
pub fn from_max_delta(max_delta: Duration) -> Self {
|
|
|
|
let mut ret = Self::default();
|
|
|
|
ret.set_max_delta(max_delta);
|
|
|
|
ret
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the maximum amount of time that can be added to this clock by a
|
|
|
|
/// single update, as [`Duration`].
|
|
|
|
///
|
|
|
|
/// This is the maximum value [`Self::delta()`] will return and also to
|
|
|
|
/// maximum time [`Self::elapsed()`] will be increased by in a single
|
|
|
|
/// update.
|
|
|
|
///
|
|
|
|
/// This ensures that even if no updates happen for an extended amount of time,
|
|
|
|
/// the clock will not have a sudden, huge advance all at once. This also indirectly
|
|
|
|
/// limits the maximum number of fixed update steps that can run in a single update.
|
|
|
|
///
|
|
|
|
/// The default value is 250 milliseconds.
|
|
|
|
#[inline]
|
|
|
|
pub fn max_delta(&self) -> Duration {
|
|
|
|
self.context().max_delta
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the maximum amount of time that can be added to this clock by a
|
|
|
|
/// single update, as [`Duration`].
|
|
|
|
///
|
|
|
|
/// This is the maximum value [`Self::delta()`] will return and also to
|
|
|
|
/// maximum time [`Self::elapsed()`] will be increased by in a single
|
|
|
|
/// update.
|
|
|
|
///
|
|
|
|
/// This is used to ensure that even if the game freezes for a few seconds,
|
|
|
|
/// or is suspended for hours or even days, the virtual clock doesn't
|
|
|
|
/// suddenly jump forward for that full amount, which would likely cause
|
|
|
|
/// gameplay bugs or having to suddenly simulate all the intervening time.
|
|
|
|
///
|
|
|
|
/// If no updates happen for an extended amount of time, this limit prevents
|
|
|
|
/// having a sudden, huge advance all at once. This also indirectly limits
|
|
|
|
/// the maximum number of fixed update steps that can run in a single
|
|
|
|
/// update.
|
|
|
|
///
|
|
|
|
/// The default value is 250 milliseconds. If you want to disable this
|
|
|
|
/// feature, set the value to [`Duration::MAX`].
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics if `max_delta` is zero.
|
|
|
|
#[inline]
|
|
|
|
pub fn set_max_delta(&mut self, max_delta: Duration) {
|
|
|
|
assert_ne!(max_delta, Duration::ZERO, "tried to set max delta to zero");
|
|
|
|
self.context_mut().max_delta = max_delta;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the speed the clock advances relative to your system clock, as [`f32`].
|
|
|
|
/// This is known as "time scaling" or "time dilation" in other engines.
|
|
|
|
#[inline]
|
|
|
|
pub fn relative_speed(&self) -> f32 {
|
|
|
|
self.relative_speed_f64() as f32
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the speed the clock advances relative to your system clock, as [`f64`].
|
|
|
|
/// This is known as "time scaling" or "time dilation" in other engines.
|
|
|
|
#[inline]
|
|
|
|
pub fn relative_speed_f64(&self) -> f64 {
|
|
|
|
self.context().relative_speed
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the speed the clock advanced relative to your system clock in
|
|
|
|
/// this update, as [`f32`].
|
|
|
|
///
|
|
|
|
/// Returns `0.0` if the game was paused or what the `relative_speed` value
|
|
|
|
/// was at the start of this update.
|
|
|
|
#[inline]
|
|
|
|
pub fn effective_speed(&self) -> f32 {
|
|
|
|
self.context().effective_speed as f32
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the speed the clock advanced relative to your system clock in
|
|
|
|
/// this update, as [`f64`].
|
|
|
|
///
|
|
|
|
/// Returns `0.0` if the game was paused or what the `relative_speed` value
|
|
|
|
/// was at the start of this update.
|
|
|
|
#[inline]
|
|
|
|
pub fn effective_speed_f64(&self) -> f64 {
|
|
|
|
self.context().effective_speed
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the speed the clock advances relative to your system clock, given as an [`f32`].
|
|
|
|
///
|
|
|
|
/// For example, setting this to `2.0` will make the clock advance twice as fast as your system
|
|
|
|
/// clock.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics if `ratio` is negative or not finite.
|
|
|
|
#[inline]
|
|
|
|
pub fn set_relative_speed(&mut self, ratio: f32) {
|
|
|
|
self.set_relative_speed_f64(ratio as f64);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the speed the clock advances relative to your system clock, given as an [`f64`].
|
|
|
|
///
|
|
|
|
/// For example, setting this to `2.0` will make the clock advance twice as fast as your system
|
|
|
|
/// clock.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics if `ratio` is negative or not finite.
|
|
|
|
#[inline]
|
|
|
|
pub fn set_relative_speed_f64(&mut self, ratio: f64) {
|
|
|
|
assert!(ratio.is_finite(), "tried to go infinitely fast");
|
|
|
|
assert!(ratio >= 0.0, "tried to go back in time");
|
|
|
|
self.context_mut().relative_speed = ratio;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Stops the clock, preventing it from advancing until resumed.
|
|
|
|
#[inline]
|
|
|
|
pub fn pause(&mut self) {
|
|
|
|
self.context_mut().paused = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Resumes the clock if paused.
|
|
|
|
#[inline]
|
|
|
|
pub fn unpause(&mut self) {
|
|
|
|
self.context_mut().paused = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if the clock is currently paused.
|
|
|
|
#[inline]
|
|
|
|
pub fn is_paused(&self) -> bool {
|
|
|
|
self.context().paused
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if the clock was paused at the start of this update.
|
|
|
|
#[inline]
|
|
|
|
pub fn was_paused(&self) -> bool {
|
|
|
|
self.context().effective_speed == 0.0
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Updates the elapsed duration of `self` by `raw_delta`, up to the `max_delta`.
|
|
|
|
fn advance_with_raw_delta(&mut self, raw_delta: Duration) {
|
|
|
|
let max_delta = self.context().max_delta;
|
|
|
|
let clamped_delta = if raw_delta > max_delta {
|
2023-10-16 19:18:39 +00:00
|
|
|
debug!(
|
Unify `FixedTime` and `Time` while fixing several problems (#8964)
# Objective
Current `FixedTime` and `Time` have several problems. This pull aims to
fix many of them at once.
- If there is a longer pause between app updates, time will jump forward
a lot at once and fixed time will iterate on `FixedUpdate` for a large
number of steps. If the pause is merely seconds, then this will just
mean jerkiness and possible unexpected behaviour in gameplay. If the
pause is hours/days as with OS suspend, the game will appear to freeze
until it has caught up with real time.
- If calculating a fixed step takes longer than specified fixed step
period, the game will enter a death spiral where rendering each frame
takes longer and longer due to more and more fixed step updates being
run per frame and the game appears to freeze.
- There is no way to see current fixed step elapsed time inside fixed
steps. In order to track this, the game designer needs to add a custom
system inside `FixedUpdate` that calculates elapsed or step count in a
resource.
- Access to delta time inside fixed step is `FixedStep::period` rather
than `Time::delta`. This, coupled with the issue that `Time::elapsed`
isn't available at all for fixed steps, makes it that time requiring
systems are either implemented to be run in `FixedUpdate` or `Update`,
but rarely work in both.
- Fixes #8800
- Fixes #8543
- Fixes #7439
- Fixes #5692
## Solution
- Create a generic `Time<T>` clock that has no processing logic but
which can be instantiated for multiple usages. This is also exposed for
users to add custom clocks.
- Create three standard clocks, `Time<Real>`, `Time<Virtual>` and
`Time<Fixed>`, all of which contain their individual logic.
- Create one "default" clock, which is just `Time` (or `Time<()>`),
which will be overwritten from `Time<Virtual>` on each update, and
`Time<Fixed>` inside `FixedUpdate` schedule. This way systems that do
not care specifically which time they track can work both in `Update`
and `FixedUpdate` without changes and the behaviour is intuitive.
- Add `max_delta` to virtual time update, which limits how much can be
added to virtual time by a single update. This fixes both the behaviour
after a long freeze, and also the death spiral by limiting how many
fixed timestep iterations there can be per update. Possible future work
could be adding `max_accumulator` to add a sort of "leaky bucket" time
processing to possibly smooth out jumps in time while keeping frame rate
stable.
- Many minor tweaks and clarifications to the time functions and their
documentation.
## Changelog
- `Time::raw_delta()`, `Time::raw_elapsed()` and related methods are
moved to `Time<Real>::delta()` and `Time<Real>::elapsed()` and now match
`Time` API
- `FixedTime` is now `Time<Fixed>` and matches `Time` API.
- `Time<Fixed>` default timestep is now 64 Hz, or 15625 microseconds.
- `Time` inside `FixedUpdate` now reflects fixed timestep time, making
systems portable between `Update ` and `FixedUpdate`.
- `Time::pause()`, `Time::set_relative_speed()` and related methods must
now be called as `Time<Virtual>::pause()` etc.
- There is a new `max_delta` setting in `Time<Virtual>` that limits how
much the clock can jump by a single update. The default value is 0.25
seconds.
- Removed `on_fixed_timer()` condition as `on_timer()` does the right
thing inside `FixedUpdate` now.
## Migration Guide
- Change all `Res<Time>` instances that access `raw_delta()`,
`raw_elapsed()` and related methods to `Res<Time<Real>>` and `delta()`,
`elapsed()`, etc.
- Change access to `period` from `Res<FixedTime>` to `Res<Time<Fixed>>`
and use `delta()`.
- The default timestep has been changed from 60 Hz to 64 Hz. If you wish
to restore the old behaviour, use
`app.insert_resource(Time::<Fixed>::from_hz(60.0))`.
- Change `app.insert_resource(FixedTime::new(duration))` to
`app.insert_resource(Time::<Fixed>::from_duration(duration))`
- Change `app.insert_resource(FixedTime::new_from_secs(secs))` to
`app.insert_resource(Time::<Fixed>::from_seconds(secs))`
- Change `system.on_fixed_timer(duration)` to
`system.on_timer(duration)`. Timers in systems placed in `FixedUpdate`
schedule automatically use the fixed time clock.
- Change `ResMut<Time>` calls to `pause()`, `is_paused()`,
`set_relative_speed()` and related methods to `ResMut<Time<Virtual>>`
calls. The API is the same, with the exception that `relative_speed()`
will return the actual last ste relative speed, while
`effective_relative_speed()` returns 0.0 if the time is paused and
corresponds to the speed that was set when the update for the current
frame started.
## Todo
- [x] Update pull name and description
- [x] Top level documentation on usage
- [x] Fix examples
- [x] Decide on default `max_delta` value
- [x] Decide naming of the three clocks: is `Real`, `Virtual`, `Fixed`
good?
- [x] Decide if the three clock inner structures should be in prelude
- [x] Decide on best way to configure values at startup: is manually
inserting a new clock instance okay, or should there be config struct
separately?
- [x] Fix links in docs
- [x] Decide what should be public and what not
- [x] Decide how `wrap_period` should be handled when it is changed
- [x] ~~Add toggles to disable setting the clock as default?~~ No,
separate pull if needed.
- [x] Add tests
- [x] Reformat, ensure adheres to conventions etc.
- [x] Build documentation and see that it looks correct
## Contributors
Huge thanks to @alice-i-cecile and @maniwani while building this pull.
It was a shared effort!
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Cameron <51241057+maniwani@users.noreply.github.com>
Co-authored-by: Jerome Humbert <djeedai@gmail.com>
2023-10-16 01:57:55 +00:00
|
|
|
"delta time larger than maximum delta, clamping delta to {:?} and skipping {:?}",
|
|
|
|
max_delta,
|
|
|
|
raw_delta - max_delta
|
|
|
|
);
|
|
|
|
max_delta
|
|
|
|
} else {
|
|
|
|
raw_delta
|
|
|
|
};
|
|
|
|
let effective_speed = if self.context().paused {
|
|
|
|
0.0
|
|
|
|
} else {
|
|
|
|
self.context().relative_speed
|
|
|
|
};
|
|
|
|
let delta = if effective_speed != 1.0 {
|
|
|
|
clamped_delta.mul_f64(effective_speed)
|
|
|
|
} else {
|
|
|
|
// avoid rounding when at normal speed
|
|
|
|
clamped_delta
|
|
|
|
};
|
|
|
|
self.context_mut().effective_speed = effective_speed;
|
|
|
|
self.advance_by(delta);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Virtual {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
max_delta: Time::<Virtual>::DEFAULT_MAX_DELTA,
|
|
|
|
paused: false,
|
|
|
|
relative_speed: 1.0,
|
|
|
|
effective_speed: 1.0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Advances [`Time<Virtual>`] and [`Time`] based on the elapsed [`Time<Real>`].
|
|
|
|
///
|
|
|
|
/// The virtual time will be advanced up to the provided [`Time::max_delta`].
|
Optimize Event Updates (#12936)
# Objective
Improve performance scalability when adding new event types to a Bevy
app. Currently, just using Bevy in the default configuration, all apps
spend upwards of 100+us in the `First` schedule, every app tick,
evaluating if it should update events or not, even if events are not
being used for that particular frame, and this scales with the number of
Events registered in the app.
## Solution
As `Events::update` is guaranteed `O(1)` by just checking if a
resource's value, swapping two Vecs, and then clearing one of them, the
actual cost of running `event_update_system` is *very* cheap. The
overhead of doing system dependency injection, task scheduling ,and the
multithreaded executor outweighs the cost of running the system by a
large margin.
Create an `EventRegistry` resource that keeps a number of function
pointers that update each event. Replace the per-event type
`event_update_system` with a singular exclusive system uses the
`EventRegistry` to update all events instead. Update `SubApp::add_event`
to use `EventRegistry` instead.
## Performance
This speeds reduces the cost of the `First` schedule in both many_foxes
and many_cubes by over 80%. Note this is with system spans on. The
majority of this is now context-switching costs from launching
`time_system`, which should be mostly eliminated with #12869.
![image](https://github.com/bevyengine/bevy/assets/3137680/037624be-21a2-4dc2-a42f-9d0bfa3e9b4a)
The actual `event_update_system` is usually *very* short, using only a
few microseconds on average.
![image](https://github.com/bevyengine/bevy/assets/3137680/01ff1689-3595-49b6-8f09-5c44bcf903e8)
---
## Changelog
TODO
## Migration Guide
TODO
---------
Co-authored-by: Josh Matthews <josh@joshmatthews.net>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-04-13 14:11:28 +00:00
|
|
|
pub fn update_virtual_time(current: &mut Time, virt: &mut Time<Virtual>, real: &Time<Real>) {
|
Unify `FixedTime` and `Time` while fixing several problems (#8964)
# Objective
Current `FixedTime` and `Time` have several problems. This pull aims to
fix many of them at once.
- If there is a longer pause between app updates, time will jump forward
a lot at once and fixed time will iterate on `FixedUpdate` for a large
number of steps. If the pause is merely seconds, then this will just
mean jerkiness and possible unexpected behaviour in gameplay. If the
pause is hours/days as with OS suspend, the game will appear to freeze
until it has caught up with real time.
- If calculating a fixed step takes longer than specified fixed step
period, the game will enter a death spiral where rendering each frame
takes longer and longer due to more and more fixed step updates being
run per frame and the game appears to freeze.
- There is no way to see current fixed step elapsed time inside fixed
steps. In order to track this, the game designer needs to add a custom
system inside `FixedUpdate` that calculates elapsed or step count in a
resource.
- Access to delta time inside fixed step is `FixedStep::period` rather
than `Time::delta`. This, coupled with the issue that `Time::elapsed`
isn't available at all for fixed steps, makes it that time requiring
systems are either implemented to be run in `FixedUpdate` or `Update`,
but rarely work in both.
- Fixes #8800
- Fixes #8543
- Fixes #7439
- Fixes #5692
## Solution
- Create a generic `Time<T>` clock that has no processing logic but
which can be instantiated for multiple usages. This is also exposed for
users to add custom clocks.
- Create three standard clocks, `Time<Real>`, `Time<Virtual>` and
`Time<Fixed>`, all of which contain their individual logic.
- Create one "default" clock, which is just `Time` (or `Time<()>`),
which will be overwritten from `Time<Virtual>` on each update, and
`Time<Fixed>` inside `FixedUpdate` schedule. This way systems that do
not care specifically which time they track can work both in `Update`
and `FixedUpdate` without changes and the behaviour is intuitive.
- Add `max_delta` to virtual time update, which limits how much can be
added to virtual time by a single update. This fixes both the behaviour
after a long freeze, and also the death spiral by limiting how many
fixed timestep iterations there can be per update. Possible future work
could be adding `max_accumulator` to add a sort of "leaky bucket" time
processing to possibly smooth out jumps in time while keeping frame rate
stable.
- Many minor tweaks and clarifications to the time functions and their
documentation.
## Changelog
- `Time::raw_delta()`, `Time::raw_elapsed()` and related methods are
moved to `Time<Real>::delta()` and `Time<Real>::elapsed()` and now match
`Time` API
- `FixedTime` is now `Time<Fixed>` and matches `Time` API.
- `Time<Fixed>` default timestep is now 64 Hz, or 15625 microseconds.
- `Time` inside `FixedUpdate` now reflects fixed timestep time, making
systems portable between `Update ` and `FixedUpdate`.
- `Time::pause()`, `Time::set_relative_speed()` and related methods must
now be called as `Time<Virtual>::pause()` etc.
- There is a new `max_delta` setting in `Time<Virtual>` that limits how
much the clock can jump by a single update. The default value is 0.25
seconds.
- Removed `on_fixed_timer()` condition as `on_timer()` does the right
thing inside `FixedUpdate` now.
## Migration Guide
- Change all `Res<Time>` instances that access `raw_delta()`,
`raw_elapsed()` and related methods to `Res<Time<Real>>` and `delta()`,
`elapsed()`, etc.
- Change access to `period` from `Res<FixedTime>` to `Res<Time<Fixed>>`
and use `delta()`.
- The default timestep has been changed from 60 Hz to 64 Hz. If you wish
to restore the old behaviour, use
`app.insert_resource(Time::<Fixed>::from_hz(60.0))`.
- Change `app.insert_resource(FixedTime::new(duration))` to
`app.insert_resource(Time::<Fixed>::from_duration(duration))`
- Change `app.insert_resource(FixedTime::new_from_secs(secs))` to
`app.insert_resource(Time::<Fixed>::from_seconds(secs))`
- Change `system.on_fixed_timer(duration)` to
`system.on_timer(duration)`. Timers in systems placed in `FixedUpdate`
schedule automatically use the fixed time clock.
- Change `ResMut<Time>` calls to `pause()`, `is_paused()`,
`set_relative_speed()` and related methods to `ResMut<Time<Virtual>>`
calls. The API is the same, with the exception that `relative_speed()`
will return the actual last ste relative speed, while
`effective_relative_speed()` returns 0.0 if the time is paused and
corresponds to the speed that was set when the update for the current
frame started.
## Todo
- [x] Update pull name and description
- [x] Top level documentation on usage
- [x] Fix examples
- [x] Decide on default `max_delta` value
- [x] Decide naming of the three clocks: is `Real`, `Virtual`, `Fixed`
good?
- [x] Decide if the three clock inner structures should be in prelude
- [x] Decide on best way to configure values at startup: is manually
inserting a new clock instance okay, or should there be config struct
separately?
- [x] Fix links in docs
- [x] Decide what should be public and what not
- [x] Decide how `wrap_period` should be handled when it is changed
- [x] ~~Add toggles to disable setting the clock as default?~~ No,
separate pull if needed.
- [x] Add tests
- [x] Reformat, ensure adheres to conventions etc.
- [x] Build documentation and see that it looks correct
## Contributors
Huge thanks to @alice-i-cecile and @maniwani while building this pull.
It was a shared effort!
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Cameron <51241057+maniwani@users.noreply.github.com>
Co-authored-by: Jerome Humbert <djeedai@gmail.com>
2023-10-16 01:57:55 +00:00
|
|
|
let raw_delta = real.delta();
|
|
|
|
virt.advance_with_raw_delta(raw_delta);
|
|
|
|
*current = virt.as_generic();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_default() {
|
|
|
|
let time = Time::<Virtual>::default();
|
|
|
|
|
|
|
|
assert!(!time.is_paused()); // false
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
assert_eq!(time.max_delta(), Time::<Virtual>::DEFAULT_MAX_DELTA);
|
|
|
|
assert_eq!(time.delta(), Duration::ZERO);
|
|
|
|
assert_eq!(time.elapsed(), Duration::ZERO);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_advance() {
|
|
|
|
let mut time = Time::<Virtual>::default();
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(125));
|
|
|
|
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(125));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(125));
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(125));
|
|
|
|
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(125));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(250));
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(125));
|
|
|
|
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(125));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(375));
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(125));
|
|
|
|
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(125));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(500));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_relative_speed() {
|
|
|
|
let mut time = Time::<Virtual>::default();
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(250));
|
|
|
|
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
assert_eq!(time.effective_speed(), 1.0);
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(250));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(250));
|
|
|
|
|
|
|
|
time.set_relative_speed_f64(2.0);
|
|
|
|
|
|
|
|
assert_eq!(time.relative_speed(), 2.0);
|
|
|
|
assert_eq!(time.effective_speed(), 1.0);
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(250));
|
|
|
|
|
|
|
|
assert_eq!(time.relative_speed(), 2.0);
|
|
|
|
assert_eq!(time.effective_speed(), 2.0);
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(500));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(750));
|
|
|
|
|
|
|
|
time.set_relative_speed_f64(0.5);
|
|
|
|
|
|
|
|
assert_eq!(time.relative_speed(), 0.5);
|
|
|
|
assert_eq!(time.effective_speed(), 2.0);
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(250));
|
|
|
|
|
|
|
|
assert_eq!(time.relative_speed(), 0.5);
|
|
|
|
assert_eq!(time.effective_speed(), 0.5);
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(125));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(875));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_pause() {
|
|
|
|
let mut time = Time::<Virtual>::default();
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(250));
|
|
|
|
|
|
|
|
assert!(!time.is_paused()); // false
|
|
|
|
assert!(!time.was_paused()); // false
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
assert_eq!(time.effective_speed(), 1.0);
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(250));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(250));
|
|
|
|
|
|
|
|
time.pause();
|
|
|
|
|
|
|
|
assert!(time.is_paused()); // true
|
|
|
|
assert!(!time.was_paused()); // false
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
assert_eq!(time.effective_speed(), 1.0);
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(250));
|
|
|
|
|
|
|
|
assert!(time.is_paused()); // true
|
|
|
|
assert!(time.was_paused()); // true
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
assert_eq!(time.effective_speed(), 0.0);
|
|
|
|
assert_eq!(time.delta(), Duration::ZERO);
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(250));
|
|
|
|
|
|
|
|
time.unpause();
|
|
|
|
|
|
|
|
assert!(!time.is_paused()); // false
|
|
|
|
assert!(time.was_paused()); // true
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
assert_eq!(time.effective_speed(), 0.0);
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(250));
|
|
|
|
|
|
|
|
assert!(!time.is_paused()); // false
|
|
|
|
assert!(!time.was_paused()); // false
|
|
|
|
assert_eq!(time.relative_speed(), 1.0);
|
|
|
|
assert_eq!(time.effective_speed(), 1.0);
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(250));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(500));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_max_delta() {
|
|
|
|
let mut time = Time::<Virtual>::default();
|
|
|
|
time.set_max_delta(Duration::from_millis(500));
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(250));
|
|
|
|
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(250));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(250));
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(500));
|
|
|
|
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(500));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(750));
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(750));
|
|
|
|
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(500));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(1250));
|
|
|
|
|
|
|
|
time.set_max_delta(Duration::from_secs(1));
|
|
|
|
|
|
|
|
assert_eq!(time.max_delta(), Duration::from_secs(1));
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(750));
|
|
|
|
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(750));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(2000));
|
|
|
|
|
|
|
|
time.advance_with_raw_delta(Duration::from_millis(1250));
|
|
|
|
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(1000));
|
|
|
|
assert_eq!(time.elapsed(), Duration::from_millis(3000));
|
|
|
|
}
|
|
|
|
}
|