Skip to content

Physics3D: OOM when stacking scenes (Jolt world not freed on scene pause) #8640

@Bouh

Description

@Bouh

Origin

Reported by a user on the GDevelop Big Game Jam 9 forum:
https://itch.io/jam/gdevelop-big-game-jam-9/topic/6382098/error-in-gdevelop-physics3d

The user got the following crash when switching levels by pausing the current scene and starting a new one (push scene), with Physics3D objects in the scenes:

Aborted(OOM). Build with -sASSERTIONS for more info.
RuntimeError: Aborted(OOM). Build with -sASSERTIONS for more info.
    at ... jolt-physics.wasm ...
    at new JoltInterface ...
    at new Physics3DSharedData ...

They worked around it by using "Change scene" (replace) instead of "Pause and start a new scene".

Root cause

In Extensions/Physics3DBehavior/Physics3DRuntimeBehavior.ts, the Jolt WASM memory held by the shared data is only freed in the scene unloaded callback:

gdjs.registerRuntimeSceneUnloadedCallback(function (runtimeScene) {
  const physics3DSharedData = runtimeScene.physics3DSharedData;
  if (physics3DSharedData) {
    Jolt.destroy(physics3DSharedData.contactListener);
    Jolt.destroy(physics3DSharedData._tempVec3);
    Jolt.destroy(physics3DSharedData._tempRVec3);
    Jolt.destroy(physics3DSharedData._tempQuat);
    Jolt.destroy(physics3DSharedData.jolt);
    runtimeScene.physics3DSharedData = null;
  }
});

There is no registerRuntimeScenePausedCallback for Physics3D. When a scene is paused (push), its JoltInterface (a full physics world, plus all bodies/shapes/listeners) keeps living in the WASM heap. Pushing a second scene with Physics3D allocates a brand new JoltInterface, so each level swap leaks the previous physics world. After 2–3 levels the WASM linear memory is exhausted → Aborted(OOM).

This does not happen with "Change scene" (replace) because that triggers the unloaded callback and the world is freed.

The same root cause likely affects the 2D Physics behavior (Box2D); worth checking it too.

Possible fixes

  1. Free the Jolt world on scene pause and rebuild it on resume. Add registerRuntimeScenePausedCallback mirroring the unloaded cleanup, and on Resumed recreate the shared data + recreate bodies from each behavior. The trade-off is the loss of in-flight physics state (velocities, active contacts, constraints) across a pause — arguably acceptable since the scene was paused.
  2. Share a single JoltInterface across scenes at the RuntimeGame level instead of per-scene. Bodies stay per-scene but the physics world is reused. Needs care if more than one Physics3D scene can ever run simultaneously.
  3. As a minimum, document the limitation and warn in the editor when a project uses "Pause and start a new scene" together with Physics3D.

Option 1 seems the safest and most contained.

Metadata

Metadata

Assignees

No one assigned

    Labels

    💥crashBug that is crashing the software

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions