Serialization
When the serde-serialize feature of bevy_rapier is enabled, most data structures of Rapier become serializable using serde.
Serialization can be useful to save the complete state of the simulation, e.g., to send it over the network, or to restore
this state later.
The complete state of the simulation is serialized by serializing the components of its
physics context: RapierContextSimulation, RapierContextColliders,
RapierRigidBodySet, and RapierContextJoints all implement the Serialize and Deserialize traits of serde. The
RapierContext structure given by the ReadRapierContext system parameter implements Serialize as well, so the
four of them can be serialized at once. The RapierConfiguration component of the context implements these traits
too, so it can be saved with them, as in the following example. Restoring a snapshot is done by inserting the
deserialized components on the entity of the physics context, replacing the previous ones:
/// The components holding the complete state of the simulation of a physics context, and its
/// configuration.
type ContextComponents = (
RapierContextSimulation,
RapierContextColliders,
RapierRigidBodySet,
RapierContextJoints,
RapierConfiguration,
);
/// Serializes the default physics context when S is pressed.
fn save_snapshot(
mut commands: Commands,
keys: Res<ButtonInput<KeyCode>>,
context: Query<
(
&RapierContextSimulation,
&RapierContextColliders,
&RapierRigidBodySet,
&RapierContextJoints,
&RapierConfiguration,
),
With<DefaultRapierContext>,
>,
) {
if keys.just_pressed(KeyCode::KeyS) {
let components = context.single().unwrap();
let serialized =
bincode::serde::encode_to_vec(components, bincode::config::standard()).unwrap();
commands.insert_resource(Snapshot(serialized));
}
}
/// Restores the snapshot when R is pressed.
fn restore_snapshot(
mut commands: Commands,
keys: Res<ButtonInput<KeyCode>>,
snapshot: Option<Res<Snapshot>>,
context: Query<Entity, With<DefaultRapierContext>>,
) {
if let (true, Some(snapshot)) = (keys.just_pressed(KeyCode::KeyR), snapshot) {
// The maps from entities to handles are rebuilt automatically by the deserialization.
let (components, _): (ContextComponents, usize) =
bincode::serde::decode_from_slice(&snapshot.0, bincode::config::standard()).unwrap();
// Replace the components of the physics context by the deserialized ones.
commands.entity(context.single().unwrap()).insert(components);
}
}
Some parts of the physics context are skipped by the serialization: the PhysicsPipeline and the CollisionPipeline
(which don't hold any useful state), the event handler installed with RapierContextSimulation::set_event_handler,
the events not sent yet, and the step statistics. The maps from entities to handles aren't serialized either: they
are rebuilt automatically when the components are deserialized, from the user-data of the Rapier objects (which
contain the bits of their entity). If you restore the sets of a physics context in another way, call
RapierContextColliders::rebuild_entity_maps, RapierRigidBodySet::rebuild_entity_maps, and
RapierContextJoints::rebuild_entity_maps to rebuild them.
The Rapier objects refer to their entities by their identifier. Therefore a deserialized physics context is only
meaningful in a world where the same entities exist with the same identifiers (e.g. when restoring a snapshot of the
same application). Note as well that the components of your entities (RigidBody, Collider, Transform, etc.),
and the TimestepMode resource, are not part of the serialized components: they must be saved separately (the
Collider component and the TimestepMode resource implement the traits of serde as well, whereas most of the other
components can be serialized through the reflection of Bevy).
If the enhanced-determinism feature of Rapier is enabled, and if your platform fulfills the required determinism
requirements, then you have the guarantee that running the exact same simulation on two different machine
will result in the exact same byte vectors if the physics state is serialized on both machines after the same
number of timesteps.