Skip to content

Independent worlds stepped at once: worker slots of their own per world - #106

Merged
nicolas-maman merged 1 commit into
mainfrom
feat/concurrent-worlds
Oct 4, 2026
Merged

nicolas-maman merged 1 commit into
mainfrom
feat/concurrent-worlds

Conversation

@nicolas-maman

Copy link
Copy Markdown
Collaborator

Closes #104.

Until now a world's step ran as worker 0 on whatever thread called it, with its tasks as workers 1..n. Two worlds stepped at once on different threads therefore shared every module's per-worker scratch, and also parallel_for's task records, which were module globals. The new test crashes on main for that reason. Box3D supports stepping independent worlds in parallel. This change:

  • Worker slots per world. A world claims a run of worker slots of its own when it's created (a 64-bit mask over the MAX_WORKERS slots) and releases them when destroyed. If none are free, it starts at slot 0, shared, as before.
  • Entering a world. step, the collide pass and the solve make the calling thread the world's first slot, then give the thread its index back. Tasks run as the slots following their caller's. task_context subtracts the world's base; a serial caller outside a pass is treated as worker 0.
  • Scheduler-owned task records. parallel_for's task records belong to the scheduler, not module globals. enqueue and finish restore the caller's index instead of setting it to 0.
  • Bullet flags. The bullet pass reads another bullet's flags atomically and sets its own with atomic ORs. This was Concurrent stepping of independent worlds #104's second item: harmless in practice, but formally a data race in C.

Unchanged: worlds are still created and destroyed on one thread at a time, as in upstream, and queries run as the calling thread's slot.

test_determinism's test_concurrent_worlds steps two two-worker pyramids at once, one on a host thread and one on the main thread. Each ends bit-identical to the same world stepped alone. It passed 10 of 10 runs; main crashes every time.

All 28 test programs pass locally on aether 0.772.0, with no warnings.

A world's steps ran as worker 0 on whatever thread called them, with its
tasks as workers 1..n, so two worlds stepped at once on different threads
shared every module's per-worker scratch and parallel_for's task records
(module globals) -- a crash, as the new test shows on main. Box3D supports
stepping independent worlds in parallel. Now (#104):

- A world claims a run of worker slots of its own when made (a 64-bit
  mask, MAX_WORKERS slots) and gives it back when destroyed; with none
  free it starts at slot 0, shared, as before.
- step, the collide pass and the solve enter the world (the thread becomes
  the world's first slot) and give the thread its index back after; tasks
  run as the slots on from their caller's; task_context subtracts the
  world's base (a serial caller outside a pass is worker 0).
- parallel_for's task records are the scheduler's own, not globals; enqueue
  and finish give the caller its index back rather than setting 0.
- The bullet pass reads another bullet's flags atomically and sets its own
  with atomic ors (the other part of #104: harmless in practice, but a
  data race in C).

Worlds are still made and destroyed on one thread at a time, as the
reference's are; queries run as the calling thread's slot.

test_determinism's test_concurrent_worlds: two two-worker pyramids stepped
at once, one on a host thread and one on the main thread, each end where the
same world stepped alone does, bit for bit (main crashes).
@nicolas-maman
nicolas-maman merged commit 5ba23f2 into main Oct 4, 2026
1 check passed
@nicolas-maman
nicolas-maman deleted the feat/concurrent-worlds branch October 6, 2026 23:09
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.

Concurrent stepping of independent worlds

1 participant