persistent views are now more reliable (fix #160)
This commit is contained in:
5
TODO
5
TODO
@@ -17,9 +17,8 @@
|
||||
* meta: move-to-head optimization when searching by name on parts (data, func, etc)
|
||||
* hashed string: add implicit check on construction for uniqueness (optional)
|
||||
* 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)
|
||||
* 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?)
|
||||
* introduce support for const instances * in delegate/sigh if possible
|
||||
* add support for composable filters
|
||||
|
||||
@@ -835,8 +835,8 @@ All of them have pros and cons to take in consideration. In particular:
|
||||
|
||||
Pros:
|
||||
|
||||
* Creating and destroying them isn't expensive at all because they don't have
|
||||
any type of initialization.
|
||||
* 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 multiple components when most entities
|
||||
have them all.
|
||||
|
||||
@@ -847,10 +847,9 @@ All of them have pros and cons to take in consideration. In particular:
|
||||
* 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).
|
||||
components, as well as the sort functionalities. In other terms: the more
|
||||
persistent views there will be, the less performing will be creating and
|
||||
destroying entities and components or sorting a pool.
|
||||
|
||||
* Raw views:
|
||||
|
||||
@@ -1034,7 +1033,11 @@ 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.
|
||||
`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
|
||||
creating them when no components have been assigned yet. If the registry is
|
||||
empty, preparation is extremely fast.
|
||||
|
||||
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
|
||||
@@ -1043,18 +1046,6 @@ 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
|
||||
|
||||
@@ -42,7 +42,8 @@ template<typename Entity = std::uint32_t>
|
||||
class registry {
|
||||
using component_family = family<struct internal_registry_component_family>;
|
||||
using handler_family = family<struct internal_registry_handler_family>;
|
||||
using signal_type = sigh<void(registry &, const Entity)>;
|
||||
using component_signal_type = sigh<void(registry &, const Entity)>;
|
||||
using pool_signal_type = sigh<void(const typename component_family::family_type)>;
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
template<std::size_t N>
|
||||
@@ -66,17 +67,17 @@ class registry {
|
||||
sparse_set<Entity, Component>::destroy(entity);
|
||||
}
|
||||
|
||||
typename signal_type::sink_type construction() ENTT_NOEXCEPT {
|
||||
typename component_signal_type::sink_type construction() ENTT_NOEXCEPT {
|
||||
return ctor.sink();
|
||||
}
|
||||
|
||||
typename signal_type::sink_type destruction() ENTT_NOEXCEPT {
|
||||
typename component_signal_type::sink_type destruction() ENTT_NOEXCEPT {
|
||||
return dtor.sink();
|
||||
}
|
||||
|
||||
private:
|
||||
signal_type ctor;
|
||||
signal_type dtor;
|
||||
component_signal_type ctor;
|
||||
component_signal_type dtor;
|
||||
registry *reg;
|
||||
};
|
||||
|
||||
@@ -84,7 +85,7 @@ class registry {
|
||||
static void creating(registry ®, const Entity 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)...);
|
||||
handler->construct(entity, reg.pools[component_family::type<Component>]->get(entity)...);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,37 +111,51 @@ class registry {
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
inline auto & pool() const ENTT_NOEXCEPT {
|
||||
inline const auto & pool() const ENTT_NOEXCEPT {
|
||||
assert(managed<Component>());
|
||||
return static_cast<component_pool<std::decay_t<Component>> &>(*pools[component_family::type<Component>]);
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
inline auto & pool() ENTT_NOEXCEPT {
|
||||
return const_cast<component_pool<std::decay_t<Component>> &>(std::as_const(*this).template pool<Component>());
|
||||
}
|
||||
|
||||
template<typename Comp, std::size_t Index, typename... Component, std::size_t... Indexes>
|
||||
void connect(std::index_sequence<Indexes...>) const {
|
||||
void connect(std::index_sequence<Indexes...>) {
|
||||
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>
|
||||
void connect(std::index_sequence<Indexes...>) const {
|
||||
void connect(std::index_sequence<Indexes...>) {
|
||||
(assure<Component>(), ...);
|
||||
(connect<Component, Indexes, Component...>(std::make_index_sequence<sizeof...(Component)-1>{}), ...);
|
||||
}
|
||||
|
||||
template<typename Comp, std::size_t Index, typename... Component, std::size_t... Indexes>
|
||||
void disconnect(std::index_sequence<Indexes...>) const {
|
||||
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>
|
||||
void rebuild(const typename component_family::family_type ctype, std::index_sequence<Indexes...>) {
|
||||
auto index = sizeof...(Indexes);
|
||||
((index = (component_family::type<Component> == ctype) ? Indexes : index), ...);
|
||||
|
||||
if(index != sizeof...(Indexes)) {
|
||||
auto *handler = static_cast<handler_type<sizeof...(Component)> *>(handlers[handler_family::type<Component...>].get());
|
||||
auto cbegin = handler->sparse_set<Entity>::cbegin();
|
||||
const auto &cpool = *pools[ctype];
|
||||
|
||||
for(auto &&indexes: *handler) {
|
||||
indexes[index] = cpool.get(*(cbegin++));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Component, std::size_t... Indexes>
|
||||
void disconnect(std::index_sequence<Indexes...>) const {
|
||||
// if a set exists, pools have already been created for it
|
||||
(disconnect<Component, Indexes, Component...>(std::make_index_sequence<sizeof...(Component)-1>{}), ...);
|
||||
template<typename... Component>
|
||||
void refresh(const typename component_family::family_type ctype) {
|
||||
rebuild<Component...>(ctype, std::make_index_sequence<sizeof...(Component)>{});
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
void assure() const {
|
||||
void assure() {
|
||||
const auto ctype = component_family::type<Component>;
|
||||
|
||||
if(!(ctype < pools.size())) {
|
||||
@@ -162,7 +177,7 @@ public:
|
||||
/*! @brief Unsigned integer type. */
|
||||
using component_type = typename component_family::family_type;
|
||||
/*! @brief Type of sink for the given component. */
|
||||
using sink_type = typename signal_type::sink_type;
|
||||
using sink_type = typename component_signal_type::sink_type;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
registry() ENTT_NOEXCEPT = default;
|
||||
@@ -642,7 +657,7 @@ public:
|
||||
assert((managed<Component>() && ...));
|
||||
|
||||
if constexpr(sizeof...(Component) == 1) {
|
||||
return std::as_const(pool<Component...>()).get(entity);
|
||||
return pool<Component...>().get(entity);
|
||||
} else {
|
||||
return std::tuple<std::add_const_t<Component> &...>{get<Component>(entity)...};
|
||||
}
|
||||
@@ -720,7 +735,7 @@ public:
|
||||
assert(valid(entity));
|
||||
|
||||
if constexpr(sizeof...(Component) == 1) {
|
||||
return managed<Component...>() ? std::as_const(pool<Component...>()).try_get(entity) : nullptr;
|
||||
return managed<Component...>() ? pool<Component...>().try_get(entity) : nullptr;
|
||||
} else {
|
||||
return std::tuple<std::add_const_t<Component> *...>{try_get<Component>(entity)...};
|
||||
}
|
||||
@@ -911,10 +926,7 @@ 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();
|
||||
});
|
||||
invalidate.publish(component_family::type<Component>);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -952,10 +964,7 @@ public:
|
||||
assure<To>();
|
||||
assure<From>();
|
||||
pool<To>().respect(pool<From>());
|
||||
|
||||
std::for_each(handlers.begin(), handlers.end(), [](auto &handler) {
|
||||
return handler ? handler->reset() : void();
|
||||
});
|
||||
invalidate.publish(component_family::type<To>);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1191,7 +1200,10 @@ public:
|
||||
* internal data structures.<br/>
|
||||
* Feel free to discard a view after the use. Creating and destroying a view
|
||||
* is an incredibly cheap operation because they do not require any type of
|
||||
* initialization.<br/>
|
||||
* initialization, but for the first time they are used. 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/>
|
||||
* As a rule of thumb, storing a view should never be an option.
|
||||
*
|
||||
* Persistent views are the right choice to iterate entities when the number
|
||||
@@ -1203,7 +1215,7 @@ public:
|
||||
* is allocated within the registry and it increases memory pressure.
|
||||
* * Internal data structures used to construct persistent views must be
|
||||
* kept updated and it affects slightly construction and destruction of
|
||||
* entities and components.
|
||||
* entities and components, as well as sort functionalities.
|
||||
*
|
||||
* That being said, persistent views are an incredibly powerful tool if used
|
||||
* with care and offer a boost of performance undoubtedly.
|
||||
@@ -1235,11 +1247,18 @@ public:
|
||||
if(!handlers[htype]) {
|
||||
(assure<Component>(), ...);
|
||||
connect<Component...>(std::make_index_sequence<sizeof...(Component)>{});
|
||||
invalidate.sink().template connect<®istry::refresh<Component...>>(this);
|
||||
|
||||
handlers[htype] = std::make_unique<handler_type<sizeof...(Component)>>();
|
||||
auto *handler = static_cast<handler_type<sizeof...(Component)> *>(handlers[htype].get());
|
||||
|
||||
for(const auto entity: view<Component...>()) {
|
||||
handler->construct(entity, pools[component_family::type<Component>]->get(entity)...);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
static_cast<handler_type<sizeof...(Component)> *>(handlers[handler_family::type<Component...>].get()),
|
||||
static_cast<handler_type<sizeof...(Component)> *>(handlers[htype].get()),
|
||||
&pool<Component>()...
|
||||
};
|
||||
}
|
||||
@@ -1251,7 +1270,10 @@ public:
|
||||
* internal data structures.<br/>
|
||||
* Feel free to discard a view after the use. Creating and destroying a view
|
||||
* is an incredibly cheap operation because they do not require any type of
|
||||
* initialization.<br/>
|
||||
* initialization, but for the first time they are used. 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/>
|
||||
* As a rule of thumb, storing a view should never be an option.
|
||||
*
|
||||
* Persistent views are the right choice to iterate entities when the number
|
||||
@@ -1263,7 +1285,7 @@ public:
|
||||
* is allocated within the registry and it increases memory pressure.
|
||||
* * Internal data structures used to construct persistent views must be
|
||||
* kept updated and it affects slightly construction and destruction of
|
||||
* entities and components.
|
||||
* entities and components, as well as sort functionalities.
|
||||
*
|
||||
* That being said, persistent views are an incredibly powerful tool if used
|
||||
* with care and offer a boost of performance undoubtedly.
|
||||
@@ -1452,11 +1474,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::vector<std::unique_ptr<sparse_set<Entity>>> handlers;
|
||||
mutable std::vector<std::unique_ptr<sparse_set<Entity>>> pools;
|
||||
std::vector<std::unique_ptr<sparse_set<Entity>>> handlers;
|
||||
std::vector<std::unique_ptr<sparse_set<Entity>>> pools;
|
||||
std::vector<entity_type> entities;
|
||||
size_type available{};
|
||||
entity_type next{};
|
||||
pool_signal_type invalidate;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class registry;
|
||||
* @note
|
||||
* Views share references to the underlying data structures with the registry
|
||||
* that generated them. Therefore any change to the entities and to the
|
||||
* components made by means of the registry are immediately reflected by
|
||||
* components made by means of the registry are immediately reflected by all the
|
||||
* views.<br/>
|
||||
* Moreover, sorting a persistent view affects all the other views of the same
|
||||
* type (it means that users don't have to call `sort` on each view to sort all
|
||||
@@ -102,22 +102,10 @@ class persistent_view final {
|
||||
|
||||
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 = std::move(func), raw = handler->cbegin(), this](const auto entity) mutable {
|
||||
std::array<typename view_type::size_type, sizeof...(Indexes)> indexes{(*raw)[Indexes]...};
|
||||
func(entity, pool<Component>()->raw()[std::get<Indexes>(indexes)]...);
|
||||
++raw;
|
||||
});
|
||||
}
|
||||
std::for_each(handler->view_type::cbegin(), handler->view_type::cend(), [func = std::move(func), raw = handler->cbegin(), this](const auto entity) mutable {
|
||||
func(entity, pool<Component>()->raw()[(*raw)[Indexes]]...);
|
||||
++raw;
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -276,33 +264,6 @@ public:
|
||||
each(std::move(func), 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() const {
|
||||
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)...);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sort the shared pool of entities according to the given component.
|
||||
*
|
||||
|
||||
@@ -260,8 +260,6 @@ TEST(Benchmark, IterateTwoComponentsPersistent1M) {
|
||||
timer.elapsed();
|
||||
};
|
||||
|
||||
registry.persistent_view<position, velocity>().initialize();
|
||||
|
||||
test([](auto, const auto &...) {});
|
||||
test([](auto, auto &... comp) {
|
||||
((comp.x = {}), ...);
|
||||
@@ -459,8 +457,6 @@ 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 = {}), ...);
|
||||
@@ -714,8 +710,6 @@ 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 = {}), ...);
|
||||
|
||||
@@ -722,6 +722,57 @@ TEST(Registry, CreateManyEntitiesAtOnce) {
|
||||
|
||||
ASSERT_EQ(registry.entity(entities[2]), entt::registry<>::entity_type{2});
|
||||
ASSERT_EQ(registry.version(entities[2]), entt::registry<>::version_type{0});
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
TEST(Registry, PersistentViewInterleaved) {
|
||||
entt::registry<> registry;
|
||||
typename entt::registry<>::entity_type entity = entt::null;
|
||||
|
||||
entity = registry.create();
|
||||
registry.assign<int>(entity);
|
||||
registry.assign<char>(entity);
|
||||
|
||||
const auto view = registry.persistent_view<int, char>();
|
||||
|
||||
entity = registry.create();
|
||||
registry.assign<int>(entity);
|
||||
registry.assign<char>(entity);
|
||||
|
||||
decltype(view)::size_type cnt{0};
|
||||
view.each([&cnt](auto...) { ++cnt; });
|
||||
|
||||
ASSERT_EQ(cnt, decltype(view)::size_type{2});
|
||||
}
|
||||
|
||||
TEST(Registry, PersistentViewSortInterleaved) {
|
||||
entt::registry<> registry;
|
||||
const auto view = registry.persistent_view<int, char>();
|
||||
|
||||
auto e0 = registry.create();
|
||||
registry.assign<int>(e0, 0);
|
||||
registry.assign<char>(e0, '0');
|
||||
|
||||
auto e1 = registry.create();
|
||||
registry.assign<int>(e1, 1);
|
||||
registry.assign<char>(e1, '1');
|
||||
|
||||
registry.sort<int>([](auto lhs, auto rhs) { return lhs > rhs; });
|
||||
registry.sort<char>([](auto lhs, auto rhs) { return lhs < rhs; });
|
||||
|
||||
auto e2 = registry.create();
|
||||
registry.assign<int>(e2, 2);
|
||||
registry.assign<char>(e2, '2');
|
||||
|
||||
view.each([e0, e1, e2](const auto entity, const auto &i, const auto &c) {
|
||||
if(entity == e0) {
|
||||
ASSERT_EQ(i, 0);
|
||||
ASSERT_EQ(c, '0');
|
||||
} else if(entity == e1) {
|
||||
ASSERT_EQ(i, 1);
|
||||
ASSERT_EQ(c, '1');
|
||||
} else if(entity == e2) {
|
||||
ASSERT_EQ(i, 2);
|
||||
ASSERT_EQ(c, '2');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -171,128 +171,6 @@ 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 = std::as_const(registry).persistent_view<const int, const 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>();
|
||||
|
||||
Reference in New Issue
Block a user