ECS overview
How Meep's Entity Component System differs from Unity, Bevy, and the rest — and why.
Meep is a Pure ECS engine. The phrase carries weight, so this page unpacks what it means and how it shapes everything else in the engine.
The three pieces
Entities are identifiers. An entity is a 32-bit integer plus a generation counter — nothing more. No methods, no state of its own.
Components are plain data. A Transform is { x, y, z, qx, qy, qz, qw, sx, sy, sz }. A Velocity is { x, y, z }. Components don’t contain logic.
Systems are functions that iterate over entities matching a component shape. A MovementSystem runs over every entity with Transform + Velocity and advances position by velocity each frame.
”Pure” ECS
Many engines call themselves ECS but smuggle in OOP under the hood. Unity’s GameObject + MonoBehaviour is the canonical example — components are objects with methods, lifecycle hooks, and references to other components. The result is faster to prototype but slower to query and harder to optimize.
Meep is strict: one component instance per type per entity, no methods on components, no lifecycle hooks beyond init/destroy. Every system iterates over flat typed arrays whenever possible. Memory layout is predictable. Component queries are O(1) cached.
The trade is real — you write a little more code to wire systems together. In exchange, you get architectural clarity and performance that holds at scale.
The hierarchy problem
Pure ECS engines have historically struggled with scene graphs. Parent-child transforms imply a tree, and trees don’t fit naturally into flat component arrays.
Meep solves this with a dedicated hierarchy system that maintains parent-child links separately from the component arrays. Transforms still iterate as flat arrays for speed; hierarchy queries walk the tree only when needed. You get composable scene graphs without paying for them in the hot path. (Bevy uses a similar approach.)
Tiered access
Meep gives you two levels of API for the same data:
- High level.
transform.set({ x, y, z }),entity.add(component). Comfortable, slightly slower. - Low level. Raw typed array views, manual index math. For tight inner loops where you measured a bottleneck.
You drop down only when you need to. The high-level API is usually fast enough.
Why this matters on the web
JavaScript’s garbage collector punishes per-frame allocation patterns. Every new Vector3() in your update loop is a potential GC pause; on low-spec devices, those pauses turn into frame drops you can’t paper over.
Meep’s zero-allocation discipline isn’t a marketing line — it’s a technical requirement. Components live in pre-allocated pools. Math operations write into out-parameters instead of returning new objects. Particle systems re-use buffers. The engine can run a million entities on a mid-range phone with stable frame timing because nothing is being allocated and garbage-collected each tick.
Where to go next
- Rendering pipeline — how Forward+ clustered lighting fits on top of the ECS.
- FAQ — common questions.
- The source itself:
src/engine/ecs/is well-organized. Start withEntityManager.js.