registry: disable optimizations based on is_iterator_type, free pools allow to implement them on the user side and more efficiently when needed
This commit is contained in:
1
TODO
1
TODO
@@ -4,7 +4,6 @@
|
||||
* add examples (and credits) from @alanjfs :)
|
||||
|
||||
WIP:
|
||||
* Making the most of range-destroy: disable this optimization, free pools make it possible to implement it on user side when needed
|
||||
* investigate: meta as_type_t<T> for getters and the like
|
||||
* investigate: add the possibility of disabling entities without deleting components thanks to the new full check
|
||||
* fast-contains for sparse sets (low prio but nice-to-have)
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
* [Pointer stability](#pointer-stability)
|
||||
* [In-place delete](#in-place-delete)
|
||||
* [Hierarchies and the like](#hierarchies-and-the-like)
|
||||
* [Making the most of range-destroy](#making-the-most-of-range-destroy)
|
||||
* [Meet the runtime](#meet-the-runtime)
|
||||
* [A base class to rule them all](#a-base-class-to-rule-them-all)
|
||||
* [Beam me up, registry](#beam-me-up-registry)
|
||||
@@ -1035,46 +1034,6 @@ time and therefore fallback into adjacent positions, thus favoring locality even
|
||||
on random accesses. Locality that won't be sacrificed over time given the
|
||||
stability of storage positions, with undoubted performance advantages.
|
||||
|
||||
## Making the most of range-destroy
|
||||
|
||||
The range-destroy functionality offers an improved path under the hood. To
|
||||
understand it, let's try to describe what problem it tries to solve.<br/>
|
||||
This function accepts two iterators that point to the beginning and end of a
|
||||
range of entities. If the iterators are those returned from a view, this pair
|
||||
cannot be passed to the first storage asking to remove all entities and then to
|
||||
all other storage. This is because the range may be empty when passed to the
|
||||
second pool, as not all of those entities still own all the components iterated
|
||||
from the view itself.<br/>
|
||||
As a result, only one component is removed and no entities are destroyed.
|
||||
|
||||
To avoid this, in many cases the registry doesn't pass the range to all pools.
|
||||
Instead, it iterates the range and passes an entity at a time to all pools.<br/>
|
||||
It goes without saying that the latter is slightly slower than the former.
|
||||
|
||||
On the other side, the `destroy` function also uses `is_iterator_type` under the
|
||||
hood to detect _dangerous_ iterators. Whenever possible, it still chooses the
|
||||
fastest path.<br/>
|
||||
This means that performance will improve if, for example, two iterators returned
|
||||
from an `std::vector` are used or, more in general, with all iterators that are
|
||||
not part of `EnTT`.
|
||||
|
||||
Unfortunately, this risks falling into the error described above in some corner
|
||||
cases. In particular, where an iterator is used that is not defined by `EnTT`
|
||||
but which uses one of the latter _within_ it.<br/>
|
||||
It's quite unlikely to happen even in large software. However, the library
|
||||
offers a solution also in this case, so as to allow for custom iterators and
|
||||
better performance at the same time.<br/>
|
||||
In particular, it's necessary to either expose the member type `iterator_type`
|
||||
and declare that an iterator from `EnTT` is used internally or specialize the
|
||||
`is_iterator_type` class to drive the choice of the `destroy` function.<br/>
|
||||
In both cases, the aim is to not choose the optimized route if it can cause
|
||||
problems.
|
||||
|
||||
With a good chance, the last note can be ignored and there will never be a need
|
||||
to do the above even after writing millions of lines of code.<br/>
|
||||
However, it's good to know how to exploit the `destroy` function to get the best
|
||||
out of it.
|
||||
|
||||
## Meet the runtime
|
||||
|
||||
`EnTT` takes advantage of what the language offers at compile-time. However,
|
||||
|
||||
@@ -718,18 +718,8 @@ public:
|
||||
*/
|
||||
template<typename It>
|
||||
void destroy(It first, It last) {
|
||||
ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity");
|
||||
|
||||
if constexpr(is_iterator_type_v<typename base_type::iterator, It>) {
|
||||
for(; first != last; ++first) {
|
||||
destroy(*first, entity_traits::to_version(*first) + 1u);
|
||||
}
|
||||
} else {
|
||||
for(auto &&curr: pools) {
|
||||
curr.second->remove(first, last);
|
||||
}
|
||||
|
||||
release(first, last);
|
||||
for(; first != last; ++first) {
|
||||
destroy(*first, entity_traits::to_version(*first) + 1u);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -885,24 +875,17 @@ public:
|
||||
* @sa remove
|
||||
*
|
||||
* @tparam Component Types of components to remove.
|
||||
* @tparam Other Other types of components to remove.
|
||||
* @tparam It Type of input iterator.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @return The number of components actually removed.
|
||||
*/
|
||||
template<typename Component, typename... Other, typename It>
|
||||
template<typename... Component, typename It>
|
||||
size_type remove(It first, It last) {
|
||||
ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity");
|
||||
size_type count{};
|
||||
|
||||
if constexpr(is_iterator_type_v<typename base_type::iterator, It> && sizeof...(Other) != 0u) {
|
||||
for(const auto cpools = std::forward_as_tuple(assure<Component>(), assure<Other>()...); first != last; ++first) {
|
||||
const auto entity = *first;
|
||||
count += (std::get<storage_type<Component> &>(cpools).remove(entity) + ... + std::get<storage_type<Other> &>(cpools).remove(entity));
|
||||
}
|
||||
} else {
|
||||
count = (assure<Component>().remove(first, last) + ... + assure<Other>().remove(first, last));
|
||||
for(; first != last; ++first) {
|
||||
count += remove<Component...>(*first);
|
||||
}
|
||||
|
||||
return count;
|
||||
@@ -931,22 +914,14 @@ public:
|
||||
* @sa erase
|
||||
*
|
||||
* @tparam Component Types of components to erase.
|
||||
* @tparam Other Other types of components to erase.
|
||||
* @tparam It Type of input iterator.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
*/
|
||||
template<typename Component, typename... Other, typename It>
|
||||
template<typename... Component, typename It>
|
||||
void erase(It first, It last) {
|
||||
ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity");
|
||||
|
||||
if constexpr(is_iterator_type_v<typename base_type::iterator, It> && sizeof...(Other) != 0u) {
|
||||
for(const auto cpools = std::forward_as_tuple(assure<Component>(), assure<Other>()...); first != last; ++first) {
|
||||
const auto entity = *first;
|
||||
(std::get<storage_type<Component> &>(cpools).erase(entity), (std::get<storage_type<Other> &>(cpools).erase(entity), ...));
|
||||
}
|
||||
} else {
|
||||
(assure<Component>().erase(first, last), (assure<Other>().erase(first, last), ...));
|
||||
for(; first != last; ++first) {
|
||||
erase<Component...>(*first);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user