improved multi component standard view
This commit is contained in:
21
README.md
21
README.md
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user