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};
|
2022-08-17 00:21:15 +00:00
|
|
|
use bevy_reflect::{FromReflect, Reflect};
|
2020-11-22 00:38:24 +00:00
|
|
|
use bevy_utils::{Duration, Instant};
|
2019-12-03 08:30:30 +00:00
|
|
|
|
2022-09-23 20:15:57 +00:00
|
|
|
const SECONDS_PER_HOUR: u64 = 60 * 60;
|
|
|
|
|
2020-08-09 23:13:04 +00:00
|
|
|
/// Tracks elapsed time since the last update and since the App has started
|
2022-08-17 00:21:15 +00:00
|
|
|
#[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 {
|
2020-11-28 21:08:31 +00:00
|
|
|
delta: Duration,
|
|
|
|
last_update: Option<Instant>,
|
|
|
|
delta_seconds_f64: f64,
|
|
|
|
delta_seconds: f32,
|
|
|
|
seconds_since_startup: f64,
|
2021-12-07 01:30:08 +00:00
|
|
|
time_since_startup: Duration,
|
2020-11-28 21:08:31 +00:00
|
|
|
startup: Instant,
|
2022-09-23 20:15:57 +00:00
|
|
|
/// The maximum period before [`Time::seconds_since_startup_wrapped_f32`] wraps to 0
|
|
|
|
///
|
|
|
|
/// Defaults to 1 hour
|
|
|
|
pub wrap_period: Duration,
|
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),
|
2020-11-28 21:08:31 +00:00
|
|
|
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,
|
2021-12-07 01:30:08 +00:00
|
|
|
time_since_startup: Duration::from_secs(0),
|
2019-12-03 08:30:30 +00:00
|
|
|
delta_seconds: 0.0,
|
2022-09-23 20:15:57 +00:00
|
|
|
wrap_period: Duration::from_secs(SECONDS_PER_HOUR),
|
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 {
|
2022-01-02 20:36:40 +00:00
|
|
|
/// Updates the internal time measurements.
|
2022-04-24 23:15:27 +00:00
|
|
|
///
|
|
|
|
/// 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
|
2022-05-26 00:27:18 +00:00
|
|
|
/// [`TimePlugin`](crate::TimePlugin).
|
2020-05-31 04:15:39 +00:00
|
|
|
pub fn update(&mut self) {
|
2022-01-02 20:36:40 +00:00
|
|
|
self.update_with_instant(Instant::now());
|
2020-11-28 21:08:31 +00:00
|
|
|
}
|
|
|
|
|
2022-04-24 23:15:27 +00:00
|
|
|
/// 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
|
2022-05-26 00:27:18 +00:00
|
|
|
/// ordinarily managed by the [`TimePlugin`](crate::TimePlugin).
|
2022-04-24 23:15:27 +00:00
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
2022-05-26 00:27:18 +00:00
|
|
|
/// # use bevy_time::prelude::*;
|
2022-04-24 23:15:27 +00:00
|
|
|
/// # 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)]
|
2022-04-24 23:15:27 +00:00
|
|
|
/// 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) {
|
2020-11-28 21:08:31 +00:00
|
|
|
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
|
|
|
|
2021-12-07 01:30:08 +00:00
|
|
|
self.time_since_startup = instant - self.startup;
|
|
|
|
self.seconds_since_startup = self.time_since_startup.as_secs_f64();
|
2020-11-28 21:08:31 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-12-07 01:30:08 +00:00
|
|
|
/// The time from startup to the last update in seconds
|
2022-09-23 20:15:57 +00:00
|
|
|
///
|
|
|
|
/// If you intend to cast this to an `f32` value, note that this value is monotonically increasing,
|
|
|
|
/// and that its precision as an `f32` will noticeably degrade over time (in a matter of hours).
|
|
|
|
/// If that precision loss is unacceptable, you should use [`Time::seconds_since_startup_wrapped_f32`],
|
|
|
|
/// which will return the time from startup modulo a wrapping period.
|
2020-11-28 21:08:31 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn seconds_since_startup(&self) -> f64 {
|
|
|
|
self.seconds_since_startup
|
|
|
|
}
|
|
|
|
|
2022-09-23 20:15:57 +00:00
|
|
|
/// The time from startup to the last update, modulo the [`Time::wrap_period`], in seconds.
|
|
|
|
///
|
|
|
|
/// Time from startup is a monotonically increasing value and so its precision when read as an `f32`
|
|
|
|
/// will noticeably degrade over time, which causes issues for some uses, e.g. shaders.
|
|
|
|
/// This method avoids noticeable degradation by limiting the values to a much smaller range.
|
|
|
|
///
|
|
|
|
/// The default wrapping period is one hour.
|
|
|
|
#[inline]
|
|
|
|
pub fn seconds_since_startup_wrapped_f32(&self) -> f32 {
|
|
|
|
(self.seconds_since_startup % self.wrap_period.as_secs_f64()) as f32
|
|
|
|
}
|
|
|
|
|
2020-11-28 21:08:31 +00:00
|
|
|
/// The [`Instant`] the app was started
|
|
|
|
#[inline]
|
|
|
|
pub fn startup(&self) -> Instant {
|
|
|
|
self.startup
|
|
|
|
}
|
|
|
|
|
2022-04-13 21:34:21 +00:00
|
|
|
/// The [`Instant`] when [`Time::update`] was last called, if it exists
|
2020-11-28 21:08:31 +00:00
|
|
|
#[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
|
|
|
|
2022-04-13 21:34:21 +00:00
|
|
|
/// The [`Duration`] from startup to the last update
|
2021-12-07 01:30:08 +00:00
|
|
|
#[inline]
|
2020-05-31 04:32:47 +00:00
|
|
|
pub fn time_since_startup(&self) -> Duration {
|
2021-12-07 01:30:08 +00:00
|
|
|
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
|
|
|
|
2020-11-28 21:08:31 +00:00
|
|
|
#[cfg(test)]
|
2021-02-22 08:42:19 +00:00
|
|
|
#[allow(clippy::float_cmp)]
|
2020-11-28 21:08:31 +00:00
|
|
|
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);
|
2021-12-07 01:30:08 +00:00
|
|
|
assert_eq!(time.time_since_startup(), Duration::from_secs(0));
|
2020-11-28 21:08:31 +00:00
|
|
|
assert_eq!(time.delta_seconds(), 0.0);
|
2022-09-23 20:15:57 +00:00
|
|
|
assert_eq!(time.seconds_since_startup_wrapped_f32(), 0.0);
|
2020-11-28 21:08:31 +00:00
|
|
|
|
|
|
|
// 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()
|
|
|
|
);
|
2021-12-07 01:30:08 +00:00
|
|
|
assert_eq!(
|
|
|
|
time.time_since_startup(),
|
|
|
|
(first_update_instant - start_instant)
|
|
|
|
);
|
2022-09-23 20:15:57 +00:00
|
|
|
assert_eq!(time.delta_seconds(), 0.0);
|
|
|
|
assert_float_eq(
|
|
|
|
time.seconds_since_startup_wrapped_f32(),
|
|
|
|
time.seconds_since_startup() as f32,
|
|
|
|
);
|
2020-11-28 21:08:31 +00:00
|
|
|
|
|
|
|
// 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()
|
|
|
|
);
|
2021-12-07 01:30:08 +00:00
|
|
|
assert_eq!(
|
|
|
|
time.time_since_startup(),
|
|
|
|
(second_update_instant - start_instant)
|
|
|
|
);
|
2020-11-28 21:08:31 +00:00
|
|
|
assert_eq!(time.delta_seconds(), time.delta().as_secs_f32());
|
2022-09-23 20:15:57 +00:00
|
|
|
assert_float_eq(
|
|
|
|
time.seconds_since_startup_wrapped_f32(),
|
|
|
|
time.seconds_since_startup() as f32,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn update_wrapping() {
|
|
|
|
let start_instant = Instant::now();
|
|
|
|
|
|
|
|
let mut time = Time {
|
|
|
|
startup: start_instant,
|
|
|
|
wrap_period: Duration::from_secs(3),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
assert_eq!(time.seconds_since_startup_wrapped_f32(), 0.0);
|
|
|
|
|
|
|
|
time.update_with_instant(start_instant + Duration::from_secs(1));
|
|
|
|
assert_float_eq(time.seconds_since_startup_wrapped_f32(), 1.0);
|
|
|
|
|
|
|
|
time.update_with_instant(start_instant + Duration::from_secs(2));
|
|
|
|
assert_float_eq(time.seconds_since_startup_wrapped_f32(), 2.0);
|
|
|
|
|
|
|
|
time.update_with_instant(start_instant + Duration::from_secs(3));
|
|
|
|
assert_float_eq(time.seconds_since_startup_wrapped_f32(), 0.0);
|
|
|
|
|
|
|
|
time.update_with_instant(start_instant + Duration::from_secs(4));
|
|
|
|
assert_float_eq(time.seconds_since_startup_wrapped_f32(), 1.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn assert_float_eq(a: f32, b: f32) {
|
|
|
|
assert!((a - b).abs() <= f32::EPSILON, "{a} != {b}");
|
2020-11-28 21:08:31 +00:00
|
|
|
}
|
|
|
|
}
|