mirror of
https://github.com/bevyengine/bevy
synced 2024-12-23 03:23:20 +00:00
3d79dc4cdc
# 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>
541 lines
20 KiB
Rust
541 lines
20 KiB
Rust
use bevy_ecs::{reflect::ReflectResource, system::Resource};
|
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
|
use bevy_utils::Duration;
|
|
|
|
/// A generic clock resource that tracks how much it has advanced since its
|
|
/// previous update and since its creation.
|
|
///
|
|
/// Multiple instances of this resource are inserted automatically by
|
|
/// [`TimePlugin`](crate::TimePlugin):
|
|
///
|
|
/// - [`Time<Real>`](crate::real::Real) tracks real wall-clock time elapsed.
|
|
/// - [`Time<Virtual>`](crate::virt::Virtual) tracks virtual game time that may
|
|
/// be paused or scaled.
|
|
/// - [`Time<Fixed>`](crate::fixed::Fixed) tracks fixed timesteps based on
|
|
/// virtual time.
|
|
/// - [`Time`] is a generic clock that corresponds to "current" or "default"
|
|
/// time for systems. It contains [`Time<Virtual>`](crate::virt::Virtual)
|
|
/// except inside the [`FixedUpdate`](bevy_app::FixedUpdate) schedule when it
|
|
/// contains [`Time<Fixed>`](crate::fixed::Fixed).
|
|
///
|
|
/// The time elapsed since the previous time this clock was advanced is saved as
|
|
/// [`delta()`](Time::delta) and the total amount of time the clock has advanced
|
|
/// is saved as [`elapsed()`](Time::elapsed). Both are represented as exact
|
|
/// [`Duration`] values with fixed nanosecond precision. The clock does not
|
|
/// support time moving backwards, but it can be updated with [`Duration::ZERO`]
|
|
/// which will set [`delta()`](Time::delta) to zero.
|
|
///
|
|
/// These values are also available in seconds as `f32` via
|
|
/// [`delta_seconds()`](Time::delta_seconds) and
|
|
/// [`elapsed_seconds()`](Time::elapsed_seconds), and also in seconds as `f64`
|
|
/// via [`delta_seconds_f64()`](Time::delta_seconds_f64) and
|
|
/// [`elapsed_seconds_f64()`](Time::elapsed_seconds_f64).
|
|
///
|
|
/// Since [`elapsed_seconds()`](Time::elapsed_seconds) will grow constantly and
|
|
/// is `f32`, it will exhibit gradual precision loss. For applications that
|
|
/// require an `f32` value but suffer from gradual precision loss there is
|
|
/// [`elapsed_seconds_wrapped()`](Time::elapsed_seconds_wrapped) available. The
|
|
/// same wrapped value is also available as [`Duration`] and `f64` for
|
|
/// consistency. The wrap period is by default 1 hour, and can be set by
|
|
/// [`set_wrap_period()`](Time::set_wrap_period).
|
|
///
|
|
/// # Accessing clocks
|
|
///
|
|
/// By default, any systems requiring current [`delta()`](Time::delta) or
|
|
/// [`elapsed()`](Time::elapsed) should use `Res<Time>` to access the default
|
|
/// time configured for the program. By default, this refers to
|
|
/// [`Time<Virtual>`](crate::virt::Virtual) except during the
|
|
/// [`FixedUpdate`](bevy_app::FixedUpdate) schedule when it refers to
|
|
/// [`Time<Fixed>`](crate::fixed::Fixed). This ensures your system can be used
|
|
/// either in [`Update`](bevy_app::Update) or
|
|
/// [`FixedUpdate`](bevy_app::FixedUpdate) schedule depending on what is needed.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_time::prelude::*;
|
|
/// #
|
|
/// fn ambivalent_system(time: Res<Time>) {
|
|
/// println!("this how I see time: delta {:?}, elapsed {:?}", time.delta(), time.elapsed());
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// If your system needs to react based on real time (wall clock time), like for
|
|
/// user interfaces, it should use `Res<Time<Real>>`. The
|
|
/// [`delta()`](Time::delta) and [`elapsed()`](Time::elapsed) values will always
|
|
/// correspond to real time and will not be affected by pause, time scaling or
|
|
/// other tweaks.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_time::prelude::*;
|
|
/// #
|
|
/// fn real_time_system(time: Res<Time<Real>>) {
|
|
/// println!("this will always be real time: delta {:?}, elapsed {:?}", time.delta(), time.elapsed());
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// If your system specifically needs to access fixed timestep clock, even when
|
|
/// placed in `Update` schedule, you should use `Res<Time<Fixed>>`. The
|
|
/// [`delta()`](Time::delta) and [`elapsed()`](Time::elapsed) values will
|
|
/// correspond to the latest fixed timestep that has been run.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_time::prelude::*;
|
|
/// #
|
|
/// fn fixed_time_system(time: Res<Time<Fixed>>) {
|
|
/// println!("this will always be the last executed fixed timestep: delta {:?}, elapsed {:?}", time.delta(), time.elapsed());
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// Finally, if your system specifically needs to know the current virtual game
|
|
/// time, even if placed inside [`FixedUpdate`](bevy_app::FixedUpdate), for
|
|
/// example to know if the game is [`was_paused()`](Time::was_paused) or to use
|
|
/// [`effective_speed()`](Time::effective_speed), you can use
|
|
/// `Res<Time<Virtual>>`. However, if the system is placed in
|
|
/// [`FixedUpdate`](bevy_app::FixedUpdate), extra care must be used because your
|
|
/// system might be run multiple times with the same [`delta()`](Time::delta)
|
|
/// and [`elapsed()`](Time::elapsed) values as the virtual game time has not
|
|
/// changed between the iterations.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_time::prelude::*;
|
|
/// #
|
|
/// fn fixed_time_system(time: Res<Time<Virtual>>) {
|
|
/// println!("this will be virtual time for this update: delta {:?}, elapsed {:?}", time.delta(), time.elapsed());
|
|
/// println!("also the relative speed of the game is now {}", time.effective_speed());
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// If you need to change the settings for any of the clocks, for example to
|
|
/// [`pause()`](Time::pause) the game, you should use `ResMut<Time<Virtual>>`.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_time::prelude::*;
|
|
/// #
|
|
/// #[derive(Event)]
|
|
/// struct PauseEvent(bool);
|
|
///
|
|
/// fn pause_system(mut time: ResMut<Time<Virtual>>, mut events: EventReader<PauseEvent>) {
|
|
/// for ev in events.iter() {
|
|
/// if ev.0 {
|
|
/// time.pause();
|
|
/// } else {
|
|
/// time.unpause();
|
|
/// }
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// # Adding custom clocks
|
|
///
|
|
/// New custom clocks can be created by creating your own struct as a context
|
|
/// and passing it to [`new_with()`](Time::new_with). These clocks can be
|
|
/// inserted as resources as normal and then accessed by systems. You can use
|
|
/// the [`advance_by()`](Time::advance_by) or [`advance_to()`](Time::advance_to)
|
|
/// methods to move the clock forwards based on your own logic.
|
|
///
|
|
/// If you want to add methods for your time instance and they require access to
|
|
/// both your context and the generic time part, it's probably simplest to add a
|
|
/// custom trait for them and implement it for `Time<Custom>`.
|
|
///
|
|
/// Your context struct will need to implement the [`Default`] trait because
|
|
/// [`Time`] structures support reflection. It also makes initialization trivial
|
|
/// by being able to call `app.init_resource::<Time<Custom>>()`.
|
|
///
|
|
/// You can also replace the "generic" `Time` clock resource if the "default"
|
|
/// time for your game should not be the default virtual time provided. You can
|
|
/// get a "generic" snapshot of your clock by calling `as_generic()` and then
|
|
/// overwrite the [`Time`] resource with it. The default systems added by
|
|
/// [`TimePlugin`](crate::TimePlugin) will overwrite the [`Time`] clock during
|
|
/// [`First`](bevy_app::First) and [`FixedUpdate`](bevy_app::FixedUpdate)
|
|
/// schedules.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # use bevy_time::prelude::*;
|
|
/// # use bevy_utils::Instant;
|
|
/// #
|
|
/// #[derive(Debug)]
|
|
/// struct Custom {
|
|
/// last_external_time: Instant,
|
|
/// }
|
|
///
|
|
/// impl Default for Custom {
|
|
/// fn default() -> Self {
|
|
/// Self {
|
|
/// last_external_time: Instant::now(),
|
|
/// }
|
|
/// }
|
|
/// }
|
|
///
|
|
/// trait CustomTime {
|
|
/// fn update_from_external(&mut self, instant: Instant);
|
|
/// }
|
|
///
|
|
/// impl CustomTime for Time<Custom> {
|
|
/// fn update_from_external(&mut self, instant: Instant) {
|
|
/// let delta = instant - self.context().last_external_time;
|
|
/// self.advance_by(delta);
|
|
/// self.context_mut().last_external_time = instant;
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
#[derive(Resource, Debug, Copy, Clone, Reflect)]
|
|
#[reflect(Resource, Default)]
|
|
pub struct Time<T: Default = ()> {
|
|
context: T,
|
|
wrap_period: Duration,
|
|
delta: Duration,
|
|
delta_seconds: f32,
|
|
delta_seconds_f64: f64,
|
|
elapsed: Duration,
|
|
elapsed_seconds: f32,
|
|
elapsed_seconds_f64: f64,
|
|
elapsed_wrapped: Duration,
|
|
elapsed_seconds_wrapped: f32,
|
|
elapsed_seconds_wrapped_f64: f64,
|
|
}
|
|
|
|
impl<T: Default> Time<T> {
|
|
const DEFAULT_WRAP_PERIOD: Duration = Duration::from_secs(3600); // 1 hour
|
|
|
|
/// Create a new clock from context with [`Self::delta`] and [`Self::elapsed`] starting from
|
|
/// zero.
|
|
pub fn new_with(context: T) -> Self {
|
|
Self {
|
|
context,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Advance this clock by adding a `delta` duration to it.
|
|
///
|
|
/// The added duration will be returned by [`Self::delta`] and
|
|
/// [`Self::elapsed`] will be increased by the duration. Adding
|
|
/// [`Duration::ZERO`] is allowed and will set [`Self::delta`] to zero.
|
|
pub fn advance_by(&mut self, delta: Duration) {
|
|
self.delta = delta;
|
|
self.delta_seconds = self.delta.as_secs_f32();
|
|
self.delta_seconds_f64 = self.delta.as_secs_f64();
|
|
self.elapsed += delta;
|
|
self.elapsed_seconds = self.elapsed.as_secs_f32();
|
|
self.elapsed_seconds_f64 = self.elapsed.as_secs_f64();
|
|
self.elapsed_wrapped = duration_rem(self.elapsed, self.wrap_period);
|
|
self.elapsed_seconds_wrapped = self.elapsed_wrapped.as_secs_f32();
|
|
self.elapsed_seconds_wrapped_f64 = self.elapsed_wrapped.as_secs_f64();
|
|
}
|
|
|
|
/// Advance this clock to a specific `elapsed` time.
|
|
///
|
|
/// [`Self::delta()`] will return the amount of time the clock was advanced
|
|
/// and [`Self::elapsed()`] will be the `elapsed` value passed in. Cannot be
|
|
/// used to move time backwards.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if `elapsed` is less than `Self::elapsed()`.
|
|
pub fn advance_to(&mut self, elapsed: Duration) {
|
|
assert!(
|
|
elapsed >= self.elapsed,
|
|
"tried to move time backwards to an earlier elapsed moment"
|
|
);
|
|
self.advance_by(elapsed - self.elapsed);
|
|
}
|
|
|
|
/// Returns the modulus used to calculate [`elapsed_wrapped`](#method.elapsed_wrapped).
|
|
///
|
|
/// **Note:** The default modulus is one hour.
|
|
#[inline]
|
|
pub fn wrap_period(&self) -> Duration {
|
|
self.wrap_period
|
|
}
|
|
|
|
/// Sets the modulus used to calculate [`elapsed_wrapped`](#method.elapsed_wrapped).
|
|
///
|
|
/// **Note:** This will not take effect until the next update.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if `wrap_period` is a zero-length duration.
|
|
#[inline]
|
|
pub fn set_wrap_period(&mut self, wrap_period: Duration) {
|
|
assert!(!wrap_period.is_zero(), "division by zero");
|
|
self.wrap_period = wrap_period;
|
|
}
|
|
|
|
/// Returns how much time has advanced since the last [`update`](#method.update), as a
|
|
/// [`Duration`].
|
|
#[inline]
|
|
pub fn delta(&self) -> Duration {
|
|
self.delta
|
|
}
|
|
|
|
/// Returns how much time has advanced since the last [`update`](#method.update), as [`f32`]
|
|
/// seconds.
|
|
#[inline]
|
|
pub fn delta_seconds(&self) -> f32 {
|
|
self.delta_seconds
|
|
}
|
|
|
|
/// Returns how much time has advanced since the last [`update`](#method.update), as [`f64`]
|
|
/// seconds.
|
|
#[inline]
|
|
pub fn delta_seconds_f64(&self) -> f64 {
|
|
self.delta_seconds_f64
|
|
}
|
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup), as [`Duration`].
|
|
#[inline]
|
|
pub fn elapsed(&self) -> Duration {
|
|
self.elapsed
|
|
}
|
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup), as [`f32`] seconds.
|
|
///
|
|
/// **Note:** This is a monotonically increasing value. It's precision will degrade over time.
|
|
/// If you need an `f32` but that precision loss is unacceptable,
|
|
/// use [`elapsed_seconds_wrapped`](#method.elapsed_seconds_wrapped).
|
|
#[inline]
|
|
pub fn elapsed_seconds(&self) -> f32 {
|
|
self.elapsed_seconds
|
|
}
|
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup), as [`f64`] seconds.
|
|
#[inline]
|
|
pub fn elapsed_seconds_f64(&self) -> f64 {
|
|
self.elapsed_seconds_f64
|
|
}
|
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup) modulo
|
|
/// the [`wrap_period`](#method.wrap_period), as [`Duration`].
|
|
#[inline]
|
|
pub fn elapsed_wrapped(&self) -> Duration {
|
|
self.elapsed_wrapped
|
|
}
|
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup) modulo
|
|
/// the [`wrap_period`](#method.wrap_period), as [`f32`] seconds.
|
|
///
|
|
/// This method is intended for applications (e.g. shaders) that require an [`f32`] value but
|
|
/// suffer from the gradual precision loss of [`elapsed_seconds`](#method.elapsed_seconds).
|
|
#[inline]
|
|
pub fn elapsed_seconds_wrapped(&self) -> f32 {
|
|
self.elapsed_seconds_wrapped
|
|
}
|
|
|
|
/// Returns how much time has advanced since [`startup`](#method.startup) modulo
|
|
/// the [`wrap_period`](#method.wrap_period), as [`f64`] seconds.
|
|
#[inline]
|
|
pub fn elapsed_seconds_wrapped_f64(&self) -> f64 {
|
|
self.elapsed_seconds_wrapped_f64
|
|
}
|
|
|
|
/// Returns a reference to the context of this specific clock.
|
|
#[inline]
|
|
pub fn context(&self) -> &T {
|
|
&self.context
|
|
}
|
|
|
|
/// Returns a mutable reference to the context of this specific clock.
|
|
#[inline]
|
|
pub fn context_mut(&mut self) -> &mut T {
|
|
&mut self.context
|
|
}
|
|
|
|
/// Returns a copy of this clock as fully generic clock without context.
|
|
#[inline]
|
|
pub fn as_generic(&self) -> Time<()> {
|
|
Time {
|
|
context: (),
|
|
wrap_period: self.wrap_period,
|
|
delta: self.delta,
|
|
delta_seconds: self.delta_seconds,
|
|
delta_seconds_f64: self.delta_seconds_f64,
|
|
elapsed: self.elapsed,
|
|
elapsed_seconds: self.elapsed_seconds,
|
|
elapsed_seconds_f64: self.elapsed_seconds_f64,
|
|
elapsed_wrapped: self.elapsed_wrapped,
|
|
elapsed_seconds_wrapped: self.elapsed_seconds_wrapped,
|
|
elapsed_seconds_wrapped_f64: self.elapsed_seconds_wrapped_f64,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Default> Default for Time<T> {
|
|
fn default() -> Self {
|
|
Self {
|
|
context: Default::default(),
|
|
wrap_period: Self::DEFAULT_WRAP_PERIOD,
|
|
delta: Duration::ZERO,
|
|
delta_seconds: 0.0,
|
|
delta_seconds_f64: 0.0,
|
|
elapsed: Duration::ZERO,
|
|
elapsed_seconds: 0.0,
|
|
elapsed_seconds_f64: 0.0,
|
|
elapsed_wrapped: Duration::ZERO,
|
|
elapsed_seconds_wrapped: 0.0,
|
|
elapsed_seconds_wrapped_f64: 0.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn duration_rem(dividend: Duration, divisor: Duration) -> Duration {
|
|
// `Duration` does not have a built-in modulo operation
|
|
let quotient = (dividend.as_nanos() / divisor.as_nanos()) as u32;
|
|
dividend - (quotient * divisor)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_initial_state() {
|
|
let time: Time = Time::default();
|
|
|
|
assert_eq!(time.wrap_period(), Time::<()>::DEFAULT_WRAP_PERIOD);
|
|
assert_eq!(time.delta(), Duration::ZERO);
|
|
assert_eq!(time.delta_seconds(), 0.0);
|
|
assert_eq!(time.delta_seconds_f64(), 0.0);
|
|
assert_eq!(time.elapsed(), Duration::ZERO);
|
|
assert_eq!(time.elapsed_seconds(), 0.0);
|
|
assert_eq!(time.elapsed_seconds_f64(), 0.0);
|
|
assert_eq!(time.elapsed_wrapped(), Duration::ZERO);
|
|
assert_eq!(time.elapsed_seconds_wrapped(), 0.0);
|
|
assert_eq!(time.elapsed_seconds_wrapped_f64(), 0.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_advance_by() {
|
|
let mut time: Time = Time::default();
|
|
|
|
time.advance_by(Duration::from_millis(250));
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(250));
|
|
assert_eq!(time.delta_seconds(), 0.25);
|
|
assert_eq!(time.delta_seconds_f64(), 0.25);
|
|
assert_eq!(time.elapsed(), Duration::from_millis(250));
|
|
assert_eq!(time.elapsed_seconds(), 0.25);
|
|
assert_eq!(time.elapsed_seconds_f64(), 0.25);
|
|
|
|
time.advance_by(Duration::from_millis(500));
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(500));
|
|
assert_eq!(time.delta_seconds(), 0.5);
|
|
assert_eq!(time.delta_seconds_f64(), 0.5);
|
|
assert_eq!(time.elapsed(), Duration::from_millis(750));
|
|
assert_eq!(time.elapsed_seconds(), 0.75);
|
|
assert_eq!(time.elapsed_seconds_f64(), 0.75);
|
|
|
|
time.advance_by(Duration::ZERO);
|
|
|
|
assert_eq!(time.delta(), Duration::ZERO);
|
|
assert_eq!(time.delta_seconds(), 0.0);
|
|
assert_eq!(time.delta_seconds_f64(), 0.0);
|
|
assert_eq!(time.elapsed(), Duration::from_millis(750));
|
|
assert_eq!(time.elapsed_seconds(), 0.75);
|
|
assert_eq!(time.elapsed_seconds_f64(), 0.75);
|
|
}
|
|
|
|
#[test]
|
|
fn test_advance_to() {
|
|
let mut time: Time = Time::default();
|
|
|
|
time.advance_to(Duration::from_millis(250));
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(250));
|
|
assert_eq!(time.delta_seconds(), 0.25);
|
|
assert_eq!(time.delta_seconds_f64(), 0.25);
|
|
assert_eq!(time.elapsed(), Duration::from_millis(250));
|
|
assert_eq!(time.elapsed_seconds(), 0.25);
|
|
assert_eq!(time.elapsed_seconds_f64(), 0.25);
|
|
|
|
time.advance_to(Duration::from_millis(750));
|
|
|
|
assert_eq!(time.delta(), Duration::from_millis(500));
|
|
assert_eq!(time.delta_seconds(), 0.5);
|
|
assert_eq!(time.delta_seconds_f64(), 0.5);
|
|
assert_eq!(time.elapsed(), Duration::from_millis(750));
|
|
assert_eq!(time.elapsed_seconds(), 0.75);
|
|
assert_eq!(time.elapsed_seconds_f64(), 0.75);
|
|
|
|
time.advance_to(Duration::from_millis(750));
|
|
|
|
assert_eq!(time.delta(), Duration::ZERO);
|
|
assert_eq!(time.delta_seconds(), 0.0);
|
|
assert_eq!(time.delta_seconds_f64(), 0.0);
|
|
assert_eq!(time.elapsed(), Duration::from_millis(750));
|
|
assert_eq!(time.elapsed_seconds(), 0.75);
|
|
assert_eq!(time.elapsed_seconds_f64(), 0.75);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn test_advance_to_backwards_panics() {
|
|
let mut time: Time = Time::default();
|
|
|
|
time.advance_to(Duration::from_millis(750));
|
|
|
|
time.advance_to(Duration::from_millis(250));
|
|
}
|
|
|
|
#[test]
|
|
fn test_wrapping() {
|
|
let mut time: Time = Time::default();
|
|
time.set_wrap_period(Duration::from_secs(3));
|
|
|
|
time.advance_by(Duration::from_secs(2));
|
|
|
|
assert_eq!(time.elapsed_wrapped(), Duration::from_secs(2));
|
|
assert_eq!(time.elapsed_seconds_wrapped(), 2.0);
|
|
assert_eq!(time.elapsed_seconds_wrapped_f64(), 2.0);
|
|
|
|
time.advance_by(Duration::from_secs(2));
|
|
|
|
assert_eq!(time.elapsed_wrapped(), Duration::from_secs(1));
|
|
assert_eq!(time.elapsed_seconds_wrapped(), 1.0);
|
|
assert_eq!(time.elapsed_seconds_wrapped_f64(), 1.0);
|
|
|
|
time.advance_by(Duration::from_secs(2));
|
|
|
|
assert_eq!(time.elapsed_wrapped(), Duration::ZERO);
|
|
assert_eq!(time.elapsed_seconds_wrapped(), 0.0);
|
|
assert_eq!(time.elapsed_seconds_wrapped_f64(), 0.0);
|
|
|
|
time.advance_by(Duration::new(3, 250_000_000));
|
|
|
|
assert_eq!(time.elapsed_wrapped(), Duration::from_millis(250));
|
|
assert_eq!(time.elapsed_seconds_wrapped(), 0.25);
|
|
assert_eq!(time.elapsed_seconds_wrapped_f64(), 0.25);
|
|
}
|
|
|
|
#[test]
|
|
fn test_wrapping_change() {
|
|
let mut time: Time = Time::default();
|
|
time.set_wrap_period(Duration::from_secs(5));
|
|
|
|
time.advance_by(Duration::from_secs(8));
|
|
|
|
assert_eq!(time.elapsed_wrapped(), Duration::from_secs(3));
|
|
assert_eq!(time.elapsed_seconds_wrapped(), 3.0);
|
|
assert_eq!(time.elapsed_seconds_wrapped_f64(), 3.0);
|
|
|
|
time.set_wrap_period(Duration::from_secs(2));
|
|
|
|
assert_eq!(time.elapsed_wrapped(), Duration::from_secs(3));
|
|
assert_eq!(time.elapsed_seconds_wrapped(), 3.0);
|
|
assert_eq!(time.elapsed_seconds_wrapped_f64(), 3.0);
|
|
|
|
time.advance_by(Duration::ZERO);
|
|
|
|
// Time will wrap to modulo duration from full `elapsed()`, not to what
|
|
// is left in `elapsed_wrapped()`. This test of values is here to ensure
|
|
// that we notice if we change that behaviour.
|
|
assert_eq!(time.elapsed_wrapped(), Duration::from_secs(0));
|
|
assert_eq!(time.elapsed_seconds_wrapped(), 0.0);
|
|
assert_eq!(time.elapsed_seconds_wrapped_f64(), 0.0);
|
|
}
|
|
}
|