Skip to content

Engine Module

The Engine module provides the ECS backbone for Astra. It initializes the core managers - EntityManager, ComponentManager, and SceneManager - and handles system lifecycles based on your project configuration.

Purpose

The Engine module:

  • Initializes the engine singleton and manages its lifecycle.
  • Registers and starts systems dynamically from project configuration.
  • Coordinates between the core ECS managers.
  • Provides the integration point for rendering, physics, and scene logic.

Key Components

Engine

The main engine singleton that coordinates initialization and system registration.

Key Methods:

  • Engine::init() - Creates singleton instance
  • Engine::get() - Returns singleton pointer
  • start() - Initializes managers and registers systems from project config
  • update() - Main update loop (currently empty placeholder)
  • end() - Destroys singleton instance

Behavior:

  • Constructor initializes EntityManager, ComponentManager, and SceneManager
  • start() reads active project config and conditionally registers RenderSystem and PhysicsSystem
  • SceneSystem is always registered

EntityManager

Manages the creation, destruction, and querying of entities. Lives in /src/shared/ecs/managers/.

Key Methods:

  • add_entity<T>(name, params...) - Creates a new entity with unique name and returns pointer
  • destroy_entity(entity_id) - Removes an entity and all its components
  • get_entity(entity_id) - Retrieves entity pointer by ID
  • get_entity_by_name(name) - Retrieves entity pointer by name
  • get_entity<T>() - Retrieves first entity of type T
  • get_entities<T>() - Returns vector of all entities of type T
  • get_entity_with_component<T>() - Finds first entity with component type T
  • for_each(fn) - Iterates over all entities or entities of a specific type

ComponentManager

Handles component storage and provides type-safe component access. Lives in /src/shared/ecs/managers/.

Key Methods:

  • add_component<T>(entity_id, params...) - Adds a component to an entity
  • remove_component<T>(entity_id) - Removes a component from an entity
  • get_component<T>(entity_id) - Retrieves a component pointer (returns nullptr if not found)
  • get_or_add_component<T>(entity_id, params...) - Gets existing or creates new component
  • get_components<T>() - Returns vector of all components of type T

SystemManager

Manages the registration, dependency resolution, and execution of systems. Lives in /src/shared/ecs/managers/.

Key Methods:

  • add_system<T>(params...) - Registers a new system (returns existing if already registered)
  • add_subsystem<System, Target>(parent, params...) - Registers a subsystem under a parent
  • add_system_dependency<Target, Dependency>(target, dependency) - Declares system dependencies
  • get_system<T>() - Retrieves a system pointer
  • enable_system<T>() / disable_system<T>() - Toggles system execution
  • set_system_update_interval<T>(interval_ms) - Sets update frequency
  • start() - Initializes all systems
  • update(dt_ms) / fixed_update(fixed_dt_ms) - Updates all enabled systems
  • update_system_work_order() - Recalculates execution order based on dependencies

SceneManager

Manages scenes. Lives in /src/modules/renderer/managers/.

Key Methods:

  • add_scene<T>() - Adds a new scene of type T
  • get_active_scene() - Returns the first scene (or nullptr if none exist)

Architecture

Entity Component System (ECS)

Astra uses a data-oriented ECS architecture that separates data (components) from behavior (systems):

Entity (ID only)
    ├── Component A (data)
    ├── Component B (data)
    └── Component C (data)

Systems (behavior)
    └── Operate on entities with specific component combinations

Benefits:

  • Better cache locality and performance
  • Flexible composition over inheritance
  • Easy to add new behaviors without modifying existing code
  • Parallelization-friendly architecture

Entity Representation

Entities inherit from IEntity abstract base class:

cpp
namespace astralix {

class IEntity {
public:
    EntityID get_entity_id() const;
    virtual EntityTypeID get_entity_type_id() const = 0;

    bool is_active() const;
    void set_active(bool active);

    // Component access through entity
    template<typename T> T* get_component() const;
    template<typename T> T* add_component(Args&&... params);
    template<typename T> bool has_component();
    template<typename T> void remove_component();
    std::vector<IComponent*> get_components() const;

    virtual void on_enable() = 0;
    virtual void on_disable() = 0;

