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
57 changes: 56 additions & 1 deletion crates/bevy_time/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ pub struct Time {
seconds_since_startup: f64,
time_since_startup: Duration,
startup: Instant,
/// The maximum duration before `Self::seconds_since_last_wrapping_period()` wraps back to 0.0.
/// Defaults to one hour.
pub max_wrapping_period: Duration,
}

impl Default for Time {
Expand All @@ -25,6 +28,7 @@ impl Default for Time {
seconds_since_startup: 0.0,
time_since_startup: Duration::from_secs(0),
delta_seconds: 0.0,
max_wrapping_period: Duration::from_secs(60 * 60), // 1 hour
}
}
}
Expand Down Expand Up @@ -122,11 +126,26 @@ 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
IceSentry marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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`
IceSentry marked this conversation as resolved.
Show resolved Hide resolved
///
/// Defaults to wrapping every hour
IceSentry marked this conversation as resolved.
Show resolved Hide resolved
#[inline]
pub fn seconds_since_startup_f32_wrapped(&self) -> f32 {
(self.seconds_since_startup % self.max_wrapping_period.as_secs_f64()) as f32
}

/// The [`Instant`] the app was started
#[inline]
pub fn startup(&self) -> Instant {
Expand Down Expand Up @@ -170,6 +189,7 @@ 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(), 0.0);

// Update `time` and check results
let first_update_instant = Instant::now();
Expand All @@ -188,7 +208,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(),
time.seconds_since_startup() as f32,
);

// Update `time` again and check results
let second_update_instant = Instant::now();
Expand All @@ -210,5 +234,36 @@ 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(),
time.seconds_since_startup() as f32,
);
}

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

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

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

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

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

// wraps on next update
time.update_with_instant(start_instant + Duration::from_secs(3));
assert_float_eq(time.seconds_since_startup_f32_wrapped(), 1.0);
}

fn assert_float_eq(a: f32, b: f32) {
assert!((a - b) <= f32::EPSILON, "{a} != {b}");
IceSentry marked this conversation as resolved.
Show resolved Hide resolved
}
}