Refactor Time API and internals (#934)

Refactor Time API and internals
This commit is contained in:
Amber Kowalski 2020-11-28 16:08:31 -05:00 committed by GitHub
parent f3b49e44c9
commit 097a55948c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 146 additions and 40 deletions

View File

@ -13,9 +13,18 @@ current changes on git with [previous release tags][git_tag_comparison].
### Changed ### Changed
- [Breaking changes to timer API][914] - [Breaking changes to timer API][914]
- Created getters and setters rather than exposing struct members.
- [Removed timer auto-ticking system][931] - [Removed timer auto-ticking system][931]
- Added an example of how to tick timers manually.
- [Breaking changes to Time API][934]
- Created getters to get `Time` state and made members private.
- Modifying `Time`'s values directly is no longer possible outside of bevy.
### Fixed ### Fixed
[914]: https://github.com/bevyengine/bevy/pull/914
[931]: https://github.com/bevyengine/bevy/pull/931
[934]: https://github.com/bevyengine/bevy/pull/934
## Version 0.3.0 (2020-11-03) ## Version 0.3.0 (2020-11-03)

View File

@ -4,19 +4,19 @@ use bevy_utils::{Duration, Instant};
/// Tracks elapsed time since the last update and since the App has started /// Tracks elapsed time since the last update and since the App has started
#[derive(Debug)] #[derive(Debug)]
pub struct Time { pub struct Time {
pub delta: Duration, delta: Duration,
pub instant: Option<Instant>, last_update: Option<Instant>,
pub delta_seconds_f64: f64, delta_seconds_f64: f64,
pub delta_seconds: f32, delta_seconds: f32,
pub seconds_since_startup: f64, seconds_since_startup: f64,
pub startup: Instant, startup: Instant,
} }
impl Default for Time { impl Default for Time {
fn default() -> Time { fn default() -> Time {
Time { Time {
delta: Duration::from_secs(0), delta: Duration::from_secs(0),
instant: None, last_update: None,
startup: Instant::now(), startup: Instant::now(),
delta_seconds_f64: 0.0, delta_seconds_f64: 0.0,
seconds_since_startup: 0.0, seconds_since_startup: 0.0,
@ -28,15 +28,55 @@ impl Default for Time {
impl Time { impl Time {
pub fn update(&mut self) { pub fn update(&mut self) {
let now = Instant::now(); let now = Instant::now();
if let Some(instant) = self.instant { self.update_with_instant(now);
self.delta = now - instant; }
pub(crate) fn update_with_instant(&mut self, instant: Instant) {
if let Some(last_update) = self.last_update {
self.delta = instant - last_update;
self.delta_seconds_f64 = self.delta.as_secs_f64(); self.delta_seconds_f64 = self.delta.as_secs_f64();
self.delta_seconds = self.delta.as_secs_f32(); self.delta_seconds = self.delta.as_secs_f32();
} }
let duration_since_startup = now - self.startup; let duration_since_startup = instant - self.startup;
self.seconds_since_startup = duration_since_startup.as_secs_f64(); self.seconds_since_startup = duration_since_startup.as_secs_f64();
self.instant = Some(now); 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 since startup 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
} }
pub fn time_since_startup(&self) -> Duration { pub fn time_since_startup(&self) -> Duration {
@ -47,3 +87,60 @@ impl Time {
pub(crate) fn time_system(mut time: ResMut<Time>) { pub(crate) fn time_system(mut time: ResMut<Time>) {
time.update(); time.update();
} }
#[cfg(test)]
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.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.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.delta_seconds(), time.delta().as_secs_f32());
}
}

View File

@ -40,11 +40,11 @@ impl FrameTimeDiagnosticsPlugin {
state.frame_count += 1.0; state.frame_count += 1.0;
diagnostics.add_measurement(Self::FRAME_COUNT, state.frame_count); diagnostics.add_measurement(Self::FRAME_COUNT, state.frame_count);
if time.delta_seconds_f64 == 0.0 { if time.delta_seconds_f64() == 0.0 {
return; return;
} }
diagnostics.add_measurement(Self::FRAME_TIME, time.delta_seconds_f64); diagnostics.add_measurement(Self::FRAME_TIME, time.delta_seconds_f64());
if let Some(fps) = diagnostics if let Some(fps) = diagnostics
.get(Self::FRAME_TIME) .get(Self::FRAME_TIME)
.and_then(|frame_time_diagnostic| { .and_then(|frame_time_diagnostic| {

View File

@ -66,7 +66,7 @@ impl PrintDiagnosticsPlugin {
time: Res<Time>, time: Res<Time>,
diagnostics: Res<Diagnostics>, diagnostics: Res<Diagnostics>,
) { ) {
if state.timer.tick(time.delta_seconds).finished() { if state.timer.tick(time.delta_seconds()).finished() {
println!("Diagnostics:"); println!("Diagnostics:");
println!("{}", "-".repeat(93)); println!("{}", "-".repeat(93));
if let Some(ref filter) = state.filter { if let Some(ref filter) = state.filter {
@ -86,7 +86,7 @@ impl PrintDiagnosticsPlugin {
time: Res<Time>, time: Res<Time>,
diagnostics: Res<Diagnostics>, diagnostics: Res<Diagnostics>,
) { ) {
if state.timer.tick(time.delta_seconds).finished() { if state.timer.tick(time.delta_seconds()).finished() {
println!("Diagnostics (Debug):"); println!("Diagnostics (Debug):");
println!("{}", "-".repeat(93)); println!("{}", "-".repeat(93));
if let Some(ref filter) = state.filter { if let Some(ref filter) = state.filter {

View File

@ -217,7 +217,7 @@ fn deselect(
/// Applies gravity to all entities with velocity /// Applies gravity to all entities with velocity
fn velocity_system(time: Res<Time>, mut q: Query<Mut<Velocity>>) { fn velocity_system(time: Res<Time>, mut q: Query<Mut<Velocity>>) {
let delta = time.delta_seconds; let delta = time.delta_seconds();
for mut v in q.iter_mut() { for mut v in q.iter_mut() {
v.translation += Vec3::new(0.0, GRAVITY * delta, 0.0); v.translation += Vec3::new(0.0, GRAVITY * delta, 0.0);
@ -274,7 +274,7 @@ fn collision_system(
/// Apply velocity to positions and rotations. /// Apply velocity to positions and rotations.
fn move_system(time: Res<Time>, mut q: Query<(&Velocity, Mut<Transform>)>) { fn move_system(time: Res<Time>, mut q: Query<(&Velocity, Mut<Transform>)>) {
let delta = time.delta_seconds; let delta = time.delta_seconds();
for (v, mut t) in q.iter_mut() { for (v, mut t) in q.iter_mut() {
t.translation += delta * v.translation; t.translation += delta * v.translation;

View File

@ -14,7 +14,7 @@ fn animate_sprite_system(
mut query: Query<(&mut Timer, &mut TextureAtlasSprite, &Handle<TextureAtlas>)>, mut query: Query<(&mut Timer, &mut TextureAtlasSprite, &Handle<TextureAtlas>)>,
) { ) {
for (mut timer, mut sprite, texture_atlas_handle) in query.iter_mut() { for (mut timer, mut sprite, texture_atlas_handle) in query.iter_mut() {
timer.tick(time.delta_seconds); timer.tick(time.delta_seconds());
if timer.finished() { if timer.finished() {
let texture_atlas = texture_atlases.get(texture_atlas_handle).unwrap(); let texture_atlas = texture_atlases.get(texture_atlas_handle).unwrap();
sprite.index = ((sprite.index as usize + 1) % texture_atlas.textures.len()) as u32; sprite.index = ((sprite.index as usize + 1) % texture_atlas.textures.len()) as u32;

View File

@ -17,7 +17,7 @@ struct Rotator;
/// rotates the parent, which will result in the child also rotating /// rotates the parent, which will result in the child also rotating
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Rotator>>) { fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Rotator>>) {
for mut transform in query.iter_mut() { for mut transform in query.iter_mut() {
transform.rotation *= Quat::from_rotation_x(3.0 * time.delta_seconds); transform.rotation *= Quat::from_rotation_x(3.0 * time.delta_seconds());
} }
} }

View File

@ -25,9 +25,9 @@ fn move_cubes(
) { ) {
for (mut transform, material_handle) in query.iter_mut() { for (mut transform, material_handle) in query.iter_mut() {
let material = materials.get_mut(material_handle).unwrap(); let material = materials.get_mut(material_handle).unwrap();
transform.translation += Vec3::new(1.0, 0.0, 0.0) * time.delta_seconds; transform.translation += Vec3::new(1.0, 0.0, 0.0) * time.delta_seconds();
material.albedo = material.albedo =
Color::BLUE * Vec3::splat((3.0 * time.seconds_since_startup as f32).sin()); Color::BLUE * Vec3::splat((3.0 * time.seconds_since_startup() as f32).sin());
} }
} }

View File

@ -21,7 +21,7 @@ struct Rotator;
/// rotates the parent, which will result in the child also rotating /// rotates the parent, which will result in the child also rotating
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Rotator>>) { fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Rotator>>) {
for mut transform in query.iter_mut() { for mut transform in query.iter_mut() {
transform.rotation *= Quat::from_rotation_x(3.0 * time.delta_seconds); transform.rotation *= Quat::from_rotation_x(3.0 * time.delta_seconds());
} }
} }

View File

@ -38,7 +38,7 @@ struct PrintMessageState {
} }
fn print_message_system(mut state: ResMut<PrintMessageState>, time: Res<Time>) { fn print_message_system(mut state: ResMut<PrintMessageState>, time: Res<Time>) {
if state.timer.tick(time.delta_seconds).finished() { if state.timer.tick(time.delta_seconds()).finished() {
println!("{}", state.message); println!("{}", state.message);
} }
} }

View File

@ -34,7 +34,7 @@ fn event_trigger_system(
mut state: ResMut<EventTriggerState>, mut state: ResMut<EventTriggerState>,
mut my_events: ResMut<Events<MyEvent>>, mut my_events: ResMut<Events<MyEvent>>,
) { ) {
if state.event_timer.tick(time.delta_seconds).finished() { if state.event_timer.tick(time.delta_seconds()).finished() {
my_events.send(MyEvent { my_events.send(MyEvent {
message: "MyEvent just happened!".to_string(), message: "MyEvent just happened!".to_string(),
}); });

View File

@ -97,24 +97,24 @@ fn rotate(
let angle = std::f32::consts::PI / 2.0; let angle = std::f32::consts::PI / 2.0;
for (parent, children) in parents_query.iter_mut() { for (parent, children) in parents_query.iter_mut() {
if let Ok(mut transform) = transform_query.get_mut(parent) { if let Ok(mut transform) = transform_query.get_mut(parent) {
transform.rotate(Quat::from_rotation_z(-angle * time.delta_seconds)); transform.rotate(Quat::from_rotation_z(-angle * time.delta_seconds()));
} }
// To iterate through the entities children, just treat the Children component as a Vec // To iterate through the entities children, just treat the Children component as a Vec
// Alternatively, you could query entities that have a Parent component // Alternatively, you could query entities that have a Parent component
for child in children.iter() { for child in children.iter() {
if let Ok(mut transform) = transform_query.get_mut(*child) { if let Ok(mut transform) = transform_query.get_mut(*child) {
transform.rotate(Quat::from_rotation_z(angle * 2.0 * time.delta_seconds)); transform.rotate(Quat::from_rotation_z(angle * 2.0 * time.delta_seconds()));
} }
} }
// To demonstrate removing children, we'll start to remove the children after a couple of seconds // To demonstrate removing children, we'll start to remove the children after a couple of seconds
if time.seconds_since_startup >= 2.0 && children.len() == 3 { if time.seconds_since_startup() >= 2.0 && children.len() == 3 {
let child = children.last().copied().unwrap(); let child = children.last().copied().unwrap();
commands.despawn(child); commands.despawn(child);
} }
if time.seconds_since_startup >= 4.0 { if time.seconds_since_startup() >= 4.0 {
// This will remove the entity from its parent's list of children, as well as despawn // This will remove the entity from its parent's list of children, as well as despawn
// any children the entity has. // any children the entity has.
commands.despawn_recursive(parent); commands.despawn_recursive(parent);

View File

@ -39,7 +39,7 @@ fn setup_system(commands: &mut Commands) {
/// using bevy's `Time` resource to get the delta between each update. /// using bevy's `Time` resource to get the delta between each update.
fn timer_system(time: Res<Time>, mut query: Query<&mut Timer>) { fn timer_system(time: Res<Time>, mut query: Query<&mut Timer>) {
for mut timer in query.iter_mut() { for mut timer in query.iter_mut() {
if timer.tick(time.delta_seconds).just_finished() { if timer.tick(time.delta_seconds()).just_finished() {
info!("Entity timer just finished") info!("Entity timer just finished")
} }
} }
@ -48,14 +48,14 @@ fn timer_system(time: Res<Time>, mut query: Query<&mut Timer>) {
/// This system controls ticking the timer within the countdown resource and /// This system controls ticking the timer within the countdown resource and
/// handling its state. /// handling its state.
fn countdown_system(time: Res<Time>, mut countdown: ResMut<Countdown>) { fn countdown_system(time: Res<Time>, mut countdown: ResMut<Countdown>) {
countdown.main_timer.tick(time.delta_seconds); countdown.main_timer.tick(time.delta_seconds());
// The API encourages this kind of timer state checking (if you're only checking for one value) // The API encourages this kind of timer state checking (if you're only checking for one value)
// Additionally, `finished()` would accomplish the same thing as `just_finished` due to the timer // Additionally, `finished()` would accomplish the same thing as `just_finished` due to the timer
// being repeating, however this makes more sense visually. // being repeating, however this makes more sense visually.
if countdown if countdown
.percent_trigger .percent_trigger
.tick(time.delta_seconds) .tick(time.delta_seconds())
.just_finished() .just_finished()
{ {
if !countdown.main_timer.finished() { if !countdown.main_timer.finished() {

View File

@ -174,7 +174,7 @@ fn paddle_movement_system(
let translation = &mut transform.translation; let translation = &mut transform.translation;
// move the paddle horizontally // move the paddle horizontally
translation.x += time.delta_seconds * direction * paddle.speed; translation.x += time.delta_seconds() * direction * paddle.speed;
// bound the paddle within the walls // bound the paddle within the walls
translation.x = translation.x.min(380.0).max(-380.0); translation.x = translation.x.min(380.0).max(-380.0);
} }
@ -182,7 +182,7 @@ fn paddle_movement_system(
fn ball_movement_system(time: Res<Time>, mut ball_query: Query<(&Ball, &mut Transform)>) { fn ball_movement_system(time: Res<Time>, mut ball_query: Query<(&Ball, &mut Transform)>) {
// clamp the timestep to stop the ball from escaping when the game starts // clamp the timestep to stop the ball from escaping when the game starts
let delta_seconds = f32::min(0.2, time.delta_seconds); let delta_seconds = f32::min(0.2, time.delta_seconds());
for (ball, mut transform) in ball_query.iter_mut() { for (ball, mut transform) in ball_query.iter_mut() {
transform.translation += ball.velocity * delta_seconds; transform.translation += ball.velocity * delta_seconds;

View File

@ -84,7 +84,7 @@ fn mouse_handler(
mut counter: ResMut<BevyCounter>, mut counter: ResMut<BevyCounter>,
) { ) {
if mouse_button_input.pressed(MouseButton::Left) { if mouse_button_input.pressed(MouseButton::Left) {
let spawn_count = (BIRDS_PER_SECOND as f32 * time.delta_seconds) as u128; let spawn_count = (BIRDS_PER_SECOND as f32 * time.delta_seconds()) as u128;
let bird_x = (window.width as i32 / -2) as f32 + HALF_BIRD_SIZE; let bird_x = (window.width as i32 / -2) as f32 + HALF_BIRD_SIZE;
let bird_y = (window.height / 2) as f32 - HALF_BIRD_SIZE; let bird_y = (window.height / 2) as f32 - HALF_BIRD_SIZE;
@ -118,9 +118,9 @@ fn mouse_handler(
fn movement_system(time: Res<Time>, mut bird_query: Query<(&mut Bird, &mut Transform)>) { fn movement_system(time: Res<Time>, mut bird_query: Query<(&mut Bird, &mut Transform)>) {
for (mut bird, mut transform) in bird_query.iter_mut() { for (mut bird, mut transform) in bird_query.iter_mut() {
transform.translation.x += bird.velocity.x * time.delta_seconds; transform.translation.x += bird.velocity.x * time.delta_seconds();
transform.translation.y += bird.velocity.y * time.delta_seconds; transform.translation.y += bird.velocity.y * time.delta_seconds();
bird.velocity.y += GRAVITY * time.delta_seconds; bird.velocity.y += GRAVITY * time.delta_seconds();
} }
} }

View File

@ -62,7 +62,7 @@ fn atlas_render_system(
} }
fn text_update_system(mut state: ResMut<State>, time: Res<Time>, mut query: Query<&mut Text>) { fn text_update_system(mut state: ResMut<State>, time: Res<Time>, mut query: Query<&mut Text>) {
if state.timer.tick(time.delta_seconds).finished() { if state.timer.tick(time.delta_seconds()).finished() {
for mut text in query.iter_mut() { for mut text in query.iter_mut() {
let c = rand::random::<u8>() as char; let c = rand::random::<u8>() as char;
if !text.value.contains(c) { if !text.value.contains(c) {

View File

@ -34,7 +34,7 @@ fn counter(mut state: Local<CounterState>, time: Res<Time>) {
"tick {} @ {:?} [Δ{}]", "tick {} @ {:?} [Δ{}]",
state.count, state.count,
time.time_since_startup(), time.time_since_startup(),
time.delta_seconds time.delta_seconds()
); );
} }
state.count += 1; state.count += 1;

View File

@ -22,7 +22,7 @@ fn change_title(time: Res<Time>, mut windows: ResMut<Windows>) {
let window = windows.get_primary_mut().unwrap(); let window = windows.get_primary_mut().unwrap();
window.set_title(format!( window.set_title(format!(
"Seconds since startup: {}", "Seconds since startup: {}",
time.seconds_since_startup.round() time.seconds_since_startup().round()
)); ));
} }