Generic Entity Component System framework for the Gramarye game engine.
gramarye-ecs provides a flexible, generic ECS framework where games can register their own component types and systems. It's designed to be game-agnostic, allowing different game types (top-down, FPS, etc.) to implement their own components and systems.
- Generic entity management with UUID-based entity IDs
- Dynamic component type registration
- System registration with component requirements
- Query utilities for entity iteration
- Arena-based memory management
- Zero dependencies beyond gramarye-libcore
#include "gramarye_ecs/ecs.h"
#include "arena.h"
Arena_T arena = Arena_new();
ECS* ecs = ECS_new(arena);
// Register component types
ComponentTypeId positionType = ECS_register_component_type(ecs, "Position", sizeof(Position));
ComponentTypeId healthType = ECS_register_component_type(ecs, "Health", sizeof(Health));
// Create entities
EntityId player = ECS_create_entity(ecs);
EntityId enemy = ECS_create_entity(ecs);
// Add components
Position pos = {100, 200};
ECS_add_component(ecs, player, positionType, &pos);
Health hp = {100, 100};
ECS_add_component(ecs, player, healthType, &hp);
// Register systems
SystemId movementSystem = ECS_register_system(ecs, "Movement",
(ComponentTypeId[]){positionType}, 1,
movement_update, NULL);
// Update systems
ECS_update_systems(ecs, deltaTime);Entities are managed via the EntityRegistry:
- UUID-based IDs: Entities use 128-bit UUIDs (high/low 64-bit pairs) for unique identification
- Component Tracking: Each entity tracks its component set as a 64-bit bitmask
- Lifecycle: Entities are created via
Entity_create()and destroyed viaEntity_destroy() - Existence Checking:
Entity_exists()verifies an entity is still valid
Entity IDs are compared and hashed using UUID comparison/hashing functions from gramarye-libcore.
Components are stored per type in separate ComponentStorage structures:
- Type Registration: Components are registered with a name and size via
ECS_register_component_type() - Type IDs: Each component type gets a unique
ComponentTypeId(starting at 1, 0 is invalid) - Storage: Each component type has its own Table mapping EntityId → component data
- Name Lookup: Component types can be looked up by name (O(n) operation - consider adding name→ID mapping if needed)
- Component Sets: Each entity tracks which components it has via a bitmask for fast queries
Important: Component type names must be unique. Duplicate names will return COMPONENT_TYPE_INVALID.
Systems are registered with component requirements and update functions:
- Component Requirements: Systems specify which components entities must have
- Priority: Systems can be registered with priorities for execution order
- Execution Order: Systems are sorted by priority before execution (lower priority = earlier execution)
- Enabled/Disabled: Systems can be enabled or disabled at runtime
- Update Function: Each system provides an update function that receives ECS, deltaTime, and userData
System execution uses insertion sort by priority. Systems with the same priority execute in registration order.
The query system provides utilities for finding entities by component types:
- AND Queries: Find entities with ALL specified components (
ECS_query_entities) - OR Queries: Find entities with ANY specified components (
ECS_query_entities_any) - Exclusion Queries: Find entities WITHOUT specified components (
ECS_query_entities_excluding) - Iteration: Iterate over entities matching component requirements (
ECS_iterate_entities)
Query Behavior:
- AND/OR queries only check entities that have at least one component
- Exclusion queries check ALL entities, including those without components
- Queries use the component bitmask for fast filtering
- Arena-based: All allocations use the provided Arena
- No Manual Cleanup: Tables and arenas are managed externally - cleanup is handled by arena deallocation
- Component Data: Component data is copied when added (not stored by reference)
- gramarye-libcore (for Table, Arena, UUID hashing, etc.)
mkdir build
cd build
cmake ..
makeThis library is designed to be used as a submodule in game projects. Games register their own component types and systems at initialization, making the ECS framework completely generic and reusable.