Skip to main content

Component Lifecycle Management

Understanding how components are created, modified, and destroyed is essential for working with Olympe Engine's ECS architecture.

Entity Creation

Basic Entity Creation

// Create empty entity
EntityID entity = World::Get().CreateEntity();

// Entity ID is a unique identifier (unsigned int)
std::cout << "Created entity: " << entity << std::endl;

Entity with Components

// Create entity and immediately add components
EntityID player = World::Get().CreateEntity();

Identity_data identity;
identity.name = "Player";
identity.entityType = EntityType::Player;
World::Get().AddComponent<Identity_data>(player, identity);

Position_data position;
position.position = Vector(100, 200, 0);
World::Get().AddComponent<Position_data>(player, position);

Movement_data movement;
movement.maxSpeed = 150.0f;
World::Get().AddComponent<Movement_data>(player, movement);

Using Prefabs

The preferred method for creating complex entities:

// Load from prefab JSON
EntityID enemy = PrefabFactory::CreateFromFile(
"Blueprints/Enemies/Orc.json"
);

// Prefab automatically adds all configured components

Component Addition

Adding Components

// Create component instance
Health_data health;
health.maxHealth = 100.0f;
health.currentHealth = 100.0f;

// Add to entity
World::Get().AddComponent<Health_data>(entity, health);

Adding Multiple Components

// Add several components at once
Sprite_data sprite;
sprite.texturePath = "Resources/Textures/Character.png";
World::Get().AddComponent<Sprite_data>(entity, sprite);

Animation_data animation;
animation.totalFrames = 8;
World::Get().AddComponent<Animation_data>(entity, animation);

Collision_data collision;
collision.solid = true;
World::Get().AddComponent<Collision_data>(entity, collision);

Component Addition Rules

  1. One instance per type - Each entity can have only one instance of each component type
  2. Duplicate prevention - Adding the same component type twice will replace the first
  3. No dependencies - Components are independent; add in any order
// This replaces the first Health component
Health_data health1;
health1.maxHealth = 100.0f;
World::Get().AddComponent<Health_data>(entity, health1);

Health_data health2;
health2.maxHealth = 200.0f;
World::Get().AddComponent<Health_data>(entity, health2); // Replaces health1

Component Access

Getting Components

// Get pointer to component (returns nullptr if not found)
Health_data* health = World::Get().GetComponent<Health_data>(entity);

if (health) {
std::cout << "Health: " << health->currentHealth << std::endl;

// Modify component
health->currentHealth -= 10.0f;
} else {
std::cout << "Entity has no Health component" << std::endl;
}

Checking for Components

// Check if entity has specific component
if (World::Get().HasComponent<Movement_data>(entity)) {
// Entity has movement
auto* movement = World::Get().GetComponent<Movement_data>(entity);
movement->velocity.x += 100.0f;
}

Getting Multiple Components

// Get several components for one entity
auto* position = World::Get().GetComponent<Position_data>(entity);
auto* movement = World::Get().GetComponent<Movement_data>(entity);
auto* sprite = World::Get().GetComponent<Sprite_data>(entity);

// Check all exist before using
if (position && movement && sprite) {
// Process entity with all three components
}

Component Modification

Direct Modification

// Get and modify
auto* health = World::Get().GetComponent<Health_data>(entity);
if (health) {
health->currentHealth -= damageAmount;

if (health->currentHealth <= 0.0f) {
health->currentHealth = 0.0f;
// Handle death
}
}

Set Component (Replace)

// Completely replace component
Health_data newHealth;
newHealth.maxHealth = 200.0f;
newHealth.currentHealth = 200.0f;
newHealth.regeneration = 1.0f;

World::Get().SetComponent<Health_data>(entity, newHealth);
// Old health data is replaced

Conditional Modification

// Modify only if component exists
if (auto* movement = World::Get().GetComponent<Movement_data>(entity)) {
// Boost speed temporarily
movement->maxSpeed *= 1.5f;
}

Component Removal

Removing Components

// Remove specific component
World::Get().RemoveComponent<Movement_data>(entity);

// Entity still exists, just without Movement component

Removing Multiple Components

// Remove several components
World::Get().RemoveComponent<Health_data>(entity);
World::Get().RemoveComponent<Collision_data>(entity);
World::Get().RemoveComponent<AIBehavior_data>(entity);

Conditional Removal

// Remove component if it exists
if (World::Get().HasComponent<TriggerZone_data>(entity)) {
World::Get().RemoveComponent<TriggerZone_data>(entity);
}

Entity Destruction

Destroying Entities

// Mark entity for destruction
World::Get().DestroyEntity(entity);

// Entity and ALL its components will be removed
// Destruction happens at end of frame

Deferred Destruction

Entities are not destroyed immediately to prevent issues during system iteration:

void DamageSystem::Process() {
auto entities = World::Get().GetEntitiesWithComponents<Health_data>();

for (EntityID entity : entities) {
auto* health = World::Get().GetComponent<Health_data>(entity);

if (health->currentHealth <= 0.0f) {
// Mark for destruction
World::Get().DestroyEntity(entity);
// Entity still exists during this frame
// Will be removed at frame end
}
}
// Safe to continue iteration
}

Cleanup

// At end of frame, World removes all marked entities
void World::ProcessDestroyQueue() {
for (EntityID entity : m_destroyQueue) {
// Remove all components
RemoveAllComponents(entity);

// Free entity ID for reuse
FreeEntity(entity);
}
m_destroyQueue.clear();
}

Component Queries

Single Component Query

// Get all entities with Health component
auto entities = World::Get().GetEntitiesWithComponent<Health_data>();

