Skip to content

fix: Freeze the length unit across isolates - #128

Merged
spydon merged 1 commit into
mainfrom
fix/length-unit-lock-across-isolates
Oct 19, 2026
Merged

spydon merged 1 commit into
mainfrom
fix/length-unit-lock-across-isolates

Conversation

@spydon

@spydon spydon commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Description

Box2D's length unit is process-wide, and initializeForge2D refuses to change it once a World exists, since every world was built for the old unit. The flag that tracked whether a world exists was a Dart static, though, and each isolate has its own copy of Dart statics. So isolate B could still change the length unit under isolate A's live worlds, which is exactly the silent corruption the StateError is meant to prevent. #127 documents that several isolates can each run worlds, which makes this easier to hit.

This PR:

  • moves the flag behind the backend. RawBox2D.setLengthUnitsPerMeter becomes trySetLengthUnitsPerMeter, which refuses the change once a world has been created, and a new resetLengthUnitLock backs debugResetLengthUnitLock;
  • on the native backend, keeps the flag in native/ffi/f2d_world_lock.c. Creating a world sets it, and setting the length unit checks it, both under the world lock from fix: Create and destroy worlds under a process-wide lock #127, so the check and the change cannot race with another isolate creating a world;
  • on the WebAssembly backend, keeps the flag in the backend instance. It runs on one thread and each module instance has its own length unit, so no lock is needed;
  • removes markWorldCreated from World, since the backends now track this themselves;
  • adds a test where one isolate holds a world and another tries to change the length unit, and makes length_unit_test.dart reset the flag first, since earlier suites in the same process have now created worlds.

No public API changes. Code that changes the length unit from another isolate after a world exists now gets the StateError instead of silently corrupting the simulation.

Testing (macOS arm64): the new test fails without the fix. With it, the full suite (117 tests) passes on the VM, the length unit, world and tolerance tests pass on the WebAssembly backend in Chrome, and dart analyze --fatal-infos and dart format are clean.

Checklist

  • The title of my PR starts with a Conventional Commit prefix (fix:, feat:, docs: etc).
  • I have read the Contributor Guide and followed the process outlined for submitting PRs.
  • I have updated/added tests for ALL new/updated/fixed functionality.
  • I have updated/added relevant documentation in docs and added dartdoc comments with ///.
  • I have updated/added relevant examples in examples.

Breaking Change

  • Yes, this is a breaking change.
  • No, this is not a breaking change.

Related Issues

Follow-up to #127.

The flag that freezes the length unit once a world exists was a Dart static, so each isolate had its own copy while the length unit itself is process-wide. The native backend now keeps the flag next to the world lock and checks it under that lock, and the WebAssembly backend keeps it in the backend instance.
@spydon
spydon force-pushed the fix/length-unit-lock-across-isolates branch from f355538 to 3037def Compare October 19, 2026 08:17
@spydon
spydon merged commit b853bae into main Oct 19, 2026
8 checks passed
@spydon
spydon deleted the fix/length-unit-lock-across-isolates branch October 19, 2026 09:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant