boost persistent views (around 100x)
This commit is contained in:
6
TODO
6
TODO
@@ -6,6 +6,7 @@
|
||||
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
|
||||
* define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
|
||||
* define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
|
||||
* runner proposal: https://en.wikipedia.org/wiki/Fork%E2%80%93join_model https://slide-rs.github.io/specs/03_dispatcher.html
|
||||
* optimize for empty components, it would be a mid improvement in terms of memory usage (see std::is_empty)
|
||||
* deep copy of a registry (or use the snapshot stuff to copy components and keep intact ids at least)
|
||||
* is it possible to iterate all the components assigned to an entity through a common base class?
|
||||
@@ -18,4 +19,7 @@
|
||||
* signals on entity creation/destruction
|
||||
* flexible views with "composable" filters like negative queries: find entites that have components A, B, but not C.
|
||||
* destroy overload that accepts a couple of iterators (see create)
|
||||
* review persistent views and avoid indirection to get components
|
||||
* filtered groups of entities to support comple queries like all of, one of, none of (sparse set + listeners)
|
||||
* "negative" queries with sort of exclude<Comp> as template parameter for persistent views
|
||||
* allow for built-in parallel each if possible
|
||||
* add on-the-fly sort functionality (is it possible?)
|
||||
|
||||
@@ -820,9 +820,9 @@ All of them have pros and cons to take in consideration. In particular:
|
||||
* They work out-of-the-box and don't require any dedicated data structure.
|
||||
* Creating and destroying them isn't expensive at all because they don't have
|
||||
any type of initialization.
|
||||
* They are the best tool for iterating entities for a single component.
|
||||
* They are the best tool for iterating entities for multiple components when
|
||||
one of the components is assigned to a significantly low number of entities.
|
||||
* They are the best tool for iterating a single component.
|
||||
* They are the best tool for iterating multiple components when one of them is
|
||||
assigned to a significantly low number of entities.
|
||||
* They don't affect any other operations of the registry.
|
||||
|
||||
Cons:
|
||||
@@ -834,20 +834,22 @@ All of them have pros and cons to take in consideration. In particular:
|
||||
|
||||
Pros:
|
||||
|
||||
* Once prepared, creating and destroying them isn't expensive at all because
|
||||
they don't have any type of initialization.
|
||||
* They are the best tool for iterating entities for multiple components when
|
||||
most entities have them all.
|
||||
* Creating and destroying them isn't expensive at all because they don't have
|
||||
any type of initialization.
|
||||
* They are the best tool for iterating multiple components when most entities
|
||||
have them all.
|
||||
|
||||
Cons:
|
||||
|
||||
* They have dedicated data structures and thus affect the memory usage to a
|
||||
minimal extent.
|
||||
* If not previously prepared, the first time they are used they go through an
|
||||
initialization step that could take a while.
|
||||
* If not previously initialized, the first time they are used they go through
|
||||
an initialization step that is slightly more expensive.
|
||||
* They affect to a minimum the creation and destruction of entities and
|
||||
components. In other terms: the more persistent views there will be, the
|
||||
less performing will be creating and destroying entities and components.
|
||||
* They don't perform well if the `sort` member function of a registry is
|
||||
invoked frequently (but this shouldn't be the case in general).
|
||||
|
||||
* Raw views:
|
||||
|
||||
@@ -885,19 +887,16 @@ To sum up and as a rule of thumb:
|
||||
* Use a raw view to iterate components only (no entities) for a given type.
|
||||
* Use a standard view to iterate entities and components for a single type.
|
||||
* Use a standard view to iterate entities and components for multiple types when
|
||||
the number of types is low. Standard views are really optimized and persistent
|
||||
a significantly low number of entities have one of the components, persistent
|
||||
views won't add much in this case.
|
||||
* Use a standard view to iterate entities and components for multiple types when
|
||||
a significantly low number of entities have one of the components.
|
||||
* Use a standard view in all those cases where a persistent view would give a
|
||||
boost to performance but the iteration isn't performed frequently.
|
||||
* Prepare and use a persistent view when you want to iterate only entities for
|
||||
multiple components.
|
||||
* Prepare and use a persistent view when you want to iterate entities for
|
||||
multiple components and each component is assigned to a great number of
|
||||
entities but the intersection between the sets of entities is small.
|
||||
* Prepare and use a persistent view in all the cases where a standard view
|
||||
wouldn't fit well otherwise.
|
||||
boost to performance but the iteration isn't performed frequently or isn't on
|
||||
a critical path.
|
||||
* Use a persistent view when you want to iterate multiple components and each
|
||||
component is assigned to a great number of entities but the intersection
|
||||
between the sets of entities is small.
|
||||
* Use a persistent view in all the cases where a standard view wouldn't fit well
|
||||
otherwise.
|
||||
* Finally, in case you don't know at compile-time what are the components to
|
||||
use, choose a runtime view and set them during execution.
|
||||
|
||||
@@ -1025,7 +1024,7 @@ tightly packed in memory for fast iterations.<br/>
|
||||
In general, persistent views don't stay true to the order of any set of
|
||||
components unless users explicitly sort them.
|
||||
|
||||
Persistent views can be used only to iterate multiple components:
|
||||
Persistent views can be used only to iterate multiple components at once:
|
||||
|
||||
```cpp
|
||||
auto view = registry.persistent_view<position, velocity>();
|
||||
@@ -1034,19 +1033,7 @@ auto view = registry.persistent_view<position, velocity>();
|
||||
There is no need to store views around for they are extremely cheap to
|
||||
construct, even though they can be copied without problems and reused freely. In
|
||||
fact, they return newly created and correctly initialized iterators whenever
|
||||
`begin` or `end` are invoked.<br/>
|
||||
That being said, persistent views perform an initialization step the very first
|
||||
time they are constructed and this could be quite costly. To avoid it, consider
|
||||
asking to the registry to _prepare_ them when no entities have been created yet:
|
||||
|
||||
```cpp
|
||||
registry.prepare_persistent_view<position, velocity>();
|
||||
```
|
||||
|
||||
If the registry is empty, preparation is extremely fast. Moreover the
|
||||
`prepare_persistent_view` member function template is idempotent. Feel free to
|
||||
invoke it even more than once: if the view has been already prepared before, the
|
||||
function returns immediately and does nothing.
|
||||
`begin` or `end` are invoked.
|
||||
|
||||
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
|
||||
@@ -1055,6 +1042,18 @@ 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 inline documentation for all the details.
|
||||
|
||||
The underlying data structure of a persistent view is initialized through a
|
||||
slightly slower iteration the first time the `each` member function is invoked
|
||||
or by means of an explicit call to the `initialize` member function. An
|
||||
uninitialized persistent view is empty and has a size of zero.<br/>
|
||||
Once initialized, these data structures are kept up-to-date automatically,
|
||||
unless users invoke the sort functionalities made available by the registry. The
|
||||
reasons behind this limit go beyond the purposes of the document. However, keep
|
||||
in mind that all the data structures otherwise supporting persistent views are
|
||||
discarded in this case and they must be reinitialized somehow.<br/>
|
||||
For reasons of clarity, sorting a persistent view has no side effects and the
|
||||
view won't need to be reinitialized in this case.
|
||||
|
||||
To iterate a persistent view, either use it in a range-for loop:
|
||||
|
||||
```cpp
|
||||
@@ -1081,8 +1080,8 @@ registry.persistent_view<position, velocity>().each([](auto entity, auto &pos, a
|
||||
});
|
||||
```
|
||||
|
||||
Performance are more or less the same. The best approach depends mainly on
|
||||
whether all the components have to be accessed or not.
|
||||
The `each` member function is highly optimized. Unless users want to iterate
|
||||
only entities, using `each` should be the preferred approach.
|
||||
|
||||
**Note**: prefer the `get` member function of a view instead of the `get` member
|
||||
function template of a registry during iterations, if possible. However, keep in
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define ENTT_ENTITY_REGISTRY_HPP
|
||||
|
||||
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
@@ -44,6 +45,9 @@ class registry {
|
||||
using signal_type = sigh<void(registry &, const Entity)>;
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
template<std::size_t N>
|
||||
using handler_type = sparse_set<Entity, std::array<typename sparse_set<Entity>::size_type, N>>;
|
||||
|
||||
template<typename Component>
|
||||
struct component_pool: sparse_set<Entity, Component> {
|
||||
component_pool(registry *reg) ENTT_NOEXCEPT
|
||||
@@ -76,17 +80,27 @@ class registry {
|
||||
registry *reg;
|
||||
};
|
||||
|
||||
template<auto *Type, typename... Component>
|
||||
template<auto Has, typename... Component>
|
||||
static void creating(registry ®, const Entity entity) {
|
||||
if(reg.has<Component...>(entity)) {
|
||||
reg.handlers[*Type]->construct(entity);
|
||||
if((reg.*Has)(entity)) {
|
||||
auto *handler = static_cast<handler_type<sizeof...(Component)> *>(reg.handlers[handler_family::type<Component...>].get());
|
||||
handler->construct(entity, reg.pool<Component>().sparse_set<Entity>::get(entity)...);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Component>
|
||||
template<typename Comp, std::size_t Index, typename... Component>
|
||||
static void destroying(registry ®, const Entity entity) {
|
||||
auto &handler = *reg.handlers[handler_family::type<Component...>];
|
||||
return handler.has(entity) ? handler.destroy(entity) : void();
|
||||
auto *handler = static_cast<handler_type<sizeof...(Component)> *>(reg.handlers[handler_family::type<Component...>].get());
|
||||
const sparse_set<Entity> &cpool = reg.pool<Comp>();
|
||||
const auto last = *cpool.cbegin();
|
||||
|
||||
if(handler->has(last)) {
|
||||
handler->get(last)[Index] = cpool.get(entity);
|
||||
}
|
||||
|
||||
if(handler->has(entity)) {
|
||||
handler->destroy(entity);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
@@ -108,8 +122,8 @@ class registry {
|
||||
|
||||
template<typename Comp, std::size_t Index, typename... Component, std::size_t... Indexes>
|
||||
void connect(std::index_sequence<Indexes...>) {
|
||||
pool<Comp>().construction().template connect<®istry::creating<&handler_family::type<Component...>, std::tuple_element_t<(Indexes < Index ? Indexes : (Indexes+1)), std::tuple<Component...>>...>>();
|
||||
pool<Comp>().destruction().template connect<®istry::destroying<Component...>>();
|
||||
pool<Comp>().construction().template connect<®istry::creating<®istry::has<std::tuple_element_t<(Indexes < Index ? Indexes : (Indexes+1)), std::tuple<Component...>>...>, Component...>>();
|
||||
pool<Comp>().destruction().template connect<®istry::destroying<Comp, Index, Component...>>();
|
||||
}
|
||||
|
||||
template<typename... Component, std::size_t... Indexes>
|
||||
@@ -120,8 +134,8 @@ class registry {
|
||||
|
||||
template<typename Comp, std::size_t Index, typename... Component, std::size_t... Indexes>
|
||||
void disconnect(std::index_sequence<Indexes...>) {
|
||||
pool<Comp>().construction().template disconnect<®istry::creating<&handler_family::type<Component...>, std::tuple_element_t<(Indexes < Index ? Indexes : (Indexes+1)), std::tuple<Component...>>...>>();
|
||||
pool<Comp>().destruction().template disconnect<®istry::destroying<Component...>>();
|
||||
pool<Comp>().construction().template disconnect<®istry::creating<®istry::has<std::tuple_element_t<(Indexes < Index ? Indexes : (Indexes+1)), std::tuple<Component...>>...>, Component...>>();
|
||||
pool<Comp>().destruction().template disconnect<®istry::destroying<Comp, Index, Component...>>();
|
||||
}
|
||||
|
||||
template<typename... Component, std::size_t... Indexes>
|
||||
@@ -869,6 +883,10 @@ public:
|
||||
void sort(Compare compare, Sort sort = Sort{}, Args &&... args) {
|
||||
assure<Component>();
|
||||
pool<Component>().sort(std::move(compare), std::move(sort), std::forward<Args>(args)...);
|
||||
|
||||
std::for_each(handlers.begin(), handlers.end(), [](auto &handler) {
|
||||
return handler ? handler->reset() : void();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -906,6 +924,10 @@ public:
|
||||
assure<To>();
|
||||
assure<From>();
|
||||
pool<To>().respect(pool<From>());
|
||||
|
||||
std::for_each(handlers.begin(), handlers.end(), [](auto &handler) {
|
||||
return handler ? handler->reset() : void();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1090,77 +1112,6 @@ public:
|
||||
return { &pool<Component>()... };
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Prepares the internal data structures used by persistent views.
|
||||
*
|
||||
* Persistent views are an incredibly fast tool used to iterate a packed
|
||||
* array of entities all of which have specific components.<br/>
|
||||
* The initialization of a persistent view is also a pretty cheap operation,
|
||||
* but for the first time they are created. That's mainly because of the
|
||||
* internal data structures of the registry that are dedicated to this kind
|
||||
* of views and that don't exist yet the very first time they are
|
||||
* 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 construct the view.
|
||||
*
|
||||
* @tparam Component Types of components used to prepare the view.
|
||||
*/
|
||||
template<typename... Component>
|
||||
void prepare_persistent_view() {
|
||||
static_assert(sizeof...(Component) > 1);
|
||||
const auto htype = handler_family::type<Component...>;
|
||||
|
||||
if(!(htype < handlers.size())) {
|
||||
handlers.resize(htype + 1);
|
||||
}
|
||||
|
||||
if(!handlers[htype]) {
|
||||
connect<Component...>(std::make_index_sequence<sizeof...(Component)>{});
|
||||
handlers[htype] = std::make_unique<sparse_set<entity_type>>();
|
||||
auto &handler = *handlers[htype];
|
||||
|
||||
for(auto entity: view<Component...>()) {
|
||||
handler.construct(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Discards all the data structures used for a given persitent view.
|
||||
*
|
||||
* Persistent views occupy memory, no matter if they are in use or not.<br/>
|
||||
* This function can be used to discard all the internal data structures
|
||||
* dedicated to a specific persistent view, with the goal of reducing the
|
||||
* memory pressure.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to use a persistent view created before calling this function
|
||||
* results in undefined behavior. No assertion available in this case,
|
||||
* neither in debug mode nor in release mode.
|
||||
*
|
||||
* @tparam Component Types of components of the persistent view.
|
||||
*/
|
||||
template<typename... Component>
|
||||
void discard_persistent_view() {
|
||||
if(has_persistent_view<Component...>()) {
|
||||
disconnect<Component...>(std::make_index_sequence<sizeof...(Component)>{});
|
||||
handlers[handler_family::type<Component...>].reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a persistent view has already been prepared.
|
||||
* @tparam Component Types of components of the persistent view.
|
||||
* @return True if the view has already been prepared, false otherwise.
|
||||
*/
|
||||
template<typename... Component>
|
||||
bool has_persistent_view() const ENTT_NOEXCEPT {
|
||||
static_assert(sizeof...(Component) > 1);
|
||||
const auto htype = handler_family::type<Component...>;
|
||||
return (htype < handlers.size() && handlers[htype]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a persistent view for the given components.
|
||||
*
|
||||
@@ -1202,9 +1153,25 @@ public:
|
||||
*/
|
||||
template<typename... Component>
|
||||
entt::persistent_view<Entity, Component...> persistent_view() {
|
||||
prepare_persistent_view<Component...>();
|
||||
(assure<Component>(), ...);
|
||||
return { handlers[handler_family::type<Component...>].get(), &pool<Component>()... };
|
||||
static_assert(sizeof...(Component) > 1);
|
||||
const auto htype = handler_family::type<Component...>;
|
||||
|
||||
if(!(htype < handlers.size() && handlers[htype])) {
|
||||
if(!(htype < handlers.size())) {
|
||||
handlers.resize(htype + 1);
|
||||
}
|
||||
|
||||
if(!handlers[htype]) {
|
||||
(assure<Component>(), ...);
|
||||
connect<Component...>(std::make_index_sequence<sizeof...(Component)>{});
|
||||
handlers[htype] = std::make_unique<handler_type<sizeof...(Component)>>();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
static_cast<handler_type<sizeof...(Component)> *>(handlers[htype].get()),
|
||||
&pool<Component>()...
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -366,15 +366,6 @@ public:
|
||||
return cend();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the identifier that occupies the given position.
|
||||
* @param pos Position of the element to return.
|
||||
* @return The identifier that occupies the given position.
|
||||
*/
|
||||
inline entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
|
||||
return cbegin()[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if a sparse set contains an entity.
|
||||
* @param entity A valid entity identifier.
|
||||
@@ -862,24 +853,6 @@ public:
|
||||
return iterator_type{&instances, {}};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the element at the given position.
|
||||
* @param pos Position of the element to return.
|
||||
* @return A reference to the requested element.
|
||||
*/
|
||||
inline const object_type & operator[](const size_type pos) const ENTT_NOEXCEPT {
|
||||
return cbegin()[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a reference to the element at the given position.
|
||||
* @param pos Position of the element to return.
|
||||
* @return A reference to the requested element.
|
||||
*/
|
||||
inline object_type & operator[](const size_type pos) ENTT_NOEXCEPT {
|
||||
return const_cast<object_type &>(std::as_const(*this).operator[](pos));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the object associated with an entity.
|
||||
*
|
||||
|
||||
@@ -79,12 +79,44 @@ class persistent_view final {
|
||||
using pool_type = sparse_set<Entity, Comp>;
|
||||
|
||||
using view_type = sparse_set<Entity>;
|
||||
using persistent_type = sparse_set<Entity, std::array<typename view_type::size_type, sizeof...(Component)>>;
|
||||
using pattern_type = std::tuple<pool_type<Component> *...>;
|
||||
|
||||
persistent_view(view_type *view, pool_type<Component> *... pools) ENTT_NOEXCEPT
|
||||
: view{view}, pools{pools...}
|
||||
persistent_view(persistent_type *handler, pool_type<Component> *... pools) ENTT_NOEXCEPT
|
||||
: handler{handler},
|
||||
pools{pools...}
|
||||
{}
|
||||
|
||||
template<typename Comp>
|
||||
const pool_type<Comp> * pool() const ENTT_NOEXCEPT {
|
||||
return std::get<pool_type<Comp> *>(pools);
|
||||
}
|
||||
|
||||
const view_type * candidate() const ENTT_NOEXCEPT {
|
||||
return std::min({ static_cast<const view_type *>(pool<Component>())... }, [](const auto *lhs, const auto *rhs) {
|
||||
return lhs->size() < rhs->size();
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Func, std::size_t... Indexes>
|
||||
void each(Func func, std::index_sequence<Indexes...>) const {
|
||||
if(handler->empty()) {
|
||||
const auto *view = candidate();
|
||||
|
||||
std::for_each(view->cbegin(), view->cend(), [func = std::move(func), this](const auto entity) {
|
||||
if((pool<Component>()->has(entity) && ...)) {
|
||||
const auto &indexes = handler->construct(entity, pool<Component>()->sparse_set<Entity>::get(entity)...);
|
||||
func(entity, pool<Component>()->raw()[indexes[Indexes]]...);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
std::for_each(handler->view_type::cbegin(), handler->view_type::cend(), [&func, raw = handler->cbegin(), this](const auto entity) mutable {
|
||||
func(entity, pool<Component>()->raw()[(*raw)[Indexes]]...);
|
||||
++raw;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename view_type::entity_type;
|
||||
@@ -110,7 +142,7 @@ public:
|
||||
* @return Number of entities that have the given components.
|
||||
*/
|
||||
size_type size() const ENTT_NOEXCEPT {
|
||||
return view->size();
|
||||
return handler->size();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,7 +150,7 @@ public:
|
||||
* @return True if the view is empty, false otherwise.
|
||||
*/
|
||||
bool empty() const ENTT_NOEXCEPT {
|
||||
return view->empty();
|
||||
return handler->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,7 +166,7 @@ public:
|
||||
* @return A pointer to the array of entities.
|
||||
*/
|
||||
const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return view->data();
|
||||
return handler->data();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,7 +184,7 @@ public:
|
||||
* @return An iterator to the first entity that has the given components.
|
||||
*/
|
||||
const_iterator_type cbegin() const ENTT_NOEXCEPT {
|
||||
return view->cbegin();
|
||||
return handler->view_type::cbegin();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,7 +220,7 @@ public:
|
||||
* @return An iterator to the first entity that has the given components.
|
||||
*/
|
||||
iterator_type begin() ENTT_NOEXCEPT {
|
||||
return view->begin();
|
||||
return handler->view_type::begin();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,7 +239,7 @@ public:
|
||||
* given components.
|
||||
*/
|
||||
const_iterator_type cend() const ENTT_NOEXCEPT {
|
||||
return view->cend();
|
||||
return handler->view_type::cend();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,7 +277,7 @@ public:
|
||||
* given components.
|
||||
*/
|
||||
iterator_type end() ENTT_NOEXCEPT {
|
||||
return view->end();
|
||||
return handler->view_type::end();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,7 +286,7 @@ public:
|
||||
* @return The identifier that occupies the given position.
|
||||
*/
|
||||
entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
|
||||
return (*view)[pos];
|
||||
return handler->view_type::cbegin()[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,7 +295,7 @@ public:
|
||||
* @return True if the view contains the given entity, false otherwise.
|
||||
*/
|
||||
bool contains(const entity_type entity) const ENTT_NOEXCEPT {
|
||||
return view->has(entity) && (view->data()[view->get(entity)] == entity);
|
||||
return handler->has(entity) && (handler->view_type::data()[handler->view_type::get(entity)] == entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,7 +321,7 @@ public:
|
||||
assert(contains(entity));
|
||||
|
||||
if constexpr(sizeof...(Comp) == 1) {
|
||||
return (std::get<pool_type<Comp> *>(pools)->get(entity), ...);
|
||||
return (pool<Comp>()->get(entity), ...);
|
||||
} else {
|
||||
return std::tuple<const Comp &...>{get<Comp>(entity)...};
|
||||
}
|
||||
@@ -339,10 +371,8 @@ public:
|
||||
* @param func A valid function object.
|
||||
*/
|
||||
template<typename Func>
|
||||
void each(Func func) const {
|
||||
std::for_each(view->cbegin(), view->cend(), [&func, this](const auto entity) {
|
||||
func(entity, std::get<pool_type<Component> *>(pools)->get(entity)...);
|
||||
});
|
||||
inline void each(Func func) const {
|
||||
each(std::move(func), std::make_index_sequence<sizeof...(Component)>{});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,9 +393,36 @@ public:
|
||||
*/
|
||||
template<typename Func>
|
||||
inline void each(Func func) {
|
||||
std::as_const(*this).each([&func](const entity_type entity, const Component &... component) {
|
||||
each([&func](const entity_type entity, const Component &... component) {
|
||||
func(entity, const_cast<Component &>(component)...);
|
||||
});
|
||||
}, std::make_index_sequence<sizeof...(Component)>{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the internal data structures used by persistent views.
|
||||
*
|
||||
* Persistent views are an incredibly fast tool used to iterate a packed
|
||||
* array of entities all of which have specific components.<br/>
|
||||
* The initialization of a persistent view is also a pretty cheap operation,
|
||||
* but for the first time they are created. That's mainly because of the
|
||||
* internal data structures of the registry that are dedicated to this kind
|
||||
* of views and that don't exist yet the very first time they are
|
||||
* requested.
|
||||
*
|
||||
* Consider using consistently the `each` member function instead of this
|
||||
* one if in doubt. Initializing the view during an iteration allows to
|
||||
* considerably reduce the cost of this operation.
|
||||
*/
|
||||
void initialize() {
|
||||
if(empty()) {
|
||||
const auto *view = candidate();
|
||||
|
||||
std::for_each(view->cbegin(), view->cend(), [this](const auto entity) {
|
||||
if((pool<Component>()->has(entity) && ...)) {
|
||||
handler->construct(entity, pool<Component>()->sparse_set<Entity>::get(entity)...);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -386,11 +443,11 @@ public:
|
||||
*/
|
||||
template<typename Comp>
|
||||
void sort() {
|
||||
view->respect(*std::get<pool_type<Comp> *>(pools));
|
||||
handler->respect(*pool<Comp>());
|
||||
}
|
||||
|
||||
private:
|
||||
view_type *view;
|
||||
persistent_type *handler;
|
||||
const pattern_type pools;
|
||||
};
|
||||
|
||||
@@ -1106,7 +1163,7 @@ public:
|
||||
* @return The identifier that occupies the given position.
|
||||
*/
|
||||
entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
|
||||
return pool->view_type::operator[](pos);
|
||||
return pool->view_type::cbegin()[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1453,7 +1510,7 @@ public:
|
||||
* @return A reference to the requested element.
|
||||
*/
|
||||
const raw_type & operator[](const size_type pos) const ENTT_NOEXCEPT {
|
||||
return (*pool)[pos];
|
||||
return pool->cbegin()[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -245,7 +245,6 @@ TEST(Benchmark, IterateTwoComponents1MOne) {
|
||||
|
||||
TEST(Benchmark, IterateTwoComponentsPersistent1M) {
|
||||
entt::registry<> registry;
|
||||
registry.prepare_persistent_view<position, velocity>();
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, two components, persistent view" << std::endl;
|
||||
|
||||
@@ -261,6 +260,8 @@ TEST(Benchmark, IterateTwoComponentsPersistent1M) {
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
registry.persistent_view<position, velocity>().initialize();
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
((comp.x = {}), ...);
|
||||
@@ -269,7 +270,6 @@ TEST(Benchmark, IterateTwoComponentsPersistent1M) {
|
||||
|
||||
TEST(Benchmark, IterateTwoComponentsRuntime1M) {
|
||||
entt::registry<> registry;
|
||||
registry.prepare_persistent_view<position, velocity>();
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, two components, runtime view" << std::endl;
|
||||
|
||||
@@ -441,7 +441,6 @@ TEST(Benchmark, IterateFiveComponents1MOne) {
|
||||
|
||||
TEST(Benchmark, IterateFiveComponentsPersistent1M) {
|
||||
entt::registry<> registry;
|
||||
registry.prepare_persistent_view<position, velocity, comp<1>, comp<2>, comp<3>>();
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, five components, persistent view" << std::endl;
|
||||
|
||||
@@ -460,6 +459,8 @@ TEST(Benchmark, IterateFiveComponentsPersistent1M) {
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
registry.persistent_view<position, velocity, comp<1>, comp<2>, comp<3>>().initialize();
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
((comp.x = {}), ...);
|
||||
@@ -468,7 +469,6 @@ TEST(Benchmark, IterateFiveComponentsPersistent1M) {
|
||||
|
||||
TEST(Benchmark, IterateFiveComponentsRuntime1M) {
|
||||
entt::registry<> registry;
|
||||
registry.prepare_persistent_view<position, velocity, comp<1>, comp<2>, comp<3>>();
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, five components, runtime view" << std::endl;
|
||||
|
||||
@@ -691,7 +691,6 @@ TEST(Benchmark, IterateTenComponents1MOne) {
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsPersistent1M) {
|
||||
entt::registry<> registry;
|
||||
registry.prepare_persistent_view<position, velocity, comp<1>, comp<2>, comp<3>, comp<4>, comp<5>, comp<6>, comp<7>, comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, ten components, persistent view" << std::endl;
|
||||
|
||||
@@ -715,6 +714,8 @@ TEST(Benchmark, IterateTenComponentsPersistent1M) {
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
registry.persistent_view<position, velocity, comp<1>, comp<2>, comp<3>, comp<4>, comp<5>, comp<6>, comp<7>, comp<8>>().initialize();
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
((comp.x = {}), ...);
|
||||
@@ -723,7 +724,6 @@ TEST(Benchmark, IterateTenComponentsPersistent1M) {
|
||||
|
||||
TEST(Benchmark, IterateTenComponentsRuntime1M) {
|
||||
entt::registry<> registry;
|
||||
registry.prepare_persistent_view<position, velocity, comp<1>, comp<2>, comp<3>, comp<4>, comp<5>, comp<6>, comp<7>, comp<8>>();
|
||||
|
||||
std::cout << "Iterating over 1000000 entities, ten components, runtime view" << std::endl;
|
||||
|
||||
|
||||
@@ -393,18 +393,6 @@ TEST(Registry, StandardView) {
|
||||
|
||||
TEST(Registry, PersistentView) {
|
||||
entt::registry<> registry;
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
|
||||
ASSERT_TRUE((registry.has_persistent_view<int, char>()));
|
||||
ASSERT_FALSE((registry.has_persistent_view<int, double>()));
|
||||
|
||||
registry.prepare_persistent_view<int, double>();
|
||||
|
||||
ASSERT_TRUE((registry.has_persistent_view<int, double>()));
|
||||
|
||||
registry.discard_persistent_view<int, double>();
|
||||
|
||||
ASSERT_FALSE((registry.has_persistent_view<int, double>()));
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 0);
|
||||
@@ -417,6 +405,7 @@ TEST(Registry, PersistentView) {
|
||||
registry.assign<int>(e2, 0);
|
||||
registry.assign<char>(e2, 'c');
|
||||
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
decltype(view)::size_type cnt{0};
|
||||
view.each([&cnt](auto...) { ++cnt; });
|
||||
|
||||
@@ -698,12 +687,12 @@ TEST(Registry, DestroyByComponents) {
|
||||
TEST(Registry, SignalsOnAccommodate) {
|
||||
entt::registry<> registry;
|
||||
const auto entity = registry.create();
|
||||
const auto view = registry.persistent_view<int, char>();
|
||||
|
||||
registry.prepare_persistent_view<int, char>();
|
||||
registry.assign<int>(entity);
|
||||
registry.accommodate<char>(entity);
|
||||
|
||||
ASSERT_FALSE((registry.persistent_view<int, char>().empty()));
|
||||
ASSERT_FALSE((view.empty()));
|
||||
}
|
||||
|
||||
TEST(Registry, CreateManyEntitiesAtOnce) {
|
||||
|
||||
@@ -56,19 +56,6 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
other = std::move(set);
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, ElementAccess) {
|
||||
entt::sparse_set<std::uint64_t> set;
|
||||
const auto &cset = set;
|
||||
|
||||
set.construct(42);
|
||||
set.construct(3);
|
||||
|
||||
for(typename entt::sparse_set<std::uint64_t>::size_type i{}; i < set.size(); ++i) {
|
||||
ASSERT_EQ(set[i], i ? 42 : 3);
|
||||
ASSERT_EQ(cset[i], i ? 42 : 3);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, Iterator) {
|
||||
using iterator_type = typename entt::sparse_set<std::uint64_t>::iterator_type;
|
||||
|
||||
@@ -405,20 +392,7 @@ TEST(SparseSetWithType, Functionalities) {
|
||||
other = std::move(set);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, ElementAccess) {
|
||||
entt::sparse_set<std::uint64_t, int> set;
|
||||
const auto &cset = set;
|
||||
|
||||
set.construct(42, 1);
|
||||
set.construct(3, 0);
|
||||
|
||||
for(typename entt::sparse_set<std::uint64_t, int>::size_type i{}; i < set.size(); ++i) {
|
||||
ASSERT_EQ(set[i], i);
|
||||
ASSERT_EQ(cset[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, AggregatesMustWork) {
|
||||
TEST(SparseSetWithType, AggregatesMustWork) {
|
||||
struct aggregate_type { int value; };
|
||||
// the goal of this test is to enforce the requirements for aggregate types
|
||||
entt::sparse_set<std::uint64_t, aggregate_type>{}.construct(0, 42);
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
#include <entt/entity/registry.hpp>
|
||||
#include <entt/entity/view.hpp>
|
||||
|
||||
TEST(PersistentView, Prepare) {
|
||||
TEST(PersistentView, Functionalities) {
|
||||
entt::registry<> registry;
|
||||
registry.prepare_persistent_view<int, char>();
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
const auto &cview = view;
|
||||
|
||||
@@ -56,55 +55,6 @@ TEST(PersistentView, Prepare) {
|
||||
ASSERT_TRUE(view.empty());
|
||||
}
|
||||
|
||||
TEST(PersistentView, NoPrepare) {
|
||||
entt::registry<> registry;
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<char>(e0);
|
||||
|
||||
const auto e1 = registry.create();
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
ASSERT_FALSE(view.empty());
|
||||
ASSERT_NO_THROW((registry.persistent_view<int, char>().begin()++));
|
||||
ASSERT_NO_THROW((++registry.persistent_view<int, char>().begin()));
|
||||
|
||||
ASSERT_NE(view.begin(), view.end());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.assign<int>(e0);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
|
||||
|
||||
registry.remove<int>(e0);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.get<char>(e0) = '1';
|
||||
registry.get<char>(e1) = '2';
|
||||
registry.get<int>(e1) = 42;
|
||||
|
||||
for(auto entity: view) {
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
|
||||
ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
|
||||
ASSERT_EQ(cview.get<char>(entity), '2');
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(view.data() + 0), e1);
|
||||
|
||||
registry.remove<char>(e0);
|
||||
registry.remove<char>(e1);
|
||||
|
||||
ASSERT_EQ(view.begin(), view.end());
|
||||
ASSERT_EQ(view.cbegin(), view.cend());
|
||||
ASSERT_TRUE(view.empty());
|
||||
}
|
||||
|
||||
TEST(PersistentView, ElementAccess) {
|
||||
entt::registry<> registry;
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
@@ -126,6 +76,7 @@ TEST(PersistentView, ElementAccess) {
|
||||
|
||||
TEST(PersistentView, Contains) {
|
||||
entt::registry<> registry;
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
@@ -137,8 +88,6 @@ TEST(PersistentView, Contains) {
|
||||
|
||||
registry.destroy(e0);
|
||||
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
|
||||
ASSERT_FALSE(view.contains(e0));
|
||||
ASSERT_TRUE(view.contains(e1));
|
||||
}
|
||||
@@ -168,7 +117,7 @@ TEST(PersistentView, Empty) {
|
||||
|
||||
TEST(PersistentView, Each) {
|
||||
entt::registry<> registry;
|
||||
registry.prepare_persistent_view<int, char>();
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
|
||||
const auto e0 = registry.create();
|
||||
registry.assign<int>(e0);
|
||||
@@ -178,7 +127,6 @@ TEST(PersistentView, Each) {
|
||||
registry.assign<int>(e1);
|
||||
registry.assign<char>(e1);
|
||||
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
const auto &cview = static_cast<const decltype(view) &>(view);
|
||||
std::size_t cnt = 0;
|
||||
|
||||
@@ -193,7 +141,7 @@ TEST(PersistentView, Each) {
|
||||
|
||||
TEST(PersistentView, Sort) {
|
||||
entt::registry<> registry;
|
||||
registry.prepare_persistent_view<int, unsigned int>();
|
||||
auto view = registry.persistent_view<int, unsigned int>();
|
||||
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
@@ -210,8 +158,6 @@ TEST(PersistentView, Sort) {
|
||||
registry.assign<int>(e1, ival++);
|
||||
registry.assign<int>(e2, ival++);
|
||||
|
||||
auto view = registry.persistent_view<int, unsigned int>();
|
||||
|
||||
for(auto entity: view) {
|
||||
ASSERT_EQ(view.get<unsigned int>(entity), --uval);
|
||||
ASSERT_EQ(view.get<int>(entity), --ival);
|
||||
@@ -226,6 +172,156 @@ TEST(PersistentView, Sort) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(PersistentView, Initialize) {
|
||||
entt::registry<> registry;
|
||||
|
||||
registry.assign<int>(registry.create());
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<char>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
registry.assign<int>(registry.create());
|
||||
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{});
|
||||
|
||||
for(auto entt: view) {
|
||||
(void)entt;
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{});
|
||||
|
||||
view.initialize();
|
||||
|
||||
for(auto entt: view) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
}
|
||||
|
||||
ASSERT_FALSE(view.empty());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
}
|
||||
|
||||
TEST(PersistentView, RebuildOnFirstUse) {
|
||||
entt::registry<> registry;
|
||||
|
||||
registry.assign<int>(registry.create());
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<char>(entity);
|
||||
registry.assign<int>(entity);
|
||||
|
||||
registry.assign<int>(registry.create());
|
||||
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{});
|
||||
|
||||
for(auto entt: view) {
|
||||
(void)entt;
|
||||
FAIL();
|
||||
}
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{});
|
||||
|
||||
view.each([entity](auto entt, auto &&...) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
ASSERT_FALSE(view.empty());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
}
|
||||
|
||||
TEST(PersistentView, RebuildOnFirstUseAfterDirectSort) {
|
||||
entt::registry<> registry;
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
|
||||
registry.assign<int>(registry.create(), 1);
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<char>(entity, 'c');
|
||||
registry.assign<int>(entity, 0);
|
||||
|
||||
registry.assign<int>(registry.create(), 2);
|
||||
|
||||
ASSERT_FALSE(view.empty());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.sort<int>([](auto lhs, auto rhs) { return lhs < rhs; });
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{});
|
||||
|
||||
view.each([entity](auto entt, auto &&...) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
ASSERT_FALSE(view.empty());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
}
|
||||
|
||||
TEST(PersistentView, RebuildOnFirstUseAfterDependentSort) {
|
||||
entt::registry<> registry;
|
||||
auto view = registry.persistent_view<int, char>();
|
||||
|
||||
registry.assign<int>(registry.create(), 1);
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.assign<char>(entity, 'c');
|
||||
registry.assign<int>(entity, 0);
|
||||
|
||||
registry.assign<int>(registry.create(), 2);
|
||||
|
||||
ASSERT_FALSE(view.empty());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
|
||||
registry.sort<int, char>();
|
||||
|
||||
ASSERT_TRUE(view.empty());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{});
|
||||
|
||||
view.each([entity](auto entt, auto &&...) {
|
||||
ASSERT_EQ(entity, entt);
|
||||
});
|
||||
|
||||
ASSERT_FALSE(view.empty());
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
}
|
||||
|
||||
TEST(PersistentView, IndexRebuiltOnDestroy) {
|
||||
entt::registry<> registry;
|
||||
auto view = registry.persistent_view<int, unsigned int>();
|
||||
|
||||
const auto e0 = registry.create();
|
||||
const auto e1 = registry.create();
|
||||
|
||||
registry.assign<unsigned int>(e0, 0u);
|
||||
registry.assign<unsigned int>(e1, 1u);
|
||||
|
||||
registry.assign<int>(e0, 0);
|
||||
registry.assign<int>(e1, 1);
|
||||
|
||||
registry.destroy(e0);
|
||||
registry.assign<int>(registry.create(), 42);
|
||||
|
||||
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
|
||||
ASSERT_EQ(view[{}], e1);
|
||||
ASSERT_EQ(view.get<int>(e1), 1);
|
||||
ASSERT_EQ(view.get<unsigned int>(e1), 1u);
|
||||
|
||||
view.each([e1](auto entity, auto ivalue, auto uivalue) {
|
||||
ASSERT_EQ(entity, e1);
|
||||
ASSERT_EQ(ivalue, 1);
|
||||
ASSERT_EQ(uivalue, 1u);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, Functionalities) {
|
||||
entt::registry<> registry;
|
||||
auto view = registry.view<char>();
|
||||
|
||||
Reference in New Issue
Block a user