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 instanceEngine::get()- Returns singleton pointerstart()- Initializes managers and registers systems from project configupdate()- 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 pointerdestroy_entity(entity_id)- Removes an entity and all its componentsget_entity(entity_id)- Retrieves entity pointer by IDget_entity_by_name(name)- Retrieves entity pointer by nameget_entity<T>()- Retrieves first entity of type Tget_entities<T>()- Returns vector of all entities of type Tget_entity_with_component<T>()- Finds first entity with component type Tfor_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 entityremove_component<T>(entity_id)- Removes a component from an entityget_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 componentget_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 parentadd_system_dependency<Target, Dependency>(target, dependency)- Declares system dependenciesget_system<T>()- Retrieves a system pointerenable_system<T>()/disable_system<T>()- Toggles system executionset_system_update_interval<T>(interval_ms)- Sets update frequencystart()- Initializes all systemsupdate(dt_ms)/fixed_update(fixed_dt_ms)- Updates all enabled systemsupdate_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 Tget_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 combinationsBenefits:
- 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:
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:
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:
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:
// 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:
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:
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:
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:
// 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:
// 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:
// 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/.
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/.
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/.
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/.
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.hppRelated Modules
- 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
- Favor Composition: Build entities by composing components rather than inheritance
- Access Components via Entities: Use
entity->get_component<T>()for cleaner code - Systems for Logic: All behavior logic should be in systems, not components
- Use Manager Singletons: Access managers via
EntityManager::get(),ComponentManager::get(), etc. - Clean Up: Always destroy entities via
entity_manager->destroy_entity(entity_id)when no longer needed - 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:
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:
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:
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
);
}