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!
Building Flecs
To use Flecs, copy the distr/flecs.c and distr/flecs.h files from the distr folder to your project's source folder. When building, make sure your build system is setup to do the following:
- If it is a C++ project, make sure to compile distr/flecs.c as C code, for example by using
gcc
/clang
instead of g++
/clang++
.
- If you are building on Windows and you're not using the Microsoft Visual Studio compiler, make sure to add
-lWs2_32
to the end(!) of the linker command. The socket API is used for connecting to Flecs explorer.
- When compiling Flecs with
gcc
/clang
, add -std=gnu99
to the compiler command. This ensures that addons that rely on time & socket functions are compiled correctly.
- C++ files that use Flecs must be compiled with
-std=c++0x
(C++11) or higher.
Dynamic linking
To build Flecs as a dynamic library, remove this line from the top of the distr/flecs.h file:
When compiling distr/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.
Building with CMake
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:
target_link_libraries(${PROJECT_NAME} flecs::flecs_static)
Building with Bake
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:
Running tests (bake)
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):
# Core test suite
bake run test/core -- -j 4
# Addon tests
bake run test/addons -- -j 4
# Reflection tests
bake run test/meta -- -j 4
# C++ tests
bake run test/cpp -- -j 4
To run tests with asan enabled, add --cfg sanitize
to the command:
bake run --cfg sanitize test/api -- -j 4
Running tests (cmake, experimental)
First make sure to clone bake.
Run the following commands to run all the tests:
# Generate make files for Flecs and tests
cmake -DFLECS_TESTS=ON -DBAKE_DIRECTORY="path to cloned bake repository"
# Build flecs and test suites
cmake --build . -j 4
# Run the tests
ctest -C Debug --verbose
Emscripten
When building for emscripten, add the following command line options to the emcc
link command:
-s ALLOW_MEMORY_GROWTH=1
-s STACK_SIZE=1mb
-s EXPORTED_RUNTIME_METHODS=cwrap
-s MODULARIZE=1
-s EXPORT_NAME="my_app"
Addons
Flecs has a modular architecture that makes it easy to only build the features you really need. By default all addons are built. To customize a build, first define FLECS_CUSTOM_BUILD
, then add defines for the addons you need. For example:
#define FLECS_CUSTOM_BUILD
#define FLECS_SYSTEM
Additionally, you can also specify addons to exclude from a build by adding NO
to the define:
The following addons can be configured:
Addon | Description | Define |
Cpp | C++11 API | FLECS_CPP |
Module | Organize game logic into reusable modules | FLECS_MODULE |
System | Create & run systems | FLECS_SYSTEM |
Pipeline | Automatically schedule & multithread systems | FLECS_PIPELINE |
Timer | Run systems at time intervals or at a rate | FLECS_TIMER |
Meta | Flecs reflection system | FLECS_META |
Units | Builtin unit types | FLECS_UNITS |
JSON | JSON format | FLECS_JSON |
Doc | Add documentation to components, systems & more | FLECS_DOC |
Http | Tiny HTTP server for processing simple requests | FLECS_HTTP |
Rest | REST API for showing entities in the browser | FLECS_REST |
Script | DSL for assets, scenes and configuration | FLECS_SCRIPT |
Stats | Functions for collecting statistics | FLECS_STATS |
Metrics | Create metrics from user-defined components | FLECS_METRICS |
Alerts | Create alerts from user-defined queries | FLECS_ALERTS |
Log | Extended tracing and error logging | FLECS_LOG |
Journal | Journaling of API functions | FLECS_JOURNAL |
App | Flecs application framework | FLECS_APP |
OS API Impl | Default OS API implementation for Posix/Win32 | FLECS_OS_API_IMPL |
Concepts
This section contains an overview of all the different concepts in Flecs and how they wire together. The sections in the quickstart go over them in more detail and with code examples.
Flecs Overview
World
The world is the container for all ECS data. It stores the entities and their components, does queries and runs systems. Typically there is only a single world, but there is no limit on the number of worlds an application can create.
-
C
struct ecs_world_t ecs_world_t
A world is the container for all ECS data and supporting features.
int ecs_fini(ecs_world_t *world)
Delete a world.
ecs_world_t * ecs_init(void)
Create a new world.
-
C++
-
C#
using World world = World.Create();
-
Rust
let world = World::new();
// Do the ECS stuff
Entity
An entity is a unique thing in the world, and is represented by a 64 bit id. Entities can be created and deleted. If an entity is deleted it is no longer considered "alive". A world can contain up to 4 billion(!) alive entities. Entity identifiers contain a few bits that make it possible to check whether an entity is alive or not.
-
C
ecs_id_t ecs_entity_t
An entity identifier.
ecs_entity_t ecs_new(ecs_world_t *world)
Create new entity id.
void ecs_delete(ecs_world_t *world, ecs_entity_t entity)
Delete an entity.
bool ecs_is_alive(const ecs_world_t *world, ecs_entity_t e)
Test whether an entity is alive.
-
C++
e.destruct();
e.is_alive();
flecs::entity entity(Args &&... args) const
Create an entity.
bool is_alive() const
Check if entity is alive.
-
C#
Entity e = world.Entity();
e.IsAlive();
e.Destruct();
e.IsAlive();
-
Rust
let e = world.entity();
e.is_alive(); // true!
e.destruct();
e.is_alive(); // false!
Entities can have names which makes it easier to identify them in an application. In C++ the name can be passed to the constructor. If a name is provided during entity creation time and an entity with that name already exists, the existing entity will be returned.
Entities can be looked up by name with the lookup
function:
-
C
ecs_entity_t ecs_lookup(const ecs_world_t *world, const char *path)
Lookup an entity by it's path.
-
C++
flecs::entity lookup(const char *name, const char *sep="::", const char *root_sep="::", bool recursive=true) const
Lookup entity by name.
-
C#
Entity e = world.Lookup("Bob");
-
Rust
let e = world.lookup("bob");
Id
An id is a 64 bit number that can encode anything that can be added to an entity. In flecs this can be either a component, tag or a pair. A component is data that can be added to an entity. A tag is an "empty" component. A pair is a combination of two component/tag ids which is used to encode entity relationships. All entity/component/tag identifiers are valid ids, but not all ids are valid entity identifier.
The following sections describe components, tags and pairs in more detail.
Component
A component is a type of which instances can be added and removed to entities. Each component can be added only once to an entity (though not really, see Pair). In C applications components must be registered before use. By default in C++ this happens automatically.
-
C
ecs_add(world, e, Velocity);
ecs_set(world, e, Position, {10, 20});
ecs_set(world, e, Velocity, {1, 2});
const Position *p = ecs_get(world, e, Position);
ecs_remove(world, e, Position);
#define ECS_COMPONENT(world, id)
Declare & define a component.
-
C++
e.set<Position>({10, 20})
.set<Velocity>({1, 2});
const Position *p = e.
get<Position>();
e.remove<Position>();
const Self & add() const
Add a component to an entity.
const T * get() const
Get component value.
-
C#
Entity e = world.Entity();
e.Add<Velocity>();
e.Set<Position>(new(10, 20))
.Set<Velocity>(new(1, 2));
ref readonly Position p = ref e.Get<Position>();
e.Remove<Position>();
-
Rust
let 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 { x: 10.0, y: 20.0 })
.set(Velocity { x: 1.0, y: 2.0 });
// Get a component
e.get::<&Position>(|p| {
println!("Position: ({}, {})", p.x, p.y);
});
// Remove component
e.remove::<Position>();
Each component is associated by a unique entity identifier by Flecs. This makes it possible to inspect component data, or attach your own data to components.
-
C
C applications can use the ecs_id
macro to get the entity id for a component.
ecs_add(world, pos_e, Serializable);
FLECS_API const ecs_entity_t ecs_id(EcsDocDescription)
Component id for EcsDocDescription.
-
C++
C++ applications can use the world::entity
function.
std::cout <<
"Name: " << pos_e.
name() << std::endl;
pos_e.
add<Serializable>();
-
C#
C# applications can use the World.Entity()
function.
Entity posE = world.Entity<Position>();
Console.WriteLine($"Name: {posE.Name()}");
posE.Add<Serializable>();
-
Rust
Rust applications can use the world::entity_from
function.
let pos_e = world.entity_from::<Position>();
println!("Name: {}", pos_e.name()); // outputs 'Name: Position'
// It's possible to add components like you would for any entity
pos_e.add::<Serializable>();
The thing that makes an ordinary entity a component is the EcsComponent
(or flecs::Component
, in C++) component. This is a builtin component that tells Flecs how much space is needed to store a component, and can be inspected by applications:
-
C
printf(
"Component size: %u\n", c->
size);
ecs_size_t size
Component size.
-
C++
std::cout <<
"Component size: " << c->
size << std::endl;
-
C#
Entity posE = world.Entity<Position>();
Console.WriteLine($"Component size: {c.size}");
-
Rust
let pos_e = world.entity_from::<Position>();
pos_e.get::<&flecs::Component>(|c| {
println!("Component size: {}", c.size);
});
Because components are stored as regular entities, they can in theory also be deleted. To prevent unexpected accidents however, by default components are registered with a tag that prevents them from being deleted. If this tag were to be removed, deleting a component would cause it to be removed from all entities. For more information on these policies, see Relationship cleanup properties.
Tag
A tag is a component that does not have any data. In Flecs tags can be either empty types (in C++) or regular entities (C & C++) that do not have the EcsComponent
component (or have an EcsComponent
component with size 0). Tags can be added & removed using the same APIs as adding & removing components, but because tags have no data, they cannot be assigned a value. Because tags (like components) are regular entities, they can be created & deleted at runtime.
-
C
void ecs_add_id(ecs_world_t *world, ecs_entity_t entity, ecs_id_t id)
Add a (component) id to an entity.
void ecs_remove_id(ecs_world_t *world, ecs_entity_t entity, ecs_id_t id)
Remove a (component) id from an entity.
bool ecs_has_id(const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id)
Test if an entity has an id.
-
C++
struct Enemy { };
e.remove<Enemy>();
e.has<Enemy>();
e.remove(Enemy);
e.has(Enemy);
bool has(flecs::id_t e) const
Check if entity has the provided entity.
-
C#
public struct Enemy { }
Entity e = world.Entity().Add<Enemy>();
e.Has<Enemy>();
e.Remove<Enemy>();
e.Has<Enemy>();
Entity Enemy = world.Entity();
Entity e = world.Entity().Add(Enemy);
e.Has(Enemy);
e.Remove(Enemy);
e.Has(Enemy);
-
Rust
// Option 1: create Tag as empty struct
#[derive(Component)]
struct Enemy;
// Create entity, add Enemy tag
let e = world.entity().add::<Enemy>();
e.has::<Enemy>(); // true!
e.remove::<Enemy>();
e.has::<Enemy>(); // false!
// Option 2: create Tag as entity
let enemy = world.entity();
// Create entity, add Enemy tag
let e = world.entity().add_id(enemy);
e.has_id(enemy); // true!
e.remove_id(enemy);
e.has_id(enemy); // false!
Note that both options in the C++ example achieve the same effect. The only difference is that in option 1 the tag is fixed at compile time, whereas in option 2 the tag can be created dynamically at runtime.
When a tag is deleted, the same rules apply as for components (see Relationship cleanup properties).
Pair
A pair is a combination of two entity ids. Pairs can be used to store entity relationships, where the first id represents the relationship kind and the second id represents the relationship target (called "object"). This is best explained by an example:
-
C
ecs_add_pair(world, Bob, Likes, Alice);
ecs_add_pair(world, Alice, Likes, Bob);
ecs_has_pair(world, Bob, Likes, Alice);
ecs_remove_pair(world, Bob, Likes, Alice);
ecs_has_pair(world, Bob, Likes, Alice);
-
C++
struct Likes { };
Bob.remove<Likes>(Alice);
Bob.has<Likes>(Alice);
-
C#
public struct Likes { }
Entity Bob = world.Entity();
Entity Alice = world.Entity();
Bob.Add<Likes>(Alice);
Alice.Add<Likes>(Bob);
Bob.Has<Likes>(Alice);
Bob.Remove<Likes>(Alice);
Bob.Has<Likes>(Alice);
-
Rust
// Create Likes relationship as empty type (tag)
#[derive(Component)]
struct Likes;
// Create a small graph with two entities that like each other
let bob = world.entity();
let alice = world.entity();
bob.add_first::<Likes>(alice); // bob likes alice
alice.add_first::<Likes>(bob); // alice likes bob
bob.has_first::<Likes>(alice); // true!
bob.remove_first::<Likes>(alice);
bob.has_first::<Likes>(alice); // false!
A pair can be encoded in a single 64 bit identifier by using the ecs_pair
macro in C, or the world.pair
function in C++:
-
C
uint64_t ecs_id_t
Ids are the things that can be added to an entity.
-
C++
Class that wraps around a flecs::id_t.
flecs::id pair() const
Get pair id from relationship, object.
-
C#
Id id = world.Pair<Likes>(bob);
-
Rust
let id = world.id_first::<Likes>(bob);
The following examples show how to get back the elements from a pair:
-
C
}
bool ecs_id_is_pair(ecs_id_t id)
Utility to check if id is a pair.
-
C++
if (id.is_pair()) {
auto relationship = id.first();
auto target = id.second();
}
-
C#
Id id = ...;
if (id.IsPair())
{
Entity relationship = id.First();
Entity target = id.Second();
}
-
Rust
let id = world.id_from::<(Likes, Apples)>();
if id.is_pair() {
let relationship = id.first_id();
let target = id.second_id();
}
A component or tag can be added multiple times to the same entity as long as it is part of a pair, and the pair itself is unique:
-
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);
ecs_has_pair(world, Bob, Eats, Pears);
ecs_has_pair(world, Bob, Grows, Pears);
-
C++
bob.add(Eats, Apples);
bob.add(Eats, Pears);
bob.add(Grows, Pears);
bob.has(Eats, Apples);
bob.has(Eats, Pears);
bob.has(Grows, Pears);
-
C#
Entity Bob = ...;
Bob.Add(Eats, Apples);
Bob.Add(Eats, Pears);
Bob.Add(Grows, Pears);
Bob.Has(Eats, Apples);
Bob.Has(Eats, Pears);
Bob.Has(Grows, Pears);
-
Rust
let bob = world.entity();
bob.add_id((eats, apples));
bob.add_id((eats, pears));
bob.add_id((grows, pears));
bob.has_id((eats, apples)); // true!
bob.has_id((eats, pears)); // true!
bob.has_id((grows, pears)); // true!
The target
function can be used in C and C++ to get the object for a relationship:
-
C
ecs_entity_t ecs_get_target(const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel, int32_t index)
Get the target of a relationship.
-
C++
auto o = alice.target<Likes>();
-
C#
Entity Alice = ...;
Entity o = Alice.Target<Likes>();
-
Rust
let alice = world.entity().add_first::<Likes>(bob);
let o = alice.target::<Likes>(0); // Returns bob
Entity relationships enable lots of interesting patterns and possibilities. Make sure to check out the Relationships manual.
Hierarchies
Flecs has builtin support for hierarchies with the builtin EcsChildOf
(or flecs::ChildOf
, in C++) relationship. A hierarchy can be created with the regular relationship API, or with the child_of
shortcut in C++:
-
C
-
C++
const Self & child_of(entity_t second) const
Shortcut for add(ChildOf, entity).
void destruct() const
Delete an entity.
-
C#
Entity parent = world.Entity();
Entity child = world.Entity().ChildOf(parent);
parent.Destruct();
-
Rust
let parent = world.entity();
let child = world.entity().child_of_id(parent);
// Deleting the parent also deletes its children
parent.destruct();
When entities have names, they can be used together with hierarchies to generate path names or do relative lookups:
-
C
.name = "parent"
});
.name = "child"
});
char *path = ecs_get_path(world, child);
printf("%s\n", path);
ecs_os_free(path);
ecs_lookup_from(world, parent, "child");
-
C++
auto parent = world.
entity(
"parent");
std::cout << child.
path() << std::endl;
world.
lookup(
"parent::child");
flecs::string path(const char *sep="::", const char *init_sep="::") const
Return the entity path.
flecs::entity lookup(const char *path, bool search_path=false) const
Lookup an entity by name.
-
C#
Entity parent = world.Entity("parent");
Entity child = world.Entity("child").ChildOf(parent);
Console.WriteLine(child.Path());
world.Lookup("parent.child");
parent.Lookup("child");
-
Rust
let parent = world.entity_named("parent");
let child = world.entity_named("child").child_of_id(parent);
println!("Child path: {}", child.path().unwrap()); // output: 'parent::child'
world.lookup("parent::child"); // returns child
parent.lookup("child"); // returns child
Queries (see below) can use hierarchies to order data breadth-first, which can come in handy when you're implementing a transform system:
-
C
.terms = {
}}
}
});
Position *p = ecs_field(&it, Position, 0);
Position *p_parent = ecs_field(&it, Position, 1);
for (int i = 0; i < it.count; i++) {
}
}
#define ecs_query(world,...)
Shorthand for creating a query with ecs_query_cache_init.
bool ecs_query_next(ecs_iter_t *it)
Progress query iterator.
#define EcsCascade
Sort results breadth first.
ecs_iter_t ecs_query_iter(const ecs_world_t *world, const ecs_query_t *query)
Create a query iterator.
Queries are lists of constraints (terms) that match entities.
-
C++
.term_at(1).parent().cascade()
.build();
q.each([](Position& p, Position& p_parent) {
});
flecs::query_builder< Comps... > query_builder(Args &&... args) const
Create a query builder.
-
C#
Query q = world.QueryBuilder<Position, Position>()
.TermAt(1).Parent().Cascade()
.Build();
q.Each((ref Position p, ref Position pParent) =>
{
});
-
Rust
let q = world
.query::<(&Position, &mut Position)>()
.term_at(1)
.parent()
.cascade()
.build();
q.each(|(p, p_parent)| {
// Do the thing
});
Type
The type (often referred to as "archetype") is the list of ids an entity has. Types can be used for introspection which is useful when debugging, or when for example building an entity editor. The most common thing to do with a type is to convert it to text and print it:
-
C
ecs_add(world, e, Position);
ecs_add(world, e, Velocity);
printf("Type: %s\n", type_str);
ecs_os_free(type_str);
const ecs_type_t * ecs_get_type(const ecs_world_t *world, ecs_entity_t entity)
Get the type of an entity.
char * ecs_type_str(const ecs_world_t *world, const ecs_type_t *type)
Convert type to string.
A type is a list of (component) ids.
-
C++
auto e = ecs.entity()
.add<Position>()
.add<Velocity>();
std::cout << e.type().str() << std::endl;
-
C#
Entity e = ecs.Entity()
.Add<Position>()
.Add<Velocity>();
Console.WriteLine(e.Type().Str());
-
Rust
let e = world.entity().add::<Position>().add::<Velocity>();
println!("Components: {}", e.archetype().to_string().unwrap()); // output: 'Position,Velocity'
A type can also be iterated by an application:
-
C
for (
int i = 0; i < type->
count; i++) {
}
}
ecs_id_t * array
Array with ids.
int32_t count
Number of elements in array.
-
C++
if (
id == world.
id<Position>()) {
}
});
flecs::id id(E value) const
Convert enum constant to entity.
-
C#
e.Each((Id id) =>
{
if (id == world.Id<Position>())
{
}
});
-
Rust
e.each_component(|id| {
if id == world.component_id::<Position>() {
// Found Position component!
}
});
Singleton
A singleton is a single instance of a component that can be retrieved without an entity. The functions for singletons are very similar to the regular API:
-
C
ecs_singleton_set(world, Gravity, { 9.81 });
const Gravity *g = ecs_singleton_get(world, Gravity);
-
C++
world.
set<Gravity>({ 9.81 });
const Gravity *g = world.
get<Gravity>();
const T * get() const
Get singleton component.
void set(const T &value) const
Set singleton component.
-
C#
world.Set<Gravity>(new(9.81));
ref readonly Gravity g = ref world.Get<Gravity>();
-
Rust
// Set singleton component
world.set(Gravity { x: 10, y: 20 });
// Get singleton component
world.get::<&Gravity>(|g| {
println!("Gravity: {}, {}", g.x, g.y);
});
Singleton components are created by adding the component to its own entity id. The above code examples are shortcuts for these regular API calls:
-
C
ecs_set(world,
ecs_id(Gravity), Gravity, {10, 20});
const Gravity *g = ecs_get(world,
ecs_id(Gravity), Gravity);
-
C++
grav_e.set<Gravity>({10, 20});
const Gravity *g = grav_e.
get<Gravity>();
-
C#
Entity gravE = world.Entity<Gravity>();
gravE.Set<Gravity>(new(10, 20));
ref readonly Gravity g = ref gravE.Get<Gravity>();
-
Rust
let grav_e = world.entity_from::<Gravity>();
grav_e.set(Gravity { x: 10, y: 20 });
grav_e.get::<&Gravity>(|g| {
println!("Gravity: {}, {}", g.x, g.y);
});
The following examples show how to query for a singleton component:
-
C
.terms = {
}
});
#define ECS_SYSTEM(world, id, phase,...)
Declare & define a system.
-
C++
.term_at(1).singleton()
.build();
-
C#
world.QueryBuilder<Velocity, Gravity>()
.TermAt(1).Singleton()
.Build();
-
Rust
world
.query::<(&Velocity, &Gravity)>()
.term_at(1)
.singleton()
.build();
Query
Queries are the main mechanism for finding and iterating through entities. Queries are used in many parts of the API, such as for systems and observers. The following example shows a simple query:
-
C
.terms = {
}
});
Position *p = ecs_field(&it, Position, 0);
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);
}
}
void ecs_query_fini(ecs_query_t *query)
Delete a query.
-
C++
world.
each([](Position& p, Velocity& v) {
p.x += v.x;
p.y += v.y;
});
.with(flecs::ChildOf, parent)
.build();
std::cout << e.
name() <<
": {" << p.x <<
", " << p.y <<
"}" << std::endl;
});
auto p = it.
field<Position>(0);
for (auto i : it) {
<< ": {" << p[i].x << ", " << p[i].y << "}" << std::endl;
}
}
});
void each(Func &&func) const
Iterate over all entities with components in argument list of function.
Class for iterating over query results.
flecs::field< A > field(int8_t index) const
Get readonly access to field data.
flecs::entity entity(size_t row) const
Obtain mutable handle to entity being iterated over.
bool next()
Progress iterator.
-
C#
world.Each((ref Position p, ref Velocity v) =>
{
p.X += v.X;
p.Y += v.Y;
});
using Query q = world.QueryBuilder<Position>()
.With(Ecs.ChildOf, parent)
.Build();
q.Each((Entity e, ref Position p) =>
{
Console.WriteLine($"{e.Name()}: ({p.X}, {p.Y})")
});
q.Iter((Iter it, Field<Position> p) =>
{
foreach (int i in it)
Console.WriteLine($"{it.Entity(i).Name()}: ({p[i].X}, {p[i].Y})")
});
-
Rust
// For simple queries the world::each function can be used
world.each::<(&mut Position, &Velocity)>(|(p, v)| {
// EntityView argument is optional, use each_entity to get it
p.x += v.x;
p.y += v.y;
});
// More complex queries can first be created, then iterated
let q = world
.query::<&Position>()
.with_id((flecs::ChildOf::ID, parent))
.build();
// Option 1: the each() callback iterates over each entity
q.each_entity(|e, p| {
println!("{}: ({}, {})", e.name(), p.x, p.y);
});
// Option 2: the run() callback offers more control over the iteration
q.run(|mut it| {
while it.next() {
let p = it.field::<Position>(0).unwrap();
for i in it.iter() {
println!("{}: ({}, {})", it.entity(i).name(), p[i].x, p[i].y);
}
}
});
Queries can use operators to exclude components, optionally match components or match one out of a list of components. Additionally filters may contain wildcards for terms which is especially useful when combined with pairs.
The following example shows a query that matches all entities with a parent that do not have Position
:
-
C
.terms = {
}
});
@ EcsNot
The term must not match.
-
C++
.with(flecs::ChildOf, flecs::Wildcard)
.with<Position>().oper(flecs::Not)
.build();
-
C#
using Query q = world.QueryBuilder()
.With(Ecs.ChildOf, Ecs.Wildcard)
.With<Position>().Oper(Ecs.Not)
.Build();
-
Rust
let q = world
.query::<()>()
.with::<(flecs::ChildOf, flecs::Wildcard)>()
.with::<Position>()
.set_oper(OperKind::Not)
.build();
// Iteration code is the same
See the query manual for more details.
System
A system is a query combined with a callback. Systems can be either ran manually or ran as part of an ECS-managed main loop (see Pipeline). The system API looks similar to queries:
-
C
ecs_run(world, Move, delta_time, NULL);
.query.terms = {
},
.callback = Move
});
ecs_run(world, move_sys, delta_time, NULL);
Position *p = ecs_field(it, Position, 0);
Velocity *v = ecs_field(it, Velocity, 1);
for (
int i = 0; i < it->
count; i++) {
}
}
#define ecs_system(world,...)
Shorthand for creating a system with ecs_system_init().
FLECS_API ecs_entity_t ecs_run(ecs_world_t *world, ecs_entity_t system, ecs_ftime_t delta_time, void *param)
Run a specific system manually.
float delta_time
Time elapsed since last frame.
int32_t count
Number of entities to iterate.
-
C++
auto move_sys = world.
system<Position, Velocity>()
.each([](
flecs::iter& it,
size_t, Position& p, Velocity& v) {
p.x += v.x * it.delta_time();
p.y += v.y * it.delta_time();
});
move_sys.run();
flecs::system system(flecs::entity e) const
Upcast entity to a system.
-
C#
System<Position, Velocity> moveSys = world.System<Position, Velocity>()
.Each((Entity e, ref Position p, ref Velocity v) =>
{
p.X += v.X * it.DeltaTime();
p.Y += v.Y * it.DeltaTime();
});
moveSys.Run();
-
Rust
// Use each_entity() function that iterates each individual entity
let move_sys = world
.system::<(&mut Position, &Velocity)>()
.each_iter(|it, i, (p, v)| {
p.x += v.x * it.delta_time();
p.y += v.y * it.delta_time();
});
// Just like with queries, systems have both the run() and
// each() methods to iterate entities.
move_sys.run();
Systems are stored as entities with an EcsSystem
component (flecs::System
in C++), similar to components. That means that an application can use a system as a regular entity:
-
C
-
C++
std::cout << "System: " << move_sys.name() << std::endl;
move_sys.add(flecs::OnUpdate);
move_sys.destruct();
-
C#
Console.WriteLine($"System: {moveSys.Name()}");
moveSys.Entity.Add(Ecs.OnUpdate);
moveSys.Entity.Destruct();
-
Rust
println!("System: {}", move_sys.name());
move_sys.add::<flecs::pipeline::OnUpdate>();
move_sys.destruct();
Pipeline
A pipeline is a list of tags that when matched, produces a list of systems to run. These tags are also referred to as a system "phase". Flecs comes with a default pipeline that has the following phases:
-
C
-
C++
flecs::OnLoad
flecs::PostLoad
flecs::PreUpdate
flecs::OnUpdate
flecs::OnValidate
flecs::PostUpdate
flecs::PreStore
flecs::OnStore
-
C#
Ecs.OnLoad
Ecs.PostLoad
Ecs.PreUpdate
Ecs.OnUpdate
Ecs.OnValidate
Ecs.PostUpdate
Ecs.PreStore
Ecs.OnStore
-
Rust
flecs::pipeline::OnLoad;
flecs::pipeline::PostLoad;
flecs::pipeline::PreUpdate;
flecs::pipeline::OnUpdate;
flecs::pipeline::OnValidate;
flecs::pipeline::PostUpdate;
flecs::pipeline::PreStore;
flecs::pipeline::OnStore;
When a pipeline is executed, systems are ran in the order of the phases. This makes pipelines and phases the primary mechanism for defining ordering between systems. The following code shows how to assign systems to a pipeline, and how to run the pipeline with the progress()
function:
-
C
FLECS_API bool ecs_progress(ecs_world_t *world, ecs_ftime_t delta_time)
Progress a world.
-
C++
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( ... );
bool progress(ecs_ftime_t delta_time=0.0) const
Progress world one tick.
void each(const Func &func) const
Iterate (component) ids of an entity.
-
C#
world.System<Position, Velocity>("Move").Kind(Ecs.OnUpdate).Each( ... );
world.System<Position, Transform>("Transform").Kind(Ecs.PostUpdate).Each( ... );
world.System<Transform, Mesh>("Render").Kind(Ecs.OnStore).Each( ... );
world.Progress();
-
Rust
world
.system_named::<(&mut Position, &Velocity)>("Move")
.kind::<flecs::pipeline::OnUpdate>()
.each(|(p, v)| {});
world
.system_named::<(&mut Position, &Transform)>("Transform")
.kind::<flecs::pipeline::PostUpdate>()
.each(|(p, t)| {});
world
.system_named::<(&Transform, &mut Mesh)>("Render")
.kind::<flecs::pipeline::OnStore>()
.each(|(t, m)| {});
world.progress();
Because phases are just tags that are added to systems, applications can use the regular API to add/remove systems to a phase:
-
C
-
C++
move_sys.add(flecs::OnUpdate);
move_sys.remove(flecs::PostUpdate);
-
C#
moveSys.Add(Ecs.OnUpdate);
moveSys.Remove(Ecs.PostUpdate);
-
Rust
move_sys.add::<flecs::pipeline::OnUpdate>();
move_sys.remove::<flecs::pipeline::PostUpdate>();
Inside a phase, systems are guaranteed to be ran in their declaration order.
Observer
Observers are callbacks that are invoked when one or more events matches the query of an observer. Events can be either user defined or builtin. Examples of builtin events are OnAdd
, OnRemove
and OnSet
.
When an observer has a query with more than one component, the observer will not be invoked until the entity for which the event is emitted satisfies the entire query.
An example of an observer with two components:
-
C
.query.terms = { {
ecs_id(Position) }, {
ecs_id(Velocity) }},
.callback = OnSetPosition
});
ecs_set(world, e, Position, {10, 20});
ecs_set(world, e, Velocity, {1, 2});
ecs_set(world, e, Position, {20, 40});
#define ecs_observer(world,...)
Shorthand for creating an observer with ecs_observer_init().
-
C++
world.
observer<Position, Velocity>(
"OnSetPosition")
.event(flecs::OnSet)
auto e = ecs.entity();
e.set<Position>({10, 20});
e.set<Velocity>({1, 2});
e.set<Position>({20, 30});
flecs::observer observer(flecs::entity e) const
Observer builder.
-
C#
world.Observer<Position, Velocity>("OnSetPosition")
.Event(Ecs.OnSet)
.Each( ... );
Entity e = ecs.Entity();
e.Set<Position>(new(10, 20));
e.Set<Velocity>(new(1, 2));
e.Set<Position>(new(20, 30));
-
Rust
world
.observer_named::<flecs::OnSet, (&Position, &Velocity)>("OnSetPosition")
.each(|(p, v)| {}); // Callback code is same as system
let e = world.entity(); // Doesn't invoke the observer
e.set(Position { x: 10.0, y: 20.0 }); // Doesn't invoke the observer
e.set(Velocity { x: 1.0, y: 2.0 }); // Invokes the observer
e.set(Position { x: 30.0, y: 40.0 }); // Invokes the observer
Module
A module is a function that imports and organizes components, systems, triggers, observers, prefabs into the world as reusable units of code. A well designed module has no code that directly relies on code of another module, except for components definitions. All module contents are stored as child entities inside the module scope with the ChildOf
relationship. The following examples show how to define a module in C and C++:
-
C
typedef struct {
float x;
float y;
} Position;
}
#define ECS_MODULE(world, id)
Create a module.
#define ECS_IMPORT(world, id)
Wrapper around ecs_import().
#define ECS_COMPONENT_DEFINE(world, id_)
Define a forward declared component.
#define ECS_COMPONENT_DECLARE(id)
Forward declare a component.
-
C++
struct my_module {
}
};
flecs::entity import()
Import a module.
flecs::entity module(const char *name=nullptr) const
Define a module.
-
C#
public struct MyModule : IFlecsModule
{
public void InitModule(ref World world)
{
world.Module<MyModule>();
}
};
world.Import<MyModule>();
-
Rust
#[derive(Component)]
struct MyModule;
impl Module for MyModule {
fn module(world: &World) {
world.module::<MyModule>("MyModule");
// Define components, systems, triggers, ... as usual. They will be
// automatically created inside the scope of the module.
}
}
// Import code
world.import::<MyModule>();