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

Use FixedPostUpdate by default and simplify scheduling #457

Merged
merged 7 commits into from
Aug 11, 2024
Merged

Conversation

Jondolf
Copy link
Owner

@Jondolf Jondolf commented Jul 19, 2024

Objective

Closes #263 (the removal detection issue should also already be fixed thanks to hooks and observers)

So far, Avian has run in PostUpdate by default, but with a custom fixed timestep solution. This has given us more control over scheduling and allowed working around some historical issues with Bevy's own FixedUpdate, but nowadays Bevy's fixed timestep schedules are much more usable.

Having two different fixed timesteps is confusing, annoying to maintain, and duplicates a lot of API surface area. The scheduling in Avian 0.1 also has other serious issues:

  • Physics slows down at lower frame rates, for example when the window is not focused.
  • Physics can be clearly indeterministic when running in PostUpdate, even with the built-in fixed timestep.

For a native experience, Avian should be using Bevy's own fixed timestep and scheduling.

Solution

Physics now runs in FixedPostUpdate by default. TimestepMode has been removed, and Time<Physics> no longer has a custom timestep. Instead, it follows the clock of the schedule where physics is run in. In FixedPostUpdate, physics uses Time<Fixed>, but if physics is instead configured to run in a schedule like PostUpdate, it will use Time<Virtual>.

Previously, the physics timestep could be configured like this:

app.insert_resource(Time::new_with(Physics::fixed_hz(60.0)));

In schedules with a fixed timestep, you even needed to use fixed_once_hz, which was rather confusing and footgunny:

app.insert_resource(Time::new_with(Physics::fixed_once_hz(60.0)));

Now, if you are running physics in FixedPostUpdate, you should simply configure Time<Fixed> directly:

app.insert_resource(Time::<Fixed>::from_hz(60.0)));

Time<Physics> still exists to allow people to configure the simulation speed, pause and unpause the simulation independently of the schedule's default clock, and set up their own custom scheduling for physics.

Running physics with Bevy's fixed timestep has also fixed the other issues mentioned earlier: physics no longer runs slower at lower frame rates, and behavior seems a lot more deterministic (at least without the parallel feature). More testing is required to determine if we have full cross-platform determinism though.

Why FixedPostUpdate instead of FixedUpdate?

FixedUpdate is very often used for gameplay logic and various kinds of simulations. It is also commonly used for applying physics logic, like character movement, explosions, moving platforms, effects that apply forces/impulses, custom gravity, and so on.

For a lot of these use cases, it is important to run logic before physics, and if physics was run in FixedUpdate, systems would need to be ordered explicitly, which would not be a good experience. And if you didn't do that, you could get determinism issues caused by system ordering ambiguities, along with frame delay issues.

And as for FixedPreUpdate: if we ran physics before gameplay logic in FixedUpdate, movement and anything else that affects physics could have an additional delay of one or more frames.

I believe that using FixedPostUpdate is the sensible default, and it is also in line with engines like Unity and Godot, where internal physics is run near the end of the fixed update/process step. People can also always configure the ordering in their own applications if needed.

Caveats

Bevy's fixed timestep is 64 Hz by default, unlike our old default of 60 Hz. This can lead to noticeable jitter on 60 Hz displays, as physics is sometimes run twice within a single frame. This can be partially worked around by configuring Time<Fixed>, but I am also implementing transform interpolation and extrapolation, which should make it possible to fix the issue properly by smoothing out the visual result.

Another change to the old scheduling is that physics no longer runs during the first frame. This is because Bevy's FixedUpdate and other fixed timestep schedules don't seem to run until the second update.


Migration Guide

Previously, physics was run in PostUpdate with a custom fixed timestep by default. The primary purpose of the fixed timestep is to make behavior consistent and frame rate independent.

This custom scheduling logic has been removed, and physics now runs in Bevy's FixedFixedUpdate by default. This further unifies physics with Bevy's own APIs and simplifies scheduling. However, it also means that physics now runs before Update, unlike before.

For most users, no changes should be necessary, and systems that were running in Update can remain there. If you want to run systems at the same fixed timestep as physics, consider using FixedUpdate.