    std::string name;

protected:
    EntityID m_entity_id;
    EntityFamilyID m_family_id;
    bool m_active;
};

}

Component Storage

Components are stored in hash maps indexed by ComponentID and ComponentTypeID:

cpp
namespace astralix {

class ComponentManager {
private:
    // Primary storage: component ID -> component instance
    std::unordered_map<ComponentID, Scope<IComponent>> m_component_table;

    // Type index: component type ID -> list of component IDs of that type
    std::unordered_map<ComponentTypeID, std::vector<ComponentID>> m_type_component_table;

    // Entity index: entity ID -> {component type ID -> component ID}
    std::unordered_map<EntityID, std::unordered_map<ComponentTypeID, ComponentID>>
        m_entity_component_table;
};

}

System Architecture

Systems implement the logic that operates on entities and components:

cpp
namespace astralix {

class ISystem {
public:
    virtual SystemTypeID get_system_type_id() const = 0;
    virtual const char* get_system_type_name() const = 0;

    virtual void start() = 0;
    virtual void fixed_update(double fixed_dt) = 0;
    virtual void pre_update(double dt) = 0;
    virtual void update(double dt) = 0;

    bool is_active();
    void set_active(bool is_active);

protected:
    explicit ISystem(SystemPriority priority = NORMAL_SYSTEM_PRIORITY,
                     double update_interval_ms = -1.0);

private:
    SystemPriority m_priority;
    double m_updater_internal;
    bool m_enabled;
    bool m_is_active;
};

}

Key Features

Component-Based Design

Add functionality to entities by composing components:

cpp
// Create a player entity
auto player = EntityManager::get()->add_entity<Entity>("Player");

// Add components to define behavior
player->add_component<TransformComponent>(
    glm::vec3(0.0f, 0.0f, 0.0f),  // position
    glm::vec3(1.0f, 1.0f, 1.0f),  // scale
    glm::quat(1.0f, 0.0f, 0.0f, 0.0f)  // rotation
);

// Mesh component doesn't store mesh/material in constructor
auto mesh_comp = player->add_component<MeshComponent>();
mesh_comp->attach_mesh(playerMesh);

player->add_component<RigidBodyComponent>(
    RigidType::Dynamic,  // rigid_type
    0.5f,  // gravity
    2.0f,  // velocity
    2.0f,  // acceleration
    0.0f,  // drag
    75.0f  // mass
);

System Registration

Register systems to process entities each frame:

cpp
auto system_manager = SystemManager::get();

// Systems are registered by Engine::start() based on project config
// SceneSystem is always registered
system_manager->add_system<SceneSystem>();

// Conditionally registered from project descriptor
system_manager->add_system<RenderSystem>(render_config);
system_manager->add_system<PhysicsSystem>(physics_config);

// Custom game systems can be added after engine start
system_manager->add_system<PlayerControllerSystem>();
system_manager->add_system<AIBehaviorSystem>();

// Systems can have dependencies
system_manager->add_system_dependency(render_system, scene_system);

Entity Queries

Query entities and components separately:

cpp
auto entity_manager = EntityManager::get();
auto component_manager = ComponentManager::get();

// Get all entities (returns vector of IEntity pointers)
auto all_entities = entity_manager->get_entities();

// Filter entities with specific components
for (auto entity : all_entities) {
    if (entity->has_component<TransformComponent>() &&
        entity->has_component<MeshComponent>()) {

        auto transform = entity->get_component<TransformComponent>();
        auto mesh = entity->get_component<MeshComponent>();

        // Render entity
        RenderMesh(mesh, transform);
    }
}

// Or query components directly
auto transforms = component_manager->get_components<TransformComponent>();

Scene Management

Manage scenes:

cpp
auto scene_manager = SceneManager::get();

// Add a new scene
scene_manager->add_scene<Scene>();

// Get the active scene
auto scene = scene_manager->get_active_scene();

// Scene serialization is handled by SceneSerializer
// (attached automatically when scene is added)

Integration with Other Modules

With Application Module

The Engine is initialized and started by the Application module:

cpp
// Application initializes engine singleton
Engine::init();

// Start registers systems based on project config
Engine::get()->start();

