Determinism
By default, Rapier is locally deterministic, meaning that running the exact same simulation (with the same initial conditions) twice with the same machine, using the same version of Rapier, and the same version of the Rust compiler, will result in the exact same simulation results. However, doing this on two different computers may result in completely different results.
Two simulations run with the same initial conditions if all the simulation structures are initialized with the same values, rigid-bodies/colliders/joints are constructed the same way, and they are added/removed to sets (rigid-body sets, etc.) in the exact same order.
It is possible to make Rapier cross-platform deterministic, meaning that running a simulation with two different computers (including different OS and/or different processors) will result in the exact same results. In order to achieve this, both computers must start the simulation with the same initial conditions as discussed above, and the following additional conditions must be met:
- The
enhanced-determinismfeature of Rapier is enabled. Note that theenhanced-determinismfeature cannot be enabled at the same time as thesimd8feature, which changes the SIMD lane width and is therefore its own determinism domain. Theparallelfeature, on the other hand, can be combined with it: the results of the parallel solver are identical to the results of the sequential one, and don't depend on the number of threads of the rayon pool (nor, therefore, on the number of cores of the machine running the simulation). - The target platforms must strictly comply to the IEEE 754-2008 floating-points standard. This ensures that floating-point computations behave the same on all platforms. This include most modern mainstream processors as well as WASM targets.
- If some of the values used to initialized Rapier structures are computed using floating points functions (sin, cos, tan, etc.) other
than addition/subtraction/multiplication/division, then you need to make sure the functions being used originate from
the
ComplexFieldorRealFieldtraits fromnalgebra(re-exported by Rapier asna). For example, doComplexField::sin(0.4)(whereComplexFieldis imported byuse bevy_rapier3d::na::ComplexField) instead of0.4.sin():
// WRONG version:
// The following will not work cross-platform-deterministically because the values
// given to `Transform::from_xyz` won't be cross-platform deterministic.
commands.spawn((
Transform::from_xyz(1.0f32.sqrt(), 2.0f32.sin(), 3.0f32.cos()),
Collider::ball(0.5),
));
// CORRECT version:
// The following will work cross-platform-deterministically because we use the
// functions from nalgebra.
commands.spawn((
Transform::from_xyz(
ComplexField::sqrt(1.0),
ComplexField::sin(2.0),
ComplexField::cos(3.0),
),
Collider::ball(0.5),
));
In Bevy, a few more conditions must be met:
- The simulation must be advanced by the same timesteps on every machine, which excludes the default
TimestepMode::Variabletimestep mode since it depends on the frame rate. UseTimestepMode::Fixedinstead, ideally with the physics running in theFixedUpdateschedule so the number of timesteps doesn't depend on the frame rate either:
App::new()
.add_plugins(DefaultPlugins)
// Run `FixedUpdate` 60 times per second, and advance the simulation by exactly 1/60
// seconds at each of these updates, whatever the frame rate.
.insert_resource(Time::<Fixed>::from_hz(60.0))
.insert_resource(TimestepMode::Fixed {
dt: 1.0 / 60.0,
substeps: 1,
})
.add_plugins(RapierPhysicsPlugin::<NoUserData>::default().in_fixed_schedule())
- The entities with physics components must be spawned (and modified, and despawned) in the same order, with the same components, since this is what determines the order in which the plugin inserts them into the sets.
- The math functions of Bevy (e.g. the trigonometric functions used by
Quat::from_rotation_y) are not cross-platform deterministic either unless thelibmfeature of Bevy is enabled.