Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - add time wrapping to Time #5982

Closed
wants to merge 12 commits into from
88 changes: 86 additions & 2 deletions crates/bevy_time/src/time.rs
Expand Up @@ -2,6 +2,32 @@ use bevy_ecs::{reflect::ReflectResource, system::Resource};
use bevy_reflect::{FromReflect, Reflect};
use bevy_utils::{Duration, Instant};

/// The duration after which the time will go wrap back to 0
#[derive(Debug, Clone, Copy)]
pub enum WrapDuration {
/// Will wrap after 1 hour or 3600 seconds
Default,
/// Used to provide any duration to use as the period.
///
/// It is highly recommended to not go above the maximum value of a day
Custom(Duration),
/// Will wrap after 1 day or 86400 seconds.
///
/// `f32`'s have about 6-7 significant numbers and a day is 86400 seconds,
/// add a few decimal places for millis and you will start to get precision errors.
Max,
}
IceSentry marked this conversation as resolved.
Show resolved Hide resolved

impl From<WrapDuration> for Duration {
fn from(val: WrapDuration) -> Self {
match val {
WrapDuration::Default => Duration::from_secs(60 * 60), // 1 hour
WrapDuration::Custom(duration) => duration,
WrapDuration::Max => Duration::from_secs(60 * 60 * 24), // 1 day
IceSentry marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

/// Tracks elapsed time since the last update and since the App has started
#[derive(Resource, Reflect, FromReflect, Debug, Clone)]
#[reflect(Resource)]
Expand Down Expand Up @@ -122,11 +148,27 @@ impl Time {
}

/// The time from startup to the last update in seconds
///
/// If you need an `f32` value, it is highly recommended to use [`Time::seconds_since_startup_f32_wrapped`]
/// instead of casting it. Casting to an `f32` can cause floating point precision issues pretty fast.
IceSentry marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
pub fn seconds_since_startup(&self) -> f64 {
self.seconds_since_startup
}

/// The time from the last wrap period to the last update in seconds.
///
/// When used in shaders, the time is limited to `f32` which can introduce floating point precision issues
/// fairly quickly if the app is left open for a while.
/// This will wrap the value to 0.0 on the update after the `Self::max_wrapping_period`.
///
/// Defaults to wrapping every hour.
IceSentry marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
pub fn seconds_since_startup_f32_wrapped(&self, duration: WrapDuration) -> f32 {
IceSentry marked this conversation as resolved.
Show resolved Hide resolved
let duration: Duration = duration.into();
(self.seconds_since_startup % duration.as_secs_f64()) as f32
}

/// The [`Instant`] the app was started
#[inline]
pub fn startup(&self) -> Instant {
Expand All @@ -149,7 +191,7 @@ impl Time {
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::Time;
use super::{Time, WrapDuration};
use bevy_utils::{Duration, Instant};

#[test]
Expand All @@ -170,6 +212,10 @@ mod tests {
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);
assert_eq!(
time.seconds_since_startup_f32_wrapped(WrapDuration::Default),
0.0
);

// Update `time` and check results
let first_update_instant = Instant::now();
Expand All @@ -188,7 +234,11 @@ mod tests {
time.time_since_startup(),
(first_update_instant - start_instant)
);
assert_eq!(time.delta_seconds, 0.0);
assert_eq!(time.delta_seconds(), 0.0);
assert_float_eq(
time.seconds_since_startup_f32_wrapped(WrapDuration::Default),
time.seconds_since_startup() as f32,
);

// Update `time` again and check results
let second_update_instant = Instant::now();
Expand All @@ -210,5 +260,39 @@ mod tests {
(second_update_instant - start_instant)
);
assert_eq!(time.delta_seconds(), time.delta().as_secs_f32());
assert_float_eq(
time.seconds_since_startup_f32_wrapped(WrapDuration::Default),
time.seconds_since_startup() as f32,
);
}

#[test]
fn update_wrapping() {
let start_instant = Instant::now();

let mut time = Time {
startup: start_instant,
..Default::default()
};

let wrap_duration = WrapDuration::Custom(Duration::from_secs(3));

assert_eq!(time.seconds_since_startup_f32_wrapped(wrap_duration), 0.0);

time.update_with_instant(start_instant + Duration::from_secs(1));
assert_float_eq(time.seconds_since_startup_f32_wrapped(wrap_duration), 1.0);

time.update_with_instant(start_instant + Duration::from_secs(2));
assert_float_eq(time.seconds_since_startup_f32_wrapped(wrap_duration), 2.0);

time.update_with_instant(start_instant + Duration::from_secs(3));
assert_float_eq(time.seconds_since_startup_f32_wrapped(wrap_duration), 0.0);

time.update_with_instant(start_instant + Duration::from_secs(4));
assert_float_eq(time.seconds_since_startup_f32_wrapped(wrap_duration), 1.0);
}

fn assert_float_eq(a: f32, b: f32) {
assert!((a - b).abs() <= f32::EPSILON, "{a} != {b}");
}
}