Entity Component System
Traditional game architectures use inheritance hierarchies that scale poorly and abuse the CPU cache. ECS flips the design: instead of objects that have behaviour, we have data tables (components) that functions (systems) transform. The result can be 10–100× faster for large worlds.
1. The OOP Problem
Classic game object hierarchies look like: Entity → Actor → Pawn → Character → FPSCharacter. Problems at scale:
- Diamond inheritance and brittle hierarchies break when you want a "ghost NPC" (solid? visible? AI? sound?)
- Cache misses: Iterating 10,000 entities to run a physics update means following pointers to scattered RAM locations. Modern CPUs stall waiting for cache fills.
- Feature coupling: Adding a new feature requires modifying base classes or adding virtual functions that all subclasses pay for.
OOP approach
- GameObject with all possible fields
- Virtual update() methods per type
- Inheritance for "kinds" of objects
- Pointer-chased per-object allocation
- Feature = subclass or flag
ECS approach
- Entity = just an ID (integer)
- Components = pure data structs
- Systems = functions over component sets
- Dense array storage per component type
- Feature = add/remove component
2. Core Concepts
- Entity: A unique integer ID (64-bit). Has no data. Serves as a key to look up associated components.
- Component: A plain-old-data struct (no methods, no virtual functions). Examples:
Position{x,y,z},Velocity{dx,dy,dz},Health{value, max}. - System: A function that queries for entities with a specific set of components and transforms them. Example: "for all entities with Position and Velocity, update position += velocity * dt."
- World: The container managing entity IDs, component storage, and system scheduling.
3. Archetype Storage
The dominant storage strategy is archetypes: group entities that have exactly the same set of components into contiguous arrays. An archetype is defined by its component set (e.g., {Position, Velocity, Health} → archetype A).
All components for entities in an archetype are stored in dense arrays. System iteration = sequential memory access → excellent cache performance. Adding a component to an entity moves it to a different archetype.
An alternative is sparse sets: per component type, maintain a sparse→dense mapping. Better for very large entity ID spaces or frequent archetype changes. Flecs uses archetypes; bitECS uses sparse sets; both can be cache-friendly.
4. Systems and Queries
A system declares a query: the required, optional, and excluded component types. The ECS engine returns matching entities. Systems can be:
- Sequential: PhysicsSystem, RenderSystem, AISystem run in order per frame.
- Parallel: Systems with non-overlapping component sets can run in parallel (data dependency analysis). This is critical for DOTS/Unity's job system.
- Reactive: Triggered when components are added/removed (useful for init/cleanup logic).
5. Cache Efficiency
Modern CPUs are 10–100× faster than RAM access latency. A cache line is 64 bytes. Random pointer chasing kills performance; sequential array access maximises cache line reuse.
- SoA layout (Struct of Arrays): each component type is a separate array. Iterating only Position[]: reads Position[0..N-1] sequentially — perfect. Ignore Velocity entirely (not touched).
- AoS layout (Array of Structs): each element contains all components. Iterating only Position means touching every component — wasted bandwidth.
Archetype ECS naturally delivers SoA per component, per archetype. A simple physics system iterating 100,000 entities can run in ~1 ms (cache-friendly) vs ~10 ms with pointer-chased OOP objects.
6. Relationships and Tags
Modern ECS frameworks extend beyond flat components:
- Tags: Zero-size components used for filtering.
addComponent(eid, IsEnemy). Querying [Position, IsEnemy] finds only enemy entities. - Relationships: Pairs of (relation, target).
ChildOf(parent),LikedBy(friend). Enables hierarchies, spatial indexing, and semantic graphs without inheritance. - Prefabs: Entity templates. Inherit components from a template entity; override specific fields.
7. Implementations
- Flecs (C/C++): Full-featured, rich query language, relationships, scripting. Used in AAA games. Open source (MIT).
- Unity DOTS / Entities: Archetype-based ECS integrated with Unity's C# job system and Burst compiler. Required for 10,000+ dynamic objects.
- EnTT (C++): Header-only, sparse-set storage, widely used in indie/mid-tier games. Simple API.
- bitECS (JavaScript): TypedArray-backed sparse sets, minimal API. Used in browser games and Three.js projects.
- Bevy ECS (Rust): Archetype-based, compile-time query safety, parallel scheduling built-in. The core of the Bevy game engine.