// Engine::update() is currently a no-op placeholder
// Actual update loop is driven by SystemManager
auto system_manager = SystemManager::get();
system_manager->update(dt_ms);

With Physics Module

Physics components are managed through the ECS:

cpp
// PhysicsSystem is registered by Engine::start() from project config
// It operates on entities with RigidBodyComponent and MeshCollisionComponent
class PhysicsSystem : public System<PhysicsSystem> {
    void update(double dt) override {
        auto component_manager = ComponentManager::get();
        auto rigidbodies = component_manager->get_components<RigidBodyComponent>();

        for (auto rb : rigidbodies) {
            // Update physics simulation using PhysX
        }
    }
};

With Renderer Module

Rendering uses component queries to find drawable entities:

cpp
// RenderSystem is registered by Engine::start() from project config
// It operates on entities with TransformComponent and MeshComponent
class RenderSystem : public System<RenderSystem> {
    void update(double dt) override {
        auto component_manager = ComponentManager::get();
        auto meshes = component_manager->get_components<MeshComponent>();
        auto transforms = component_manager->get_components<TransformComponent>();

        // Match components by entity ID and submit to render queue
    }
};

Common Components

TransformComponent

Position, rotation, and scale. Lives in /src/modules/renderer/components/transform/.

cpp
namespace astralix {

class TransformComponent : public Component<TransformComponent> {
public:
    glm::vec3 position;
    glm::vec3 scale;
    glm::quat rotation;
    glm::mat4 matrix;
    bool m_dirty;

    void set_position(glm::vec3 new_pos);
    void set_scale(glm::vec3 p_scale);
    void translate(glm::vec3 p_position);
    void rotate(glm::vec3 axis, float degrees);
    void rotate(glm::quat rotation);
    glm::vec3 forward();
    void recalculate_transform();
};

}

MeshComponent

Visual mesh representation. Lives in /src/modules/renderer/components/mesh/.

cpp
namespace astralix {

class MeshComponent : public Component<MeshComponent> {
public:
    std::vector<Mesh> get_meshes();
    void attach_mesh(Mesh mesh);
    void attach_meshes(std::vector<Mesh> meshes);
    void change_draw_type(RendererAPI::DrawPrimitive primitive_type);
    void set_draw_type(VertexBuffer::DrawType draw_type);

private:
    std::vector<Mesh> m_meshes;
    VertexBuffer::DrawType m_draw_type;
};

}

RigidBodyComponent

Physics properties. Lives in /src/modules/physics/components/rigidbody/.

cpp
namespace astralix {

enum RigidType { Static = 0, Dynamic = 1 };

class RigidBodyComponent : public Component<RigidBodyComponent> {
public:
    RigidBodyComponent(COMPONENT_INIT_PARAMS,
        RigidType rigid_type = RigidType::Dynamic,
        float gravity = 0.5f, float velocity = 2.0f,
        float acceleration = 2.0f, float drag = 0.0f,
        float mass = 1.0f);

private:
    float velocity;     // Scalar, not vector
    float gravity;
    float drag;
    float mass;
    float acceleration;
    RigidType m_rigid_type;
};

}

CameraComponent

Camera settings. Lives in /src/modules/renderer/components/camera/.

cpp
namespace astralix {

class CameraComponent : public Component<CameraComponent> {
public:
    glm::vec3 up;
    glm::vec3 front;
    glm::vec3 rotation;
    glm::vec3 direction;

    glm::mat4 get_view_matrix();
    glm::mat4 get_projection_matrix();

    void use_perspective();
    void use_orthographic();
    void recalculate_projection_matrix(Framebuffer* target_framebuffer);
    void recalculate_view_matrix();

private:
    glm::mat4 m_view_matrix;
    glm::mat4 m_projection_matrix;
    bool m_is_orthographic;
};

}

File Locations

/src/modules/engine/
├── engine.hpp              # Main engine singleton
├── engine.cpp              # Engine implementation
└── CMakeLists.txt          # Links to ecs, window, physics, renderer, streams

