-
-
Notifications
You must be signed in to change notification settings - Fork 9
Background fixed update #7
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
base: main
Are you sure you want to change the base?
Conversation
1161a12
to
8effd48
Compare
fn ease_translation_lerp( | ||
mut query: Query<(&mut Transform, &TranslationEasingState)>, | ||
time: Query<(&TaskToRenderTime, &Timestep)>, | ||
) { | ||
let Ok((time, timestep)) = time.get_single() else { | ||
return; | ||
}; | ||
let overstep = (time.diff.max(0.0) / timestep.timestep.as_secs_f64()).min(1.0) as f32; | ||
query.iter_mut().for_each(|(mut transform, interpolation)| { | ||
if let (Some(start), Some(end)) = (interpolation.start, interpolation.end) { | ||
transform.translation = start.lerp(end, overstep); | ||
} | ||
}); | ||
} | ||
|
||
/// Eases the rotations of entities with spherical linear interpolation. | ||
fn ease_rotation_slerp( | ||
mut query: Query<(&mut Transform, &RotationEasingState)>, | ||
time: Query<(&TaskToRenderTime, &Timestep)>, | ||
) { | ||
let Ok((time, timestep)) = time.get_single() else { | ||
return; | ||
}; | ||
let overstep = (time.diff.max(0.0) / timestep.timestep.as_secs_f64()).min(1.0) as f32; | ||
|
||
query | ||
.par_iter_mut() | ||
.for_each(|(mut transform, interpolation)| { | ||
if let (Some(start), Some(end)) = (interpolation.start, interpolation.end) { | ||
// Note: `slerp` will always take the shortest path, but when the two rotations are more than | ||
// 180 degrees apart, this can cause visual artifacts as the rotation "flips" to the other side. | ||
transform.rotation = start.slerp(end, overstep); | ||
} | ||
}); | ||
} | ||
|
||
/// Eases the scales of entities with linear interpolation. | ||
fn ease_scale_lerp( | ||
mut query: Query<(&mut Transform, &ScaleEasingState)>, | ||
time: Query<(&TaskToRenderTime, &Timestep)>, | ||
) { | ||
let Ok((time, timestep)) = time.get_single() else { | ||
return; | ||
}; | ||
let overstep = (time.diff.max(0.0) / timestep.timestep.as_secs_f64()).min(1.0) as f32; | ||
|
||
query.iter_mut().for_each(|(mut transform, interpolation)| { | ||
if let (Some(start), Some(end)) = (interpolation.start, interpolation.end) { | ||
transform.scale = start.lerp(end, overstep); | ||
} | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those systems could use a helper function to only retrieve the overstep()
, the rest of the code is similar to the easing plugin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is still relevant, as it could be more practical to opt-out of Res<Time<Fixed>>
time management. That said, it's a minor inconvenience.
@@ -83,7 +83,7 @@ use bevy::prelude::*; | |||
/// Then, add the [`TransformExtrapolationPlugin`] to the app with the velocity sources: | |||
/// | |||
/// ``` | |||
/// use bevy::{ecs::query::QueryData, prelude::*}; | |||
/// use bevy::{ecs::query::QueryData, time::TimePlugin, prelude::*}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those are slightly out of scope for this PR, but needed for bevy 0.15.1 due to #7.
Objective
I published https://github.com/vrixyz/bevy_fixed_update_task ; This PR allows to tweak schedules used by the plugin.
See https://github.com/Vrixyz/bevy_fixed_update_task/blob/main/examples/interpolate_custom_schedule.rs for an example integration.
Changelog
FixedUpdate
.ease_[translation|rotation|scale]_lerp
, useful when your time management does not rely onRes<Time<Fixed>
.Migration Guide
default()
is supported.Previous description for historical context
Objective
bevy's
FixedUpdate
runs synchronously whenTime<Virtual>
exceedsTime<Fixed> + its accumulated time
. This can lead to lag if the fixed update logic takes too long.When the FixedUpdate doesn't run every frame, we can take advantage of that by running it in parallel.
Solution
This PR is quite similar to how bevy's fixed update works, but eagerly extracts ECS data into a background task, to synchronize it only when we exceed
Time<Fixed> + its accumulated time
.The implementation doesn't use
Time<Fixed>
but a component approachTimeStep
+TaskToRender
.bevy_transform_ interpolation
. I think a component approach is cleaner long term, but definitely error prone right now.background_fixed_schedule
.Fixed
because I think we could have a different implementation involving a FixedUpdate being run in advance of the Virtual time, resulting in a "negative accumulated time".mermaid
Unfortunately mermaid has a few bugs and github doesn't rely on latest mermaid, you can paste that is https://mermaid.live for a better formatting:
Demo
This recording simulates (through sleep) a constant task time of 0.5 second.
We can see that at a too high fixed update rate, the rendering is struggling. but at a lower fixed framerate, the rendering has a steady 60FPS.
Using bevy's synchronous
FixedUpdate
would result in a 0.5 lag at every fixed update. While an extreme example, it illustrates quite well the advantages of this approach.Screencast.from.12-24-2024.03_58_11.PM.webm
This approach also could allow some "time deviation" between virtual time and "task" time, to avoid a "catch back" phase (where the time speed appears to go quicker). The objective is to allow user to customize the "fixed steps" to consume before spawning the task.
How to test
Run the example with
bevy/multi_threaded
to have the same behaviour as the above demo. Add inbevy/trace_tracy
feature and profile with tracy to have a better visualization of what's going on.cargo run --example interpolate_custom_schedule --features bevy/multi_threaded
Background task drawbacks
Transform
outside of expectedFixedUpdate
, so we'd need some special handlingTime<Virtual>
, and it's non trivial to make work with interpolation.