bevy/crates/bevy_time/src/time.rs

713 lines
26 KiB
Rust
Raw Normal View History

Make `Resource` trait opt-in, requiring `#[derive(Resource)]` V2 (#5577) *This PR description is an edited copy of #5007, written by @alice-i-cecile.* # Objective Follow-up to https://github.com/bevyengine/bevy/pull/2254. The `Resource` trait currently has a blanket implementation for all types that meet its bounds. While ergonomic, this results in several drawbacks: * it is possible to make confusing, silent mistakes such as inserting a function pointer (Foo) rather than a value (Foo::Bar) as a resource * it is challenging to discover if a type is intended to be used as a resource * we cannot later add customization options (see the [RFC](https://github.com/bevyengine/rfcs/blob/main/rfcs/27-derive-component.md) for the equivalent choice for Component). * dependencies can use the same Rust type as a resource in invisibly conflicting ways * raw Rust types used as resources cannot preserve privacy appropriately, as anyone able to access that type can read and write to internal values * we cannot capture a definitive list of possible resources to display to users in an editor ## Notes to reviewers * Review this commit-by-commit; there's effectively no back-tracking and there's a lot of churn in some of these commits. *ira: My commits are not as well organized :')* * I've relaxed the bound on Local to Send + Sync + 'static: I don't think these concerns apply there, so this can keep things simple. Storing e.g. a u32 in a Local is fine, because there's a variable name attached explaining what it does. * I think this is a bad place for the Resource trait to live, but I've left it in place to make reviewing easier. IMO that's best tackled with https://github.com/bevyengine/bevy/issues/4981. ## Changelog `Resource` is no longer automatically implemented for all matching types. Instead, use the new `#[derive(Resource)]` macro. ## Migration Guide Add `#[derive(Resource)]` to all types you are using as a resource. If you are using a third party type as a resource, wrap it in a tuple struct to bypass orphan rules. Consider deriving `Deref` and `DerefMut` to improve ergonomics. `ClearColor` no longer implements `Component`. Using `ClearColor` as a component in 0.8 did nothing. Use the `ClearColorConfig` in the `Camera3d` and `Camera2d` components instead. Co-authored-by: Alice <alice.i.cecile@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: devil-ira <justthecooldude@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-08-08 21:36:35 +00:00
use bevy_ecs::{reflect::ReflectResource, system::Resource};
use bevy_reflect::{FromReflect, Reflect};
use bevy_utils::{Duration, Instant};
2019-12-03 08:30:30 +00:00
/// Tracks how much time has advanced (and also how much real time has elapsed) since
/// the previous app update and since the app was started.
#[derive(Resource, Reflect, FromReflect, Debug, Clone)]
remove blanket `Serialize + Deserialize` requirement for `Reflect` on generic types (#5197) # Objective Some generic types like `Option<T>`, `Vec<T>` and `HashMap<K, V>` implement `Reflect` when where their generic types `T`/`K`/`V` implement `Serialize + for<'de> Deserialize<'de>`. This is so that in their `GetTypeRegistration` impl they can insert the `ReflectSerialize` and `ReflectDeserialize` type data structs. This has the annoying side effect that if your struct contains a `Option<NonSerdeStruct>` you won't be able to derive reflect (https://github.com/bevyengine/bevy/issues/4054). ## Solution - remove the `Serialize + Deserialize` bounds on wrapper types - this means that `ReflectSerialize` and `ReflectDeserialize` will no longer be inserted even for `.register::<Option<DoesImplSerde>>()` - add `register_type_data<T, D>` shorthand for `registry.get_mut(T).insert(D::from_type<T>())` - require users to register their specific generic types **and the serde types** separately like ```rust .register_type::<Option<String>>() .register_type_data::<Option<String>, ReflectSerialize>() .register_type_data::<Option<String>, ReflectDeserialize>() ``` I believe this is the best we can do for extensibility and convenience without specialization. ## Changelog - `.register_type` for generic types like `Option<T>`, `Vec<T>`, `HashMap<K, V>` will no longer insert `ReflectSerialize` and `ReflectDeserialize` type data. Instead you need to register it separately for concrete generic types like so: ```rust .register_type::<Option<String>>() .register_type_data::<Option<String>, ReflectSerialize>() .register_type_data::<Option<String>, ReflectDeserialize>() ``` TODO: more docs and tweaks to the scene example to demonstrate registering generic types.
2022-07-21 14:57:37 +00:00
#[reflect(Resource)]
2019-12-03 08:30:30 +00:00
pub struct Time {
startup: Instant,
first_update: Option<Instant>,
last_update: Option<Instant>,
relative_speed: f64, // using `f64` instead of `f32` to minimize drift from rounding errors
paused: bool,
delta: Duration,
delta_seconds: f32,
delta_seconds_f64: f64,
elapsed: Duration,
elapsed_seconds: f32,
elapsed_seconds_f64: f64,
raw_delta: Duration,
raw_delta_seconds: f32,
raw_delta_seconds_f64: f64,
raw_elapsed: Duration,
raw_elapsed_seconds: f32,
raw_elapsed_seconds_f64: f64,
// wrapping
wrap_period: Duration,
elapsed_wrapped: Duration,
elapsed_seconds_wrapped: f32,
elapsed_seconds_wrapped_f64: f64,
raw_elapsed_wrapped: Duration,
raw_elapsed_seconds_wrapped: f32,
raw_elapsed_seconds_wrapped_f64: f64,
2019-12-03 08:30:30 +00:00
}
2020-05-13 23:35:38 +00:00
impl Default for Time {
fn default() -> Self {
Self {
2020-05-31 04:32:47 +00:00
startup: Instant::now(),
first_update: None,
last_update: None,
relative_speed: 1.0,
paused: false,
delta: Duration::ZERO,
2019-12-03 08:30:30 +00:00
delta_seconds: 0.0,
delta_seconds_f64: 0.0,
elapsed: Duration::ZERO,
elapsed_seconds: 0.0,
elapsed_seconds_f64: 0.0,
raw_delta: Duration::ZERO,
raw_delta_seconds: 0.0,
raw_delta_seconds_f64: 0.0,
raw_elapsed: Duration::ZERO,
raw_elapsed_seconds: 0.0,
raw_elapsed_seconds_f64: 0.0,
wrap_period: Duration::from_secs(3600), // 1 hour
elapsed_wrapped: Duration::ZERO,
elapsed_seconds_wrapped: 0.0,
elapsed_seconds_wrapped_f64: 0.0,
raw_elapsed_wrapped: Duration::ZERO,
raw_elapsed_seconds_wrapped: 0.0,
raw_elapsed_seconds_wrapped_f64: 0.0,
2019-12-03 08:30:30 +00:00
}
}
2020-05-13 23:35:38 +00:00
}
2019-12-03 08:30:30 +00:00
2020-05-13 23:35:38 +00:00
impl Time {
/// Constructs a new `Time` instance with a specific startup `Instant`.
pub fn new(startup: Instant) -> Self {
Self {
startup,
..Default::default()
}
}
/// Updates the internal time measurements.
///
/// Calling this method as part of your app will most likely result in inaccurate timekeeping,
/// as the `Time` resource is ordinarily managed by the [`TimePlugin`](crate::TimePlugin).
2020-05-31 04:15:39 +00:00
pub fn update(&mut self) {
let now = Instant::now();
self.update_with_instant(now);
}
/// Updates time with a specified [`Instant`].
///
/// This method is provided for use in tests. Calling this method as part of your app will most
/// likely result in inaccurate timekeeping, as the `Time` resource is ordinarily managed by the
/// [`TimePlugin`](crate::TimePlugin).
///
/// # Examples
///
/// ```
/// # use bevy_time::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # use bevy_utils::Duration;
/// # fn main () {
/// # test_health_system();
/// # }
Make `Resource` trait opt-in, requiring `#[derive(Resource)]` V2 (#5577) *This PR description is an edited copy of #5007, written by @alice-i-cecile.* # Objective Follow-up to https://github.com/bevyengine/bevy/pull/2254. The `Resource` trait currently has a blanket implementation for all types that meet its bounds. While ergonomic, this results in several drawbacks: * it is possible to make confusing, silent mistakes such as inserting a function pointer (Foo) rather than a value (Foo::Bar) as a resource * it is challenging to discover if a type is intended to be used as a resource * we cannot later add customization options (see the [RFC](https://github.com/bevyengine/rfcs/blob/main/rfcs/27-derive-component.md) for the equivalent choice for Component). * dependencies can use the same Rust type as a resource in invisibly conflicting ways * raw Rust types used as resources cannot preserve privacy appropriately, as anyone able to access that type can read and write to internal values * we cannot capture a definitive list of possible resources to display to users in an editor ## Notes to reviewers * Review this commit-by-commit; there's effectively no back-tracking and there's a lot of churn in some of these commits. *ira: My commits are not as well organized :')* * I've relaxed the bound on Local to Send + Sync + 'static: I don't think these concerns apply there, so this can keep things simple. Storing e.g. a u32 in a Local is fine, because there's a variable name attached explaining what it does. * I think this is a bad place for the Resource trait to live, but I've left it in place to make reviewing easier. IMO that's best tackled with https://github.com/bevyengine/bevy/issues/4981. ## Changelog `Resource` is no longer automatically implemented for all matching types. Instead, use the new `#[derive(Resource)]` macro. ## Migration Guide Add `#[derive(Resource)]` to all types you are using as a resource. If you are using a third party type as a resource, wrap it in a tuple struct to bypass orphan rules. Consider deriving `Deref` and `DerefMut` to improve ergonomics. `ClearColor` no longer implements `Component`. Using `ClearColor` as a component in 0.8 did nothing. Use the `ClearColorConfig` in the `Camera3d` and `Camera2d` components instead. Co-authored-by: Alice <alice.i.cecile@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: devil-ira <justthecooldude@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-08-08 21:36:35 +00:00
/// #[derive(Resource)]
/// struct Health {
/// // Health value between 0.0 and 1.0
/// health_value: f32,
/// }
///
/// fn health_system(time: Res<Time>, mut health: ResMut<Health>) {
/// // Increase health value by 0.1 per second, independent of frame rate,
/// // but not beyond 1.0
/// health.health_value = (health.health_value + 0.1 * time.delta_seconds()).min(1.0);
/// }
///
/// // Mock time in tests
/// fn test_health_system() {
/// let mut world = World::default();
/// let mut time = Time::default();
/// time.update();
/// world.insert_resource(time);
/// world.insert_resource(Health { health_value: 0.2 });
///
/// let mut update_stage = SystemStage::single_threaded();
/// update_stage.add_system(health_system);
///
/// // Simulate that 30 ms have passed
/// let mut time = world.resource_mut::<Time>();
/// let last_update = time.last_update().unwrap();
/// time.update_with_instant(last_update + Duration::from_millis(30));
///
/// // Run system
/// update_stage.run(&mut world);
///
/// // Check that 0.003 has been added to the health value
/// let expected_health_value = 0.2 + 0.1 * 0.03;
/// let actual_health_value = world.resource::<Health>().health_value;
/// assert_eq!(expected_health_value, actual_health_value);
/// }
/// ```
pub fn update_with_instant(&mut self, instant: Instant) {
let raw_delta = instant - self.last_update.unwrap_or(self.startup);
let delta = if self.paused {
Duration::ZERO
} else if self.relative_speed != 1.0 {
raw_delta.mul_f64(self.relative_speed)
} else {
// avoid rounding errors at normal speed
raw_delta
};
if self.last_update.is_some() {
self.delta = delta;
2020-06-04 02:53:41 +00:00
self.delta_seconds = self.delta.as_secs_f32();
self.delta_seconds_f64 = self.delta.as_secs_f64();
self.raw_delta = raw_delta;
self.raw_delta_seconds = self.raw_delta.as_secs_f32();
self.raw_delta_seconds_f64 = self.raw_delta.as_secs_f64();
} else {
self.first_update = Some(instant);
2020-05-31 04:15:39 +00:00
}
2020-05-31 04:32:47 +00:00
self.elapsed += delta;
self.elapsed_seconds = self.elapsed.as_secs_f32();
self.elapsed_seconds_f64 = self.elapsed.as_secs_f64();
self.raw_elapsed += raw_delta;
self.raw_elapsed_seconds = self.raw_elapsed.as_secs_f32();
self.raw_elapsed_seconds_f64 = self.raw_elapsed.as_secs_f64();
self.elapsed_wrapped = duration_div_rem(self.elapsed, self.wrap_period).1;
self.elapsed_seconds_wrapped = self.elapsed_wrapped.as_secs_f32();
self.elapsed_seconds_wrapped_f64 = self.elapsed_wrapped.as_secs_f64();
self.raw_elapsed_wrapped = duration_div_rem(self.raw_elapsed, self.wrap_period).1;
self.raw_elapsed_seconds_wrapped = self.raw_elapsed_wrapped.as_secs_f32();
self.raw_elapsed_seconds_wrapped_f64 = self.raw_elapsed_wrapped.as_secs_f64();
self.last_update = Some(instant);
}
/// Returns the [`Instant`] the app was started.
#[inline]
pub fn startup(&self) -> Instant {
self.startup
}
/// Returns the [`Instant`] when [`update`](#method.update) was first called, if it exists.
#[inline]
pub fn first_update(&self) -> Option<Instant> {
self.first_update
}
/// Returns the [`Instant`] when [`update`](#method.update) was last called, if it exists.
#[inline]
pub fn last_update(&self) -> Option<Instant> {
self.last_update
}
/// 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 the exact clock time elapsed since the last [`update`](#method.update), as a [`Duration`].
#[inline]
pub fn raw_delta(&self) -> Duration {
self.raw_delta
}
/// Returns the exact clock time elapsed since the last [`update`](#method.update), as [`f32`] seconds.
#[inline]
pub fn raw_delta_seconds(&self) -> f32 {
self.raw_delta_seconds
}
/// Returns the exact clock time elapsed since the last [`update`](#method.update), as [`f64`] seconds.
#[inline]
pub fn raw_delta_seconds_f64(&self) -> f64 {
self.raw_delta_seconds_f64
}
/// Returns the exact clock time elapsed since [`startup`](#method.startup), as [`Duration`].
#[inline]
pub fn raw_elapsed(&self) -> Duration {
self.raw_elapsed
}
/// Returns the exact clock time elapsed 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 [`raw_elapsed_seconds_wrapped`](#method.raw_elapsed_seconds_wrapped).
#[inline]
pub fn raw_elapsed_seconds(&self) -> f32 {
self.raw_elapsed_seconds
}
/// Returns the exact clock time elapsed since [`startup`](#method.startup), as [`f64`] seconds.
#[inline]
pub fn raw_elapsed_seconds_f64(&self) -> f64 {
self.raw_elapsed_seconds_f64
}
/// Returns the exact clock time elapsed since [`startup`](#method.startup) modulo
/// the [`wrap_period`](#method.wrap_period), as [`Duration`].
#[inline]
pub fn raw_elapsed_wrapped(&self) -> Duration {
self.raw_elapsed_wrapped
}
/// Returns the exact clock time elapsed 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 [`raw_elapsed_seconds`](#method.raw_elapsed_seconds).
#[inline]
pub fn raw_elapsed_seconds_wrapped(&self) -> f32 {
self.raw_elapsed_seconds_wrapped
}
/// Returns the exact clock time elapsed since [`startup`](#method.startup) modulo
/// the [`wrap_period`](#method.wrap_period), as [`f64`] seconds.
#[inline]
pub fn raw_elapsed_seconds_wrapped_f64(&self) -> f64 {
self.raw_elapsed_seconds_wrapped_f64
}
/// Returns the modulus used to calculate [`elapsed_wrapped`](#method.elapsed_wrapped) and
/// [`raw_elapsed_wrapped`](#method.raw_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) and
/// [`raw_elapsed_wrapped`](#method.raw_elapsed_wrapped).
///
/// # Panics
///
/// Panics if `wrap_period` is zero.
#[inline]
pub fn set_wrap_period(&mut self, wrap_period: Duration) {
assert!(wrap_period != Duration::ZERO, "division by zero");
self.wrap_period = wrap_period;
}
/// Returns the rate that time advances relative to real time, as [`f32`].
/// You might recognize this as "time scaling" or "time dilation" in other engines.
///
/// **Note:** This function will return zero when time is paused.
#[inline]
pub fn relative_speed(&self) -> f32 {
self.relative_speed_f64() as f32
}
/// Returns the rate that time advances relative to real time, as [`f64`].
/// You might recognize this as "time scaling" or "time dilation" in other engines.
///
/// **Note:** This function will return zero when time is paused.
#[inline]
pub fn relative_speed_f64(&self) -> f64 {
if self.paused {
0.0
} else {
self.relative_speed
}
}
/// Sets the rate that time advances relative to real time, given as an [`f32`].
///
/// For example, if set to `2.0`, time will 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);
2019-12-03 08:30:30 +00:00
}
2020-05-31 04:32:47 +00:00
/// Sets the rate that time advances relative to real time, given as an [`f64`].
///
/// For example, if set to `2.0`, time will 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.is_sign_positive(), "tried to go back in time");
self.relative_speed = ratio;
}
/// Stops time, preventing it from advancing until resumed. Does not affect raw measurements.
#[inline]
pub fn pause(&mut self) {
self.paused = true;
}
/// Resumes time if paused.
#[inline]
pub fn unpause(&mut self) {
self.paused = false;
2020-05-31 04:32:47 +00:00
}
/// Returns `true` if time has been paused.
#[inline]
pub fn is_paused(&self) -> bool {
self.paused
}
}
fn duration_div_rem(dividend: Duration, divisor: Duration) -> (u32, Duration) {
// `Duration` does not have a built-in modulo operation
let quotient = (dividend.as_nanos() / divisor.as_nanos()) as u32;
let remainder = dividend - (quotient * divisor);
(quotient, remainder)
2020-01-11 10:11:27 +00:00
}
2020-04-06 03:19:02 +00:00
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::Time;
use bevy_utils::{Duration, Instant};
fn assert_float_eq(a: f32, b: f32) {
assert!((a - b).abs() <= f32::EPSILON, "{a} != {b}");
}
#[test]
fn update_test() {
let start_instant = Instant::now();
let mut time = Time::new(start_instant);
// Ensure `time` was constructed correctly.
assert_eq!(time.startup(), start_instant);
assert_eq!(time.first_update(), None);
assert_eq!(time.last_update(), None);
assert_eq!(time.relative_speed(), 1.0);
assert_eq!(time.delta(), Duration::ZERO);
assert_eq!(time.delta_seconds(), 0.0);
assert_eq!(time.delta_seconds_f64(), 0.0);
assert_eq!(time.raw_delta(), Duration::ZERO);
assert_eq!(time.raw_delta_seconds(), 0.0);
assert_eq!(time.raw_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.raw_elapsed(), Duration::ZERO);
assert_eq!(time.raw_elapsed_seconds(), 0.0);
assert_eq!(time.raw_elapsed_seconds_f64(), 0.0);
// Update `time` and check results.
// The first update to `time` normally happens before other systems have run,
// so the first delta doesn't appear until the second update.
let first_update_instant = Instant::now();
time.update_with_instant(first_update_instant);
assert_eq!(time.startup(), start_instant);
assert_eq!(time.first_update(), Some(first_update_instant));
assert_eq!(time.last_update(), Some(first_update_instant));
assert_eq!(time.relative_speed(), 1.0);
assert_eq!(time.delta(), Duration::ZERO);
assert_eq!(time.delta_seconds(), 0.0);
assert_eq!(time.delta_seconds_f64(), 0.0);
assert_eq!(time.raw_delta(), Duration::ZERO);
assert_eq!(time.raw_delta_seconds(), 0.0);
assert_eq!(time.raw_delta_seconds_f64(), 0.0);
assert_eq!(time.elapsed(), first_update_instant - start_instant,);
assert_eq!(
time.elapsed_seconds(),
(first_update_instant - start_instant).as_secs_f32(),
);
assert_eq!(
time.elapsed_seconds_f64(),
(first_update_instant - start_instant).as_secs_f64(),
);
assert_eq!(time.raw_elapsed(), first_update_instant - start_instant,);
assert_eq!(
time.raw_elapsed_seconds(),
(first_update_instant - start_instant).as_secs_f32(),
);
assert_eq!(
time.raw_elapsed_seconds_f64(),
(first_update_instant - start_instant).as_secs_f64(),
);
// Update `time` again and check results.
// At this point its safe to use time.delta().
let second_update_instant = Instant::now();
time.update_with_instant(second_update_instant);
assert_eq!(time.startup(), start_instant);
assert_eq!(time.first_update(), Some(first_update_instant));
assert_eq!(time.last_update(), Some(second_update_instant));
assert_eq!(time.relative_speed(), 1.0);
assert_eq!(time.delta(), second_update_instant - first_update_instant);
assert_eq!(
time.delta_seconds(),
(second_update_instant - first_update_instant).as_secs_f32(),
);
assert_eq!(
time.delta_seconds_f64(),
(second_update_instant - first_update_instant).as_secs_f64(),
);
assert_eq!(
time.raw_delta(),
second_update_instant - first_update_instant,
);
assert_eq!(
time.raw_delta_seconds(),
(second_update_instant - first_update_instant).as_secs_f32(),
);
assert_eq!(
time.raw_delta_seconds_f64(),
(second_update_instant - first_update_instant).as_secs_f64(),
);
assert_eq!(time.elapsed(), second_update_instant - start_instant,);
assert_eq!(
time.elapsed_seconds(),
(second_update_instant - start_instant).as_secs_f32(),
);
assert_eq!(
time.elapsed_seconds_f64(),
(second_update_instant - start_instant).as_secs_f64(),
);
assert_eq!(time.raw_elapsed(), second_update_instant - start_instant,);
assert_eq!(
time.raw_elapsed_seconds(),
(second_update_instant - start_instant).as_secs_f32(),
);
assert_eq!(
time.raw_elapsed_seconds_f64(),
(second_update_instant - start_instant).as_secs_f64(),
);
}
#[test]
fn wrapping_test() {
let start_instant = Instant::now();
let mut time = Time {
startup: start_instant,
wrap_period: Duration::from_secs(3),
..Default::default()
};
assert_eq!(time.elapsed_seconds_wrapped(), 0.0);
time.update_with_instant(start_instant + Duration::from_secs(1));
assert_float_eq(time.elapsed_seconds_wrapped(), 1.0);
time.update_with_instant(start_instant + Duration::from_secs(2));
assert_float_eq(time.elapsed_seconds_wrapped(), 2.0);
time.update_with_instant(start_instant + Duration::from_secs(3));
assert_float_eq(time.elapsed_seconds_wrapped(), 0.0);
time.update_with_instant(start_instant + Duration::from_secs(4));
assert_float_eq(time.elapsed_seconds_wrapped(), 1.0);
}
#[test]
fn relative_speed_test() {
let start_instant = Instant::now();
let mut time = Time::new(start_instant);
let first_update_instant = Instant::now();
time.update_with_instant(first_update_instant);
// Update `time` again and check results.
// At this point its safe to use time.delta().
let second_update_instant = Instant::now();
time.update_with_instant(second_update_instant);
assert_eq!(time.startup(), start_instant);
assert_eq!(time.first_update(), Some(first_update_instant));
assert_eq!(time.last_update(), Some(second_update_instant));
assert_eq!(time.relative_speed(), 1.0);
assert_eq!(time.delta(), second_update_instant - first_update_instant);
assert_eq!(
time.delta_seconds(),
(second_update_instant - first_update_instant).as_secs_f32(),
);
assert_eq!(
time.delta_seconds_f64(),
(second_update_instant - first_update_instant).as_secs_f64(),
);
assert_eq!(
time.raw_delta(),
second_update_instant - first_update_instant,
);
assert_eq!(
time.raw_delta_seconds(),
(second_update_instant - first_update_instant).as_secs_f32(),
);
assert_eq!(
time.raw_delta_seconds_f64(),
(second_update_instant - first_update_instant).as_secs_f64(),
);
assert_eq!(time.elapsed(), second_update_instant - start_instant,);
assert_eq!(
time.elapsed_seconds(),
(second_update_instant - start_instant).as_secs_f32(),
);
assert_eq!(
time.elapsed_seconds_f64(),
(second_update_instant - start_instant).as_secs_f64(),
);
assert_eq!(time.raw_elapsed(), second_update_instant - start_instant,);
assert_eq!(
time.raw_elapsed_seconds(),
(second_update_instant - start_instant).as_secs_f32(),
);
assert_eq!(
time.raw_elapsed_seconds_f64(),
(second_update_instant - start_instant).as_secs_f64(),
);
// Make app time advance at 2x the rate of your system clock.
time.set_relative_speed(2.0);
// Update `time` again 1 second later.
let elapsed = Duration::from_secs(1);
let third_update_instant = second_update_instant + elapsed;
time.update_with_instant(third_update_instant);
// Since app is advancing 2x your system clock, expect time
// to have advanced by twice the amount of real time elapsed.
assert_eq!(time.startup(), start_instant);
assert_eq!(time.first_update(), Some(first_update_instant));
assert_eq!(time.last_update(), Some(third_update_instant));
assert_eq!(time.relative_speed(), 2.0);
assert_eq!(time.delta(), elapsed.mul_f32(2.0));
assert_eq!(time.delta_seconds(), elapsed.mul_f32(2.0).as_secs_f32());
assert_eq!(time.delta_seconds_f64(), elapsed.mul_f32(2.0).as_secs_f64());
assert_eq!(time.raw_delta(), elapsed);
assert_eq!(time.raw_delta_seconds(), elapsed.as_secs_f32());
assert_eq!(time.raw_delta_seconds_f64(), elapsed.as_secs_f64());
assert_eq!(
time.elapsed(),
second_update_instant - start_instant + elapsed.mul_f32(2.0),
);
assert_eq!(
time.elapsed_seconds(),
(second_update_instant - start_instant + elapsed.mul_f32(2.0)).as_secs_f32(),
);
assert_eq!(
time.elapsed_seconds_f64(),
(second_update_instant - start_instant + elapsed.mul_f32(2.0)).as_secs_f64(),
);
assert_eq!(
time.raw_elapsed(),
second_update_instant - start_instant + elapsed,
);
assert_eq!(
time.raw_elapsed_seconds(),
(second_update_instant - start_instant + elapsed).as_secs_f32(),
);
assert_eq!(
time.raw_elapsed_seconds_f64(),
(second_update_instant - start_instant + elapsed).as_secs_f64(),
);
}
#[test]
fn pause_test() {
let start_instant = Instant::now();
let mut time = Time::new(start_instant);
let first_update_instant = Instant::now();
time.update_with_instant(first_update_instant);
assert!(!time.is_paused());
assert_eq!(time.relative_speed(), 1.0);
time.pause();
assert!(time.is_paused());
assert_eq!(time.relative_speed(), 0.0);
let second_update_instant = Instant::now();
time.update_with_instant(second_update_instant);
assert_eq!(time.startup(), start_instant);
assert_eq!(time.first_update(), Some(first_update_instant));
assert_eq!(time.last_update(), Some(second_update_instant));
assert_eq!(time.delta(), Duration::ZERO);
assert_eq!(
time.raw_delta(),
second_update_instant - first_update_instant,
);
assert_eq!(time.elapsed(), first_update_instant - start_instant);
assert_eq!(time.raw_elapsed(), second_update_instant - start_instant);
time.unpause();
assert!(!time.is_paused());
assert_eq!(time.relative_speed(), 1.0);
let third_update_instant = Instant::now();
time.update_with_instant(third_update_instant);
assert_eq!(time.startup(), start_instant);
assert_eq!(time.first_update(), Some(first_update_instant));
assert_eq!(time.last_update(), Some(third_update_instant));
assert_eq!(time.delta(), third_update_instant - second_update_instant);
assert_eq!(
time.raw_delta(),
third_update_instant - second_update_instant,
);
assert_eq!(
time.elapsed(),
(third_update_instant - second_update_instant) + (first_update_instant - start_instant),
);
assert_eq!(time.raw_elapsed(), third_update_instant - start_instant);
}
}