Motivation
For rollback netcode, takeSnapshot() is called every simulated frame — the state has to be captured before anyone knows whether a correction will arrive. That makes its size and cost the dominant fixed per-frame charge, and it is currently serializing far more than a rollback needs.
Measured with @dimforge/rapier2d-deterministic-compat 0.20.0 on a contact-rich scene:
| bodies |
takeSnapshot() |
bytes/body |
time |
| 50 |
50.2 KB |
~1.0 KB |
0.035 ms |
| 200 |
219.9 KB |
~1.1 KB |
0.079 ms |
| 800 |
988.7 KB |
~1.2 KB |
0.404 ms |
At 800 bodies that is ~1 MB serialized every frame, and it dominates allocation churn (~1.07 MB/tick, ~96% of it snapshot copies — around 64 MB/s of GC traffic at 60 Hz).
The dynamic state a rollback actually needs is small: position, rotation, linear and angular velocity, and sleep state — roughly 64 bytes/body, or ~19× less than the current whole-world encoding. Colliders, shapes, mass properties and the broad-phase structure are unchanged between two frames of the same world and are re-serialized every time.
The obstacle, as I understand it
Restoring only positions and velocities is not sound today, and I want to be explicit that I understand why rather than asking for something unsafe. The TGS solver warm-starts from cached contact impulses; a world restored without them re-simulates along a slightly different trajectory than the original pass, which in a lockstep/rollback setting is a divergence between the peer that rolled back and the peer that did not. That is presumably exactly why the snapshot is whole-world.
So the request is really one of:
- A dynamic-state snapshot that includes the warm-start cache — positions, velocities, sleep state, and the contact impulse cache — but omits the static topology (shapes, colliders, mass properties, broad-phase tree) that a caller can guarantee is unchanged. Presumably with a validation handle so a mismatched restore fails loudly rather than corrupting.
- Or a documented way to disable warm starting, so callers who accept the solver-quality cost can use a positions-and-velocities snapshot safely.
IntegrationParameters currently exposes numSolverIterations, lengthUnit and predictionDistance through the TS bindings, but nothing for warm starting, so this cannot be traded off from JS today.
- Or a snapshot API that writes into a caller-provided buffer, which would at least remove the per-frame allocation even if the size stayed the same.
Any of the three would help; (1) is the one that would make rollback cheap.
Prior art
Jolt exposes SaveState/RestoreState with a StateRecorderFilter so applications can omit state they know is unchanged — static or inactive bodies in particular — explicitly for network rollback. Something in that spirit for Rapier would fit rollback netcode very well.
I appreciate that whole-world snapshots are the conservative and obviously-correct design; this is a request for an opt-in fast path where the caller can uphold the extra invariants, not a change to the default. Happy to help test or benchmark.
Motivation
For rollback netcode,
takeSnapshot()is called every simulated frame — the state has to be captured before anyone knows whether a correction will arrive. That makes its size and cost the dominant fixed per-frame charge, and it is currently serializing far more than a rollback needs.Measured with
@dimforge/rapier2d-deterministic-compat0.20.0 on a contact-rich scene:takeSnapshot()At 800 bodies that is ~1 MB serialized every frame, and it dominates allocation churn (~1.07 MB/tick, ~96% of it snapshot copies — around 64 MB/s of GC traffic at 60 Hz).
The dynamic state a rollback actually needs is small: position, rotation, linear and angular velocity, and sleep state — roughly 64 bytes/body, or ~19× less than the current whole-world encoding. Colliders, shapes, mass properties and the broad-phase structure are unchanged between two frames of the same world and are re-serialized every time.
The obstacle, as I understand it
Restoring only positions and velocities is not sound today, and I want to be explicit that I understand why rather than asking for something unsafe. The TGS solver warm-starts from cached contact impulses; a world restored without them re-simulates along a slightly different trajectory than the original pass, which in a lockstep/rollback setting is a divergence between the peer that rolled back and the peer that did not. That is presumably exactly why the snapshot is whole-world.
So the request is really one of:
IntegrationParameterscurrently exposesnumSolverIterations,lengthUnitandpredictionDistancethrough the TS bindings, but nothing for warm starting, so this cannot be traded off from JS today.Any of the three would help; (1) is the one that would make rollback cheap.
Prior art
Jolt exposes
SaveState/RestoreStatewith aStateRecorderFilterso applications can omit state they know is unchanged — static or inactive bodies in particular — explicitly for network rollback. Something in that spirit for Rapier would fit rollback netcode very well.I appreciate that whole-world snapshots are the conservative and obviously-correct design; this is a request for an opt-in fast path where the caller can uphold the extra invariants, not a change to the default. Happy to help test or benchmark.