improved multi component standard view

This commit is contained in:
Michele Caini
2018-03-07 17:25:19 +01:00
parent c588fff5ca
commit a2e243d992
3 changed files with 85 additions and 32 deletions

View File

@@ -186,7 +186,7 @@ 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.2.0 on a
Here 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 |
@@ -194,18 +194,21 @@ Dell XPS 13 out of the mid 2014):
| Create 1M entities | 0.0167s | **0.0046s** |
| Destroy 1M entities | 0.0053s | **0.0022s** |
| Standard view, 1M entities, one component | 0.0012s | **1.9e-07s** |
| Standard view, 1M entities, two components | **0.0012s** | 0.0013s |
| Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **0.0007s** |
| Standard view, 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.3e-06s** |
| 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<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.0034s |
| Standard view, 1M entities, five components | **0.0010s** | 0.0024s |
| Persistent view, 1M entities, five components | 0.0010s | **2.8e-07s** |
| Standard view, 1M entities, ten components | **0.0011s** | 0.0075s |
| Standard view, 1M entities, ten components<br/>Half of the entities have all the components | **0.0010s** | 0.0041s |
| 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** |
| Persistent view, 1M entities, ten components | 0.0011s | **3.0e-07s** |
| Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0040s** |
| Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0006s** |
| 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)

View File

@@ -139,6 +139,20 @@ public:
direct.reserve(cap);
}
/**
* @brief Returns the extent of a sparse set.
*
* The extent of a sparse set is also the size of the internal sparse array.
* There is no guarantee that the internal packed array has the same size.
* Usually the size of the internal sparse array is equal or greater than
* the one of the internal packed array.
*
* @return Extent of the sparse set.
*/
size_type extent() const noexcept {
return reverse.size();
}
/**
* @brief Returns the number of elements in a sparse set.
*
@@ -219,11 +233,33 @@ public:
* @return True if the sparse set contains the entity, false otherwise.
*/
bool has(entity_type entity) const noexcept {
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
// explicit promotion to avoid warnings with std::uint16_t
const auto entt = promotion_type{entity} & traits_type::entity_mask;
const auto pos = size_type(entity & traits_type::entity_mask);
// the in-use control bit permits to avoid accessing the direct vector
return (entt < reverse.size()) && (reverse[entt] & in_use);
return (pos < reverse.size()) && (reverse[pos] & in_use);
}
/**
* @brief Checks if a sparse set contains an entity (unsafe).
*
* Alternative version of `has`. It accesses the underlying 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 `has` member function instead.
*
* @warning
* Attempting to use an entity that doesn't belong to the sparse set 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 sparse set contains the entity, false otherwise.
*/
bool fast(entity_type entity) const noexcept {
const auto pos = size_type(entity & traits_type::entity_mask);
assert(pos < reverse.size());
// the in-use control bit permits to avoid accessing the direct vector
return (reverse[pos] & in_use);
}
/**
@@ -258,17 +294,15 @@ public:
*/
void construct(entity_type entity) {
assert(!has(entity));
using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
// explicit promotion to avoid warnings with std::uint16_t
const auto entt = promotion_type{entity} & traits_type::entity_mask;
const auto pos = size_type(entity & traits_type::entity_mask);
if(!(entt < reverse.size())) {
reverse.resize(entt+1, pos_type{});
if(!(pos < reverse.size())) {
reverse.resize(pos+1, pos_type{});
}
// we exploit the fact that pos_type is equal to entity_type and pos has
// traits_type::version_mask bits unused we can use to mark it as in-use
reverse[entt] = pos_type(direct.size()) | in_use;
reverse[pos] = pos_type(direct.size()) | in_use;
direct.emplace_back(entity);
}

View File

@@ -7,6 +7,7 @@
#include <utility>
#include <algorithm>
#include <type_traits>
#include "entt_traits.hpp"
#include "sparse_set.hpp"
@@ -209,7 +210,7 @@ public:
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
get(entity_type entity) const noexcept {
return std::tuple<const Comp &...>{ get<Comp>(entity)... };
return std::tuple<const Comp &...>{get<Comp>(entity)...};
}
/**
@@ -232,7 +233,7 @@ public:
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
get(entity_type entity) noexcept {
return std::tuple<Comp &...>{ get<Comp>(entity)... };
return std::tuple<Comp &...>{get<Comp>(entity)...};
}
/**
@@ -302,7 +303,7 @@ public:
private:
view_type &view;
pattern_type pools;
const pattern_type pools;
};
@@ -358,11 +359,20 @@ class View final {
using underlying_iterator_type = typename view_type::iterator_type;
using unchecked_type = std::array<const view_type *, (sizeof...(Component) - 1)>;
using pattern_type = std::tuple<pool_type<Component> &...>;
using traits_type = entt_traits<Entity>;
class Iterator {
using size_type = typename view_type::size_type;
inline bool valid() const noexcept {
const auto entity = *begin;
const auto sz = size_type(entity & traits_type::entity_mask);
auto i = unchecked.size();
for(const auto entity = *begin; i && unchecked[i-1]->has(entity); --i);
if(sz < extent) {
for(; i && unchecked[i-1]->fast(entity); --i);
}
return !i;
}
@@ -370,8 +380,11 @@ class View final {
using difference_type = typename underlying_iterator_type::difference_type;
using value_type = typename view_type::entity_type;
Iterator(unchecked_type unchecked, underlying_iterator_type begin, underlying_iterator_type end) noexcept
: unchecked{unchecked}, begin{begin}, end{end}
Iterator(unchecked_type unchecked, size_type extent, underlying_iterator_type begin, underlying_iterator_type end) noexcept
: unchecked{unchecked},
extent{extent},
begin{begin},
end{end}
{
if(begin != end && !valid()) {
++(*this);
@@ -393,7 +406,7 @@ class View final {
}
Iterator operator+(difference_type value) noexcept {
return Iterator{unchecked, begin+value, end};
return Iterator{unchecked, extent, begin+value, end};
}
bool operator==(const Iterator &other) const noexcept {
@@ -409,7 +422,8 @@ class View final {
}
private:
unchecked_type unchecked;
const unchecked_type unchecked;
const size_type extent;
underlying_iterator_type begin;
underlying_iterator_type end;
};
@@ -451,7 +465,8 @@ public:
* @return An iterator to the first entity that has the given components.
*/
iterator_type begin() const noexcept {
return Iterator{unchecked, view->begin(), view->end()};
const auto extent = std::min({ std::get<pool_type<Component> &>(pools).extent()... });
return Iterator{unchecked, extent, view->begin(), view->end()};
}
/**
@@ -470,7 +485,8 @@ public:
* given components.
*/
iterator_type end() const noexcept {
return Iterator{unchecked, view->end(), view->end()};
const auto extent = std::min({ std::get<pool_type<Component> &>(pools).extent()... });
return Iterator{unchecked, extent, view->end(), view->end()};
}
/**
@@ -537,7 +553,7 @@ public:
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
get(entity_type entity) const noexcept {
return std::tuple<const Comp &...>{ get<Comp>(entity)... };
return std::tuple<const Comp &...>{get<Comp>(entity)...};
}
/**
@@ -560,7 +576,7 @@ public:
template<typename... Comp>
std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
get(entity_type entity) noexcept {
return std::tuple<Comp &...>{ get<Comp>(entity)... };
return std::tuple<Comp &...>{get<Comp>(entity)...};
}
/**
@@ -638,7 +654,7 @@ public:
}
private:
pattern_type pools;
const pattern_type pools;
const view_type *view;
unchecked_type unchecked;
};