Component Types
Last updated
Last updated
Note: This Wiki page is work in progress...
The common component type applicable for most use cases is IComponent. See component example in Wiki ⋅ Examples General
For specific use cases there is a set of component interfaces providing additional features.
Use case | Component interface type | Description |
---|---|---|
Component types examples using Friflo.Engine.ECS are part of the unit tests see: Tests/ECS/Examples
An entity relationship is a directed link between two entities.
Typical use case for entity relationships in a game are:
Attack systems
Path finding / Route tracing
Model social networks. E.g friendship, alliances or rivalries
Inventory Systems
Build any type of a directed graph using entities as nodes and links or relations as edges.
In this ECS relationships are modeled as components. Directed link means that a link points from a source entity to a target entity. The entity containing a link component / relation is the source entity.
There are two interfaces used to define components with entity links:
ILinkComponent - An entity can have only one link component at a time.
ILinkRelation - An entity can have multiple link relations - one per target entity.
Now you might ask why having specialized component types for entity links.
You can simply add an Entity
field to a component type and you are done.
This is absolutely correct but the specialized types provide the following features.
Features of entity links
Get link component of an entity with entity.GetComponent<AttackComponent>()
in O(1).
Get link relations of an entity with entity.GetRelations<AttackRelation>()
in O(1).
Get entities including outgoing links referencing a specific target entity with
target.GetIncomingLinks<AttackComponent>()
in O(1). This make links bidirectional.
Automatically removing links from all entities having a link to a target entity that is deleted.
Get all entities in an EntityStore linked by a specific link type using
store.GetAllLinkedEntities<AttackComponent>()
in O(1).
Add multiple links to a single entity using Link Relations.
Show and navigate all incoming entity links in a debugger
Comparison to implementations in other ECS projects.
Entity relationships in flecs and BEVY are modeled as component/entity pairs added to entities. The main differences are:
In flecs links between entities can be created adhoc. This ECS requires to define a specific component type to create links between entities. This simplifies code navigation and establish a clear overview what types of links are used in a project.
The API to create and query relations in flecs is very compact but not very intuitive - imho. It is completely different from common component handling. See flecs ⋅ Relationships
Adding, removing or updating a link does not cause archetype fragmentation. In flecs every relationship between two entities creates an individual archetype only containing a single entity / component. So each entity relationship allocates ~ 1000 bytes required by the archetype stored in the heap for each link. The more significant performance penalty is the side effect in queries. Many archetypes need to be iterated if they are query matches.
Changing an entity link does not cause a structural change. In flecs a new archetype needs to be created and the old archetype gets empty.
Big shout out to fennecs and flecs for the challenge to improve the feature set and performance of this project!
A link component enables adding a link to another target entity. An entity can have only one link component at a time. Link components are added, removed and queried like common components with
The following example illustrate the state changes by small text graphs on the right.
Link components are represented by →
icon.
This example show the graph changes when adding link components or deleting entities.
A link relation enables adding multiple links to a single entity referencing other target entities. There can be only one link relation per target entity. Link relations are added, removed and queried with
Methods to mutate and query link relation are at RelationExtensions.
The following example illustrate the state changes by small text graphs on the right.
Link relations are represented by →
icon.
This example show the graph changes when adding link relations or deleting entities.
A relation component enables adding multiple components of the same type to an entity.
A typical limitation of an archetype-based ECS is that an entity can only contain one component of a certain type. When adding a component of a specific type to an entity already present the component is updated. This is the common behavior implemented by most ECS implementations like EnTT, flecs, BEVY, fennecs, ... . An alternative approach is allowing undefined behavior like Arch resulting in memory corruption, access violation, ... .
A relation in mathematical context describes a connection between elements of two sets. Transferred to this ECS: The first set are all entities the second set are all possible relation keys.
A relation component that can be added to an entity multiple times must implement IRelationComponent<>
.
To distinguish between different relations, a relation type must implement GetRelationKey()
.
Any type can be used as relation key type. E.g. enum, string, int, long, Guid, DateTime, ... .
Relations are added, removed and queried with
The following example illustrates the state changes of a specific entity regarding its relations.
It uses an enum
as relation key type.
This ECS enables efficient search of indexed component fields.
This enables full-text search by using string
as the indexed component type like in the example below.
Any type can be used as indexed component type. E.g. int, long, float, Guid, DateTime, enum, ... .
A search / query for a specific value executes in O(1).
An IIndexedComponent<> provide the same functionality and behavior as normal components implementing IComponent
.
Indexing is implement using an inverted index ⋅ Wikipedia.
Adding, removing or updating an indexed component updates the index.
These operations are executed in O(1) but significant slower than the non indexed counterparts ~10x.
Performance: Indexing 1000 different component values ~60 μs.
Range query
In case the indexed component type implements IComparable<>
like int, string, DateTime, ... range queries can be executed.
A range query returns all entities with a component value in the specified range. See example code.
Methods to query indexed components are at IndexExtensions.
All specialized component types are optimized for performance and low memory footprint. Read and write access to those components are executed without boxing. Each component type requires a single dictionary and up to a dozen arrays, regardless of the number of components stored. E.g. one, one thousand or one million components. This make these component types as friendly for garbage collection as general components. If the array buffers are large enough, there are no further memory allocations.
The number of link relations / relations per entity should not exceed 100. The reason is that inserting and removing a relation is executed in O(N). N: number of relations per entity.
Benchmark see Tests Add 1.000.000 int relation components. Type:
The number of components having the same key value should not exceed 100. The reason is that inserting and removing a component to / from the index is executed in O(N). N: number of components having the same key value (duplicates).
Benchmark see Tests Add 1.000.000 indexed int components. Each to an individual entity. Type:
Screenshot: Show and navigate all incoming entity links in a debugger at Info.IncomingLinks
relationsPerEntity | duration ms | entities |
---|---|---|
duplicateCount | duration ms | entities |
---|---|---|
A single link on an entity referencing another entity.
Multiple links on an entity referencing other entities.
Add multiple components of the same type to an entity.
Full text search of component fields executed in O(1). Range queries on component fields having a sort order.
1
60.17
1000000
2
64.11
500000
4
62.68
250000
8
66.66
125000
16
67.71
62500
32
90.53
31250
64
150.14
15625
128
154.39
7813
256
254.26
3907
512
416.20
1954
1024
772.39
977
2048
1504.50
489
4096
2993.34
245
8192
5867.59
123
1
119.68
1000000
2
91.73
1000000
4
96.08
1000000
8
97.06
1000000
16
103.75
1000000
32
150.19
1000000
64
98.52
1000000
128
106.64
1000000
256
111.62
1000000
512
123.53
1000000
1024
137.13
1000000
2048
254.83
1000000
4096
234.58
1000000
8192
433.60
1000000
16384
817.92
1000000
32768
1662.16
1000000