DocsDecisions

ADR-0011: Keep the Mojo archetype ECS (benchmarked against flecs)

Status: accepted, 2026-11-08.

Context #

The brief requires an archetype ECS with typed queries, change detection, command buffers, hierarchy and access-scheduled systems, and asks for a benchmark against flecs through its iterator API: keep the Mojo ECS only if it is within 10% of flecs or faster.

Benchmark #

bench/ecs_bench.mojo (run with pixi run mojo build -I . bench/ecs_bench.mojo, then ecs_bench 100000 100): 100,000 entities with Position and Velocity (8 bytes each), half of them with a third component so both worlds hold two archetypes/tables; 100 frames of position += velocity * dt over a two-component query. flecs 4.2.0 is driven through ecs_query_iter/ecs_query_next/ecs_field_w_size (no callbacks), both sides from the same Mojo process on the same core.

Container on the Windows host (lavapipe machine, Ubuntu 24.04, Mojo 1.2.0), three runs:

Run Mojo ns / entity-iteration flecs ns / entity-iteration ratio
1 0.758 0.829 0.91
2 0.746 0.750 1.00
3 0.728 0.744 0.98

Two changes made the difference from the first measurement (1.13 ns, ratio 1.55):

  1. Column stride: elements were padded to 16-byte blocks, halving cache density for 8-byte components. The stride is now the component's size (a struct's size is a multiple of its alignment), while column bases stay 16-byte aligned for SIMD components.
  2. Typed column access: Chunk.column[T]() returns a tight typed pointer indexed with [unsafe_offset=row], which the compiler vectorises like the flecs loop; byte-offset stepping with a runtime stride did not.

Decision #

Keep the Mojo ECS (cinder/ecs). It is within the 10% band (and slightly faster in every run), it is the only implementation whose queries are typed at compile time with Mojo parameters, and its scheduler integrates directly with the Mojo job system. flecs stays vendored and linked for the benchmark so the comparison can be rerun whenever the ECS changes.

Consequences #

  • bench/ecs_bench.mojo is part of the benchmark suite; a regression above the band reopens this decision.
  • Components are plain Mojo structs with a unique component_id(); heap-owning components are supported through the type-erased move/destroy functions recorded at registration.
  • flecs' features the Mojo ECS does not have (relationships, reflection, the explorer) are not needed by the brief; the debug UI (Phase 7) inspects entities through the Mojo world directly.
View as MarkdownSource: docs/adr/0011-ecs-mojo-vs-flecs.md