Michele Caini 7 lat temu
rodzic
commit
10a7c54364
7 zmienionych plików z 1358 dodań i 255 usunięć
  1. 109 29
      README.md
  2. 0 3
      TODO
  3. 44 2
      src/entt/entity/registry.hpp
  4. 352 8
      src/entt/entity/view.hpp
  5. 407 0
      test/benchmark/benchmark.cpp
  6. 417 191
      test/entt/entity/view.cpp
  7. 29 22
      test/mod/mod.cpp

+ 109 - 29
README.md

@@ -36,6 +36,7 @@
          * [Multi component standard view](#multi-component-standard-view)
       * [Persistent View](#persistent-view)
       * [Raw View](#raw-view)
+      * [Runtime View](#runtime-view)
       * [Give me everything](#give-me-everything)
    * [Iterations: what is allowed and what is not](#iterations-what-is-allowed-and-what-is-not)
    * [Multithreading](#multithreading)
@@ -211,18 +212,14 @@ Dell XPS 13 out of the mid 2014):
 |-----------|-------------|-------------|
 | Create 1M entities | 0.0147s | **0.0046s** |
 | Destroy 1M entities | 0.0053s | **0.0045s** |
-| Standard view, 1M entities, one component | 0.0012s | **1.9e-07s** |
-| Standard view, 1M entities, two components | 0.0012s | **3.8e-07s** |
-| Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
-| Standard view, 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
-| Persistent view, 1M entities, two components | 0.0012s | **2.8e-07s** |
-| Standard view, 1M entities, five components | 0.0010s | **7.0e-07s** |
-| Persistent view, 1M entities, five components | 0.0010s | **2.8e-07s** |
-| Standard view, 1M entities, ten components | 0.0011s | **1.2e-06s** |
-| Standard view, 1M entities, ten components<br/>Half of the entities have all the components | 0.0010s | **1.2e-06s** |
-| Standard view, 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.2e-06s** |
-| Persistent view, 1M entities, ten components | 0.0011s | **3.0e-07s** |
-| Raw view, 1M entities | - | **2.2e-07s** |
+| 1M entities, one component | 0.0012s | **1.9e-07s** |
+| 1M entities, two components | 0.0012s | **3.8e-07s** |
+| 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
+| 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
+| 1M entities, five components | 0.0010s | **7.0e-07s** |
+| 1M entities, ten components | 0.0011s | **1.2e-06s** |
+| 1M entities, ten components<br/>Half of the entities have all the components | 0.0010s | **1.2e-06s** |
+| 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.2e-06s** |
 | Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0036s** |
 | Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0005s** |
 | Sort 150k entities, one component<br/>Arrays are almost sorted, std::sort | - | **0.0035s** |
@@ -715,9 +712,9 @@ created or destroyed.
 
 ### Runtime components
 
-Defining components at runtime is useful to support plugins and mods in general.
-However, it seems impossible with a tool designed around a bunch of templates.
-Indeed it's not that difficult.<br/>
+Defining components at runtime is useful to support plugin systems and mods in
+general. However, it seems impossible with a tool designed around a bunch of
+templates. Indeed it's not that difficult.<br/>
 Of course, some features cannot be easily exported into a runtime
 environment. As an example, sorting a group of components defined at runtime
 isn't for free if compared to most of the other operations. However, the basic
@@ -1175,9 +1172,9 @@ view can only iterate entities and their components, then read or update the
 data members of the latter.<br/>
 It is a subtle difference that can help designing a better software sometimes.
 
-There are mainly three kinds of views: standard (also known as `View`),
-persistent (also known as `PersistentView`) and raw (also known as
-`RawView`).<br/>
+There are mainly four kinds of views: standard (also known as `View`),
+persistent (also known as `PersistentView`), raw (also known as `RawView`) and
+runtime (also known as `RuntimeView`).<br/>
 All of them have pros and cons to take in consideration. In particular:
 
 * Standard views:
@@ -1232,6 +1229,21 @@ All of them have pros and cons to take in consideration. In particular:
   * They can be used to iterate only one type of component at a time.
   * They don't return the entity to which a component belongs to the caller.
 
+* Runtime views:
+
+  Pros:
+
+  * Their lists of components are defined at runtime and not at compile-time.
+  * Creating and destroying them isn't expensive at all because they don't have
+    any type of initialization.
+  * They are the best tool for things like plugin systems and mods in general.
+  * They don't affect any other operations of the registry.
+
+  Cons:
+
+  * Their performances are definitely lower than those of all the other views,
+    although they are still usable and sufficient for most of the purposes.
+
 To sum up and as a rule of thumb:
 
 * Use a raw view to iterate components only (no entities) for a given type.
@@ -1250,6 +1262,8 @@ To sum up and as a rule of thumb:
   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.
+* 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.
 
 To easily iterate entities and components, all the views offer the common
 `begin` and `end` member functions that allow users to use a view in a typical
@@ -1288,7 +1302,7 @@ 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/>
-To iterate a single component standard view, either use it in range-for loop:
+To iterate a single component standard view, either use it in a range-for loop:
 
 ```cpp
 auto view = registry.view<Renderable>();
@@ -1309,8 +1323,8 @@ registry.view<Renderable>().each([](auto entity, auto &renderable) {
 });
 ```
 
-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
@@ -1333,7 +1347,7 @@ 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/>
-To iterate a multi component standard view, either use it in range-for loop:
+To iterate a multi component standard view, either use it in a range-for loop:
 
 ```cpp
 auto view = registry.view<Position, Velocity>();
@@ -1359,8 +1373,9 @@ registry.view<Position, Velocity>().each([](auto entity, auto &position, auto &v
 });
 ```
 
-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 or get only some of the components, 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
@@ -1406,7 +1421,7 @@ 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.
 
-To iterate a persistent view, either use it in range-for loop:
+To iterate a persistent view, either use it in a range-for loop:
 
 ```cpp
 auto view = registry.view<Position, Velocity>(entt::persistent_t{});
@@ -1461,7 +1476,7 @@ 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/>
-To iterate a raw view, use it in range-for loop:
+To iterate a raw view, use it in a range-for loop:
 
 ```cpp
 auto view = registry.view<Renderable>(entt::raw_t{});
@@ -1471,9 +1486,74 @@ for(auto &&component: raw) {
 }
 ```
 
-**Note**: raw views don't have the `each` nor the `get` member function for
-obvious reasons. The former would only return the components and therefore it
-would be redundant, the latter isn't required at all.
+Or rely on the `each` member function:
+
+```cpp
+registry.view<Renderable>(entt::raw_t{}).each([](auto &renderable) {
+    // ...
+});
+```
+
+Performance are exactly the same in both cases.
+
+**Note**: raw views don't have a `get` member function for obvious reasons.
+
+### Runtime View
+
+Runtime views iterate entities that have at least all the given components in
+their bags. During construction, these views look at the number of entities
+available for each component and pick up a reference to the smallest
+set of candidates in order to speed up iterations.<br/>
+They offer more or less the same functionalities of a multi component standard
+view. However, they don't expose a `get` member function and users should refer
+to the registry that generated the view to access components. In particular, a
+runtime view exposes utility functions to get the estimated number of entities
+it is going to return and to know whether it's empty or not. It's also possible
+to ask a view if it contains a given entity.<br/>
+Refer to the inline documentation for all the details.
+
+Runtime view are extremely cheap to construct and should not be stored around in
+any case. They should be used immediately after creation and then they should be
+thrown away. The reasons for this go far beyond the scope of this document.<br/>
+To iterate a runtime view, either use it in a range-for loop:
+
+```cpp
+using component_type = typename decltype(registry)::component_type;
+component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
+
+auto view = registry.view(std::cbegin(types), std::cend(types));
+
+for(auto entity: view) {
+    // a component at a time ...
+    Position &position = registry.get<Position>(entity);
+    Velocity &velocity = registry.get<Velocity>(entity);
+
+    // ... or multiple components at once
+    std::tuple<Position &, Velocity &> tup = view.get<Position, Velocity>(entity);
+
+    // ...
+}
+```
+
+Or rely on the `each` member function to iterate entities:
+
+```cpp
+using component_type = typename decltype(registry)::component_type;
+component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
+
+auto view = registry.view(std::cbegin(types), std::cend(types)).each([](auto entity) {
+    // ...
+});
+```
+
+Performance are exactly the same in both cases.
+
+**Note**: runtime views are meant for all those cases where users don't know at
+compile-time what components to use to iterate entities. This is particularly
+well suited to plugin systems and mods in general. Where possible, don't use
+runtime views, as their performance are slightly inferior to those of the other
+views.
+
 
 ### Give me everything
 

+ 0 - 3
TODO

@@ -2,14 +2,11 @@
 * scene management (I prefer the concept of spaces, that is a kind of scene anyway)
 * review doc: separate it in multiple md/dox files, reduce the readme to a minimum and provide users with links to the online documentation on gh-pages
 * debugging tools (#60): the issue online already contains interesting tips on this, look at it
-* dynamic view, useful for runtime ecs, can be filled with the desired pool at runtime and are not constrained to a compile-time list of components
 * 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)
 * create dedicated flat map based on types implementation (sort of "type map") for types to use within the registry and so on...
 * ease the assignment of tags as string (type-less assign member function + user defined literal for hashed strings)
-* break snapshot <-> registry dependency (and get rid of utility.hpp)
 * family -> std::uint32_t (or some other fixed and known size type)
 * work stealing job system (see #100)
-* more on runtime stuff
 * C++17. That's all.
 * AOB

+ 44 - 2
src/entt/entity/registry.hpp

@@ -9,6 +9,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <cassert>
+#include <iterator>
 #include <algorithm>
 #include <type_traits>
 #include "../config/config.h"
@@ -74,13 +75,13 @@ class Registry {
     };
 
     template<typename Component>
-    bool managed() const ENTT_NOEXCEPT {
+    inline bool managed() const ENTT_NOEXCEPT {
         const auto ctype = component_family::type<Component>();
         return ctype < pools.size() && std::get<0>(pools[ctype]);
     }
 
     template<typename Component>
-    const SparseSet<Entity, Component> & pool() const ENTT_NOEXCEPT {
+    inline const SparseSet<Entity, Component> & pool() const ENTT_NOEXCEPT {
         assert(managed<Component>());
         const auto ctype = component_family::type<Component>();
         return static_cast<SparseSet<Entity, Component> &>(*std::get<0>(pools[ctype]));
@@ -1298,6 +1299,7 @@ public:
      * @see View<Entity, Component>
      * @see PersistentView
      * @see RawView
+     * @see RuntimeView
      *
      * @tparam Component Type of components used to construct the view.
      * @return A newly created standard view.
@@ -1432,6 +1434,7 @@ public:
      * @see View<Entity, Component>
      * @see PersistentView
      * @see RawView
+     * @see RuntimeView
      *
      * @tparam Component Types of components used to construct the view.
      * @return A newly created persistent view.
@@ -1461,6 +1464,7 @@ public:
      * @see View<Entity, Component>
      * @see PersistentView
      * @see RawView
+     * @see RuntimeView
      *
      * @tparam Component Type of component used to construct the view.
      * @return A newly created raw view.
@@ -1471,6 +1475,44 @@ public:
         return RawView<Entity, Component>{pool<Component>()};
     }
 
+    /**
+     * @brief Returns a runtime view for the given components.
+     *
+     * This kind of views are created on the fly and share with the registry its
+     * internal data structures.<br/>
+     * Users should throw away the view after use. Fortunately, creating and
+     * destroying a view is an incredibly cheap operation because they do not
+     * require any type of initialization.<br/>
+     * As a rule of thumb, storing a view should never be an option.
+     *
+     * Runtime views are well suited when users want to construct a view from
+     * some external inputs and don't know at compile-time what are the required
+     * components.<br/>
+     * This is particularly well suited to plugin systems and mods in general.
+     *
+     * @see View
+     * @see View<Entity, Component>
+     * @see PersistentView
+     * @see RawView
+     * @see RuntimeView
+     *
+     * @tparam It Type of forward iterator.
+     * @param first An iterator to the first element of the range of components.
+     * @param last An iterator past the last element of the range of components.
+     * @return A newly created runtime view.
+     */
+    template<typename It>
+    RuntimeView<Entity> view(It first, It last) {
+        static_assert(std::is_convertible<typename std::iterator_traits<It>::value_type, component_type>::value, "!");
+        std::vector<const SparseSet<Entity> *> set(last - first);
+
+        std::transform(first, last, set.begin(), [this](const component_type ctype) {
+            return ctype < pools.size() ? std::get<0>(pools[ctype]).get() : nullptr;
+        });
+
+        return RuntimeView<Entity>{std::move(set)};
+    }
+
     /**
      * @brief Returns a temporary object to use to create snapshots.
      *

+ 352 - 8
src/entt/entity/view.hpp

@@ -6,6 +6,7 @@
 #include <cassert>
 #include <array>
 #include <tuple>
+#include <vector>
 #include <utility>
 #include <algorithm>
 #include <type_traits>
@@ -61,6 +62,7 @@ class Registry;
  * @sa View
  * @sa View<Entity, Component>
  * @sa RawView
+ * @sa RuntimeView
  *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Component Types of components iterated by the view.
@@ -455,6 +457,7 @@ private:
  * @sa View<Entity, Component>
  * @sa PersistentView
  * @sa RawView
+ * @sa RuntimeView
  *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Component Types of components iterated by the view.
@@ -481,7 +484,7 @@ class View final {
     class Iterator {
         friend class View<Entity, Component...>;
 
-        using size_type = typename view_type::size_type;
+        using extent_type = typename view_type::size_type;
 
         Iterator(unchecked_type unchecked, underlying_iterator_type begin, underlying_iterator_type end) ENTT_NOEXCEPT
             : unchecked{unchecked},
@@ -495,7 +498,7 @@ class View final {
         }
 
         template<std::size_t... Indexes>
-        size_type min(std::index_sequence<Indexes...>) const ENTT_NOEXCEPT {
+        extent_type min(std::index_sequence<Indexes...>) const ENTT_NOEXCEPT {
             return std::min({ std::get<Indexes>(unchecked)->extent()... });
         }
 
@@ -549,7 +552,7 @@ class View final {
         unchecked_type unchecked;
         underlying_iterator_type begin;
         underlying_iterator_type end;
-        size_type extent;
+        extent_type extent;
     };
 
     View(pool_type<Component> &... pools) ENTT_NOEXCEPT
@@ -658,7 +661,7 @@ public:
      */
     const_iterator_type cbegin() const ENTT_NOEXCEPT {
         const auto *view = candidate();
-        return iterator_type{ unchecked(view), view->cbegin(), view->cend() };
+        return const_iterator_type{unchecked(view), view->cbegin(), view->cend()};
     }
 
     /**
@@ -714,7 +717,7 @@ public:
      */
     const_iterator_type cend() const ENTT_NOEXCEPT {
         const auto *view = candidate();
-        return iterator_type{ unchecked(view), view->cend(), view->cend() };
+        return const_iterator_type{unchecked(view), view->cend(), view->cend()};
     }
 
     /**
@@ -944,6 +947,7 @@ private:
  * @sa View
  * @sa PersistentView
  * @sa RawView
+ * @sa RuntimeView
  *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Component Type of component iterated by the view.
@@ -1287,6 +1291,7 @@ private:
  * @sa View
  * @sa View<Entity, Component>
  * @sa PersistentView
+ * @sa RuntimeView
  *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Component Type of component iterated by the view.
@@ -1523,12 +1528,12 @@ public:
     /**
      * @brief Iterates components and applies the given function object to them.
      *
-     * The function object is provided with a const reference to each component
-     * of the view.<br/>
+     * The function object is provided with a reference to each component of the
+     * view.<br/>
      * The signature of the function should be equivalent to the following:
      *
      * @code{.cpp}
-     * void(const Component &);
+     * void(Component &);
      * @endcode
      *
      * @tparam Func Type of the function object to invoke.
@@ -1544,6 +1549,345 @@ private:
 };
 
 
+/**
+ * @brief Runtime view.
+ *
+ * Runtime views iterate over those entities that have at least all the given
+ * components in their bags. During initialization, a runtime view looks at the
+ * number of entities available for each component and picks up a reference to
+ * the smallest set of candidate entities in order to get a performance boost
+ * when iterate.<br/>
+ * Order of elements during iterations are highly dependent on the order of the
+ * underlying data structures. See SparseSet and its specializations for more
+ * details.
+ *
+ * @b Important
+ *
+ * Iterators aren't invalidated if:
+ *
+ * * New instances of the given components are created and assigned to entities.
+ * * The entity currently pointed is modified (as an example, if one of the
+ *   given components is removed from the entity to which the iterator points).
+ *
+ * In all the other cases, modifying the pools of the given components in any
+ * way invalidates all the iterators and using them results in undefined
+ * behavior.
+ *
+ * @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 views,
+ * unless a pool wasn't missing when the view was built (in this case, the view
+ * won't have a valid reference and won't be updated accordingly).
+ *
+ * @warning
+ * Lifetime of a view must overcome the one of the registry that generated it.
+ * In any other case, attempting to use a view results in undefined behavior.
+ *
+ * @sa View
+ * @sa View<Entity, Component>
+ * @sa PersistentView
+ * @sa RawView
+ *
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+class RuntimeView {
+    /*! @brief A registry is allowed to create views. */
+    friend class Registry<Entity>;
+
+    using view_type = SparseSet<Entity>;
+    using underlying_iterator_type = typename view_type::const_iterator_type;
+    using pattern_type = std::vector<const view_type *>;
+    using extent_type = typename view_type::size_type;
+    using traits_type = entt_traits<Entity>;
+
+    class Iterator {
+        friend class RuntimeView<Entity>;
+
+        Iterator(underlying_iterator_type begin, underlying_iterator_type end, const view_type * const *first, const view_type * const *last, extent_type extent) ENTT_NOEXCEPT
+            : begin{begin},
+              end{end},
+              first{first},
+              last{last},
+              extent{extent}
+        {
+            if(begin != end && !valid()) {
+                ++(*this);
+            }
+        }
+
+        bool valid() const ENTT_NOEXCEPT {
+            const auto entity = *begin;
+            const auto sz = size_type(entity & traits_type::entity_mask);
+
+            return sz < extent && std::all_of(first, last, [entity](const auto *view) {
+                return view->fast(entity);
+            });
+        }
+
+    public:
+        using difference_type = typename underlying_iterator_type::difference_type;
+        using value_type = typename underlying_iterator_type::value_type;
+        using pointer = typename underlying_iterator_type::pointer;
+        using reference = typename underlying_iterator_type::reference;
+        using iterator_category = std::forward_iterator_tag;
+
+        Iterator() ENTT_NOEXCEPT = default;
+
+        Iterator(const Iterator &) ENTT_NOEXCEPT = default;
+        Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
+
+        Iterator & operator++() ENTT_NOEXCEPT {
+            return (++begin != end && !valid()) ? ++(*this) : *this;
+        }
+
+        Iterator operator++(int) ENTT_NOEXCEPT {
+            Iterator orig = *this;
+            return ++(*this), orig;
+        }
+
+        bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
+            return other.begin == begin;
+        }
+
+        inline bool operator!=(const Iterator &other) const ENTT_NOEXCEPT {
+            return !(*this == other);
+        }
+
+        pointer operator->() const ENTT_NOEXCEPT {
+            return begin.operator->();
+        }
+
+        inline reference operator*() const ENTT_NOEXCEPT {
+            return *operator->();
+        }
+
+    private:
+        underlying_iterator_type begin;
+        underlying_iterator_type end;
+        const view_type * const *first;
+        const view_type * const *last;
+        extent_type extent;
+    };
+
+    RuntimeView(pattern_type others) ENTT_NOEXCEPT
+        : pools{std::move(others)}
+    {
+        const auto it = std::min_element(pools.begin(), pools.end(), [](const auto *lhs, const auto *rhs) {
+            return !lhs || (rhs && (lhs->size() < rhs->size()));
+        });
+
+        // brings the best candidate (if any) on front of the vector
+        std::rotate(pools.begin(), it, pools.end());
+    }
+
+    extent_type min() const ENTT_NOEXCEPT {
+        extent_type extent{};
+
+        if(valid()) {
+            const auto it = std::min_element(pools.cbegin(), pools.cend(), [](const auto *lhs, const auto *rhs) {
+                return lhs->extent() < rhs->extent();
+            });
+
+            extent = (*it)->extent();
+        }
+
+        return extent;
+    }
+
+    inline bool valid() const ENTT_NOEXCEPT {
+        return !pools.empty() && pools.front();
+    }
+
+public:
+    /*! @brief Underlying entity identifier. */
+    using entity_type = typename view_type::entity_type;
+    /*! @brief Unsigned integer type. */
+    using size_type = typename view_type::size_type;
+    /*! @brief Input iterator type. */
+    using iterator_type = Iterator;
+    /*! @brief Constant input iterator type. */
+    using const_iterator_type = Iterator;
+
+    /**
+     * @brief Estimates the number of entities that have the given components.
+     * @return Estimated number of entities that have the given components.
+     */
+    size_type size() const ENTT_NOEXCEPT {
+        return valid() ? pools.front()->size() : size_type{};
+    }
+
+    /**
+     * @brief Checks if the view is definitely empty.
+     * @return True if the view is definitely empty, false otherwise.
+     */
+    bool empty() const ENTT_NOEXCEPT {
+        return !valid() || pools.front()->empty();
+    }
+
+    /**
+     * @brief Returns an iterator to the first entity that has the given
+     * components.
+     *
+     * The returned iterator points to the first entity that has the given
+     * components. If the view is empty, the returned iterator will be equal to
+     * `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the first entity that has the given components.
+     */
+    const_iterator_type cbegin() const ENTT_NOEXCEPT {
+        const_iterator_type it{};
+
+        if(valid()) {
+            const auto &pool = *pools.front();
+            const auto * const *data = pools.data();
+            it = { pool.cbegin(), pool.cend(), data + 1, data + pools.size(), min() };
+        }
+
+        return it;
+    }
+
+    /**
+     * @brief Returns an iterator to the first entity that has the given
+     * components.
+     *
+     * The returned iterator points to the first entity that has the given
+     * components. If the view is empty, the returned iterator will be equal to
+     * `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the first entity that has the given components.
+     */
+    inline const_iterator_type begin() const ENTT_NOEXCEPT {
+        return cbegin();
+    }
+
+    /**
+     * @brief Returns an iterator to the first entity that has the given
+     * components.
+     *
+     * The returned iterator points to the first entity that has the given
+     * components. If the view is empty, the returned iterator will be equal to
+     * `end()`.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the first entity that has the given components.
+     */
+    inline iterator_type begin() ENTT_NOEXCEPT {
+        return cbegin();
+    }
+
+    /**
+     * @brief Returns an iterator that is past the last entity that has the
+     * given components.
+     *
+     * The returned iterator points to the entity following the last entity that
+     * has the given components. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the entity following the last entity that has the
+     * given components.
+     */
+    const_iterator_type cend() const ENTT_NOEXCEPT {
+        const_iterator_type it{};
+
+        if(valid()) {
+            const auto &pool = *pools.front();
+            it = { pool.cend(), pool.cend(), nullptr, nullptr, min() };
+        }
+
+        return it;
+    }
+
+    /**
+     * @brief Returns an iterator that is past the last entity that has the
+     * given components.
+     *
+     * The returned iterator points to the entity following the last entity that
+     * has the given components. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the entity following the last entity that has the
+     * given components.
+     */
+    inline const_iterator_type end() const ENTT_NOEXCEPT {
+        return cend();
+    }
+
+    /**
+     * @brief Returns an iterator that is past the last entity that has the
+     * given components.
+     *
+     * The returned iterator points to the entity following the last entity that
+     * has the given components. Attempting to dereference the returned iterator
+     * results in undefined behavior.
+     *
+     * @note
+     * Input iterators stay true to the order imposed to the underlying data
+     * structures.
+     *
+     * @return An iterator to the entity following the last entity that has the
+     * given components.
+     */
+    inline iterator_type end() ENTT_NOEXCEPT {
+        return cend();
+    }
+
+    /**
+     * @brief Checks if a view contains an entity.
+     * @param entity A valid entity identifier.
+     * @return True if the view contains the given entity, false otherwise.
+     */
+    bool contains(const entity_type entity) const ENTT_NOEXCEPT {
+        return valid() && std::all_of(pools.cbegin(), pools.cend(), [entity](const auto *view) {
+            return view->has(entity) && view->data()[view->get(entity)] == entity;
+        });
+    }
+
+    /**
+     * @brief Iterates entities and applies the given function object to them.
+     *
+     * The function object is invoked for each entity. It is provided only with
+     * the entity itself. To get the components, users can use the registry with
+     * which the view was built.<br/>
+     * The signature of the function should be equivalent to the following:
+     *
+     * @code{.cpp}
+     * void(const entity_type);
+     * @endcode
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    void each(Func func) const {
+        std::for_each(cbegin(), cend(), func);
+    }
+
+private:
+    pattern_type pools;
+};
+
+
 }
 
 

+ 407 - 0
test/benchmark/benchmark.cpp

@@ -2,6 +2,7 @@
 #include <cstddef>
 #include <cstdint>
 #include <chrono>
+#include <iterator>
 #include <gtest/gtest.h>
 #include <entt/entity/registry.hpp>
 
@@ -135,6 +136,31 @@ TEST(Benchmark, IterateSingleComponentRaw1M) {
     });
 }
 
+TEST(Benchmark, IterateSingleComponentRuntime1M) {
+    entt::DefaultRegistry registry;
+
+    std::cout << "Iterating over 1000000 entities, one component, runtime view" << std::endl;
+
+     for(std::uint64_t i = 0; i < 1000000L; i++) {
+         const auto entity = registry.create();
+         registry.assign<Position>(entity);
+     }
+
+    auto test = [&registry](auto func) {
+        using component_type = typename entt::DefaultRegistry::component_type;
+        component_type types[] = { registry.type<Position>() };
+
+        Timer timer;
+        registry.view(std::begin(types), std::end(types)).each(func);
+        timer.elapsed();
+    };
+
+    test([](auto) {});
+    test([&registry](auto entity) {
+        registry.get<Position>(entity).x = {};
+    });
+}
+
 TEST(Benchmark, IterateTwoComponents1M) {
     entt::DefaultRegistry registry;
 
@@ -242,6 +268,94 @@ TEST(Benchmark, IterateTwoComponentsPersistent1M) {
     });
 }
 
+TEST(Benchmark, IterateTwoComponentsRuntime1M) {
+    entt::DefaultRegistry registry;
+    registry.prepare<Position, Velocity>();
+
+    std::cout << "Iterating over 1000000 entities, two components, runtime view" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<Position>(entity);
+        registry.assign<Velocity>(entity);
+    }
+
+    auto test = [&registry](auto func) {
+        using component_type = typename entt::DefaultRegistry::component_type;
+        component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
+
+        Timer timer;
+        registry.view(std::begin(types), std::end(types)).each(func);
+        timer.elapsed();
+    };
+
+    test([](auto) {});
+    test([&registry](auto entity) {
+        registry.get<Position>(entity).x = {};
+        registry.get<Velocity>(entity).x = {};
+    });
+}
+
+TEST(Benchmark, IterateTwoComponentsRuntime1MHalf) {
+    entt::DefaultRegistry registry;
+
+    std::cout << "Iterating over 1000000 entities, two components, half of the entities have all the components, runtime view" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<Velocity>(entity);
+
+        if(i % 2) {
+            registry.assign<Position>(entity);
+        }
+    }
+
+    auto test = [&registry](auto func) {
+        using component_type = typename entt::DefaultRegistry::component_type;
+        component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
+
+        Timer timer;
+        registry.view(std::begin(types), std::end(types)).each(func);
+        timer.elapsed();
+    };
+
+    test([](auto) {});
+    test([&registry](auto entity) {
+        registry.get<Position>(entity).x = {};
+        registry.get<Velocity>(entity).x = {};
+    });
+}
+
+TEST(Benchmark, IterateTwoComponentsRuntime1MOne) {
+    entt::DefaultRegistry registry;
+
+    std::cout << "Iterating over 1000000 entities, two components, only one entity has all the components, runtime view" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<Velocity>(entity);
+
+        if(i == 5000000L) {
+            registry.assign<Position>(entity);
+        }
+    }
+
+    auto test = [&registry](auto func) {
+        using component_type = typename entt::DefaultRegistry::component_type;
+        component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
+
+        Timer timer;
+        registry.view(std::begin(types), std::end(types)).each(func);
+        timer.elapsed();
+    };
+
+    test([](auto) {});
+    test([&registry](auto entity) {
+        registry.get<Position>(entity).x = {};
+        registry.get<Velocity>(entity).x = {};
+    });
+}
+
 TEST(Benchmark, IterateFiveComponents1M) {
     entt::DefaultRegistry registry;
 
@@ -361,6 +475,130 @@ TEST(Benchmark, IterateFiveComponentsPersistent1M) {
     });
 }
 
+TEST(Benchmark, IterateFiveComponentsRuntime1M) {
+    entt::DefaultRegistry registry;
+    registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
+
+    std::cout << "Iterating over 1000000 entities, five components, runtime view" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<Position>(entity);
+        registry.assign<Velocity>(entity);
+        registry.assign<Comp<1>>(entity);
+        registry.assign<Comp<2>>(entity);
+        registry.assign<Comp<3>>(entity);
+    }
+
+    auto test = [&registry](auto func) {
+        using component_type = typename entt::DefaultRegistry::component_type;
+        component_type types[] = {
+            registry.type<Position>(),
+            registry.type<Velocity>(),
+            registry.type<Comp<1>>(),
+            registry.type<Comp<2>>(),
+            registry.type<Comp<3>>()
+        };
+
+        Timer timer;
+        registry.view(std::begin(types), std::end(types)).each(func);
+        timer.elapsed();
+    };
+
+    test([](auto) {});
+    test([&registry](auto entity) {
+        registry.get<Position>(entity).x = {};
+        registry.get<Velocity>(entity).x = {};
+        registry.get<Comp<1>>(entity).x = {};
+        registry.get<Comp<2>>(entity).x = {};
+        registry.get<Comp<3>>(entity).x = {};
+    });
+}
+
+TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) {
+    entt::DefaultRegistry registry;
+
+    std::cout << "Iterating over 1000000 entities, five components, half of the entities have all the components, runtime view" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<Velocity>(entity);
+        registry.assign<Comp<1>>(entity);
+        registry.assign<Comp<2>>(entity);
+        registry.assign<Comp<3>>(entity);
+
+        if(i % 2) {
+            registry.assign<Position>(entity);
+        }
+    }
+
+    auto test = [&registry](auto func) {
+        using component_type = typename entt::DefaultRegistry::component_type;
+        component_type types[] = {
+            registry.type<Position>(),
+            registry.type<Velocity>(),
+            registry.type<Comp<1>>(),
+            registry.type<Comp<2>>(),
+            registry.type<Comp<3>>()
+        };
+
+        Timer timer;
+        registry.view(std::begin(types), std::end(types)).each(func);
+        timer.elapsed();
+    };
+
+    test([](auto) {});
+    test([&registry](auto entity) {
+        registry.get<Position>(entity).x = {};
+        registry.get<Velocity>(entity).x = {};
+        registry.get<Comp<1>>(entity).x = {};
+        registry.get<Comp<2>>(entity).x = {};
+        registry.get<Comp<3>>(entity).x = {};
+    });
+}
+
+TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
+    entt::DefaultRegistry registry;
+
+    std::cout << "Iterating over 1000000 entities, five components, only one entity has all the components, runtime view" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<Velocity>(entity);
+        registry.assign<Comp<1>>(entity);
+        registry.assign<Comp<2>>(entity);
+        registry.assign<Comp<3>>(entity);
+
+        if(i == 5000000L) {
+            registry.assign<Position>(entity);
+        }
+    }
+
+    auto test = [&registry](auto func) {
+        using component_type = typename entt::DefaultRegistry::component_type;
+        component_type types[] = {
+            registry.type<Position>(),
+            registry.type<Velocity>(),
+            registry.type<Comp<1>>(),
+            registry.type<Comp<2>>(),
+            registry.type<Comp<3>>()
+        };
+
+        Timer timer;
+        registry.view(std::begin(types), std::end(types)).each(func);
+        timer.elapsed();
+    };
+
+    test([](auto) {});
+    test([&registry](auto entity) {
+        registry.get<Position>(entity).x = {};
+        registry.get<Velocity>(entity).x = {};
+        registry.get<Comp<1>>(entity).x = {};
+        registry.get<Comp<2>>(entity).x = {};
+        registry.get<Comp<3>>(entity).x = {};
+    });
+}
+
 TEST(Benchmark, IterateTenComponents1M) {
     entt::DefaultRegistry registry;
 
@@ -500,6 +738,175 @@ TEST(Benchmark, IterateTenComponentsPersistent1M) {
     });
 }
 
+TEST(Benchmark, IterateTenComponentsRuntime1M) {
+    entt::DefaultRegistry registry;
+    registry.prepare<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;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<Position>(entity);
+        registry.assign<Velocity>(entity);
+        registry.assign<Comp<1>>(entity);
+        registry.assign<Comp<2>>(entity);
+        registry.assign<Comp<3>>(entity);
+        registry.assign<Comp<4>>(entity);
+        registry.assign<Comp<5>>(entity);
+        registry.assign<Comp<6>>(entity);
+        registry.assign<Comp<7>>(entity);
+        registry.assign<Comp<8>>(entity);
+    }
+
+    auto test = [&registry](auto func) {
+        using component_type = typename entt::DefaultRegistry::component_type;
+        component_type types[] = {
+            registry.type<Position>(),
+            registry.type<Velocity>(),
+            registry.type<Comp<1>>(),
+            registry.type<Comp<2>>(),
+            registry.type<Comp<3>>(),
+            registry.type<Comp<4>>(),
+            registry.type<Comp<5>>(),
+            registry.type<Comp<6>>(),
+            registry.type<Comp<7>>(),
+            registry.type<Comp<8>>()
+        };
+
+        Timer timer;
+        registry.view(std::begin(types), std::end(types)).each(func);
+        timer.elapsed();
+    };
+
+    test([](auto) {});
+    test([&registry](auto entity) {
+        registry.get<Position>(entity).x = {};
+        registry.get<Velocity>(entity).x = {};
+        registry.get<Comp<1>>(entity).x = {};
+        registry.get<Comp<2>>(entity).x = {};
+        registry.get<Comp<3>>(entity).x = {};
+        registry.get<Comp<4>>(entity).x = {};
+        registry.get<Comp<5>>(entity).x = {};
+        registry.get<Comp<6>>(entity).x = {};
+        registry.get<Comp<7>>(entity).x = {};
+        registry.get<Comp<8>>(entity).x = {};
+    });
+}
+
+TEST(Benchmark, IterateTenComponentsRuntime1MHalf) {
+    entt::DefaultRegistry registry;
+
+    std::cout << "Iterating over 1000000 entities, ten components, half of the entities have all the components, runtime view" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<Velocity>(entity);
+        registry.assign<Comp<1>>(entity);
+        registry.assign<Comp<2>>(entity);
+        registry.assign<Comp<3>>(entity);
+        registry.assign<Comp<4>>(entity);
+        registry.assign<Comp<5>>(entity);
+        registry.assign<Comp<6>>(entity);
+        registry.assign<Comp<7>>(entity);
+        registry.assign<Comp<8>>(entity);
+
+        if(i % 2) {
+            registry.assign<Position>(entity);
+        }
+    }
+
+    auto test = [&registry](auto func) {
+        using component_type = typename entt::DefaultRegistry::component_type;
+        component_type types[] = {
+            registry.type<Position>(),
+            registry.type<Velocity>(),
+            registry.type<Comp<1>>(),
+            registry.type<Comp<2>>(),
+            registry.type<Comp<3>>(),
+            registry.type<Comp<4>>(),
+            registry.type<Comp<5>>(),
+            registry.type<Comp<6>>(),
+            registry.type<Comp<7>>(),
+            registry.type<Comp<8>>()
+        };
+
+        Timer timer;
+        registry.view(std::begin(types), std::end(types)).each(func);
+        timer.elapsed();
+    };
+
+    test([](auto) {});
+    test([&registry](auto entity) {
+        registry.get<Position>(entity).x = {};
+        registry.get<Velocity>(entity).x = {};
+        registry.get<Comp<1>>(entity).x = {};
+        registry.get<Comp<2>>(entity).x = {};
+        registry.get<Comp<3>>(entity).x = {};
+        registry.get<Comp<4>>(entity).x = {};
+        registry.get<Comp<5>>(entity).x = {};
+        registry.get<Comp<6>>(entity).x = {};
+        registry.get<Comp<7>>(entity).x = {};
+        registry.get<Comp<8>>(entity).x = {};
+    });
+}
+
+TEST(Benchmark, IterateTenComponentsRuntime1MOne) {
+    entt::DefaultRegistry registry;
+
+    std::cout << "Iterating over 1000000 entities, ten components, only one entity has all the components, runtime view" << std::endl;
+
+    for(std::uint64_t i = 0; i < 1000000L; i++) {
+        const auto entity = registry.create();
+        registry.assign<Velocity>(entity);
+        registry.assign<Comp<1>>(entity);
+        registry.assign<Comp<2>>(entity);
+        registry.assign<Comp<3>>(entity);
+        registry.assign<Comp<4>>(entity);
+        registry.assign<Comp<5>>(entity);
+        registry.assign<Comp<6>>(entity);
+        registry.assign<Comp<7>>(entity);
+        registry.assign<Comp<8>>(entity);
+
+        if(i == 5000000L) {
+            registry.assign<Position>(entity);
+        }
+    }
+
+    auto test = [&registry](auto func) {
+        using component_type = typename entt::DefaultRegistry::component_type;
+        component_type types[] = {
+            registry.type<Position>(),
+            registry.type<Velocity>(),
+            registry.type<Comp<1>>(),
+            registry.type<Comp<2>>(),
+            registry.type<Comp<3>>(),
+            registry.type<Comp<4>>(),
+            registry.type<Comp<5>>(),
+            registry.type<Comp<6>>(),
+            registry.type<Comp<7>>(),
+            registry.type<Comp<8>>()
+        };
+
+        Timer timer;
+        registry.view(std::begin(types), std::end(types)).each(func);
+        timer.elapsed();
+    };
+
+    test([](auto) {});
+    test([&registry](auto entity) {
+        registry.get<Position>(entity).x = {};
+        registry.get<Velocity>(entity).x = {};
+        registry.get<Comp<1>>(entity).x = {};
+        registry.get<Comp<2>>(entity).x = {};
+        registry.get<Comp<3>>(entity).x = {};
+        registry.get<Comp<4>>(entity).x = {};
+        registry.get<Comp<5>>(entity).x = {};
+        registry.get<Comp<6>>(entity).x = {};
+        registry.get<Comp<7>>(entity).x = {};
+        registry.get<Comp<8>>(entity).x = {};
+    });
+}
+
 TEST(Benchmark, SortSingle) {
     entt::DefaultRegistry registry;
 

+ 417 - 191
test/entt/entity/view.cpp

@@ -1,8 +1,231 @@
 #include <utility>
+#include <iterator>
 #include <gtest/gtest.h>
 #include <entt/entity/registry.hpp>
 #include <entt/entity/view.hpp>
 
+TEST(PersistentView, Prepare) {
+    entt::Registry<std::uint64_t> registry;
+    registry.prepare<int, char>();
+    auto view = registry.view<int, char>(entt::persistent_t{});
+    const auto &cview = view;
+
+    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.view<int, char>(entt::persistent_t{}).begin()++));
+    ASSERT_NO_THROW((++registry.view<int, char>(entt::persistent_t{}).begin()));
+
+    ASSERT_NE(view.begin(), view.end());
+    ASSERT_NE(cview.begin(), cview.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, NoPrepare) {
+    entt::Registry<std::uint64_t> registry;
+    auto view = registry.view<int, char>(entt::persistent_t{});
+
+    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.view<int, char>(entt::persistent_t{}).begin()++));
+    ASSERT_NO_THROW((++registry.view<int, char>(entt::persistent_t{}).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<std::uint64_t> registry;
+    auto view = registry.view<int, char>(entt::persistent_t{});
+    const auto &cview = view;
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    for(typename decltype(view)::size_type i{}; i < view.size(); ++i) {
+        ASSERT_EQ(view[i], i ? e0 : e1);
+        ASSERT_EQ(cview[i], i ? e0 : e1);
+    }
+}
+
+TEST(PersistentView, Contains) {
+    entt::Registry<std::uint64_t> registry;
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    registry.destroy(e0);
+
+    auto view = registry.view<int, char>(entt::persistent_t{});
+
+    ASSERT_FALSE(view.contains(e0));
+    ASSERT_TRUE(view.contains(e1));
+}
+
+TEST(PersistentView, Empty) {
+    entt::Registry<std::uint64_t> registry;
+
+    const auto e0 = registry.create();
+    registry.assign<double>(e0);
+    registry.assign<int>(e0);
+    registry.assign<float>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<char>(e1);
+    registry.assign<float>(e1);
+
+    for(auto entity: registry.view<char, int, float>(entt::persistent_t{})) {
+        (void)entity;
+        FAIL();
+    }
+
+    for(auto entity: registry.view<double, char, int, float>(entt::persistent_t{})) {
+        (void)entity;
+        FAIL();
+    }
+}
+
+TEST(PersistentView, Each) {
+    entt::Registry<std::uint64_t> registry;
+    registry.prepare<int, char>();
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0);
+    registry.assign<char>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1);
+    registry.assign<char>(e1);
+
+    auto view = registry.view<int, char>(entt::persistent_t{});
+    const auto &cview = static_cast<const decltype(view) &>(view);
+    std::size_t cnt = 0;
+
+    view.each([&cnt](auto, int &, char &) { ++cnt; });
+
+    ASSERT_EQ(cnt, std::size_t{2});
+
+    cview.each([&cnt](auto, const int &, const char &) { --cnt; });
+
+    ASSERT_EQ(cnt, std::size_t{0});
+}
+
+TEST(PersistentView, Sort) {
+    entt::Registry<std::uint64_t> registry;
+    registry.prepare<int, unsigned int>();
+
+    const auto e0 = registry.create();
+    const auto e1 = registry.create();
+    const auto e2 = registry.create();
+
+    auto uval = 0u;
+    auto ival = 0;
+
+    registry.assign<unsigned int>(e0, uval++);
+    registry.assign<unsigned int>(e1, uval++);
+    registry.assign<unsigned int>(e2, uval++);
+
+    registry.assign<int>(e0, ival++);
+    registry.assign<int>(e1, ival++);
+    registry.assign<int>(e2, ival++);
+
+    auto view = registry.view<int, unsigned int>(entt::persistent_t{});
+
+    for(auto entity: view) {
+        ASSERT_EQ(view.get<unsigned int>(entity), --uval);
+        ASSERT_EQ(view.get<int>(entity), --ival);
+    }
+
+    registry.sort<unsigned int>(std::less<unsigned int>{});
+    view.sort<unsigned int>();
+
+    for(auto entity: view) {
+        ASSERT_EQ(view.get<unsigned int>(entity), uval++);
+        ASSERT_EQ(view.get<int>(entity), ival++);
+    }
+}
+
 TEST(SingleComponentView, Functionalities) {
     entt::Registry<std::uint64_t> registry;
     auto view = registry.view<char>();
@@ -62,7 +285,7 @@ TEST(SingleComponentView, ElementAccess) {
     registry.assign<int>(e1);
 
     for(typename decltype(view)::size_type i{}; i < view.size(); ++i) {
-        //ASSERT_EQ(view[i], i ? e0 : e1);
+        ASSERT_EQ(view[i], i ? e0 : e1);
         ASSERT_EQ(cview[i], i ? e0 : e1);
     }
 }
@@ -140,11 +363,10 @@ TEST(MultipleComponentView, Functionalities) {
 
     registry.assign<char>(e1);
 
-    auto it = registry.view<char>().begin();
+    auto it = registry.view<int, char>().begin();
 
     ASSERT_EQ(*it, e1);
-    ASSERT_EQ(*(it+1), e0);
-    ASSERT_EQ(it += 2, registry.view<char>().end());
+    ASSERT_EQ(++it, (registry.view<int, char>().end()));
 
     ASSERT_NO_THROW((registry.view<int, char>().begin()++));
     ASSERT_NO_THROW((++registry.view<int, char>().begin()));
@@ -163,20 +385,16 @@ TEST(MultipleComponentView, Functionalities) {
         ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
         ASSERT_EQ(cview.get<char>(entity), '2');
     }
-
-    registry.remove<char>(e0);
-    registry.remove<char>(e1);
 }
 
 TEST(MultipleComponentView, Iterator) {
-    using iterator_type = typename decltype(std::declval<entt::Registry<std::uint64_t>>().view<int, char>())::iterator_type;
-
     entt::Registry<std::uint64_t> registry;
     const auto entity = registry.create();
     registry.assign<int>(entity);
     registry.assign<char>(entity);
 
     const auto view = registry.view<int, char>();
+    using iterator_type = typename decltype(view)::iterator_type;
 
     iterator_type end{view.begin()};
     iterator_type begin{};
@@ -192,14 +410,13 @@ TEST(MultipleComponentView, Iterator) {
 }
 
 TEST(MultipleComponentView, ConstIterator) {
-    using iterator_type = typename decltype(std::declval<entt::Registry<std::uint64_t>>().view<int, char>())::const_iterator_type;
-
     entt::Registry<std::uint64_t> registry;
     const auto entity = registry.create();
     registry.assign<int>(entity);
     registry.assign<char>(entity);
 
     const auto view = registry.view<int, char>();
+    using iterator_type = typename decltype(view)::iterator_type;
 
     iterator_type cend{view.cbegin()};
     iterator_type cbegin{};
@@ -302,51 +519,54 @@ TEST(MultipleComponentView, EachWithHoles) {
     });
 }
 
-TEST(PersistentView, Prepare) {
+TEST(RawView, Functionalities) {
     entt::Registry<std::uint64_t> registry;
-    registry.prepare<int, char>();
-    auto view = registry.view<int, char>(entt::persistent_t{});
+    auto view = registry.view<char>(entt::raw_t{});
     const auto &cview = view;
 
     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.view<int, char>(entt::persistent_t{}).begin()++));
-    ASSERT_NO_THROW((++registry.view<int, char>(entt::persistent_t{}).begin()));
+    ASSERT_NO_THROW(registry.view<char>(entt::raw_t{}).begin()++);
+    ASSERT_NO_THROW(++registry.view<char>(entt::raw_t{}).begin());
 
     ASSERT_NE(view.begin(), view.end());
     ASSERT_NE(cview.begin(), cview.end());
     ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
 
-    registry.assign<int>(e0);
+    registry.assign<char>(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');
+    for(auto &&component: view) {
+        ASSERT_TRUE(component == '1' || component == '2');
     }
 
     ASSERT_EQ(*(view.data() + 0), e1);
+    ASSERT_EQ(*(view.data() + 1), e0);
 
-    registry.remove<char>(e0);
+    ASSERT_EQ(*(view.raw() + 0), '2');
+    ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
+
+    for(auto &&component: view) {
+        // verifies that iterators return references to components
+        component = '0';
+    }
+
+    for(auto &&component: view) {
+        ASSERT_TRUE(component == '0');
+    }
+
+    registry.remove<char>(e0);
     registry.remove<char>(e1);
 
     ASSERT_EQ(view.begin(), view.end());
@@ -354,9 +574,73 @@ TEST(PersistentView, Prepare) {
     ASSERT_TRUE(view.empty());
 }
 
-TEST(PersistentView, NoPrepare) {
+TEST(RawView, ElementAccess) {
     entt::Registry<std::uint64_t> registry;
-    auto view = registry.view<int, char>(entt::persistent_t{});
+    auto view = registry.view<int>(entt::raw_t{});
+    const auto &cview = view;
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0, 42);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1, 3);
+
+    for(typename decltype(view)::size_type i{}; i < view.size(); ++i) {
+        ASSERT_EQ(view[i], i ? 42 : 3);
+        ASSERT_EQ(cview[i], i ? 42 : 3);
+    }
+}
+
+TEST(RawView, Empty) {
+    entt::Registry<std::uint64_t> registry;
+
+    const auto e0 = registry.create();
+    registry.assign<char>(e0);
+    registry.assign<double>(e0);
+
+    const auto e1 = registry.create();
+    registry.assign<char>(e1);
+
+    auto view = registry.view<int>(entt::raw_t{});
+
+    ASSERT_EQ(view.size(), entt::Registry<std::uint64_t>::size_type{0});
+
+    for(auto &&component: view) {
+        (void)component;
+        FAIL();
+    }
+}
+
+TEST(RawView, Each) {
+    entt::Registry<std::uint64_t> registry;
+
+    registry.assign<int>(registry.create(), 1);
+    registry.assign<int>(registry.create(), 3);
+
+    auto view = registry.view<int>(entt::raw_t{});
+    const auto &cview = static_cast<const decltype(view) &>(view);
+    std::size_t cnt = 0;
+
+    view.each([&cnt](int &v) { cnt += (v % 2); });
+
+    ASSERT_EQ(cnt, std::size_t{2});
+
+    cview.each([&cnt](const int &v) { cnt -= (v % 2); });
+
+    ASSERT_EQ(cnt, std::size_t{0});
+}
+
+TEST(RuntimeView, Functionalities) {
+    entt::Registry<std::uint64_t> registry;
+    using component_type = typename decltype(registry)::component_type;
+
+    // forces the creation of the pools
+    registry.reserve<int>(0);
+    registry.reserve<char>(0);
+
+    component_type types[] = { registry.type<int>(), registry.type<char>() };
+    auto view = registry.view(std::begin(types), std::end(types));
+    const auto &cview = view;
 
     ASSERT_TRUE(view.empty());
 
@@ -365,65 +649,86 @@ TEST(PersistentView, NoPrepare) {
 
     const auto e1 = registry.create();
     registry.assign<int>(e1);
-    registry.assign<char>(e1);
 
     ASSERT_FALSE(view.empty());
-    ASSERT_NO_THROW((registry.view<int, char>(entt::persistent_t{}).begin()++));
-    ASSERT_NO_THROW((++registry.view<int, char>(entt::persistent_t{}).begin()));
 
-    ASSERT_NE(view.begin(), view.end());
-    ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
+    registry.assign<char>(e1);
 
-    registry.assign<int>(e0);
+    auto it = registry.view(std::begin(types), std::end(types)).begin();
 
-    ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
+    ASSERT_EQ(*it, e1);
+    ASSERT_EQ(++it, (registry.view(std::begin(types), std::end(types)).end()));
 
-    registry.remove<int>(e0);
+    ASSERT_NO_THROW((registry.view(std::begin(types), std::end(types)).begin()++));
+    ASSERT_NO_THROW((++registry.view(std::begin(types), std::end(types)).begin()));
 
-    ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
+    ASSERT_NE(view.begin(), view.end());
+    ASSERT_NE(cview.begin(), cview.end());
+    ASSERT_EQ(view.size(), decltype(view.size()){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(registry.get<int>(entity), 42);
+        ASSERT_EQ(registry.get<char>(entity), '2');
     }
+}
 
-    ASSERT_EQ(*(view.data() + 0), e1);
+TEST(RuntimeView, Iterator) {
+    entt::Registry<std::uint64_t> registry;
+    using component_type = typename decltype(registry)::component_type;
 
-    registry.remove<char>(e0);
-    registry.remove<char>(e1);
+    const auto entity = registry.create();
+    registry.assign<int>(entity);
+    registry.assign<char>(entity);
 
-    ASSERT_EQ(view.begin(), view.end());
-    ASSERT_EQ(view.cbegin(), view.cend());
-    ASSERT_TRUE(view.empty());
+    component_type types[] = { registry.type<int>(), registry.type<char>() };
+    auto view = registry.view(std::begin(types), std::end(types));
+    using iterator_type = typename decltype(view)::iterator_type;
+
+    iterator_type end{view.begin()};
+    iterator_type begin{};
+    begin = view.end();
+    std::swap(begin, end);
+
+    ASSERT_EQ(begin, view.begin());
+    ASSERT_EQ(end, view.end());
+    ASSERT_NE(begin, end);
+
+    ASSERT_EQ(view.begin()++, view.begin());
+    ASSERT_EQ(++view.begin(), view.end());
 }
 
-TEST(PersistentView, ElementAccess) {
+TEST(RuntimeView, ConstIterator) {
     entt::Registry<std::uint64_t> registry;
-    auto view = registry.view<int, char>(entt::persistent_t{});
-    const auto &cview = view;
+    using component_type = typename decltype(registry)::component_type;
 
-    const auto e0 = registry.create();
-    registry.assign<int>(e0);
-    registry.assign<char>(e0);
+    const auto entity = registry.create();
+    registry.assign<int>(entity);
+    registry.assign<char>(entity);
 
-    const auto e1 = registry.create();
-    registry.assign<int>(e1);
-    registry.assign<char>(e1);
+    component_type types[] = { registry.type<int>(), registry.type<char>() };
+    auto view = registry.view(std::begin(types), std::end(types));
+    using iterator_type = typename decltype(view)::iterator_type;
 
-    for(typename decltype(view)::size_type i{}; i < view.size(); ++i) {
-        ASSERT_EQ(view[i], i ? e0 : e1);
-        ASSERT_EQ(cview[i], i ? e0 : e1);
-    }
+    iterator_type cend{view.cbegin()};
+    iterator_type cbegin{};
+    cbegin = view.cend();
+    std::swap(cbegin, cend);
+
+    ASSERT_EQ(cbegin, view.cbegin());
+    ASSERT_EQ(cend, view.cend());
+    ASSERT_NE(cbegin, cend);
+
+    ASSERT_EQ(view.cbegin()++, view.cbegin());
+    ASSERT_EQ(++view.cbegin(), view.cend());
 }
 
-TEST(PersistentView, Contains) {
+TEST(RuntimeView, Contains) {
     entt::Registry<std::uint64_t> registry;
+    using component_type = typename decltype(registry)::component_type;
 
     const auto e0 = registry.create();
     registry.assign<int>(e0);
@@ -435,14 +740,16 @@ TEST(PersistentView, Contains) {
 
     registry.destroy(e0);
 
-    auto view = registry.view<int, char>(entt::persistent_t{});
+    component_type types[] = { registry.type<int>(), registry.type<char>() };
+    auto view = registry.view(std::begin(types), std::end(types));
 
     ASSERT_FALSE(view.contains(e0));
     ASSERT_TRUE(view.contains(e1));
 }
 
-TEST(PersistentView, Empty) {
+TEST(RuntimeView, Empty) {
     entt::Registry<std::uint64_t> registry;
+    using component_type = typename decltype(registry)::component_type;
 
     const auto e0 = registry.create();
     registry.assign<double>(e0);
@@ -453,20 +760,18 @@ TEST(PersistentView, Empty) {
     registry.assign<char>(e1);
     registry.assign<float>(e1);
 
-    for(auto entity: registry.view<char, int, float>(entt::persistent_t{})) {
-        (void)entity;
-        FAIL();
-    }
+    component_type types[] = { registry.type<char>(), registry.type<int>(), registry.type<float>() };
+    auto view = registry.view(std::begin(types), std::end(types));
 
-    for(auto entity: registry.view<double, char, int, float>(entt::persistent_t{})) {
+    for(auto entity: view) {
         (void)entity;
         FAIL();
     }
 }
 
-TEST(PersistentView, Each) {
+TEST(RuntimeView, Each) {
     entt::Registry<std::uint64_t> registry;
-    registry.prepare<int, char>();
+    using component_type = typename decltype(registry)::component_type;
 
     const auto e0 = registry.create();
     registry.assign<int>(e0);
@@ -476,161 +781,82 @@ TEST(PersistentView, Each) {
     registry.assign<int>(e1);
     registry.assign<char>(e1);
 
-    auto view = registry.view<int, char>(entt::persistent_t{});
-    const auto &cview = static_cast<const decltype(view) &>(view);
+    component_type types[] = { registry.type<int>(), registry.type<char>() };
+    auto view = registry.view(std::begin(types), std::end(types));
     std::size_t cnt = 0;
 
-    view.each([&cnt](auto, int &, char &) { ++cnt; });
+    view.each([&cnt](auto) { ++cnt; });
 
     ASSERT_EQ(cnt, std::size_t{2});
-
-    cview.each([&cnt](auto, const int &, const char &) { --cnt; });
-
-    ASSERT_EQ(cnt, std::size_t{0});
 }
 
-TEST(PersistentView, Sort) {
+TEST(RuntimeView, EachWithHoles) {
     entt::Registry<std::uint64_t> registry;
-    registry.prepare<int, unsigned int>();
+    using component_type = typename decltype(registry)::component_type;
 
     const auto e0 = registry.create();
     const auto e1 = registry.create();
     const auto e2 = registry.create();
 
-    auto uval = 0u;
-    auto ival = 0;
-
-    registry.assign<unsigned int>(e0, uval++);
-    registry.assign<unsigned int>(e1, uval++);
-    registry.assign<unsigned int>(e2, uval++);
-
-    registry.assign<int>(e0, ival++);
-    registry.assign<int>(e1, ival++);
-    registry.assign<int>(e2, ival++);
-
-    auto view = registry.view<int, unsigned int>(entt::persistent_t{});
+    registry.assign<char>(e0, '0');
+    registry.assign<char>(e1, '1');
 
-    for(auto entity: view) {
-        ASSERT_EQ(view.get<unsigned int>(entity), --uval);
-        ASSERT_EQ(view.get<int>(entity), --ival);
-    }
+    registry.assign<int>(e0, 0);
+    registry.assign<int>(e2, 2);
 
-    registry.sort<unsigned int>(std::less<unsigned int>{});
-    view.sort<unsigned int>();
+    component_type types[] = { registry.type<int>(), registry.type<char>() };
+    auto view = registry.view(std::begin(types), std::end(types));
 
-    for(auto entity: view) {
-        ASSERT_EQ(view.get<unsigned int>(entity), uval++);
-        ASSERT_EQ(view.get<int>(entity), ival++);
-    }
+    view.each([e0](auto entity) {
+        ASSERT_EQ(e0, entity);
+    });
 }
 
-TEST(RawView, Functionalities) {
+TEST(RuntimeView, MissingPool) {
     entt::Registry<std::uint64_t> registry;
-    auto view = registry.view<char>(entt::raw_t{});
-    const auto &cview = view;
-
-    ASSERT_TRUE(view.empty());
+    using component_type = typename decltype(registry)::component_type;
 
     const auto e0 = registry.create();
-    const auto e1 = registry.create();
-
-    registry.assign<int>(e1);
-    registry.assign<char>(e1);
+    registry.assign<int>(e0);
 
-    ASSERT_FALSE(view.empty());
-    ASSERT_NO_THROW(registry.view<char>(entt::raw_t{}).begin()++);
-    ASSERT_NO_THROW(++registry.view<char>(entt::raw_t{}).begin());
+    component_type types[] = { registry.type<int>(), registry.type<char>() };
+    auto view = registry.view(std::begin(types), std::end(types));
 
-    ASSERT_NE(view.begin(), view.end());
-    ASSERT_NE(cview.begin(), cview.end());
-    ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
+    ASSERT_TRUE(view.empty());
+    ASSERT_EQ(view.size(), decltype(view.size()){0});
 
     registry.assign<char>(e0);
 
-    ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
-
-    registry.get<char>(e0) = '1';
-    registry.get<char>(e1) = '2';
-
-    for(auto &&component: view) {
-        ASSERT_TRUE(component == '1' || component == '2');
-    }
-
-    ASSERT_EQ(*(view.data() + 0), e1);
-    ASSERT_EQ(*(view.data() + 1), e0);
-
-    ASSERT_EQ(*(view.raw() + 0), '2');
-    ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
-
-    for(auto &&component: view) {
-        // verifies that iterators return references to components
-        component = '0';
-    }
-
-    for(auto &&component: view) {
-        ASSERT_TRUE(component == '0');
-    }
-
-    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(RawView, ElementAccess) {
-    entt::Registry<std::uint64_t> registry;
-    auto view = registry.view<int>(entt::raw_t{});
-    const auto &cview = view;
-
-    const auto e0 = registry.create();
-    registry.assign<int>(e0, 42);
+    ASSERT_EQ(view.size(), decltype(view.size()){0});
+    ASSERT_FALSE(view.contains(e0));
 
-    const auto e1 = registry.create();
-    registry.assign<int>(e1, 3);
+    view.each([](auto) { FAIL(); });
 
-    for(typename decltype(view)::size_type i{}; i < view.size(); ++i) {
-        ASSERT_EQ(view[i], i ? 42 : 3);
-        ASSERT_EQ(cview[i], i ? 42 : 3);
+    for(auto entity: view) {
+        (void)entity;
+        FAIL();
     }
 }
 
-TEST(RawView, Empty) {
+TEST(RuntimeView, EmptyRange) {
     entt::Registry<std::uint64_t> registry;
+    using component_type = typename decltype(registry)::component_type;
 
     const auto e0 = registry.create();
-    registry.assign<char>(e0);
-    registry.assign<double>(e0);
+    registry.assign<int>(e0);
 
-    const auto e1 = registry.create();
-    registry.assign<char>(e1);
+    const component_type *ptr = nullptr;
+    auto view = registry.view(ptr, ptr);
 
-    auto view = registry.view<int>(entt::raw_t{});
+    ASSERT_TRUE(view.empty());
+    ASSERT_EQ(view.size(), decltype(view.size()){0});
+    ASSERT_FALSE(view.contains(e0));
 
-    ASSERT_EQ(view.size(), entt::Registry<std::uint64_t>::size_type{0});
+    view.each([](auto) { FAIL(); });
 
-    for(auto &&component: view) {
-        (void)component;
+    for(auto entity: view) {
+        (void)entity;
         FAIL();
     }
 }
-
-TEST(RawView, Each) {
-    entt::Registry<std::uint64_t> registry;
-
-    registry.assign<int>(registry.create(), 1);
-    registry.assign<int>(registry.create(), 3);
-
-    auto view = registry.view<int>(entt::raw_t{});
-    const auto &cview = static_cast<const decltype(view) &>(view);
-    std::size_t cnt = 0;
-
-    view.each([&cnt](int &v) { cnt += (v % 2); });
-
-    ASSERT_EQ(cnt, std::size_t{2});
-
-    cview.each([&cnt](const int &v) { cnt -= (v % 2); });
-
-    ASSERT_EQ(cnt, std::size_t{0});
-}

+ 29 - 22
test/mod/mod.cpp

@@ -235,34 +235,41 @@ public:
 
         duk_push_array(ctx);
 
-        dreg.registry.each([ctx, nargs, &pos, &dreg](auto entity) {
-            auto &registry = dreg.registry;
-            auto &func = dreg.func;
-            bool match = true;
-
-            for (duk_idx_t arg = 0; match && arg < nargs; arg++) {
-                auto type = duk_require_uint(ctx, arg);
-
-                if(type < udef) {
-                    assert(func.find(type) != func.cend());
-                    match = (registry.*func[type].test)(entity);
-                } else {
-                    const auto ctype = registry.type<DuktapeRuntime>();
-                    assert(func.find(ctype) != func.cend());
-                    match = (registry.*func[ctype].test)(entity);
-
-                    if(match) {
-                        auto &components = registry.get<DuktapeRuntime>(entity).components;
-                        match = (components.find(type) != components.cend());
-                    }
+        std::vector<typename entt::DefaultRegistry::component_type> components;
+        std::vector<typename entt::DefaultRegistry::component_type> runtime;
+
+        for(duk_idx_t arg = 0; arg < nargs; arg++) {
+            auto type = duk_require_uint(ctx, arg);
+
+            if(type < udef) {
+                components.push_back(type);
+            } else {
+                if(runtime.empty()) {
+                    components.push_back(dreg.registry.type<DuktapeRuntime>());
                 }
+
+                runtime.push_back(type);
             }
+        }
 
-            if(match) {
+        auto view = dreg.registry.view(components.cbegin(), components.cend());
+
+        for(const auto entity: view) {
+            if(runtime.empty()) {
                 duk_push_uint(ctx, entity);
                 duk_put_prop_index(ctx, -2, pos++);
+            } else {
+                const auto &components = dreg.registry.get<DuktapeRuntime>(entity).components;
+                const auto match = std::all_of(runtime.cbegin(), runtime.cend(), [&components](const auto type) {
+                    return components.find(type) != components.cend();
+                });
+
+                if(match) {
+                    duk_push_uint(ctx, entity);
+                    duk_put_prop_index(ctx, -2, pos++);
+                }
             }
-        });
+        }
 
         return 1;
     }