Skip to content

feat(world): add lock-free reservation and SpawnBatch - #11

Merged
Stephanvs merged 1 commit into
mainfrom
feat/step5-lock-free-reservation
Oct 29, 2026
Merged

Stephanvs merged 1 commit into
mainfrom
feat/step5-lock-free-reservation

Conversation

@Stephanvs

Copy link
Copy Markdown
Owner

Why

Every spawn and despawn took a lock on the entity directory, even on the idle owner thread, and there was no way to spawn many entities at once. This is ROADMAP step 5. The owner thread now reserves and releases slots without a lock. Systems that call Commands.Spawn claim a slot with one atomic decrement. A new World.SpawnBatch spawns a whole span of bundles in one call.

Scope

  • EntityAllocator owns the free list and a signed claim cursor. EntityTable keeps generations, states, locations, and the next fresh index.
  • EntityTable.ReserveConcurrent handles command-buffer claims. EntityTable.Flush records pending claims before playback, before discard, and when a run exits.
  • World.SpawnBatch<TBundle>(ReadOnlySpan<TBundle>, Span<Entity>) reserves every handle, grows the table once, and uses one change tick. If any bundle fails to write, it truncates the rows, releases every reservation, and clears the handles. A failed batch consumes no tick.
  • ROADMAP.md adds amendments after step 5. Step 8's scope and gate now include SpawnBatch, because batch spawn still stages each bundle.

Tradeoffs

The design keeps a flush step, unlike the Bevy 0.18 precedent. A claim only moves a cursor and never writes to the directory, so directory pages grow only on the owner thread.

Blast Radius

This changes entity reservation for every spawn path, and adds one public API. Merging releases v0.5.0, because the release workflow publishes every push to main and feat is a minor bump.

Verification

  • scripts/verify.ps1 passed: 164 tests, Archeoids headless, package contents, JIT and NativeAOT consumers, the NativeAOT sample, and the performance smoke check.
  • At 100,000 entities, SpawnBatch took 3,737 µs against 5,905 µs for individual spawns. Spawn went from 6,361 to 5,905 µs, and despawn from 5,986 to 4,934 µs. Full tables are in benchmarks/step5-lock-free-reservation.md.
  • A rerun of EntityPopulationBenchmarks after the review fixes showed no regression from the simplified batch reservation (SpawnBatch at 100,000 entities: 3,635 → 3,577 µs).

🤖 Generated with Claude Code

A failed SpawnBatch no longer consumes a change tick, which matches
Spawn<TBundle>. EntityTable.Reserve for a span now reuses the single-entity
reservation after growing the directory once.

ROADMAP step 8 now names SpawnBatch in its scope and gate, because step 5
still stages each bundle. Step 5's Bevy precedent now states that Archevolt
keeps a flush for command-buffer claims.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://feat-step5-lock-free-reservation-archevolt.hayman.workers.dev (commit 8a9dd22)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://8800c59a-archevolt.hayman.workers.dev 8a9dd22 2026-10-29T20:58:53.971Z Visit the dashboard ↗

@Stephanvs
Stephanvs merged commit 620f478 into main Oct 29, 2026
2 checks passed
@Stephanvs
Stephanvs deleted the feat/step5-lock-free-reservation branch October 29, 2026 21:21
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