![]() |
Flecs v3.2
A fast entity component system (ECS) for C & C++
|
This document provides a quick overview of the different features and concepts in Flecs with short examples. This is a good resource if you're just getting started or just want to get a better idea of what kind of features are available in Flecs!
To use Flecs, copy the flecs.c and flecs.h files from the repository root to your project's source folder. When building, make sure your build system is setup to do the following:
gcc
/clang
instead of g++
/clang++
.-lWs2_32
to the end(!) of the linker command. The socket API is used for connecting to Flecs explorer.gcc
/clang
, add -std=gnu99
to the compiler command. This ensures that addons that rely on time & socket functions are compiled correctly.-std=c++0x
(C++11) or higher.To build Flecs as a dynamic library, remove this line from the top of the flecs.h file:
When compiling flecs.c, make sure to define flecs_EXPORTS
, for example by adding -Dflecs_EXPORTS
to the compiler command.
Alternatively Flecs can also be built as a dynamic library with the cmake, meson, bazel or bake build files provided in the repository. These use the files from src
to build as opposed to the amalgamated files, which is better suited for development.
Locate flecs
on your system (either by cloning or as a submodule) and use add_subdirectory
or use FetchContent
to download the source code from the master branch of the flecs repository. After that, add the following to your CMakeLists.txt
file:
Download or git clone
the flecs repository and run bake
from inside the directory. After that, add the following to your project.json
file's value property:
First make sure you have bake installed (see the bake repository for instructions).
Run the following commands to run all tests (use -j
to specify the number of threads):
To run tests with asan enabled, add --cfg sanitize
to the command:
First make sure to clone bake.
Run the following commands to run all the tests:
bash -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_RUNTIME_METHODS=cwrap -s MODULARIZE=1 -s EXPORT_NAME="my_app"
c #define FLECS_CUSTOM_BUILD // Don't build all addons #define FLECS_SYSTEM // Build FLECS_SYSTEM
c #define FLECS_NO_LOG
c ecs_world_t *world = ecs_init();
// Do the ECS stuff
ecs_fini(world);
cpp flecs::world world;
// Do the ECS stuff
c ecs_entity_t e = ecs_new_id(world); ecs_is_alive(world, e); // true!
ecs_delete(world, e); ecs_is_alive(world, e); // false!
cpp auto e = world.entity(); e.is_alive(); // true!
e.destruct(); e.is_alive(); // false!
c ecs_entity_t e = ecs_entity(world, { .name = "Bob" });
printf("Entity name: %s\n", ecs_get_name(world, e));
cpp auto e = world.entity("Bob");
std::cout << "Entity name: " << e.name() << std::endl;
c ecs_entity_t e = ecs_lookup(world, "Bob");
cpp auto e = world.lookup("Bob");
c ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity);
ecs_entity_t e = ecs_new_id(world);
// Add a component. This creates the component in the ECS storage, but does not // assign it with a value. ecs_add(world, e, Velocity);
// Set the value for the Position & Velocity components. A component will be // added if the entity doesn't have it yet. ecs_set(world, e, Position, {10, 20}); ecs_set(world, e, Velocity, {1, 2});
// Get a component const Position *p = ecs_get(world, e, Position);
// Remove component ecs_remove(world, e, Position);
cpp auto e = world.entity();
// Add a component. This creates the component in the ECS storage, but does not // assign it with a value. e.add<Velocity>();
// Set the value for the Position & Velocity components. A component will be // added if the entity doesn't have it yet. e.set<Position>({10, 20}) .set<Velocity>({1, 2});
// Get a component const Position *p = e.get<Position>();
// Remove component e.remove<Position>();
c ECS_COMPONENT(world, Position);
ecs_entity_t pos_e = ecs_id(Position); printf("Name: %s\n", ecs_get_name(world, pos_e)); // outputs 'Name: Position'
// It's possible to add components like you would for any entity ecs_add(world, pos_e, Serializable);
cpp flecs::entity pos_e = world.entity<Position>(); std::cout << "Name: " << pos_e.name() << std::endl; // outputs 'Name: Position'
// It's possible to add components like you would for any entity pos_e.add<Serializable>();
c ECS_COMPONENT(world, Position);
ecs_entity_t pos_e = ecs_id(Position);
const EcsComponent *c = ecs_get(world, pos_e, EcsComponent); printf("Component size: %u\n", c->size);
cpp flecs::entity pos_e = world.entity<Position>();
const EcsComponent *c = pos_e.get<flecs::Component>(); std::cout << "Component size: " << c->size << std::endl;
c // Create Enemy tag ecs_entity_t Enemy = ecs_new_id(world);
// Create entity, add Enemy tag ecs_entity_t e = ecs_new_id(world);
ecs_add_id(world, e, Enemy); ecs_has_id(world, e, Enemy); // true!
ecs_remove_id(world, e, Enemy); ecs_has_id(world, e, Enemy); // false!
cpp // Option 1: create Tag as empty struct struct Enemy { };
// Create entity, add Enemy tag auto e = world.entity().add<Enemy>(); e.has<Enemy>(); // true!
e.remove<Enemy>(); e.has<Enemy>(); // false!
// Option 2: create Tag as entity auto Enemy = world.entity();
// Create entity, add Enemy tag auto e = world.entity().add(Enemy); e.has(Enemy); // true!
e.remove(Enemy); e.has(Enemy); // false!
c // Create Likes relationship ecs_entity_t Likes = ecs_new_id(world);
// Create a small graph with two entities that like each other ecs_entity_t Bob = ecs_new_id(world); ecs_entity_t Alice = ecs_new_id(world);
ecs_add_pair(world, Bob, Likes, Alice); // Bob likes Alice ecs_add_pair(world, Alice, Likes, Bob); // Alice likes Bob ecs_has_pair(world, Bob, Likes, Alice); // true!
ecs_remove_pair(world, Bob, Likes, Alice); ecs_has_pair(world, Bob, Likes, Alice); // false!
cpp // Create Likes relationship as empty type (tag) struct Likes { };
// Create a small graph with two entities that like each other auto Bob = world.entity(); auto Alice = world.entity();
Bob.add<Likes>(Alice); // Bob likes Alice Alice.add<Likes>(Bob); // Alice likes Bob Bob.has<Likes>(Alice); // true!
Bob.remove<Likes>(Alice); Bob.has<Likes>(Alice); // false!
c ecs_id_t id = ecs_pair(Likes, Bob);
cpp flecs::id id = world.pair<Likes>(Bob);
c if (ecs_id_is_pair(id)) { ecs_entity_t relationship = ecs_pair_first(world, id); ecs_entity_t target = ecs_pair_second(world, id); }
cpp if (id.is_pair()) { auto relationship = id.first(); auto target = id.second(); }
c ecs_add_pair(world, Bob, Eats, Apples); ecs_add_pair(world, Bob, Eats, Pears); ecs_add_pair(world, Bob, Grows, Pears);
ecs_has_pair(world, Bob, Eats, Apples); // true! ecs_has_pair(world, Bob, Eats, Pears); // true! ecs_has_pair(world, Bob, Grows, Pears); // true!
cpp Bob.add(Eats, Apples); Bob.add(Eats, Pears); Bob.add(Grows, Pears);
Bob.has(Eats, Apples); // true! Bob.has(Eats, Pears); // true! Bob.has(Grows, Pears); // true!
c ecs_entity_t o = ecs_get_target(world, Alice, Likes, 0); // Returns Bob
cpp auto o = Alice.target<Likes>(); // Returns Bob
c ecs_entity_t parent = ecs_new_id(world);
// ecs_new_w_pair is the same as ecs_new_id + ecs_add_pair ecs_entity_t child = ecs_new_w_pair(world, EcsChildOf, parent);
// Deleting the parent also deletes its children ecs_delete(world, parent);
cpp auto parent = world.entity(); auto child = world.entity().child_of(parent);
// Deleting the parent also deletes its children parent.destruct();
c ecs_entity_t parent = ecs_entity(world, { .name = "parent" });
ecs_entity_t child = ecs_entity(world, { .name = "child" });
ecs_add_pair(world, child, EcsChildOf, parent);
char *path = ecs_get_fullpath(world, child); printf("%s\n", path); // output: 'parent.child' ecs_os_free(path);
ecs_lookup_path(world, 0, "parent.child"); // returns child ecs_lookup_path(world, parent, "child"); // returns child
cpp auto parent = world.entity("parent"); auto child = world.entity("child").child_of(parent); std::cout << child.path() << std::endl; // output: 'parent::child'
world.lookup("parent::child"); // returns child parent.lookup("child"); // returns child
c ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ .filter.terms = { { ecs_id(Position) }, { ecs_id(Position), .src = { .flags = EcsCascade, // Breadth-first order .trav = EcsChildOf // Use ChildOf relationship for traversal }} } });
ecs_iter_t it = ecs_query_iter(world, q); while (ecs_query_next(&it)) { Position *p = ecs_field(&it, Position, 1); Position *p_parent = ecs_field(&it, Position, 2); for (int i = 0; i < it.count; i++) { // Do the thing } }
cpp auto q = world.query_builder<Position, Position>() .term_at(2).parent().cascade() .build();
q.each([](Position& p, Position& p_parent) { // Do the thing });
c // Shortcut to create entity & set a component ecs_entity_t base = ecs_set(world, 0, Triangle, {{0, 0}, {1, 1}, {-1, -1}});
// Create entity that shares components with base ecs_entity_t e = ecs_new_w_pair(world, EcsIsA, base); const Triangle *t = ecs_get(world, e, Triangle); // gets Triangle from base
cpp auto base = world.entity().set<Triangle>({{0, 0}, {1, 1}, {-1, -1}});
// Create entity that shares components with base auto e = world.entity().is_a(base); const Triangle *t = e.get<Triangle>(); // gets Triangle from base
c // Add private instance of Triangle to e, copy value from base ecs_add(world, e, Triangle);
cpp // Add private instance of Triangle to e, copy value from base e.add<Triangle>();
c ECS_COMPONENT(world, Position); ECS_COMPONENT(world, Velocity);
ecs_entity_t e = ecs_new_id(world); ecs_add(world, e, Position); ecs_add(world, e, Velocity);
const ecs_type_t *type = ecs_get_type(world, e); char *type_str = ecs_type_str(world, type); printf("Type: %s\n", type_str); // output: 'Position,Velocity' ecs_os_free(type_str);
cpp auto e = ecs.entity() .add<Position>() .add<Velocity>();
std::cout << e.type().str() << std::endl; // output: 'Position,Velocity'
c const ecs_type_t *type = ecs_get_type(world, e); for (int i = 0; i < type->count; i++) { if (type->array[i] == ecs_id(Position)) { // Found Position component! } }
cpp e.each([&](flecs::id id) { if (id == world.id<Position>()) { // Found Position component! } });
c // Set singleton component ecs_singleton_set(world, Gravity, { 9.81 });
// Get singleton component const Gravity *g = ecs_singleton_get(world, Gravity);
cpp // Set singleton component world.set<Gravity>({ 9.81 });
// Get singleton component const Gravity *g = world.get<Gravity>();
c ecs_set(world, ecs_id(Gravity), Gravity, {10, 20});
const Gravity *g = ecs_get(world, ecs_id(Gravity), Gravity);
cpp flecs::entity grav_e = world.entity<Gravity>();
grav_e.set<Gravity>({10, 20});
const Gravity *g = grav_e.get<Gravity>();
c // Create query that matches Gravity as singleton ecs_query_t *q = ecs_query(ecs, { .filter.terms = { // Regular component { .id = ecs_id(Velocity) }, // A singleton is a component matched on itself { .id = ecs_id(Gravity), .src.id = ecs_id(Gravity) } } });
// Create a system using the query DSL with a singleton: ECS_SYSTEM(world, ApplyGravity, EcsOnUpdate, Velocity, Gravity($));
cpp world.query_builder<Velocity, Gravity>() .term_at(2).singleton() .build();
c // Initialize a filter with 2 terms on the stack ecs_filter_t *f = ecs_filter_init(world, &(ecs_filter_desc_t){ .terms = { { ecs_id(Position) }, { ecs_pair(EcsChildOf, parent) } } });
// Iterate the filter results. Because entities are grouped by their type there // are two loops: an outer loop for the type, and an inner loop for the entities // for that type. ecs_iter_t it = ecs_filter_iter(world, f); while (ecs_filter_next(&it)) { // Each type has its own set of component arrays Position *p = ecs_field(&it, Position, 1);
// Iterate all entities for the type for (int i = 0; i < it.count; i++) { printf("%s: {%f, %f}\n", ecs_get_name(world, it.entities[i]), p[i].x, p[i].y); } }
ecs_filter_fini(f);
cpp // For simple queries the each function can be used world.each([](Position& p, Velocity& v) { // flecs::entity argument is optional p.x += v.x; p.y += v.y; });
// More complex filters can first be created, then iterated auto f = world.filter_builder<Position>() .term(flecs::ChildOf, parent) .build();
// Option 1: each() function that iterates each entity f.each([](flecs::entity e, Position& p) { std::cout << e.name() << ": {" << p.x << ", " << p.y << "}" << std::endl; });
// Option 2: iter() function that iterates each archetype f.iter([](flecs::iter& it, Position *p) { for (int i : it) { std::cout << it.entity(i).name() << ": {" << p[i].x << ", " << p[i].y << "}" << std::endl; } });
c ecs_filter_t *f = ecs_filter_init(world, &(ecs_filter_desc_t){ .terms = { { ecs_pair(EcsChildOf, EcsWildcard) } { ecs_id(Position), .oper = EcsNot }, } });
// Iteration code is the same
cpp auto f = world.filter_builder<>() .term(flecs::ChildOf, flecs::Wildcard) .term<Position>().oper(flecs::Not) .build();
// Iteration code is the same
c // Create a query with 2 terms ecs_query_t *q = ecs_query_init(world, &(ecs_query_desc_t){ .filter.terms = { { ecs_id(Position) }, { ecs_pair(EcsChildOf, EcsWildcard) } } });
ecs_iter_t it = ecs_query_iter(world, q); while (ecs_query_next(&it)) { // Same as for filters }
cpp // Create a query with two terms auto q = world.query_builder<Position>() .term(flecs::ChildOf, flecs::Wildcard) .build();
// Iteration is the same as filters
c // Option 1, use the ECS_SYSTEM convenience macro ECS_SYSTEM(world, Move, 0, Position, Velocity); ecs_run(world, Move, delta_time, NULL); // Run system
// Option 2, use the ecs_system_init function/ecs_system macro ecs_entity_t move_sys = ecs_system(world, { .query.filter.terms = { {ecs_id(Position)}, {ecs_id(Velocity)}, }, .callback = Move });
ecs_run(world, move_sys, delta_time, NULL); // Run system
// The callback code (same for both options) void Move(ecs_iter_t *it) { Position *p = ecs_field(it, Position, 1); Velocity *v = ecs_field(it, Velocity, 2);
for (int i = 0; i < it->count; i++) { p[i].x += v[i].x * it->delta_time; p[i].y += v[i].y * it->delta_time; } }
cpp // Use each() function that iterates each individual entity auto move_sys = world.system<Position, Velocity>() .iter([](flecs::iter it, Position *p, Velocity *v) { for (int i : it) { p[i].x += v[i].x * it.delta_time(); p[i].y += v[i].y * it.delta_time(); } });
// Just like with filters & queries, systems have both the iter() and // each() methods to iterate entities.
move_sys.run();
c printf("System: %s\n", ecs_get_name(world, move_sys)); ecs_add(world, move_sys, EcsOnUpdate); ecs_delete(world, move_sys);
cpp std::cout << "System: " << move_sys.name() << std::endl; move_sys.add(flecs::OnUpdate); move_sys.destruct();
c EcsOnLoad EcsPostLoad EcsPreUpdate EcsOnUpdate EcsOnValidate EcsPostUpdate EcsPreStore EcsOnStore
cpp flecs::OnLoad flecs::PostLoad flecs::PreUpdate flecs::OnUpdate flecs::OnValidate flecs::PostUpdate flecs::PreStore flecs::OnStore
c ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); ECS_SYSTEM(world, Transform, EcsPostUpdate, Position, Transform); ECS_SYSTEM(world, Render, EcsOnStore, Transform, Mesh);
ecs_progress(world, 0); // run systems in default pipeline
cpp world.system<Position, Velocity>("Move").kind(flecs::OnUpdate).each( ... ); world.system<Position, Transform>("Transform").kind(flecs::PostUpdate).each( ... ); world.system<Transform, Mesh>("Render").kind(flecs::OnStore).each( ... );
world.progress();
c ecs_remove_id(world, Move, EcsOnUpdate); ecs_add_id(world, Move, EcsPostUpdate);
cpp move_sys.add(flecs::OnUpdate); move_sys.remove(flecs::PostUpdate);
c ecs_observer(world, { .filter.terms = { { ecs_id(Position) }, { ecs_id(Velocity) }}, .event = EcsOnSet, .callback = OnSetPosition });
// Callback code is same as system
ecs_entity_t e = ecs_new_id(world); // Doesn't invoke the observer ecs_set(world, e, Position, {10, 20}); // Doesn't invoke the observer ecs_set(world, e, Velocity, {1, 2}); // Invokes the observer ecs_set(world, e, Position, {20, 40}); // Invokes the observer
cpp world.observer<Position, Velocity>("OnSetPosition").event(flecs::OnSet).each( ... );
auto e = ecs.entity(); // Doesn't invoke the observer e.set<Position>({10, 20}); // Doesn't invoke the observer e.set<Velocity>({1, 2}); // Invokes the observer e.set<Position>({20, 30}); // Invokes the observer
c // Module header (e.g. MyModule.h) typedef struct { float x; float y; } Position;
extern ECS_COMPONENT_DECLARE(Position);
// The import function name has to follow the convention: <ModuleName>Import void MyModuleImport(ecs_world_t *world);
// Module source (e.g. MyModule.c) ECS_COMPONENT_DECLARE(Position);
void MyModuleImport(ecs_world_t *world) { ECS_MODULE(world, MyModule); ECS_COMPONENT_DEFINE(world, Position); }
// Import code ECS_IMPORT(world, MyModule);
cpp struct my_module { my_module(flecs::world& world) { world.module<my_module>();
// Define components, systems, triggers, ... as usual. They will be // automatically created inside the scope of the module. } };
// Import code world.import<my_module>(); ```