Flecs v4.0
A fast entity component system (ECS) for C & C++
|
Entities and components are the main building blocks of any ECS application. This manual describes the behavior of entities and components in Flecs.
Entities are uniquely identifiable objects in a game or simulation. In a real time strategy game, there may be entities for the different units, buildings, UI elements and particle effects, but also for example the camera, world and player.
By itself, an entity is just a unique identifier that does not contain any state. You cannot tell from the entity's type what kind of entity it is, as the type is just "entity".
In Flecs, an entity is represented by a 64 bit integer, which is also how it is exposed in the C API:
The first 32 bits of the entity are the "identifying" part: it is the number that uniquely identifies an object in the game. The remaining 32 bits are used by Flecs for liveliness tracking and various other purposes. This article has more details on the exact bit representation of entities.
Zero ("`0`") is the value that is reserved for invalid ids. Functions that return an entity id may return 0
to indicate that the function failed. 0
can also be used to indicate the absence of an entity, for example when a lookup by name didn't find anything.
The following example shows how to create a new entity without any components:
C
C++
C#
Rust
Empty entities don't have any components, and are therefore not matched by any queries or systems. They won't show up in the tree view of the explorer, as it is query based.
The id of the first returned entity is not 1
, but likely a much higher number like 500
. The reason for this is that Flecs creates a bunch of builtin entities, and reserves some of the id space for components (more on that later).
The following examples show how to delete entities:
C
C++
C#
Rust
After an entity is deleted, it can no longer be used with most ECS operations. The deleted entity will be made available for reuse, so that the next time a new entity is created the deleted id can be recycled.
Whenever an id is recycled, Flecs increases a "version" counter in the upper 32 bits of the entity identifier. This can look strange, as it makes recycled entity ids large, often larger than 4 billion!. It is however 100% expected behavior, and happens as soon after the first entity is deleted.
The reason this happens is so that the Flecs API can tell whether an entity id is alive or not. Consider the following example, where "v" denotes the version part of the entity id:
C
C++
C#
Rust
It is valid to delete an already deleted entity:
C
C++
C#
Rust
An entity can be cleared, which means all components are removed without making the entity not alive. Clearing an entity is more efficient than removing components one by one. An example:
C
C++
C#
Rust
Applications can use the is_alive
operation to test if an entity is alive or not. The entity passed to is_alive
must be a valid entity, passing 0
will throw an error. An example:
C
C++
C#
Rust
The API also provides an is_valid
operation. Whereas is_alive
tests if an entity is alive, is_valid
tests if the entity can be used with operations, and may be used with invalid (0
) entity ids. An example:
C
C++
C#
Rust
Sometimes it can be useful to have direct control over which id an entity assumes. One example of this is to make sure that both sides of a networked application use the same entity id. Applications are allowed to do this, as long as the entity id is not yet in use, and it is made alive. The following example shows how to do this:
C
C++
C#
Rust
The make_alive
operation guarantees that the provided entity id will be alive after the operation finishes. The operation will fail if the provided entity id is already in use with a different version.
Applications can manually override the version of an entity with the set_version
operation. This can be useful when for example synchronizing networked ids. An example:
C
C++
C#
Rust
An application can instruct Flecs to issue ids from a specific offset and up to a certain limit with the ecs_set_entity_range
operation. This example ensures that id generation starts from id 5000:
C
C++
C#
Rust
If the last issued id was higher than 5000, the operation will not cause the last id to be reset to 5000. An application can also specify the highest id that can be generated:
C
C++
C#
Rust
If invoking ecs_new
would result in an id higher than 10000
, the application would assert. If 0
is provided for the maximum id, no upper bound will be enforced.
Note that at the moment setting the range does not affect recycled ids. It is therefore possible that ecs_new
returns an id outside of the specified range if a recycle-able id is available. This is an issue that will be addressed in future versions.
It is possible for an application to enforce that entity operations are only allowed for the configured range with the ecs_enable_range_check
operation:
C
C++
C#
Rust
This can be useful for enforcing simple ownership behavior, where different id ranges are mapped out for different owners (often game clients or servers).
Entity can be given names, which allows them to be looked up on the world. An example:
C
C++
C#
Rust
Names are namespaced. They are similar to namespaced types in a programming language, or to files on a file system. If an entity is a child of a parent, a lookup will have to include the name of the parent:
C
C++
C#
Rust
Lookups can be relative to an entity. The following example shows how to lookup an entity relative to a parent:
C
C++
C#
Rust
An application can request the name and path of an entity. The name is the direct name given to an entity, without the names of the parent. The path is the name and names of the entity's parent, separated by a separator. If an entity has no parents, the name and the path are the same. An example:
C
C++
C#
Rust
Names must be unique. There can only be one entity with a specific name in the scope of a parent. Entities can be annotated with a doc name, which does not need to be unique. See the doc addon for more details.
When the name for an existing entity is used during the creation of a named entity, the existing entity is returned. An example:
C
C++
C#
Rust
Entity names can be changed after the entity is created, as long as the specified name is unique (e.g. there isn't a sibling entity with the same name). An example:
C
C++
C#
Rust
Entity names can be plain numbers:
C
C++
C#
Rust
Entity names can be used to refer directly to an entity id by prefixing them with a #
. For example, looking up #1
will return the entity with id 1. This allows applications that work with names to treat anonymous entities the same as named entities.
Entities can be disabled which prevents them from being matched with queries. This can be used to temporarily turn off a feature in a scene during game play. It is also used for Flecs systems, which can be disabled to temporarily remove them from a schedule. The following example shows how to disable and reenable an entity:
C
C++
C#
Rust
Entity disabling can be combined with prefabs to create lists of entities that can be disabled with a single operation. The following example shows how to disable three entities with a single operation:
C
C++
C#
Rust
This also works with prefab hierarchies, as shown in the following example:
C
C++
C#
Rust
Entity disabling works by adding a Disabled
tag to the entity, which can also be added manually with regular add/remove operations. An example:
C
C++
C#
Rust
A component is something that is added to an entity. Components can simply tag an entity ("this entity is an `Npc`"), attach data to an entity ("this entity is at `Position` `{10, 20}`") and create relationships between entities ("bob `Likes` alice") that may also contain data ("bob `Eats` `{10}` apples").
To disambiguate between the different kinds of components in Flecs, they are named separately in Flecs:
Name | Has Data | Is Pair |
---|---|---|
Tag | No | No |
Component | Yes | No |
Relationship | No | Yes |
Relationship component | Yes | Yes |
Here, "has data" indicates whether a component attaches data to the entity. This means that in addition to asking whether an entity has a component, we can also ask what the value of a component is.
A pair is a component that's composed out of two elements, such as "Likes, alice" or "Eats, apples". See the Relationship manual for more details.
The following table provides the base set of operations that Flecs offers for components:
Operation | Description |
---|---|
add | Adds component to entity. If entity already has the component, add does nothing. Requires that the component is default constructible. |
remove | Removes component from entity. If entity doesn't have the component, remove does nothing. |
get | Returns a immutable reference to the component. If the entity doesn't have the component, get returns nullptr . |
get_mut | Returns a mutable reference to the component. If the entity doesn't have the component, get_mut returns nullptr . |
ensure | Returns a mutable reference to the component. ensure behaves as a combination of add and get_mut . |
emplace | Returns a mutable reference to the component. If the entity doesn't have the component, emplace returns a reference to unconstructed memory. This enables adding components that are not default constructible. |
modified | Emits a modified event for a component. This ensures that OnSet observers and on_set hooks are invoked, and updates change detection administration. |
set | Sets the value of a component. set behaves as a combination of ensure and modified . set does not take ownership of the provided value. |
The following component lifecycle diagram shows how the different operations mutate the storage and cause hooks and observers to be invoked:
In an ECS framework, components need to be uniquely identified. In Flecs this is done by making each component is its own unique entity. If an application has a component Position
and Velocity
, there will be two entities, one for each component. Component entities can be distinguished from "regular" entities as they have a Component
component. An example:
C
C++
C#
Rust
All of the APIs that apply to regular entities also apply to component entities. Many Flecs features leverage this. It is for example possible to customize component behavior by adding tags to components. The following example shows how a component can be customized to use sparse storage by adding a Sparse
tag to the component entity:
C
C++
C#
Rust
These kinds of tags are called "traits". To see which kinds of traits you can add to components to customize their behavior, see the component traits manual.
Components must be registered before they can be used. The following sections describe how component registration works for the different language bindings.
C
In C, the easiest way to register a component is with the ECS_COMPONENT
macro:
However, if you try to use the component from another function or across files, you will find that this doesn't work. To fix this, the component has to be forward declared. The following example shows how to forward declare a component that is used from multiple C files:
Note that this is just an example, and that typically you would not create a file per component. A more common way to organize components is with modules, which often register multiple components, amongst others. The following code shows an example with a module setup:
C++
In C++ components are automatically registered upon first usage. The following example shows how:
Components can be registered in advance, which can be done for several reasons:
To register a component in advance, do:
In general it is recommended to register components in advance, and to only use automatic registration during prototyping. Applications can enforce manual registration by defining the FLECS_CPP_NO_AUTO_REGISTRATION
macro at compile time, when building Flecs. This will cause a panic whenever a component is used that was not yet registered. Disabling auto registration also improves performance of the C++ API.
A convenient way to organize component registration code is to use Flecs modules. An example:
C#
In C# components are automatically registered upon first usage. The following example shows how:
Components can be registered in advance, which can be done for several reasons:
To register a component in advance, do:
In general it is recommended to register components in advance, and to only use automatic registration during prototyping.
A convenient way to organize component registration code is to use Flecs modules. An example:
Rust
In Rust components are automatically registered upon first usage. The following example shows how:
Components can be registered in advance, which can be done for several reasons:
To register a component in advance, do:
In general it is recommended to register components in advance, and to only use automatic registration during prototyping. Applications can enforce manual registration by activating the rust feature flecs_manual_registration
. This will cause a panic whenever a component is used that was not yet registered. Disabling auto registration also improves performance of the Rust API.
A convenient way to organize component registration code is to use Flecs modules. An example:
In some cases, typically when using scripting, components must be registered for types that do not exist at compile time. In Flecs this is possible by calling the ecs_component_init
function. This function returns a component entity, which can then be used with regular ECS operations. An example:
C
C++
C#
Rust
Often when registering a component at runtime, it needs to be described with reflection data so it can be serialized & deserialized. See the "reflection/runtime_component" example on how to do this.
When registering a component, it is good practice to give it a name. This makes it much easier to debug the application with tools like the explorer. To create a component with a name, we can pass a named entity to the ecs_component_init
function. An example:
C
C++
C#
Rust
See the documentation for ecs_component_desc_t
for more component registration options.
A component can be unregistered from a world by deleting its entity. When a component is deleted, by default it will be removed from all entities that have the component. This behavior is customizable, see the "cleanup traits" section in the component trait manual.
The following example shows how to unregister a component:
C
C++
C#
Rust
Singletons are components for which only a single instance exists on the world. They can be accessed on the world directly and do not require providing an entity. Singletons are useful for global game resources, such as game state, a handle to a physics engine or a network socket. An example:
C
C++
C#
Rust
Singletons are implemented as components that are added to themselves. The rationale for this design is that it allows for reusing all of the existing logic implemented for entities and components, as for the storage there is no fundamental difference between a singleton and a regular component. Additionally, it spreads out the storage of singletons across multiple entities which is favorable for performance when compared to a single entity that contains all singleton components. Since the component's entity is known during the singleton lookup, it makes most sense to use this as the entity on which to store the data.
The following example shows how the singleton APIs are equivalent to using the regular APIs with the component entity:
C
C++
C#
Rust
Components can be disabled, which prevents them from being matched by queries. When a component is disabled, it behaves as if the entity doesn't have it. Only components that have the CanToggle
trait can be disabled (see the component traits manual for more details). The following example shows how to disable a component:
C
C++
C#
Rust
Disabling a component is a cheaper alternative to removing it from an entity, as it relies on setting a bit vs. moving an entity to another table. Component toggling can also be used to restore a component with its old value.