Skip to main content

ECS Architecture

The Entity-Component-System (ECS) architecture in helios separates data (Components) from behavior (Systems), enabling flexible and cache-friendly game object composition.

Overview

┌─────────────────────────────────────────────────────────────┐
│ GameWorld │
│ ┌──────────────────┐ ┌────────────────────────────┐ │
│ │ EntityRegistry │◄───│ EntityManager │ │
│ │ (handle alloc) │ │ (component storage) │ │
│ └──────────────────┘ └────────────────────────────┘ │
│ │ ▲ │
│ ▼ │ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ EntityHandle │◄──────────│ GameObject │ │
│ │ (id+version) │ │ (facade) │ │
│ └──────────────┘ └──────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ View │ │
│ │ (queries) │ │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘

┌───────────┴───────────┐
▼ ▼
┌───────────────────┐ ┌───────────────────────────────────┐
│ Systems │ │ Component Reflection │
│ ┌──────────────┐ │ │ ┌─────────────┐ ┌─────────────┐ │
│ │ System │ │ │ │ComponentOps │ │ Traits │ │
│ │ (Updatable) │ │ │ │ Registry │ │ (concepts) │ │
│ └──────────────┘ │ │ └─────────────┘ └─────────────┘ │
└───────────────────┘ └───────────────────────────────────┘

Module Structure

The helios.engine.ecs module exports the following classes:

ClassPurpose
Component StructureRequired structure for components (Copy/Move)
GameObjectHigh-level entity facade (~16 bytes, pass-by-value)
EntityHandleVersioned entity reference (8 bytes)
EntityRegistryHandle allocation & validation
EntityManagerComponent storage via SparseSets
ViewComponent-based entity queries
SystemGame logic processor base class
UpdatableInterface for per-frame updates
SparseSetO(1) component storage
TraitsCompile-time lifecycle hook detection
ComponentOpsFunction pointers for lifecycle callbacks
ComponentOpsRegistryGlobal registry for ComponentOps
ComponentReflectorType registration helper

Quick Start

// 1. Get a GameObject from the world
auto player = gameWorld.addGameObject();

// 2. Add components
player.add<TransformComponent>(position);
player.add<HealthComponent>(100.0f);
player.add<VelocityComponent>();

// 3. Query entities in systems
for (auto [entity, transform, velocity, active] : gameWorld.view<
TransformComponent,
VelocityComponent,
Active
>().whereEnabled()) {
transform->position += velocity->direction * deltaTime;
}

Data Flow

  1. Entity Creation: EntityRegistry allocates versioned handle
  2. Component Attachment: EntityManager stores in type-indexed SparseSet
  3. Queries: View iterates entities with matching components
  4. Updates: System processes entities each frame
  5. Destruction: Handle version incremented, index recycled

Key Features

Versioned Handles

Detect stale references to destroyed entities:

auto handle = registry.create();
registry.destroy(handle);
registry.isValid(handle); // false

Type-Indexed Storage

O(1) component access without hash lookups:

auto* health = entity.get<HealthComponent>();  // Direct array access

Cache-Friendly Iteration

Contiguous memory layout in SparseSet:

for (auto [e, t, v, a] : view<Transform, Velocity, Active>()) {
// Components stored contiguously
}

Compile-Time Lifecycle Detection

Traits detect hooks without runtime overhead:

// Detected at compile time via concepts
if constexpr (traits::HasOnAcquire<T>) {
component->onAcquire();
}

Documentation

Entity Management

Querying & Processing

  • View - Efficient component queries
  • System - Writing game logic
  • Updatable - Per-frame update interface

Storage & Reflection

See Also