Skip to content

Fast entity-component-system (ECS) with compile-time archetypes

License

Notifications You must be signed in to change notification settings

MathieuDonofrio/xecs

Repository files navigation

XECS

Build Status Code Coverage Try online

XECS is a small but powerfull, header-only entity-component system that uses compile-time archetypes written in modern c++.

Table of Contents

Introduction

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:

Performance

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).

Code Examples

Tutorial

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)
{
  /* ... */
});

Build Instructions

Requirements

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

Library

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.

Tests

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

Benchmarks

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