/src/shared/ecs/            # ECS architecture
├── managers/
│   ├── entity-manager.hpp  # Entity creation and queries
│   ├── component-manager.hpp  # Component storage
│   └── system-manager.hpp  # System registration and execution
├── entities/
│   ├── ientity.hpp         # Entity base class
│   ├── entity.hpp          # Concrete entity implementation
│   └── events/
│       └── entity-events.hpp  # EntityCreatedEvent, etc.
├── components/
│   ├── icomponent.hpp      # Component base class
│   └── component.hpp       # Component CRTP template
├── systems/
│   ├── isystem.hpp         # System base class
│   └── system.hpp          # System CRTP template
└── guid.hpp                # ID type definitions

/src/modules/renderer/components/  # Visual components
├── transform/
│   └── transform-component.hpp
├── mesh/
│   └── mesh-component.hpp
├── camera/
│   └── camera-component.hpp
├── light/
│   └── light-component.hpp
└── material/
    └── material-component.hpp

/src/modules/renderer/managers/
└── scene-manager.hpp       # Scene management

/src/modules/physics/components/  # Physics components
├── rigidbody/
│   └── rigidbody-component.hpp
└── mesh-collision/
    └── mesh-collision-component.hpp
  • Application - Initializes and updates the Engine
  • Physics - Uses ECS for physics components
  • Editor - Provides UI for entity manipulation
  • Streams - Used for scene serialization

Best Practices

  1. Favor Composition: Build entities by composing components rather than inheritance
  2. Access Components via Entities: Use entity->get_component<T>() for cleaner code
  3. Systems for Logic: All behavior logic should be in systems, not components
  4. Use Manager Singletons: Access managers via EntityManager::get(), ComponentManager::get(), etc.
  5. Clean Up: Always destroy entities via entity_manager->destroy_entity(entity_id) when no longer needed
  6. Check for nullptr: Component getters return nullptr if component doesn't exist

Performance Considerations

  • Components are stored in hash maps (not contiguous arrays)
  • System execution order is determined by dependency graph
  • SystemManager supports update intervals to reduce per-frame overhead
  • Entity name uniqueness is enforced with suffix numbering (may be slow for many entities)
  • Component lookups use nested hash maps (entity ID -> component type ID -> component ID)

Common Patterns

Creating Complex Entities

Use factory functions to build entities:

cpp
Entity* CreatePlayer(glm::vec3 position) {
    auto entity_manager = EntityManager::get();
    auto player = entity_manager->add_entity<Entity>("Player");

    player->add_component<TransformComponent>(
        position,
        glm::vec3(1.0f),  // scale
        glm::quat(1.0f, 0.0f, 0.0f, 0.0f)  // rotation
    );

    auto mesh_comp = player->add_component<MeshComponent>();
    mesh_comp->attach_mesh(playerMesh);

    player->add_component<RigidBodyComponent>(
        RigidType::Dynamic,
        0.5f,  // gravity
        2.0f,  // velocity
        2.0f,  // acceleration
        0.0f,  // drag
        75.0f  // mass
    );

    player->add_component<PlayerControllerComponent>();

    return player;
}

Custom Systems

Implement custom game logic:

cpp
class EnemyAISystem : public System<EnemyAISystem> {
public:
    EnemyAISystem() : System(NORMAL_SYSTEM_PRIORITY) {}

    void start() override {
        // Initialize system
    }

    void update(double dt) override {
        auto entity_manager = EntityManager::get();
        auto entities = entity_manager->get_entities();

        for (auto entity : entities) {
            if (entity->has_component<EnemyComponent>() &&
                entity->has_component<TransformComponent>()) {
                UpdateEnemyBehavior(entity, dt);
            }
        }
    }

    void fixed_update(double fixed_dt) override {}
    void pre_update(double dt) override {}
};

Component Dependencies

Handle components that depend on each other:

cpp
void AddRigidBody(Entity* entity) {
    // Ensure entity has required components
    if (!entity->has_component<TransformComponent>()) {
        entity->add_component<TransformComponent>();
    }

    // Now add the dependent component
    entity->add_component<RigidBodyComponent>(
        RigidType::Dynamic,
        0.5f,  // gravity
        2.0f,  // velocity
        2.0f,  // acceleration
        0.0f,  // drag
        1.0f   // mass
    );
}