Skip to main content

Serialization

The complete state of the simulation can be serialized by taking a snapshot of the physics world with PhysicsWorld.snapshot. This results in a bytes object that may be saved on the disk, sent through the network, etc. The snapshot can then be restored with the PhysicsWorld.restore static method, which creates a new world in the exact same state as the serialized one:

# Serialize the whole physics world.
serialized = world.snapshot()
# Deserialize it.
deserialized = rp.PhysicsWorld.restore(serialized)
# The simulation can continue using the deserialized world.
deserialized.step()

The world also supports the pickle module of Python, which relies on these snapshots. Therefore, a world can be saved as part of any picklable structure of your application:

# The world can be pickled like any other Python object, e.g., to save it on the disk.
data = pickle.dumps(world)
unpickled = pickle.loads(data)

The snapshot contains everything the world is made of: the rigid-bodies, colliders, joints, and soft-bodies (including their user data), the gravity, the integration parameters, the island manager, the CCD solver, the broad-phase, and the narrow-phase (with its contacts). However, the thread pool configured by PhysicsWorld.set_num_threads, the PhysicsWorld.event_error_policy option, the performance counters, and the objects of your application given to the world (the event handler and the physics hooks) are not part of the snapshot: they must be configured again for the deserialized world. Each object of the deserialized world keeps the index and the generation it had in the serialized world, so the handles of the serialized world can be used with the deserialized world as well:

# The handles of the serialized world refer to the same objects in the deserialized world.
restored_ball = deserialized.rigid_bodies[ball_handle]
print("Ball altitude in the deserialized world:", restored_ball.translation.y)

# The event handler and the physics hooks aren't part of the snapshot: they must be given again.
deserialized.event_handler = rp.ChannelEventCollector()

For debugging purposes, PhysicsWorld.snapshot_json gives a human-readable snapshot as a JSON string instead, which is restored with PhysicsWorld.restore_json. It is much larger and slower than the binary snapshot:

# A human-readable (but much larger and slower) JSON snapshot, e.g., for debugging.
json_snapshot = world.snapshot_json()
from_json = rp.PhysicsWorld.restore_json(json_snapshot)

Most of the other objects of the bindings can be pickled on their own too, e.g., the rigid-bodies, colliders, and joints (as well as their builders), the shapes, the handles, the sets, the integration parameters, or the mass properties. On the other hand, the pipelines and the event collectors can't be pickled:

# Most objects can be pickled on their own too, e.g., the rigid-body and collider builders.
ball_builder = rp.RigidBody.dynamic(translation=(0.0, 10.0, 0.0))
restored_builder = pickle.loads(pickle.dumps(ball_builder))
new_ball = world.add_body(restored_builder, colliders=[rp.Collider.ball(0.5)])
warning

A snapshot is not a stable file format: it should only be restored by the same version of the bindings as the one that serialized it. The snapshots recognized as incompatible (or corrupted) are rejected with a SerializationError. Like any pickled data, only restore snapshots coming from a trusted source.

info

If the bindings are built with the determinism feature, and if your platform fulfills the required determinism requirements, then you have the guarantee that running the exact same simulation on two different machines will result in the exact same snapshot bytes if the world is serialized on both machines after the same number of timesteps.