Keine Beschreibung

Michele Caini d26e163f66 rollback to libstdc++-4.9-dev vor 8 Jahren
build b0b8ee7aea init vor 9 Jahren
cmake 63c1b046e0 WIP: tests + bug fixing vor 8 Jahren
deps a2b0a09f9a Build system (#7) vor 8 Jahren
src 6791cf1e2e minor changes vor 8 Jahren
test 6c925a32b4 updated registry/sparse_set vor 8 Jahren
.gitignore b0b8ee7aea init vor 9 Jahren
.travis.yml d26e163f66 rollback to libstdc++-4.9-dev vor 8 Jahren
AUTHORS b0b8ee7aea init vor 9 Jahren
CMakeLists.txt e47fb67d74 it seems that Travis CI doesn't like much libc++ vor 8 Jahren
LICENSE b0b8ee7aea init vor 9 Jahren
README.md 2aeec2d50f WIP: docs vor 8 Jahren
appveyor.yml 87a1062bde appveyor stuff vor 8 Jahren

README.md

EnTT - Entity-Component System in modern C++

Build Status Build status Coverage Status Donate

Introduction

EnTT is a header-only, tiny and easy to use Entity-Component System in modern C++.
ECS is an architectural pattern used mostly in game development. For further details:

Code Example

#include <registry.hpp>

struct Position {
    float x;
    float y;
};

struct Velocity {
    float dx;
    float dy;
};

using ECS = entt::DefaultRegistry<Position, Velocity>;

int main() {
    ECS ecs;

    for(auto i = 0; i < 10; ++i) {
        auto entity = ecs.create();
        ecs.assign<Position>(entity, i * 1.f, i * 1.f);
        if(i % 2 == 0) { ecs.assign<Velocity>(entity, i * .1f, i * .1f); }
    }

    // single component view

    for(auto entity: ecs.view<Position>()) {
        auto &position = ecs.get<Position>(entity);
        // do whatever is needed with position
        (void)position;
    }

    // multi component view

    for(auto entity: ecs.view<Position, Velocity>()) {
        auto &position = ecs.get<Position>(entity);
        auto &velocity = ecs.get<Velocity>(entity);
        // do whatever is needed with position and velocity
        (void)position;
        (void)velocity;
    }
}

Motivation

I started working on EnTT because of the wrong reason: my goal was to beat another well known open source ECS in terms of performance. I did it, of course, but it wasn't much satisfying. Actually it wasn't satisfying at all. The fastest and nothing more, fairly little indeed.
When I realized it, I tried hard to keep intact the great performance and to add all the features I want to see in my ECS at the same time.

Today EnTT is finally what I was looking for: still faster than its rivals, a really good API and an amazing set of features.

Performance

As it stands right now, EnTT is just fast enough for my requirements if compared to my first choice (a well known open source ECS that was already amazingly fast).
Here is a comparision (both of them compiled with GCC 7.2.0 on a Dell XPS 13 coming from the late 2014):

Benchmark EntityX (experimental/compile_time) EnTT
Creating 10M entities 0.290214s 0.143111s
Destroying 10M entities 0.104589s 0.0917096s
Iterating over 10M entities, unpacking one component 0.0165628s 1.37e-07s
Iterating over 10M entities, unpacking two components 0.0137463s 0.0052857s
Iterating over 10M entities, unpacking two components, half of the entities have all the components 0.011949s 0.00268133s
Iterating over 10M entities, unpacking two components, one of the entities has all the components 0.0115935s 6.4e-07s
Iterating over 10M entities, unpacking five components 0.0143905s 0.00527808s
Iterating over 10M entities, unpacking ten components 0.0165853s 0.00528096s
Iterating over 10M entities, unpacking ten components, half of the entities have all the components 0.0169309s 0.00327983s
Iterating over 10M entities, unpacking ten components, one of the entities has all the components 0.0110304s 8.11e-07s
Iterating over 50M entities, unpacking one component 0.0827622s 1.65e-07s
Iterating over 50M entities, unpacking two components 0.0830518s 0.0265629s

See benchmark.cpp for further details.
Even though EnTT includes its own tests and benchmarks, on Github there exists also a benchmark suite that compares a bunch of different projects, one of which is EnTT.

Of course, I'll try to get out of EnTT more features and better performance anyway in the future, mainly for fun.
If you want to contribute and/or have any suggestion, feel free to make a PR or open an issue to discuss your idea.

Build Instructions

Requirements

To be able to use EnTT, users must provide a full-featured compiler that supports at least C++14.
CMake version 3.2 or later is mandatory to compile the tests, users don't have to install it otherwise.

Library

EnTT is a header-only library. This means that including the registry.hpp header is enough to use it.
It's a matter of adding the following line at the top of a file:

#include <registry.hpp>

Then pass the proper -I argument to the compiler to add the src directory to the include paths.

Documentation

API Reference

Unfortunately EnTT isn't documented yet and thus users cannot rely on in-code documentation.
Source code and names are self-documenting and I'm pretty sure that a glimpse to the API is enough for most of the users.
For all the others, below is a crash course that guides them through the project and tries to fill the gap.

Crash Course

EnTT has two main actors: the Registry and the View.
The former can be used to manage components, entities and collections of components and entities. The latter allows users to iterate over the underlying collections.

The Registry

There are two options to instantiate a registry:

  • By using the default one:

    auto registry = entt::DefaultRegistry<Components...>{args...};
    

That is, users must provide the whole list of components to be registered with the default registry.

  • By using directly the Registry class:

    auto registry = entt::Registry<std::uint16_t, Components...>{args...};
    

That is, users must provide the whole list of components to be registered with the registry and the desired type for the entities. Note that the default type is std::uint32_t, that is larger enough for almost all the games but also too big for the most of the games.

In both cases there are no requirements for the components but to be moveable, therefore POD types are just fine.

The Registry class offers a bunch of basic functionalities to query the internal structures. In almost all the cases those member functions can be used to query either the entity list or the component lists.
As an example, the member functions empty can be used to know if at least an entity exists and/or if at least one component of the given type has been assigned to an entity:

bool b = registry.empty();
// ...
bool b = registry.empty<MyComponent>();

Similarly, size can be used to know the number of entities alive and/or the number of components of a given type still assigned to entities. capacity follows the same pattern and returns the storage capacity for the given element.

To know if an entity is valid, the valid member function is part of the registry interface:

bool b = registry.valid(entity);

Let's go to something more tasty.

The create member function can be used to create a new entity and it comes in two flavors:

  • The plain version just creates a naked entity with no components assigned to it:

    auto entity = registry.create();
    
  • The member function template creates an entity and assigns to it the given default-initialized components:

    auto entity = registry.create<Position, Velocity>();
    

It's a helper function, mostly syncactic sugar and it's equivalent to the following snippet:

```cpp
auto entity = registry.create();
registry.assign<Position>();
registry.assign<Velocity>();
```

See below to find more about the assign member function.

The destroy member function can be used instead to delete an entity and all its components (if any):

registry.destroy(entity);

On the other side, if the purpose is to remove a component, the remove member function template is the way to go:

registry.remove<Position>(entity);

The reset member function can be used to obtain the same result with a strictly defined behaviour (a performance penalty is the price to pay for that anyway). In particular it removes the component if and only if it exists, otherwise it safely returns to the caller:

registry.reset<Position>(entity);

There exist also two other versions of the reset member function:

  • If no entity is passed to it, reset will remove the given component from each entity that has it:

    registry.reset<Position>();
    
  • If neither the entity nor the component are specified, all the entities and their components are destroyed:

    registry.reset();
    

Note: the registry has an assert in debug mode that verifies that entities are no longer valid when it's destructed. This function can be used to reset the registry to its initial state and thus satisfy the requirement.

To assign a component to an entity, users can use the assign member function template. It accepts a variable number of arguments that, if present, are used to construct the component itself:

registry.assign<Position>(entity, 0., 0.);

// ...

auto &velocity = registr.assign<Velocity>(entity);

velocity.dx = 0.;
velocity.dy = 0.;

If the entity already has the given component and the user wants to replace it, the replace member function template is the way to go:

registry.replace<Position>(entity, 0., 0.);

// ...

auto &velocity = registr.replace<Velocity>(entity);

velocity.dx = 0.;
velocity.dy = 0.;

In case users want to assign a component to an entity, but it's unknown whether the entity already has it or not, accomodote does the work in a single call:

registry.accomodate<Position>(entity, 0., 0.);

// ...

auto &velocity = registr.accomodate<Velocity>(entity);

velocity.dx = 0.;
velocity.dy = 0.;

Note that accomodate is a sliglhty faster alternative for the following if/else statement:

if(registry.has<Comp>(entity)) {
    registry.replace<Comp>(entity, arg1, argN);
} else {
    registry.assign<Comp>(entity, arg1, argN);
}

As already shown, if in doubt about whether or not an entity has one or more components, the has member function template may be useful:

bool b = registry.has<Position, Velocity>(entity);

Entities can also be cloned and partially or fully copied:

auto entity = registry.clone(other);

// ...

auto &velocity = registry.copy<Velocity>(to, from);

// ...

registry.copy(dst, src);

In particular:

  • The clone member function creates a new entity and copy all the components from the given one.
  • The copy member function template copies one component from an entity to another one.
  • The copy member function copies all the components from an entity to another one.

There exists also an utility member function that can be used to swap components between entities:

registry.swap<Position>(e1, e2);

The get member function template (either the non-const or the const version) gives direct access to the component of an entity instead:

auto &position = registry.get<Position>(entity);

Components can also be sorted in memory by means of the sort member function templates. In particular:

  • Components can be sorted according to a component:

    registry.sort<Renderable>([](const auto &lhs, const auto &rhs) { return lhs.z < rhs.z; });
    
  • Components can be sorted according to the order imposed by another component:

    registry.sort<Movement, Physics>();
    

In this case, Movement components are arranged in memory so that cache misses are minimized when the two components are iterated together.

Note. Several functions require that entities received as arguments are valid. In case they are not, an assertion will fail in debug mode and the behaviour is undefined in release mode.
Here is the full list of functions for which the requirement applies: destroy, assign, remove, has, get, replace, accomodate, clone, copy, swap, reset.

Finally, the view member function template returns an iterable portion of entities and components (see below for more details):

auto view = registry.view<Position, Velocity>();

Views are the other core component of EnTT and are usually extensively used by softwares that include it.

The View

TODO (ricorda di menzionare aggiunte/cancellazioni durante iterazioni)

Side notes

  • Entities are numbers and nothing more. They are not classes and they have no member functions at all.

  • Most of the ECS available out there have an annoying limitation (at least from my point of view): entities and components cannot be created, assigned or deleted while users are iterating on them.
    EnTT partially solves the problem with a few limitations:

    • Entities can be created at any time while iterating on one or more components.
    • Components can be assigned to any entity at any time while iterating on one or more components.
    • During an iteration, the current entity (that is the one returned by the iterator) can be deleted and all its components can be removed safely.

Entities that are not the current one (that is the one returned by the iterator) cannot be deleted. Components assigned to entities that are not the current one (that is the one returned by the iterator) cannot be removed.
In this case, iterators are invalidated and the behaviour is undefined if one continues to use those iterators. Possible approaches are:

* Store aside the entities and components to be removed and perform the operations at the end of the iteration.
* Mark entities and components with a proper tag component that indicates that they must be purged, then perform a second iteration to clean them up one by one.
  • Iterators aren't thread safe. Do no try to iterate over a set of components and modify them concurrently.
    That being said, as long as a thread iterates over the entities that have the component X or assign and removes that component from a set of entities and another thread does something similar with components Y and Z, it shouldn't be a problem at all.
    As an example, that means that users can freely run the rendering system over the renderable entities and update the physics concurrently on a separate thread if needed.

Tests

To compile and run the tests, EnTT requires googletest.
cmake will download and compile the library before to compile anything else.

Then, to build the tests:

  • $ cd build
  • $ cmake ..
  • $ make
  • $ make test

To build the benchmarks, use the following line instead:

  • $ cmake -DCMAKE_BUILD_TYPE=Release ..

Benchmarks are compiled only in release mode currently.

Contributors

If you want to contribute, please send patches as pull requests against the branch master.
Check the contributors list to see who has partecipated so far.

License

Code and documentation Copyright (c) 2017 Michele Caini.
Code released under the MIT license.

Donation

Developing and maintaining EnTT takes some time and lots of coffee. If you want to support this project, you can offer me an espresso. I'm from Italy, we're used to turning the best coffee ever in code.
Take a look at the donation button at the top of the page for more details or just click here.