bevy/crates/bevy_time/src/time.rs

214 lines
7 KiB
Rust
Raw Normal View History

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
use bevy_ecs::reflect::ReflectResource;
use bevy_reflect::Reflect;
use bevy_utils::{Duration, Instant};
2019-12-03 08:30:30 +00:00
/// Tracks elapsed time since the last update and since the App has started
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
#[derive(Reflect, Debug, Clone)]
#[reflect(Resource)]
2019-12-03 08:30:30 +00:00
pub struct Time {
delta: Duration,
last_update: Option<Instant>,
delta_seconds_f64: f64,
delta_seconds: f32,
seconds_since_startup: f64,
time_since_startup: Duration,
startup: Instant,
2019-12-03 08:30:30 +00:00
}
2020-05-13 23:35:38 +00:00
impl Default for Time {
fn default() -> Time {
2019-12-03 08:30:30 +00:00
Time {
delta: Duration::from_secs(0),
last_update: None,
2020-05-31 04:32:47 +00:00
startup: Instant::now(),
2020-03-27 22:03:47 +00:00
delta_seconds_f64: 0.0,
2020-05-31 04:32:47 +00:00
seconds_since_startup: 0.0,
time_since_startup: Duration::from_secs(0),
2019-12-03 08:30:30 +00:00
delta_seconds: 0.0,
}
}
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 {
/// Updates the internal time measurements.
///
/// Calling this method on the [`Time`] resource as part of your app will most likely result in
/// inaccurate timekeeping, as the resource is ordinarily managed by the
/// [`TimePlugin`](crate::TimePlugin).
2020-05-31 04:15:39 +00:00
pub fn update(&mut self) {
self.update_with_instant(Instant::now());
}
/// Update time with a specified [`Instant`]
///
/// This method is provided for use in tests. Calling this method on the [`Time`] resource as
/// part of your app will most likely result in inaccurate timekeeping, as the 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();
/// # }
/// 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) {
if let Some(last_update) = self.last_update {
self.delta = instant - last_update;
2020-06-04 03:08:20 +00:00
self.delta_seconds_f64 = self.delta.as_secs_f64();
2020-06-04 02:53:41 +00:00
self.delta_seconds = self.delta.as_secs_f32();
2020-05-31 04:15:39 +00:00
}
2020-05-31 04:32:47 +00:00
self.time_since_startup = instant - self.startup;
self.seconds_since_startup = self.time_since_startup.as_secs_f64();
self.last_update = Some(instant);
}
/// The delta between the current tick and last tick as a [`Duration`]
#[inline]
pub fn delta(&self) -> Duration {
self.delta
}
/// The delta between the current and last tick as [`f32`] seconds
#[inline]
pub fn delta_seconds(&self) -> f32 {
self.delta_seconds
}
/// The delta between the current and last tick as [`f64`] seconds
#[inline]
pub fn delta_seconds_f64(&self) -> f64 {
self.delta_seconds_f64
}
/// The time from startup to the last update in seconds
#[inline]
pub fn seconds_since_startup(&self) -> f64 {
self.seconds_since_startup
}
/// The [`Instant`] the app was started
#[inline]
pub fn startup(&self) -> Instant {
self.startup
}
/// The [`Instant`] when [`Time::update`] was last called, if it exists
#[inline]
pub fn last_update(&self) -> Option<Instant> {
self.last_update
2019-12-03 08:30:30 +00:00
}
2020-05-31 04:32:47 +00:00
/// The [`Duration`] from startup to the last update
#[inline]
2020-05-31 04:32:47 +00:00
pub fn time_since_startup(&self) -> Duration {
self.time_since_startup
2020-05-31 04:32:47 +00:00
}
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};
#[test]
fn update_test() {
let start_instant = Instant::now();
// Create a `Time` for testing
let mut time = Time {
startup: start_instant,
..Default::default()
};
// Ensure `time` was constructed correctly
assert_eq!(time.delta(), Duration::from_secs(0));
assert_eq!(time.last_update(), None);
assert_eq!(time.startup(), start_instant);
assert_eq!(time.delta_seconds_f64(), 0.0);
assert_eq!(time.seconds_since_startup(), 0.0);
assert_eq!(time.time_since_startup(), Duration::from_secs(0));
assert_eq!(time.delta_seconds(), 0.0);
// Update `time` and check results
let first_update_instant = Instant::now();
time.update_with_instant(first_update_instant);
assert_eq!(time.delta(), Duration::from_secs(0));
assert_eq!(time.last_update(), Some(first_update_instant));
assert_eq!(time.startup(), start_instant);
assert_eq!(time.delta_seconds_f64(), 0.0);
assert_eq!(
time.seconds_since_startup(),
(first_update_instant - start_instant).as_secs_f64()
);
assert_eq!(
time.time_since_startup(),
(first_update_instant - start_instant)
);
assert_eq!(time.delta_seconds, 0.0);
// Update `time` again and check results
let second_update_instant = Instant::now();
time.update_with_instant(second_update_instant);
assert_eq!(time.delta(), second_update_instant - first_update_instant);
assert_eq!(time.last_update(), Some(second_update_instant));
assert_eq!(time.startup(), start_instant);
// At this point its safe to use time.delta as a valid value
// because it's been previously verified to be correct
assert_eq!(time.delta_seconds_f64(), time.delta().as_secs_f64());
assert_eq!(
time.seconds_since_startup(),
(second_update_instant - start_instant).as_secs_f64()
);
assert_eq!(
time.time_since_startup(),
(second_update_instant - start_instant)
);
assert_eq!(time.delta_seconds(), time.delta().as_secs_f32());
}
}