for (EntityID entity : entities) {
auto* health = World::Get().GetComponent<Health_data>(entity);
// Process health
}

Multiple Component Query

// Get entities with BOTH Position AND Sprite
auto entities = World::Get().GetEntitiesWithComponents<
Position_data,
Sprite_data
>();

for (EntityID entity : entities) {
auto* pos = World::Get().GetComponent<Position_data>(entity);
auto* sprite = World::Get().GetComponent<Sprite_data>(entity);

// Both components guaranteed to exist
RenderSprite(sprite, pos->position);
}

Complex Queries

// Get entities with Position, Movement, and Health
auto movingEntities = World::Get().GetEntitiesWithComponents<
Position_data,
Movement_data,
Health_data
>();

// Filter further in code
for (EntityID entity : movingEntities) {
auto* health = World::Get().GetComponent<Health_data>(entity);

if (health->currentHealth > 0.0f) {
// Process only living, moving entities
}
}

Component Lifecycle Hooks

Creation Hooks

Components can have initialization logic:

struct CustomComponent_data {
int value = 0;

// Called when component is added
void OnCreate(EntityID owner) {
value = CalculateInitialValue(owner);
LogDebug("Created custom component for entity " + owner);
}
};

Destruction Hooks

Clean up resources when component is removed:

struct ResourceComponent_data {
SDL_Texture* texture = nullptr;

// Called before component is removed
void OnDestroy() {
if (texture) {
SDL_DestroyTexture(texture);
texture = nullptr;
}
}
};

Best Practices

Entity Initialization Pattern

EntityID CreateEnemy(const Vector& position) {
// Create entity
EntityID enemy = World::Get().CreateEntity();

// Add all components together
Identity_data id;
id.name = "Enemy";
id.entityType = EntityType::Enemy;
World::Get().AddComponent<Identity_data>(enemy, id);

Position_data pos;
pos.position = position;
World::Get().AddComponent<Position_data>(enemy, pos);

Health_data health;
health.maxHealth = 100.0f;
health.currentHealth = 100.0f;
World::Get().AddComponent<Health_data>(enemy, health);

return enemy;
}

Component Validation

Always check component existence:

// Bad: May crash if component doesn't exist
auto* health = World::Get().GetComponent<Health_data>(entity);
health->currentHealth -= 10.0f; // UNSAFE!

// Good: Check before use
auto* health = World::Get().GetComponent<Health_data>(entity);
if (health) {
health->currentHealth -= 10.0f; // SAFE
}

Efficient Queries

Cache query results when possible:

class MySystem {
static std::vector<EntityID> s_cachedEntities;

public:
static void Process(float deltaTime) {
// Update cache only when needed
static bool needsUpdate = true;
if (needsUpdate) {
auto entities = World::Get().GetEntitiesWithComponents<
Position_data, Movement_data
>();
s_cachedEntities.assign(entities.begin(), entities.end());
needsUpdate = false;
}

// Use cached entities
for (EntityID entity : s_cachedEntities) {
// Process...
}
}
};

Safe Iteration

Don't modify entity structure during iteration:

// Bad: Modifying during iteration
auto entities = World::Get().GetEntitiesWithComponent<Health_data>();
for (EntityID entity : entities) {
// DON'T DO THIS: may invalidate iterator
World::Get().RemoveComponent<Health_data>(entity);
}

// Good: Collect first, modify after
std::vector<EntityID> toRemove;
auto entities = World::Get().GetEntitiesWithComponent<Health_data>();
for (EntityID entity : entities) {
if (ShouldRemove(entity)) {
toRemove.push_back(entity);
}
}

// Now safe to modify
for (EntityID entity : toRemove) {
World::Get().RemoveComponent<Health_data>(entity);
}

Memory Management

Component Storage

Components are stored in contiguous arrays for cache efficiency:

// Internally, World manages component arrays
template<typename T>
class ComponentArray {
std::vector<T> m_components; // Dense array
std::vector<EntityID> m_entities; // Corresponding entities
std::unordered_map<EntityID, size_t> m_entityToIndex; // Fast lookup
};

Memory Allocation

  • Components are allocated when added
  • Memory is reused when entities are destroyed
  • Arrays grow as needed (std::vector reallocation)

Performance Considerations

  • Adding components - O(1) amortized
  • Removing components - O(1) with swap-and-pop
  • Getting components - O(1) hash lookup
  • Querying entities - O(n) where n = entities with component

Debugging

Component Inspector

void PrintEntityComponents(EntityID entity) {
std::cout << "Entity " << entity << " components:\n";

if (World::Get().HasComponent<Identity_data>(entity)) {
auto* id = World::Get().GetComponent<Identity_data>(entity);
std::cout << " - Identity: " << id->name << "\n";
}

if (World::Get().HasComponent<Position_data>(entity)) {
auto* pos = World::Get().GetComponent<Position_data>(entity);
std::cout << " - Position: (" << pos->position.x
<< ", " << pos->position.y << ")\n";
}

if (World::Get().HasComponent<Health_data>(entity)) {
auto* hp = World::Get().GetComponent<Health_data>(entity);
std::cout << " - Health: " << hp->currentHealth
<< "/" << hp->maxHealth << "\n";
}
}

Lifecycle Logging

#define DEBUG_COMPONENT_LIFECYCLE 1

#ifdef DEBUG_COMPONENT_LIFECYCLE
#define LOG_COMPONENT(msg) std::cout << "[Component] " << msg << std::endl
#else
#define LOG_COMPONENT(msg)
#endif

// Usage
World::Get().AddComponent<Health_data>(entity, health);
LOG_COMPONENT("Added Health to entity " + std::to_string(entity));

See Also