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

Tracking simulation steps with interpolation for determinism #465

Open
phisn opened this issue Dec 24, 2023 · 1 comment
Open

Tracking simulation steps with interpolation for determinism #465

phisn opened this issue Dec 24, 2023 · 1 comment
Labels
A-Integration very bevy specific bug Something isn't working D-Difficult Needs strong technical background, domain knowledge, or impacts are high, needs testing... P-High arbitrary important item S-not-started Work has not started

Comments

@phisn
Copy link

phisn commented Dec 24, 2023

I am currently building an application where I would like to use interpolation as well as keeping track of inputs to achieve determinism in replays.

In the following code snippet there is no sophisticated way to track the amount of iterations the loop has taken. I looked into the rapier physics pipeline and it does not have a fitting feature either.

while sim_to_render_time.diff > 0.0 {
// NOTE: in this comparison we do the same computations we
// will do for the next `while` iteration test, to make sure we
// don't get bit by potential float inaccuracy.
if sim_to_render_time.diff - dt <= 0.0 {
if let Some(interpolation_query) = interpolation_query.as_mut() {
// This is the last simulation step to be executed in the loop
// Update the previous state transforms
for (handle, mut interpolation) in interpolation_query.iter_mut() {
if let Some(body) = self.bodies.get(handle.0) {
interpolation.start = Some(*body.position());
interpolation.end = None;
}
}
}
}
let mut substep_integration_parameters = self.integration_parameters;
substep_integration_parameters.dt = dt / (substeps as Real) * time_scale;
for _ in 0..substeps {
self.pipeline.step(
&(gravity / self.physics_scale).into(),
&substep_integration_parameters,
&mut self.islands,
&mut self.broad_phase,
&mut self.narrow_phase,
&mut self.bodies,
&mut self.colliders,
&mut self.impulse_joints,
&mut self.multibody_joints,
&mut self.ccd_solver,
None,
hooks,
events,
);
}
sim_to_render_time.diff -= dt;
}

This problem does neither occur in fixed time steps since there the simulation is always only stepped once nor is it a problem in variable time steps since determinism is not a thing there.

A solution would be to either have a counter that is exposed and keeps track of the number of iterations in each schedule cycle or some sort of event / callback that is called whenever a step is taken. Since I don't really see any need for individual callbacks and there also is no additional meaningful information to be delivered, the counter solution does seem more reasonable. The counter could then also be implemented for the other time steps as a constant one for consistency.

A possible implementation could be:

@@ -24,6 +24,8 @@ use rapier::control::CharacterAutostep;
 #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
 #[derive(Resource)]
 pub struct RapierContext {
+    /// Keeps track of physics steps taken in the previous simulation step
+    pub step_counter: i32,
     /// The island manager, which detects what object is sleeping
     /// (not moving much) to reduce computations.
     pub islands: IslandManager,
@@ -235,11 +237,14 @@ impl RapierContext {
                 time_scale,
                 substeps,
             } => {
+                self.step_counter = 0;
                 self.integration_parameters.dt = dt;
 
                 sim_to_render_time.diff += time.delta_seconds();
 
                 while sim_to_render_time.diff > 0.0 {
+                    self.step_counter += 1;
+                    
                     // NOTE: in this comparison we do the same computations we
                     // will do for the next `while` iteration test, to make sure we
                     // don't get bit by potential float inaccuracy.
@@ -285,6 +290,7 @@ impl RapierContext {
                 time_scale,
                 substeps,
             } => {
+                self.step_counter = 1;
                 self.integration_parameters.dt = (time.delta_seconds() * time_scale).min(max_dt);
 
                 let mut substep_integration_parameters = self.integration_parameters;
@@ -309,6 +315,7 @@ impl RapierContext {
                 }
             }
             TimestepMode::Fixed { dt, substeps } => {
+                self.step_counter = 1;
                 self.integration_parameters.dt = dt;
 
                 let mut substep_integration_parameters = self.integration_parameters;
@phisn
Copy link
Author

phisn commented Dec 24, 2023

I continued thinking about this problem and I think the whole interpolation timestep design is flawed if your goal is to achieve determinism. This is because the system sets before and after the simulation steps are not called with each physics step call as well as my own physics.

For now I would result into trying to ignore the existing interpolation implementation, use fixed time steps and implement interpolation myself.

@Vrixyz Vrixyz added bug Something isn't working D-Difficult Needs strong technical background, domain knowledge, or impacts are high, needs testing... P-High arbitrary important item S-not-started Work has not started A-Integration very bevy specific labels May 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Integration very bevy specific bug Something isn't working D-Difficult Needs strong technical background, domain knowledge, or impacts are high, needs testing... P-High arbitrary important item S-not-started Work has not started
Projects
None yet
Development

No branches or pull requests

2 participants