XECS
is a small but powerfull, header-only entity-component system that uses compile-time archetypes written in modern c++.
Entity–component–system (ECS) is an architectural pattern used to build high performance applications such as games or simulations. This pattern is usually a combination the following two philosophies:
- Data-oriented design over object-oriented design to take advantage of the cache in todays processors making code more efficient.
- Composition over inheritance for better modularity and higher flexibility.
This particular library takes a more modern, and increasingly popular approach know as archetypes. Archetypes are essentially a grouping of components that allow us to optimize our storage layout.
For further details:
- ECS: ECS Wikipedia
Performance is a huge part of this project. I have fairly benchmarked against some of the most known and powerfull open-source ECS libraries and I can say with pretty good confidence that this library is very efficient. Not just in terms of speed but also in memory.
This project includes some benchmarks that you can try for yourself and see if your satisfied with the results.
This library performs very well generally, but where it shines the most is when iterating over multiple components with complex relations. This makes this implementation a lot more scalable in terms speed. Iterating over multiple components is at least 2 times faster (very conservative) than any other ECS library.
More memory for more speed is a typical tradeoff in most ECS implementations, however clever compile-time optimizations make this implementation have very little memory overhead. For an entity with any given archetype, the memory cost of storing it is: 2 * (size of entity identifier) + (size of all components in the archetype).
Include
#include <xecs.hpp>
Component Creation
Components are simply just structs.
struct Position
{
float x;
float y;
};
struct Velocity
{
float x;
float y;
};
Compile-time configuration
You must first choose a unsigned int type to be your entity type.
using entity = uint32_t;
You must declare all your archetypes, you can use the builder utility.
using archetypes = xecs::archetype_list_builder::
add<xecs::archetype<Position>>::
add<xecs::archetype<Position, Velocity>>::
build;
Registry creation
A registry is where your entities and components are stored and managed.
xecs::registry<entity, archetypes> registry;
Entity creation & initialization
Call the create method with all the components that the entity will have (The archetype of the component).
Excample for archetype [Position]:
registry.create(Position { 10, 20 });
Example for archetype [Position, Velocity]:
registry.create(Position { 5, 99 }, Velocity { 3, 5 });
Entity destruction
Call the destroy method and pass the entity.
entity entity_to_destroy = registry.create(Position { });
registry.destroy(entity_to_destroy);
Component unpacking
You can call the unpack method to obtain a reference of the component your trying to access.
entity entity_to_unpack = registry.create(Position { }, Velocity { });
registry.unpack<Position>(entity_to_unpack) = Position { 1, 1 };
registry.unpack<Velocity>(entity_to_unpack) = Velocity { 2, 2 };
Iterating
Iterate over all entity identifiers
registry.for_each([](const auto entity)
{
/* ... */
});
Iterate over all entities with a specified component
registry.for_each<Position>([](const auto entity, const auto& position)
{
/* ... */
});
Iterate over all entities with multiple specified component
registry.for_each<Position, Velocity>([](const auto entity, const auto& position, const auto& velocity)
{
/* ... */
});
Using a view
auto view = registry.view<Position, Velocit>();
view.for_each([](const auto entity, const auto& position, const auto& velocity)
{
/* ... */
});
To be able to use XECS
you must have a standard compliant compiler that supports at least C++17.
The following platforms and compilers are continously tested to work:
- Windows
- msvc
- Windows-2016
- default
- Ubuntu
- gcc
- clang
- MacOS
- default
XECS
is a header-only library. All you need to do is include the headers.
The simplest way would be to simply add the single header file single/xecs.hpp
to your project.
googletest
is used as our testing framework. CMake will sets all this up for you.
The cmake option XECS_BUILD_TESTING
is used to determine if testing will be built.
Using visual studio
cd build
cmake -DXECS_BUILD_TESTING=ON ..
- Open the generated solution
- Build and run the
test
project
Using make
$ cd build
$ cmake -DXECS_BUILD_TESTING=ON ..
$ make
$ ./test/tests
No dependancies are used for benchmarking.
You must make sure your are in release mode, or else compiler optimizations wont be enabled and benchmarking will be quite pointless...
The cmake option XECS_BUILD_BENCHMAKING
is used to determine if benchmarking will be built.
Using visual studio
cd build
cmake -XECS_BUILD_BENCHMAKING=ON ..
- Open the generated solution
- Set your build to release mode
- Build and run the
benchmarks
project
Using make
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Release -XECS_BUILD_BENCHMAKING=ON ..
$ make
$ ./bench/benchmarks