bevy/crates/bevy_time/src/real.rs
Gino Valente 9b32e09551
bevy_reflect: Add clone registrations project-wide (#18307)
# Objective

Now that #13432 has been merged, it's important we update our reflected
types to properly opt into this feature. If we do not, then this could
cause issues for users downstream who want to make use of
reflection-based cloning.

## Solution

This PR is broken into 4 commits:

1. Add `#[reflect(Clone)]` on all types marked `#[reflect(opaque)]` that
are also `Clone`. This is mandatory as these types would otherwise cause
the cloning operation to fail for any type that contains it at any
depth.
2. Update the reflection example to suggest adding `#[reflect(Clone)]`
on opaque types.
3. Add `#[reflect(clone)]` attributes on all fields marked
`#[reflect(ignore)]` that are also `Clone`. This prevents the ignored
field from causing the cloning operation to fail.
   
Note that some of the types that contain these fields are also `Clone`,
and thus can be marked `#[reflect(Clone)]`. This makes the
`#[reflect(clone)]` attribute redundant. However, I think it's safer to
keep it marked in the case that the `Clone` impl/derive is ever removed.
I'm open to removing them, though, if people disagree.
4. Finally, I added `#[reflect(Clone)]` on all types that are also
`Clone`. While not strictly necessary, it enables us to reduce the
generated output since we can just call `Clone::clone` directly instead
of calling `PartialReflect::reflect_clone` on each variant/field. It
also means we benefit from any optimizations or customizations made in
the `Clone` impl, including directly dereferencing `Copy` values and
increasing reference counters.

Along with that change I also took the liberty of adding any missing
registrations that I saw could be applied to the type as well, such as
`Default`, `PartialEq`, and `Hash`. There were hundreds of these to
edit, though, so it's possible I missed quite a few.

That last commit is **_massive_**. There were nearly 700 types to
update. So it's recommended to review the first three before moving onto
that last one.

Additionally, I can break the last commit off into its own PR or into
smaller PRs, but I figured this would be the easiest way of doing it
(and in a timely manner since I unfortunately don't have as much time as
I used to for code contributions).

## Testing

You can test locally with a `cargo check`:

```
cargo check --workspace --all-features
```
2025-03-17 18:32:35 +00:00

252 lines
8.8 KiB
Rust

use bevy_platform_support::time::Instant;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use core::time::Duration;
use crate::time::Time;
/// Real time clock representing elapsed wall clock time.
///
/// A specialization of the [`Time`] structure. **For method documentation, see
/// [`Time<Real>#impl-Time<Real>`].**
///
/// It is automatically inserted as a resource by
/// [`TimePlugin`](crate::TimePlugin) and updated with time instants according
/// to [`TimeUpdateStrategy`](crate::TimeUpdateStrategy).[^disclaimer]
///
/// Note:
/// Using [`TimeUpdateStrategy::ManualDuration`](crate::TimeUpdateStrategy::ManualDuration)
/// allows for mocking the wall clock for testing purposes.
/// Besides this use case, it is not recommended to do this, as it will no longer
/// represent "wall clock" time as intended.
///
/// The [`delta()`](Time::delta) and [`elapsed()`](Time::elapsed) values of this
/// clock should be used for anything which deals specifically with real time
/// (wall clock time). It will not be affected by relative game speed
/// adjustments, pausing or other adjustments.[^disclaimer]
///
/// The clock does not count time from [`startup()`](Time::startup) to
/// [`first_update()`](Time::first_update()) into elapsed, but instead will
/// start counting time from the first update call. [`delta()`](Time::delta) and
/// [`elapsed()`](Time::elapsed) will report zero on the first update as there
/// is no previous update instant. This means that a [`delta()`](Time::delta) of
/// zero must be handled without errors in application logic, as it may
/// theoretically also happen at other times.
///
/// [`Instant`]s for [`startup()`](Time::startup),
/// [`first_update()`](Time::first_update) and
/// [`last_update()`](Time::last_update) are recorded and accessible.
///
/// [^disclaimer]: When using [`TimeUpdateStrategy::ManualDuration`](crate::TimeUpdateStrategy::ManualDuration),
/// [`Time<Real>#impl-Time<Real>`] is only a *mock* of wall clock time.
///
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Clone, Default))]
pub struct Real {
startup: Instant,
first_update: Option<Instant>,
last_update: Option<Instant>,
}
impl Default for Real {
fn default() -> Self {
Self {
startup: Instant::now(),
first_update: None,
last_update: None,
}
}
}
impl Time<Real> {
/// Constructs a new `Time<Real>` instance with a specific startup
/// [`Instant`].
pub fn new(startup: Instant) -> Self {
Self::new_with(Real {
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).
pub fn update(&mut self) {
let instant = Instant::now();
self.update_with_instant(instant);
}
/// Updates time with a specified [`Duration`].
///
/// 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).
pub fn update_with_duration(&mut self, duration: Duration) {
let last_update = self.context().last_update.unwrap_or(self.context().startup);
self.update_with_instant(last_update + duration);
}
/// 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).
pub fn update_with_instant(&mut self, instant: Instant) {
let Some(last_update) = self.context().last_update else {
let context = self.context_mut();
context.first_update = Some(instant);
context.last_update = Some(instant);
return;
};
let delta = instant - last_update;
self.advance_by(delta);
self.context_mut().last_update = Some(instant);
}
/// Returns the [`Instant`] the clock was created.
///
/// This usually represents when the app was started.
#[inline]
pub fn startup(&self) -> Instant {
self.context().startup
}
/// Returns the [`Instant`] when [`Self::update`] was first called, if it
/// exists.
///
/// This usually represents when the first app update started.
#[inline]
pub fn first_update(&self) -> Option<Instant> {
self.context().first_update
}
/// Returns the [`Instant`] when [`Self::update`] was last called, if it
/// exists.
///
/// This usually represents when the current app update started.
#[inline]
pub fn last_update(&self) -> Option<Instant> {
self.context().last_update
}
}
#[cfg(test)]
mod test {
use super::*;
// Waits until Instant::now() has increased.
//
// ```
// let previous = Instant::now();
// wait();
// assert!(Instant::now() > previous);
// ```
fn wait() {
let start = Instant::now();
while Instant::now() <= start {}
}
#[test]
fn test_update() {
let startup = Instant::now();
let mut time = Time::<Real>::new(startup);
assert_eq!(time.startup(), startup);
assert_eq!(time.first_update(), None);
assert_eq!(time.last_update(), None);
assert_eq!(time.delta(), Duration::ZERO);
assert_eq!(time.elapsed(), Duration::ZERO);
wait();
time.update();
assert_ne!(time.first_update(), None);
assert_ne!(time.last_update(), None);
assert_eq!(time.delta(), Duration::ZERO);
assert_eq!(time.elapsed(), Duration::ZERO);
wait();
time.update();
assert_ne!(time.first_update(), None);
assert_ne!(time.last_update(), None);
assert_ne!(time.last_update(), time.first_update());
assert_ne!(time.delta(), Duration::ZERO);
assert_eq!(time.elapsed(), time.delta());
wait();
let prev_elapsed = time.elapsed();
time.update();
assert_ne!(time.delta(), Duration::ZERO);
assert_eq!(time.elapsed(), prev_elapsed + time.delta());
}
#[test]
fn test_update_with_instant() {
let startup = Instant::now();
let mut time = Time::<Real>::new(startup);
wait();
let first_update = Instant::now();
time.update_with_instant(first_update);
assert_eq!(time.startup(), startup);
assert_eq!(time.first_update(), Some(first_update));
assert_eq!(time.last_update(), Some(first_update));
assert_eq!(time.delta(), Duration::ZERO);
assert_eq!(time.elapsed(), Duration::ZERO);
wait();
let second_update = Instant::now();
time.update_with_instant(second_update);
assert_eq!(time.first_update(), Some(first_update));
assert_eq!(time.last_update(), Some(second_update));
assert_eq!(time.delta(), second_update - first_update);
assert_eq!(time.elapsed(), second_update - first_update);
wait();
let third_update = Instant::now();
time.update_with_instant(third_update);
assert_eq!(time.first_update(), Some(first_update));
assert_eq!(time.last_update(), Some(third_update));
assert_eq!(time.delta(), third_update - second_update);
assert_eq!(time.elapsed(), third_update - first_update);
}
#[test]
fn test_update_with_duration() {
let startup = Instant::now();
let mut time = Time::<Real>::new(startup);
time.update_with_duration(Duration::from_secs(1));
assert_eq!(time.startup(), startup);
assert_eq!(time.first_update(), Some(startup + Duration::from_secs(1)));
assert_eq!(time.last_update(), Some(startup + Duration::from_secs(1)));
assert_eq!(time.delta(), Duration::ZERO);
assert_eq!(time.elapsed(), Duration::ZERO);
time.update_with_duration(Duration::from_secs(1));
assert_eq!(time.first_update(), Some(startup + Duration::from_secs(1)));
assert_eq!(time.last_update(), Some(startup + Duration::from_secs(2)));
assert_eq!(time.delta(), Duration::from_secs(1));
assert_eq!(time.elapsed(), Duration::from_secs(1));
time.update_with_duration(Duration::from_secs(1));
assert_eq!(time.first_update(), Some(startup + Duration::from_secs(1)));
assert_eq!(time.last_update(), Some(startup + Duration::from_secs(3)));
assert_eq!(time.delta(), Duration::from_secs(1));
assert_eq!(time.elapsed(), Duration::from_secs(2));
}
}