Parcourir la source

persistent views are now more reliable (fix #160)

Michele Caini il y a 7 ans
Parent
commit
2a4e098645

+ 2 - 3
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

+ 10 - 19
docs/entity.md

@@ -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

+ 59 - 36
src/entt/entity/registry.hpp

@@ -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 &reg, 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<&registry::creating<&registry::has<std::tuple_element_t<(Indexes < Index ? Indexes : (Indexes+1)), std::tuple<Component...>>...>, Component...>>();
         pool<Comp>().destruction().template connect<&registry::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<&registry::creating<&registry::has<std::tuple_element_t<(Indexes < Index ? Indexes : (Indexes+1)), std::tuple<Component...>>...>, Component...>>();
-        pool<Comp>().destruction().template disconnect<&registry::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<&registry::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;
 };
 
 

+ 5 - 44
src/entt/entity/view.hpp

@@ -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.
      *

+ 0 - 6
test/benchmark/benchmark.cpp

@@ -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 = {}), ...);

+ 52 - 1
test/entt/entity/registry.cpp

@@ -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});
+}
+
+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; });
 
-    // TODO
+    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');
+        }
+    });
 }

+ 0 - 122
test/entt/entity/view.cpp

@@ -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>();