Skip to content

Commit

Permalink
merge from master
Browse files Browse the repository at this point in the history
  • Loading branch information
skypjack committed Mar 14, 2018
1 parent 86b7018 commit d1db029
Show file tree
Hide file tree
Showing 19 changed files with 278 additions and 137 deletions.
76 changes: 46 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Requests for feature, PR, suggestions ad feedback are highly appreciated.

If you find you can help me and want to contribute to the `EnTT` framework with
your experience or you do want to get part of the project for some other
reason, feel free to contact me directly (you can find the mail in the
reasons, feel free to contact me directly (you can find the mail in the
[profile](https://github.com/skypjack)).<br/>
I can't promise that each and every contribution will be accepted, but I can
assure that I'll do my best to take them all seriously.
Expand Down Expand Up @@ -175,8 +175,7 @@ case.<br/>
In the end, I did it, 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 of `EnTT` and to
add all the features I wanted to see in *my* entity-component system at the same
time.
add all the features I wanted to see in *my own library* at the same time.
Today `EnTT` is finally what I was looking for: still faster than its
_competitors_, lower memory usage in the average case, a really good API and an
Expand All @@ -186,39 +185,46 @@ amazing set of features. And even more, of course.
As it stands right now, `EnTT` is just fast enough for my requirements if
compared to my first choice (it was already amazingly fast actually).<br/>
Here is a comparison between the two (both of them compiled with GCC 7.3.0 on a
Below is a comparison between the two (both of them compiled with GCC 7.3.0 on a
Dell XPS 13 out of the mid 2014):
| Benchmark | EntityX (compile-time) | EnTT |
|-----------|-------------|-------------|
| Create 1M entities | 0.0167s | **0.0046s** |
| Destroy 1M entities | 0.0053s | **0.0022s** |
| Destroy 1M entities | 0.0053s | **0.0039s** |
| Standard view, 1M entities, one component | 0.0012s | **1.9e-07s** |
| Standard view, 1M entities, two components | 0.0012s | **0.0010s** |
| Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **0.0006s** |
| Standard view, 1M entities, two components | 0.0012s | **3.8e-07s** |
| Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
| Standard view, 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
| Persistent view, 1M entities, two components | 0.0012s | **2.8e-07s** |
| Standard view, 1M entities, five components | **0.0010s** | 0.0024s |
| Standard view, 1M entities, five components | 0.0010s | **7.0e-07s** |
| Persistent view, 1M entities, five components | 0.0010s | **2.8e-07s** |
| Standard view, 1M entities, ten components | **0.0011s** | 0.0058s |
| Standard view, 1M entities, ten components<br/>Half of the entities have all the components | **0.0010s** | 0.0032s |
| Standard view, 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.7e-06s** |
| Standard view, 1M entities, ten components | 0.0011s | **1.2e-06s** |
| Standard view, 1M entities, ten components<br/>Half of the entities have all the components | 0.0010s | **1.2e-06s** |
| Standard view, 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.2e-06s** |
| Persistent view, 1M entities, ten components | 0.0011s | **3.0e-07s** |
| Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0036s** |
| Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0005s** |
Note: The default version of `EntityX` (`master` branch) wasn't added to the
comparison because it's already much slower than its compile-time counterpart.
`EnTT` includes its own tests and benchmarks. See
[benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp)
for further details.<br/>
On Github users can find also a
[benchmark suite](https://github.com/abeimler/ecs_benchmark) that compares a
bunch of different projects, one of which is `EnTT`.
Probably I'll try to get out of `EnTT` more features and better performance in
the future, mainly for fun.<br/>
Pretty interesting, aren't them? In fact, these benchmarks are the same used by
`EntityX` to show _how fast it is_. To be honest, they aren't so good and these
results shouldn't be taken much seriously (they are completely unrealistic
indeed).<br/>
The proposed entity-component system is incredibly fast to iterate entities,
this is a fact. The compiler can make a lot of optimizations because of how
`EnTT` works, even more when components aren't used at all. This is exactly the
case for these benchmarks.<br/>
This is why they are completely wrong and cannot be used to evaluate any of the
entity-component systems.
If you decide to use `EnTT`, choose it because of its API and its performance,
not because there is a benchmark somewhere that makes it seem the fastest.
Probably I'll try to get out of `EnTT` more features and even better performance
in the future, mainly for fun.<br/>
If you want to contribute and/or have any suggestion, feel free to make a PR or
open an issue to discuss your idea.
Expand Down Expand Up @@ -333,7 +339,7 @@ The `Registry` to store, the `View` to iterate. That's all.

An entity (the _E_ of an _ECS_) is an opaque identifier that users should just
use as-is and store around if needed. Do not try to inspect an entity
identifier, its type can change in future and a registry offers all the
identifier, its format can change in future and a registry offers all the
functionalities to query them out-of-the-box. The underlying type of an entity
(either `std::uint16_t`, `std::uint32_t` or `std::uint64_t`) can be specified
when defining a registry (actually the `DefaultRegistry` is nothing more than a
Expand Down Expand Up @@ -432,21 +438,21 @@ 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, `accomodate` does the work in a single call
the entity already has it or not, `accommodate` does the work in a single call
(there is a performance penalty to pay for this mainly due to the fact that it
has to check if the entity already has the given component or not):

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

// ...

Velocity &velocity = registry.accomodate<Velocity>(entity);
Velocity &velocity = registry.accommodate<Velocity>(entity);
velocity.dx = 0.;
velocity.dy = 0.;
```

Note that `accomodate` is a sliglhty faster alternative for the following
Note that `accommodate` is a slightly faster alternative for the following
`if`/`else` statement and nothing more:

```cpp
Expand Down Expand Up @@ -890,6 +896,14 @@ the best way to do it. However, feel free to use it at your own risk.

## View: to persist or not to persist?

First of all, it is worth answering an obvious question: why views?<br/>
Roughly speaking, they are a good tool to enforce single responsibility. A
system that has access to a registry can create and destroy entities, as well as
assign and remove components. On the other side, a system that has access to a
view can only iterate entities and their components as well as modify their data
members.<br/>
It is a subtle difference that can help designing a better software sometimes.

There are mainly two kinds of views: standard (also known as `View`) and
persistent (also known as `PersistentView`).<br/>
Both of them have pros and cons to take in consideration. In particular:
Expand Down Expand Up @@ -967,7 +981,7 @@ terms of performance in all the situation. This kind of views can access the
underlying data structures directly and avoid superfluous checks.<br/>
They offer a bunch of functionalities to get the number of entities they are
going to return and a raw access to the entity list as well as to the component
list.<br/>
list. It's also possible to ask a view if it contains a given entity.<br/>
Refer to the [official documentation](https://skypjack.github.io/entt/) for all
the details.

Expand Down Expand Up @@ -1012,7 +1026,8 @@ set of candidates in order to speed up iterations.<br/>
They offer fewer functionalities than their companion views for single
component. In particular, a multi component standard view exposes utility
functions to reset its internal state (optimization purposes) and to get the
estimated number of entities it is going to return.<br/>
estimated number of entities it is going to return. It's also possible to ask a
view if it contains a given entity.<br/>
Refer to the [official documentation](https://skypjack.github.io/entt/) for all
the details.

Expand Down Expand Up @@ -1088,7 +1103,8 @@ immediately and does nothing.
A persistent view offers a bunch of functionalities to get the number of
entities it's going to return, a raw access to the entity list and the
possibility to sort the underlying data structures according to the order of one
of the components for which it has been constructed.<br/>
of the components for which it has been constructed. It's also possible to ask a
view if it contains a given entity.<br/>
Refer to the [official documentation](https://skypjack.github.io/entt/) for all
the details.

Expand Down Expand Up @@ -1844,7 +1860,7 @@ There are two types of signal handlers in `EnTT`, internally called _managed_
and _unmanaged_.<br/>
They differ in the way they work around the tradeoff between performance, memory
usage and safety. Managed listeners must be wrapped in an `std::shared_ptr` and
the sink will take care of disconneting them whenever they die. Unmanaged
the sink will take care of disconnecting them whenever they die. Unmanaged
listeners can be any kind of objects and the client is in charge of connecting
and disconnecting them from a sink to avoid crashes due to different lifetimes.

Expand Down Expand Up @@ -2219,7 +2235,7 @@ As an example:

```cpp
dispatcher.trigger<AnEvent>(42);
dispatcher.trigget<AnotherEvent>();
dispatcher.trigger<AnotherEvent>();
```

Listeners are invoked immediately, order of execution isn't guaranteed. This
Expand Down
6 changes: 5 additions & 1 deletion TODO
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
* to analyze, long term feature: systems organizer based on dependency graphs for implicit parallelism (I don't want to think anymore in future :-))
* save/restore functionalities - see #27
* parent-child relationships between entities directly managed by the registry. is it possible to do that in a clean and safe way?
* blueprint registry - external tool, kind of factory to create entitites template for initialization
* scene management (I prefer the concept of spaces, that is a kind of scene anyway)
* blueprint registry - kind of factory to create entitites template for initialization (get rid of the extra versions of Registry::create)
* raw view (affects sparse sets - custom iterator in derived one - and introduces a new kind of view): single component view to iterate components only instead of entities (in the right order!!)
it should speed up systems like rendering or whatever requires a single component and isn't interested in the entity, for it avoids the double check of the get
<<<<<<< HEAD
=======
* review doc: separate it in multiple md/dox files, reduce the readme to a minimum and provide users with links to the online documentation on gh-pages
>>>>>>> master
* AOB
2 changes: 1 addition & 1 deletion src/entt/entity/actor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ struct Actor {
*/
template<typename Component, typename... Args>
Component & set(Args &&... args) {
return reg.template accomodate<Component>(entity, std::forward<Args>(args)...);
return reg.template accommodate<Component>(entity, std::forward<Args>(args)...);
}

/**
Expand Down
46 changes: 35 additions & 11 deletions src/entt/entity/registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,15 +287,39 @@ class Registry {
}

/**
* @brief Verifies if an entity identifier still refers to a valid entity.
* @brief Checks if an entity identifier refers to a valid entity.
* @param entity An entity identifier, either valid or not.
* @return True if the identifier is still valid, false otherwise.
* @return True if the identifier is valid, false otherwise.
*/
bool valid(entity_type entity) const noexcept {
const auto pos = size_type(entity & traits_type::entity_mask);
return (pos < entities.size() && entities[pos] == entity);
}

/**
* @brief Checks if an entity identifier refers to a valid entity.
*
* Alternative version of `valid`. It accesses the internal data structures
* without bounds checking and thus it's both unsafe and risky to use.<br/>
* You should not invoke directly this function unless you know exactly what
* you are doing. Prefer the `valid` member function instead.
*
* @warning
* Attempting to use an entity that doesn't belong to the registry can
* result in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode in case of
* bounds violation.
*
* @param entity A valid entity identifier.
* @return True if the identifier is valid, false otherwise.
*/
bool fast(entity_type entity) const noexcept {
const auto pos = size_type(entity & traits_type::entity_mask);
assert(pos < entities.size());
// the in-use control bit permits to avoid accessing the direct vector
return (entities[pos] == entity);
}

/**
* @brief Returns the version stored along with an entity identifier.
* @param entity An entity identifier, either valid or not.
Expand Down Expand Up @@ -752,7 +776,7 @@ class Registry {
* }
* @endcode
*
* Prefer this function anyway because it has slighlty better
* Prefer this function anyway because it has slightly better
* performance.
*
* @warning
Expand All @@ -767,7 +791,7 @@ class Registry {
* @return A reference to the newly created component.
*/
template<typename Component, typename... Args>
Component & accomodate(entity_type entity, Args &&... args) {
Component & accommodate(entity_type entity, Args &&... args) {
assert(valid(entity));
auto &cpool = ensure<Component>();

Expand All @@ -779,7 +803,7 @@ class Registry {
/**
* @brief Sorts the pool of entities for the given component.
*
* The order of the elements in a pool is highly affected by assignements
* The order of the elements in a pool is highly affected by assignments
* of components to entities and deletions. Components are arranged to
* maximize the performance during iterations and users should not make any
* assumption on the order.<br/>
Expand Down Expand Up @@ -807,7 +831,7 @@ class Registry {
/**
* @brief Sorts two pools of components in the same way.
*
* The order of the elements in a pool is highly affected by assignements
* The order of the elements in a pool is highly affected by assignments
* of components to entities and deletions. Components are arranged to
* maximize the performance during iterations and users should not make any
* assumption on the order.
Expand Down Expand Up @@ -924,7 +948,7 @@ class Registry {
template<typename Func>
void each(Func func) const {
if(available) {
for(auto pos = entities.size(); pos > size_type{0}; --pos) {
for(auto pos = entities.size(); pos; --pos) {
const entity_type curr = pos - 1;
const auto entt = entities[curr] & traits_type::entity_mask;

Expand All @@ -933,7 +957,7 @@ class Registry {
}
}
} else {
for(auto pos = entities.size(); pos > size_type{0}; --pos) {
for(auto pos = entities.size(); pos; --pos) {
func(entities[pos-1]);
}
}
Expand Down Expand Up @@ -1001,7 +1025,7 @@ class Registry {
* As a rule of thumb, storing a view should never be an option.
*
* Standard views do their best to iterate the smallest set of candidate
* entites. In particular:
* entities. In particular:
*
* * Single component views are incredibly fast and iterate a packed array
* of entities, all of which has the given component.
Expand Down Expand Up @@ -1039,7 +1063,7 @@ class Registry {
* requested.<br/>
* To avoid costly operations, internal data structures for persistent views
* can be prepared with this function. Just use the same set of components
* that would have been used otherwise to contruct the view.
* that would have been used otherwise to construct the view.
*
* @tparam Component Types of components used to prepare the view.
*/
Expand Down Expand Up @@ -1098,7 +1122,7 @@ class Registry {
* initialization.<br/>
* As a rule of thumb, storing a view should never be an option.
*
* Persistent views are the right choice to iterate entites when the number
* Persistent views are the right choice to iterate entities when the number
* of components grows up and the most of the entities have all the given
* components.<br/>
* However they have also drawbacks:
Expand Down
4 changes: 2 additions & 2 deletions src/entt/entity/snapshot.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ class ContinuousLoader final {

each(archive, [&archive, this](auto entity) {
entity = restore(entity);
archive(registry.template accomodate<Component>(entity));
archive(registry.template accommodate<Component>(entity));
});
}

Expand All @@ -441,7 +441,7 @@ class ContinuousLoader final {

each(archive, [&archive, member..., this](auto entity) {
entity = restore(entity);
auto &component = registry.template accomodate<Component>(entity);
auto &component = registry.template accommodate<Component>(entity);
archive(component);

using accumulator_type = int[];
Expand Down
4 changes: 2 additions & 2 deletions src/entt/entity/sparse_set.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ class SparseSet<Entity> {

pos_type pos = direct.size() - 1;

while(pos > 0 && from != to) {
while(pos && from != to) {
if(has(*from)) {
if(*from != direct[pos]) {
swap(pos, get(*from));
Expand Down Expand Up @@ -692,7 +692,7 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
pos_type pos = underlying_type::size() - 1;
const auto *local = underlying_type::data();

while(pos > 0 && from != to) {
while(pos && from != to) {
const auto curr = *from;

if(underlying_type::has(curr)) {
Expand Down
Loading

0 comments on commit d1db029

Please sign in to comment.