The Time<Physics> clock now automatically follows the clock used by the schedule that physics is run in. In FixedPostUpdate and other schedules with a fixed timestep, Time<Fixed> is used, but if physics is instead configured to run in a schedule like PostUpdate, it will use Time<Virtual>.

Previously, the physics timestep could be configured like this:

app.insert_resource(Time::new_with(Physics::fixed_hz(60.0)));

Now, if you are running physics in FixedPostUpdate, you should simply configure Time<Fixed> directly:

app.insert_resource(Time::<Fixed>::from_hz(60.0)));

The following types and methods have also been removed as a part of this rework:

  • TimestepMode
  • Physics::from_timestep
  • Physics::fixed_hz
  • Physics::fixed_once_hz
  • Physics::variable
  • Time::<Physics>::from_timestep
  • Time::<Physics>::timestep_mode
  • Time::<Physics>::timestep_mode_mut
  • Time::<Physics>::set_timestep_mode

@Jondolf Jondolf added C-Enhancement New feature or request A-Scheduling Relates to scheduling or system sets C-Breaking-Change This change removes or changes behavior or APIs, requiring users to adapt A-Determinism Relates to consistent behavior across runs or platforms X-Contentious There are nontrivial implications that should be thought through labels Jul 19, 2024
@soeklgb
Copy link

soeklgb commented Jul 22, 2024

Now, if you are running physics in FixedUpdate, you should simply configure Time<Fixed> directly:

app.insert_resource(Time::from_hz(60.0)));

I think <Fixed> is missing.

app.insert_resource(Time::<Fixed>::from_hz(60.0)));

@Jondolf
Copy link
Owner Author

Jondolf commented Jul 22, 2024

That's optional since it's inferred, but I added it to the description for clarity.

@atornity
Copy link

That's optional since it's inferred

Wouldn't this insert the resource Time<()> since T = () by default?

@Jondolf
Copy link
Owner Author

Jondolf commented Jul 22, 2024

No, from_hz is a method on Time<Fixed>, it doesn't exist on Time<()>. But Rust can infer that Time::from_hz is referring to Time::<Fixed>::from_hz if no other methods with the same name are in scope for Time.

If you did have multiple different clocks with from_hz in scope though, I believe you would indeed need to disambiguate it as Time::<Fixed>::from_hz or otherwise the compiler would complain.

@atornity
Copy link

No, from_hz is a method on Time<Fixed>, it doesn't exist on Time<()>. But Rust can infer that Time::from_hz is referring to Time::<Fixed>::from_hz if no other methods with the same name are in scope for Time.

ooo okay. I had assumed from_hz existed on other Time<T> variants as well.

@janhohenheim
Copy link
Contributor

If you haven't merged this by then, I'll review it after the jam :)

@Aceeri
Copy link
Contributor

Aceeri commented Jul 24, 2024

Why FixedUpdate instead of FixedPostUpdate for physics?

@Jondolf Jondolf changed the title Use FixedUpdate by default and simplify scheduling Use FixedPostUpdate by default and simplify scheduling Jul 24, 2024
@Jondolf
Copy link
Owner Author

Jondolf commented Jul 24, 2024

Why FixedUpdate instead of FixedPostUpdate for physics?

I responded on Discord already, but we discussed this briefly a few days ago, and came to the conclusion that FixedPostUpdate is probably the more sensible default like you're suggesting. I have now updated the PR and its description accordingly.

src/lib.rs Outdated Show resolved Hide resolved
Co-authored-by: Jan Hohenheim <jan@hohenheim.ch>
@Jondolf Jondolf enabled auto-merge (squash) August 11, 2024 17:00
@Jondolf Jondolf merged commit 4d082a7 into main Aug 11, 2024
4 checks passed
@Jondolf Jondolf deleted the fixed-update branch August 11, 2024 17:26
@datael datael mentioned this pull request Sep 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Determinism Relates to consistent behavior across runs or platforms A-Scheduling Relates to scheduling or system sets C-Breaking-Change This change removes or changes behavior or APIs, requiring users to adapt C-Enhancement New feature or request X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for use within Bevy's FixedUpdate schedule?
5 participants