Browse Source

boost persistent views (around 100x)

Michele Caini 7 years ago
parent
commit
8f9934a7f4

+ 5 - 1
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?)

+ 35 - 36
docs/entity.md

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

+ 51 - 84
src/entt/entity/registry.hpp

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

+ 0 - 27
src/entt/entity/sparse_set.hpp

@@ -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 - 22
src/entt/entity/view.hpp

@@ -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];
     }
 
     /**

+ 6 - 6
test/benchmark/benchmark.cpp

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

+ 3 - 14
test/entt/entity/registry.cpp

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

+ 1 - 27
test/entt/entity/sparse_set.cpp

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

+ 154 - 58
test/entt/entity/view.cpp

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