Преглед изворни кода

runtime_view:
* added ::iterate and ::exclude member functions to attach pools at runtime
* removed vector-based constructor
* removed basic_registry<...>::runtime_view member function
* updated benchmarks accordingly (also reviewed them as a whole just because)

Michele Caini пре 4 година
родитељ
комит
4dd0862fad

+ 13 - 13
docs/md/entity.md

@@ -1694,8 +1694,8 @@ thrown away.<br/>
 To iterate a runtime view, either use it in a range-for loop:
 
 ```cpp
-entt::id_type types[] = { entt::type_hash<position>::value(), entt::type_hash<velocity>::value() };
-auto view = registry.runtime_view(std::cbegin(types), std::cend(types));
+entt::runtime_view view{};
+view.iterate(registry.storage<position>()).iterate(registry.storage<velocity>());
 
 for(auto entity: view) {
     // ...
@@ -1705,25 +1705,25 @@ for(auto entity: view) {
 Or rely on the `each` member function to iterate entities:
 
 ```cpp
-entt::id_type types[] = { entt::type_hash<position>::value(), entt::type_hash<velocity>::value() };
-
-registry.runtime_view(std::cbegin(types), std::cend(types)).each([](auto entity) {
-    // ...
-});
+entt::runtime_view{}
+    .iterate(registry.storage<position>())
+    .iterate(registry.storage<velocity>())
+    .each([](auto entity) {
+        // ...
+    });
 ```
 
 Performance are exactly the same in both cases.<br/>
 Filtering entities by components is also supported for this kind of views:
 
 ```cpp
-entt::id_type components[] = { entt::type_hash<position>::value() };
-entt::id_type filter[] = { entt::type_hash<velocity>::value() };
-auto view = registry.runtime_view(std::cbegin(components), std::cend(components), std::cbegin(filter), std::cend(filter));
+entt::runtime_view view{};
+view.iterate(registry.storage<position>()).exclude(registry.storage<velocity>());
 ```
 
-**Note**: runtime views are meant for all those cases where users don't know at
-compile-time what components to _use_ to iterate entities. If possible, don't
-use runtime views as their performance are inferior to those of the other views.
+Runtime views are meant for when users don't know at compile-time what types to
+_use_ to iterate entities. The `storage` member function of a registry could be
+useful in this regard.
 
 ## Groups
 

+ 0 - 48
src/entt/entity/registry.hpp

@@ -1161,54 +1161,6 @@ public:
         return {assure<std::remove_const_t<Component>>(), assure<std::remove_const_t<Other>>()..., assure<Exclude>()...};
     }
 
-    /**
-     * @brief Returns a runtime view for the given components.
-     *
-     * @sa view
-     *
-     * Runtime views are to be used when users want to construct a view from
-     * some external inputs and don't know at compile-time what are the required
-     * components.
-     *
-     * @tparam ItComp Type of input iterator for the components to use to
-     * construct the view.
-     * @tparam ItExcl Type of input iterator for the components to use to filter
-     * the view.
-     * @param first An iterator to the first element of the range of components
-     * to use to construct the view.
-     * @param last An iterator past the last element of the range of components
-     * to use to construct the view.
-     * @param from An iterator to the first element of the range of components
-     * to use to filter the view.
-     * @param to An iterator past the last element of the range of components to
-     * use to filter the view.
-     * @return A newly created runtime view.
-     */
-    template<typename ItComp, typename ItExcl = id_type *>
-    [[nodiscard]] basic_runtime_view<base_type> runtime_view(ItComp first, ItComp last, ItExcl from = {}, ItExcl to = {}) const {
-        std::vector<const base_type *> component{};
-        std::vector<const base_type *> filter{};
-
-        component.reserve(std::distance(first, last));
-        filter.reserve(std::distance(from, to));
-
-        for(; first != last; ++first) {
-            if(const auto it = pools.find(*first); it == pools.cend()) {
-                return {};
-            } else {
-                component.emplace_back(it->second.get());
-            }
-        }
-
-        for(; from != to; ++from) {
-            if(const auto it = pools.find(*from); it != pools.cend()) {
-                filter.emplace_back(it->second.get());
-            }
-        }
-
-        return {std::move(component), std::move(filter)};
-    }
-
     /**
      * @brief Returns a group for the given components.
      *

+ 21 - 12
src/entt/entity/runtime_view.hpp

@@ -163,19 +163,28 @@ struct basic_runtime_view<basic_sparse_set<Entity, Allocator>> final {
           filter{} {}
 
     /**
-     * @brief Constructs a runtime view from a set of storage classes.
-     * @param cpools The storage for the types to iterate.
-     * @param epools The storage for the types used to filter the view.
+     * @brief Appends an opaque storage object to a runtime view.
+     * @param base An opaque reference to a storage object.
+     * @return This runtime view.
      */
-    basic_runtime_view(std::vector<const base_type *> cpools, std::vector<const base_type *> epools) ENTT_NOEXCEPT
-        : pools{std::move(cpools)},
-          filter{std::move(epools)} {
-        auto candidate = std::min_element(pools.begin(), pools.end(), [](const auto *lhs, const auto *rhs) {
-            return (!lhs && rhs) || (lhs && rhs && lhs->size() < rhs->size());
-        });
-
-        // brings the best candidate (if any) on front of the vector
-        std::rotate(pools.begin(), candidate, pools.end());
+    basic_runtime_view &iterate(const base_type &base) {
+        if(pools.empty() || !(base.size() < pools[0u]->size())) {
+            pools.push_back(&base);
+        } else {
+            pools.push_back(std::exchange(pools[0u], &base));
+        }
+
+        return *this;
+    }
+
+    /**
+     * @brief Adds an opaque storage object as a filter of a runtime view.
+     * @param base An opaque reference to a storage object.
+     * @return This runtime view.
+     */
+    basic_runtime_view &exclude(const base_type &base) {
+        filter.push_back(&base);
+        return *this;
     }
 
     /**

+ 98 - 336
test/benchmark/benchmark.cpp

@@ -36,6 +36,13 @@ private:
     std::chrono::time_point<std::chrono::system_clock> start;
 };
 
+template<typename Iterable, typename Func>
+void generic(Iterable &&iterable, Func func) {
+    timer timer;
+    std::forward<Iterable>(iterable).each(func);
+    timer.elapsed();
+}
+
 template<typename Func>
 void pathological(Func func) {
     entt::registry registry;
@@ -63,9 +70,9 @@ void pathological(Func func) {
         }
     }
 
-    func(registry, [](auto &...comp) {
-        ((comp.x = {}), ...);
-    });
+    timer timer;
+    func(registry).each([](auto &...comp) { ((comp.x = {}), ...); });
+    timer.elapsed();
 }
 
 TEST(Benchmark, Create) {
@@ -100,7 +107,6 @@ TEST(Benchmark, CreateManyAndEmplaceComponents) {
     std::cout << "Creating 1000000 entities at once and emplace components" << std::endl;
 
     timer timer;
-
     registry.create(entities.begin(), entities.end());
 
     for(const auto entity: entities) {
@@ -127,6 +133,7 @@ TEST(Benchmark, CreateManyWithComponents) {
 TEST(Benchmark, Erase) {
     entt::registry registry;
     std::vector<entt::entity> entities(1000000);
+    auto view = registry.view<int>();
 
     std::cout << "Erasing 1000000 components from their entities" << std::endl;
 
@@ -135,7 +142,7 @@ TEST(Benchmark, Erase) {
 
     timer timer;
 
-    for(auto entity: registry.view<int>()) {
+    for(auto entity: view) {
         registry.erase<int>(entity);
     }
 
@@ -145,6 +152,7 @@ TEST(Benchmark, Erase) {
 TEST(Benchmark, EraseMany) {
     entt::registry registry;
     std::vector<entt::entity> entities(1000000);
+    auto view = registry.view<int>();
 
     std::cout << "Erasing 1000000 components from their entities at once" << std::endl;
 
@@ -152,7 +160,6 @@ TEST(Benchmark, EraseMany) {
     registry.insert<int>(entities.begin(), entities.end());
 
     timer timer;
-    auto view = registry.view<int>();
     registry.erase<int>(view.begin(), view.end());
     timer.elapsed();
 }
@@ -160,6 +167,7 @@ TEST(Benchmark, EraseMany) {
 TEST(Benchmark, Remove) {
     entt::registry registry;
     std::vector<entt::entity> entities(1000000);
+    auto view = registry.view<int>();
 
     std::cout << "Removing 1000000 components from their entities" << std::endl;
 
@@ -168,7 +176,7 @@ TEST(Benchmark, Remove) {
 
     timer timer;
 
-    for(auto entity: registry.view<int>()) {
+    for(auto entity: view) {
         registry.remove<int>(entity);
     }
 
@@ -178,6 +186,7 @@ TEST(Benchmark, Remove) {
 TEST(Benchmark, RemoveMany) {
     entt::registry registry;
     std::vector<entt::entity> entities(1000000);
+    auto view = registry.view<int>();
 
     std::cout << "Removing 1000000 components from their entities at once" << std::endl;
 
@@ -185,7 +194,6 @@ TEST(Benchmark, RemoveMany) {
     registry.insert<int>(entities.begin(), entities.end());
 
     timer timer;
-    auto view = registry.view<int>();
     registry.remove<int>(view.begin(), view.end());
     timer.elapsed();
 }
@@ -245,6 +253,7 @@ TEST(Benchmark, RecycleMany) {
 TEST(Benchmark, Destroy) {
     entt::registry registry;
     std::vector<entt::entity> entities(1000000);
+    auto view = registry.view<int>();
 
     std::cout << "Destroying 1000000 entities" << std::endl;
 
@@ -253,7 +262,7 @@ TEST(Benchmark, Destroy) {
 
     timer timer;
 
-    for(auto entity: registry.view<int>()) {
+    for(auto entity: view) {
         registry.destroy(entity);
     }
 
@@ -263,6 +272,7 @@ TEST(Benchmark, Destroy) {
 TEST(Benchmark, DestroyMany) {
     entt::registry registry;
     std::vector<entt::entity> entities(1000000);
+    auto view = registry.view<int>();
 
     std::cout << "Destroying 1000000 entities at once" << std::endl;
 
@@ -270,7 +280,6 @@ TEST(Benchmark, DestroyMany) {
     registry.insert<int>(entities.begin(), entities.end());
 
     timer timer;
-    auto view = registry.view<int>();
     registry.destroy(view.begin(), view.end());
     timer.elapsed();
 }
@@ -299,13 +308,7 @@ TEST(Benchmark, IterateSingleComponent1M) {
         registry.emplace<position>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<position>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<position>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -320,13 +323,7 @@ TEST(Benchmark, IterateSingleComponentTombstonePolicy1M) {
         registry.emplace<stable_position>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<stable_position>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<stable_position>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -341,15 +338,10 @@ TEST(Benchmark, IterateSingleComponentRuntime1M) {
         registry.emplace<position>(entity);
     }
 
-    auto test = [&](auto func) {
-        entt::id_type types[] = {entt::type_hash<position>::value()};
-
-        timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
-        timer.elapsed();
-    };
+    entt::runtime_view view{};
+    view.iterate(registry.storage<position>());
 
-    test([&registry](auto entity) {
+    generic(view, [&registry](auto entity) {
         registry.get<position>(entity).x = {};
     });
 }
@@ -365,13 +357,7 @@ TEST(Benchmark, IterateTwoComponents1M) {
         registry.emplace<velocity>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<position, velocity>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<position, velocity>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -387,13 +373,7 @@ TEST(Benchmark, IterateTombstonePolicyTwoComponentsTombstonePolicy1M) {
         registry.emplace<velocity>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<stable_position, velocity>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<stable_position, velocity>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -412,13 +392,7 @@ TEST(Benchmark, IterateTwoComponents1MHalf) {
         }
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<position, velocity>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<position, velocity>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -437,20 +411,13 @@ TEST(Benchmark, IterateTwoComponents1MOne) {
         }
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<position, velocity>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<position, velocity>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
 
 TEST(Benchmark, IterateTwoComponentsNonOwningGroup1M) {
     entt::registry registry;
-    const auto group = registry.group<>(entt::get<position, velocity>);
 
     std::cout << "Iterating over 1000000 entities, two components, non owning group" << std::endl;
 
@@ -460,20 +427,13 @@ TEST(Benchmark, IterateTwoComponentsNonOwningGroup1M) {
         registry.emplace<velocity>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.group<>(entt::get<position, velocity>), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
 
 TEST(Benchmark, IterateTwoComponentsFullOwningGroup1M) {
     entt::registry registry;
-    const auto group = registry.group<position, velocity>();
 
     std::cout << "Iterating over 1000000 entities, two components, full owning group" << std::endl;
 
@@ -483,20 +443,13 @@ TEST(Benchmark, IterateTwoComponentsFullOwningGroup1M) {
         registry.emplace<velocity>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.group<position, velocity>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
 
 TEST(Benchmark, IterateTwoComponentsPartialOwningGroup1M) {
     entt::registry registry;
-    const auto group = registry.group<position>(entt::get<velocity>);
 
     std::cout << "Iterating over 1000000 entities, two components, partial owning group" << std::endl;
 
@@ -506,13 +459,7 @@ TEST(Benchmark, IterateTwoComponentsPartialOwningGroup1M) {
         registry.emplace<velocity>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.group<position>(entt::get<velocity>), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -528,17 +475,11 @@ TEST(Benchmark, IterateTwoComponentsRuntime1M) {
         registry.emplace<velocity>(entity);
     }
 
-    auto test = [&](auto func) {
-        entt::id_type types[] = {
-            entt::type_hash<position>::value(),
-            entt::type_hash<velocity>::value()};
-
-        timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
-        timer.elapsed();
-    };
+    entt::runtime_view view{};
+    view.iterate(registry.storage<position>())
+        .iterate(registry.storage<velocity>());
 
-    test([&registry](auto entity) {
+    generic(view, [&registry](auto entity) {
         registry.get<position>(entity).x = {};
         registry.get<velocity>(entity).x = {};
     });
@@ -558,17 +499,11 @@ TEST(Benchmark, IterateTwoComponentsRuntime1MHalf) {
         }
     }
 
-    auto test = [&](auto func) {
-        entt::id_type types[] = {
-            entt::type_hash<position>::value(),
-            entt::type_hash<velocity>::value()};
-
-        timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
-        timer.elapsed();
-    };
+    entt::runtime_view view{};
+    view.iterate(registry.storage<position>())
+        .iterate(registry.storage<velocity>());
 
-    test([&registry](auto entity) {
+    generic(view, [&registry](auto entity) {
         registry.get<position>(entity).x = {};
         registry.get<velocity>(entity).x = {};
     });
@@ -588,17 +523,11 @@ TEST(Benchmark, IterateTwoComponentsRuntime1MOne) {
         }
     }
 
-    auto test = [&](auto func) {
-        entt::id_type types[] = {
-            entt::type_hash<position>::value(),
-            entt::type_hash<velocity>::value()};
+    entt::runtime_view view{};
+    view.iterate(registry.storage<position>())
+        .iterate(registry.storage<velocity>());
 
-        timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
-        timer.elapsed();
-    };
-
-    test([&registry](auto entity) {
+    generic(view, [&registry](auto entity) {
         registry.get<position>(entity).x = {};
         registry.get<velocity>(entity).x = {};
     });
@@ -616,13 +545,7 @@ TEST(Benchmark, IterateThreeComponents1M) {
         registry.emplace<comp<0>>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<position, velocity, comp<0>>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -639,13 +562,7 @@ TEST(Benchmark, IterateThreeComponentsTombstonePolicy1M) {
         registry.emplace<comp<0>>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<stable_position, velocity, comp<0>>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<stable_position, velocity, comp<0>>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -665,13 +582,7 @@ TEST(Benchmark, IterateThreeComponents1MHalf) {
         }
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<position, velocity, comp<0>>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -691,20 +602,13 @@ TEST(Benchmark, IterateThreeComponents1MOne) {
         }
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<position, velocity, comp<0>>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<position, velocity, comp<0>>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
 
 TEST(Benchmark, IterateThreeComponentsNonOwningGroup1M) {
     entt::registry registry;
-    const auto group = registry.group<>(entt::get<position, velocity, comp<0>>);
 
     std::cout << "Iterating over 1000000 entities, three components, non owning group" << std::endl;
 
@@ -715,20 +619,13 @@ TEST(Benchmark, IterateThreeComponentsNonOwningGroup1M) {
         registry.emplace<comp<0>>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.group<>(entt::get<position, velocity, comp<0>>), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
 
 TEST(Benchmark, IterateThreeComponentsFullOwningGroup1M) {
     entt::registry registry;
-    const auto group = registry.group<position, velocity, comp<0>>();
 
     std::cout << "Iterating over 1000000 entities, three components, full owning group" << std::endl;
 
@@ -739,20 +636,13 @@ TEST(Benchmark, IterateThreeComponentsFullOwningGroup1M) {
         registry.emplace<comp<0>>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.group<position, velocity, comp<0>>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
 
 TEST(Benchmark, IterateThreeComponentsPartialOwningGroup1M) {
     entt::registry registry;
-    const auto group = registry.group<position, velocity>(entt::get<comp<0>>);
 
     std::cout << "Iterating over 1000000 entities, three components, partial owning group" << std::endl;
 
@@ -763,13 +653,7 @@ TEST(Benchmark, IterateThreeComponentsPartialOwningGroup1M) {
         registry.emplace<comp<0>>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.group<position, velocity>(entt::get<comp<0>>), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -786,18 +670,12 @@ TEST(Benchmark, IterateThreeComponentsRuntime1M) {
         registry.emplace<comp<0>>(entity);
     }
 
-    auto test = [&](auto func) {
-        entt::id_type types[] = {
-            entt::type_hash<position>::value(),
-            entt::type_hash<velocity>::value(),
-            entt::type_hash<comp<0>>::value()};
-
-        timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
-        timer.elapsed();
-    };
+    entt::runtime_view view{};
+    view.iterate(registry.storage<position>())
+        .iterate(registry.storage<velocity>())
+        .iterate(registry.storage<comp<0>>());
 
-    test([&registry](auto entity) {
+    generic(view, [&registry](auto entity) {
         registry.get<position>(entity).x = {};
         registry.get<velocity>(entity).x = {};
         registry.get<comp<0>>(entity).x = {};
@@ -819,18 +697,12 @@ TEST(Benchmark, IterateThreeComponentsRuntime1MHalf) {
         }
     }
 
-    auto test = [&](auto func) {
-        entt::id_type types[] = {
-            entt::type_hash<position>::value(),
-            entt::type_hash<velocity>::value(),
-            entt::type_hash<comp<0>>::value()};
+    entt::runtime_view view{};
+    view.iterate(registry.storage<position>())
+        .iterate(registry.storage<velocity>())
+        .iterate(registry.storage<comp<0>>());
 
-        timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
-        timer.elapsed();
-    };
-
-    test([&registry](auto entity) {
+    generic(view, [&registry](auto entity) {
         registry.get<position>(entity).x = {};
         registry.get<velocity>(entity).x = {};
         registry.get<comp<0>>(entity).x = {};
@@ -852,18 +724,12 @@ TEST(Benchmark, IterateThreeComponentsRuntime1MOne) {
         }
     }
 
-    auto test = [&](auto func) {
-        entt::id_type types[] = {
-            entt::type_hash<position>::value(),
-            entt::type_hash<velocity>::value(),
-            entt::type_hash<comp<0>>::value()};
-
-        timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
-        timer.elapsed();
-    };
+    entt::runtime_view view{};
+    view.iterate(registry.storage<position>())
+        .iterate(registry.storage<velocity>())
+        .iterate(registry.storage<comp<0>>());
 
-    test([&registry](auto entity) {
+    generic(view, [&registry](auto entity) {
         registry.get<position>(entity).x = {};
         registry.get<velocity>(entity).x = {};
         registry.get<comp<0>>(entity).x = {};
@@ -884,13 +750,7 @@ TEST(Benchmark, IterateFiveComponents1M) {
         registry.emplace<comp<2>>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<position, velocity, comp<0>, comp<1>, comp<2>>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -909,13 +769,7 @@ TEST(Benchmark, IterateFiveComponentsTombstonePolicy1M) {
         registry.emplace<comp<2>>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<stable_position, velocity, comp<0>, comp<1>, comp<2>>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<stable_position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -937,13 +791,7 @@ TEST(Benchmark, IterateFiveComponents1MHalf) {
         }
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<position, velocity, comp<0>, comp<1>, comp<2>>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -965,20 +813,13 @@ TEST(Benchmark, IterateFiveComponents1MOne) {
         }
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        registry.view<position, velocity, comp<0>, comp<1>, comp<2>>().each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.view<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
 
 TEST(Benchmark, IterateFiveComponentsNonOwningGroup1M) {
     entt::registry registry;
-    const auto group = registry.group<>(entt::get<position, velocity, comp<0>, comp<1>, comp<2>>);
 
     std::cout << "Iterating over 1000000 entities, five components, non owning group" << std::endl;
 
@@ -991,20 +832,13 @@ TEST(Benchmark, IterateFiveComponentsNonOwningGroup1M) {
         registry.emplace<comp<2>>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.group<>(entt::get<position, velocity, comp<0>, comp<1>, comp<2>>), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
 
 TEST(Benchmark, IterateFiveComponentsFullOwningGroup1M) {
     entt::registry registry;
-    const auto group = registry.group<position, velocity, comp<0>, comp<1>, comp<2>>();
 
     std::cout << "Iterating over 1000000 entities, five components, full owning group" << std::endl;
 
@@ -1017,20 +851,13 @@ TEST(Benchmark, IterateFiveComponentsFullOwningGroup1M) {
         registry.emplace<comp<2>>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.group<position, velocity, comp<0>, comp<1>, comp<2>>(), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
 
 TEST(Benchmark, IterateFiveComponentsPartialFourOfFiveOwningGroup1M) {
     entt::registry registry;
-    const auto group = registry.group<position, velocity, comp<0>, comp<1>>(entt::get<comp<2>>);
 
     std::cout << "Iterating over 1000000 entities, five components, partial (4 of 5) owning group" << std::endl;
 
@@ -1043,20 +870,13 @@ TEST(Benchmark, IterateFiveComponentsPartialFourOfFiveOwningGroup1M) {
         registry.emplace<comp<2>>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.group<position, velocity, comp<0>, comp<1>>(entt::get<comp<2>>), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
 
 TEST(Benchmark, IterateFiveComponentsPartialThreeOfFiveOwningGroup1M) {
     entt::registry registry;
-    const auto group = registry.group<position, velocity, comp<0>>(entt::get<comp<1>, comp<2>>);
 
     std::cout << "Iterating over 1000000 entities, five components, partial (3 of 5) owning group" << std::endl;
 
@@ -1069,13 +889,7 @@ TEST(Benchmark, IterateFiveComponentsPartialThreeOfFiveOwningGroup1M) {
         registry.emplace<comp<2>>(entity);
     }
 
-    auto test = [&](auto func) {
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    };
-
-    test([](auto &...comp) {
+    generic(registry.group<position, velocity, comp<0>>(entt::get<comp<1>, comp<2>>), [](auto &...comp) {
         ((comp.x = {}), ...);
     });
 }
@@ -1094,20 +908,14 @@ TEST(Benchmark, IterateFiveComponentsRuntime1M) {
         registry.emplace<comp<2>>(entity);
     }
 
-    auto test = [&](auto func) {
-        entt::id_type types[] = {
-            entt::type_hash<position>::value(),
-            entt::type_hash<velocity>::value(),
-            entt::type_hash<comp<0>>::value(),
-            entt::type_hash<comp<1>>::value(),
-            entt::type_hash<comp<2>>::value()};
-
-        timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
-        timer.elapsed();
-    };
+    entt::runtime_view view{};
+    view.iterate(registry.storage<position>())
+        .iterate(registry.storage<velocity>())
+        .iterate(registry.storage<comp<0>>())
+        .iterate(registry.storage<comp<1>>())
+        .iterate(registry.storage<comp<2>>());
 
-    test([&registry](auto entity) {
+    generic(view, [&registry](auto entity) {
         registry.get<position>(entity).x = {};
         registry.get<velocity>(entity).x = {};
         registry.get<comp<0>>(entity).x = {};
@@ -1133,20 +941,14 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) {
         }
     }
 
-    auto test = [&](auto func) {
-        entt::id_type types[] = {
-            entt::type_hash<position>::value(),
-            entt::type_hash<velocity>::value(),
-            entt::type_hash<comp<0>>::value(),
-            entt::type_hash<comp<1>>::value(),
-            entt::type_hash<comp<2>>::value()};
+    entt::runtime_view view{};
+    view.iterate(registry.storage<position>())
+        .iterate(registry.storage<velocity>())
+        .iterate(registry.storage<comp<0>>())
+        .iterate(registry.storage<comp<1>>())
+        .iterate(registry.storage<comp<2>>());
 
-        timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
-        timer.elapsed();
-    };
-
-    test([&registry](auto entity) {
+    generic(view, [&registry](auto entity) {
         registry.get<position>(entity).x = {};
         registry.get<velocity>(entity).x = {};
         registry.get<comp<0>>(entity).x = {};
@@ -1172,20 +974,14 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
         }
     }
 
-    auto test = [&](auto func) {
-        entt::id_type types[] = {
-            entt::type_hash<position>::value(),
-            entt::type_hash<velocity>::value(),
-            entt::type_hash<comp<0>>::value(),
-            entt::type_hash<comp<1>>::value(),
-            entt::type_hash<comp<2>>::value()};
-
-        timer timer;
-        registry.runtime_view(std::begin(types), std::end(types)).each(func);
-        timer.elapsed();
-    };
+    entt::runtime_view view{};
+    view.iterate(registry.storage<position>())
+        .iterate(registry.storage<velocity>())
+        .iterate(registry.storage<comp<0>>())
+        .iterate(registry.storage<comp<1>>())
+        .iterate(registry.storage<comp<2>>());
 
-    test([&registry](auto entity) {
+    generic(view, [&registry](auto entity) {
         registry.get<position>(entity).x = {};
         registry.get<velocity>(entity).x = {};
         registry.get<comp<0>>(entity).x = {};
@@ -1196,48 +992,22 @@ TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
 
 TEST(Benchmark, IteratePathological) {
     std::cout << "Pathological case" << std::endl;
-
-    pathological([](auto &registry, auto func) {
-        timer timer;
-        registry.template view<position, velocity, comp<0>>().each(func);
-        timer.elapsed();
-    });
+    pathological([](auto &registry) { return registry.template view<position, velocity, comp<0>>(); });
 }
 
 TEST(Benchmark, IteratePathologicalNonOwningGroup) {
     std::cout << "Pathological case (non-owning group)" << std::endl;
-
-    pathological([](auto &registry, auto func) {
-        auto group = registry.template group<>(entt::get<position, velocity, comp<0>>);
-
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    });
+    pathological([](auto &registry) { return registry.template group<>(entt::get<position, velocity, comp<0>>); });
 }
 
 TEST(Benchmark, IteratePathologicalFullOwningGroup) {
     std::cout << "Pathological case (full-owning group)" << std::endl;
-
-    pathological([](auto &registry, auto func) {
-        auto group = registry.template group<position, velocity, comp<0>>();
-
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    });
+    pathological([](auto &registry) { return registry.template group<position, velocity, comp<0>>(); });
 }
 
 TEST(Benchmark, IteratePathologicalPartialOwningGroup) {
     std::cout << "Pathological case (partial-owning group)" << std::endl;
-
-    pathological([](auto &registry, auto func) {
-        auto group = registry.template group<position, velocity>(entt::get<comp<0>>);
-
-        timer timer;
-        group.each(func);
-        timer.elapsed();
-    });
+    pathological([](auto &registry) { return registry.template group<position, velocity>(entt::get<comp<0>>); });
 }
 
 TEST(Benchmark, SortSingle) {
@@ -1251,9 +1021,7 @@ TEST(Benchmark, SortSingle) {
     }
 
     timer timer;
-
     registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; });
-
     timer.elapsed();
 }
 
@@ -1271,9 +1039,7 @@ TEST(Benchmark, SortMulti) {
     registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x < rhs.x && lhs.y < rhs.y; });
 
     timer timer;
-
     registry.sort<velocity, position>();
-
     timer.elapsed();
 }
 
@@ -1299,9 +1065,7 @@ TEST(Benchmark, AlmostSortedStdSort) {
     }
 
     timer timer;
-
     registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; });
-
     timer.elapsed();
 }
 
@@ -1327,8 +1091,6 @@ TEST(Benchmark, AlmostSortedInsertionSort) {
     }
 
     timer timer;
-
     registry.sort<position>([](const auto &lhs, const auto &rhs) { return lhs.x > rhs.x && lhs.y > rhs.y; }, entt::insertion_sort{});
-
     timer.elapsed();
 }

+ 38 - 68
test/entt/entity/runtime_view.cpp

@@ -17,26 +17,33 @@ struct entt::component_traits<stable_type>: basic_component_traits {
 
 TEST(RuntimeView, Functionalities) {
     entt::registry registry;
+    entt::runtime_view view{};
+
+    const auto e0 = registry.create();
+    const auto e1 = registry.create();
+
+    ASSERT_EQ(view.size_hint(), 0u);
+    ASSERT_EQ(view.begin(), view.end());
+    ASSERT_FALSE(view.contains(e0));
+    ASSERT_FALSE(view.contains(e1));
 
     // forces the creation of the pools
     static_cast<void>(registry.storage<int>());
     static_cast<void>(registry.storage<char>());
 
-    entt::id_type types[] = {entt::type_hash<int>::value(), entt::type_hash<char>::value()};
-    auto view = registry.runtime_view(std::begin(types), std::end(types));
+    view.iterate(registry.storage<int>()).iterate(registry.storage<char>());
 
     ASSERT_EQ(view.size_hint(), 0u);
 
-    const auto e0 = registry.create();
     registry.emplace<char>(e0);
-
-    const auto e1 = registry.create();
     registry.emplace<int>(e1);
 
     ASSERT_NE(view.size_hint(), 0u);
 
     registry.emplace<char>(e1);
 
+    ASSERT_EQ(view.size_hint(), 1u);
+
     auto it = view.begin();
 
     ASSERT_EQ(*it, e1);
@@ -65,13 +72,13 @@ TEST(RuntimeView, Functionalities) {
 
 TEST(RuntimeView, Iterator) {
     entt::registry registry;
+    entt::runtime_view view{};
 
     const auto entity = registry.create();
     registry.emplace<int>(entity);
     registry.emplace<char>(entity);
 
-    entt::id_type types[] = {entt::type_hash<int>::value(), entt::type_hash<char>::value()};
-    auto view = registry.runtime_view(std::begin(types), std::end(types));
+    view.iterate(registry.storage<int>()).iterate(registry.storage<char>());
     using iterator = typename decltype(view)::iterator;
 
     iterator end{view.begin()};
@@ -95,6 +102,7 @@ TEST(RuntimeView, Iterator) {
 
 TEST(RuntimeView, Contains) {
     entt::registry registry;
+    entt::runtime_view view{};
 
     const auto e0 = registry.create();
     registry.emplace<int>(e0);
@@ -106,8 +114,7 @@ TEST(RuntimeView, Contains) {
 
     registry.destroy(e0);
 
-    entt::id_type types[] = {entt::type_hash<int>::value(), entt::type_hash<char>::value()};
-    auto view = registry.runtime_view(std::begin(types), std::end(types));
+    view.iterate(registry.storage<int>()).iterate(registry.storage<char>());
 
     ASSERT_FALSE(view.contains(e0));
     ASSERT_TRUE(view.contains(e1));
@@ -115,6 +122,7 @@ TEST(RuntimeView, Contains) {
 
 TEST(RuntimeView, Empty) {
     entt::registry registry;
+    entt::runtime_view view{};
 
     const auto e0 = registry.create();
     registry.emplace<double>(e0);
@@ -125,37 +133,38 @@ TEST(RuntimeView, Empty) {
     registry.emplace<char>(e1);
     registry.emplace<float>(e1);
 
-    entt::id_type types[] = {entt::type_hash<int>::value(), entt::type_hash<char>::value(), entt::type_hash<float>::value()};
-    auto view = registry.runtime_view(std::begin(types), std::end(types));
-
-    view.each([](auto) { FAIL(); });
+    view.iterate(registry.storage<int>())
+        .iterate(registry.storage<char>())
+        .iterate(registry.storage<float>());
 
+    ASSERT_FALSE(view.contains(e0));
+    ASSERT_FALSE(view.contains(e1));
+    ASSERT_EQ(view.begin(), view.end());
     ASSERT_EQ((std::find(view.begin(), view.end(), e0)), view.end());
     ASSERT_EQ((std::find(view.begin(), view.end(), e1)), view.end());
 }
 
 TEST(RuntimeView, Each) {
     entt::registry registry;
+    entt::runtime_view view{};
 
     const auto e0 = registry.create();
     registry.emplace<int>(e0);
     registry.emplace<char>(e0);
 
     const auto e1 = registry.create();
-    registry.emplace<int>(e1);
     registry.emplace<char>(e1);
 
-    entt::id_type types[] = {entt::type_hash<int>::value(), entt::type_hash<char>::value()};
-    auto view = registry.runtime_view(std::begin(types), std::end(types));
-    std::size_t cnt = 0;
+    view.iterate(registry.storage<int>()).iterate(registry.storage<char>());
 
-    view.each([&cnt](auto) { ++cnt; });
-
-    ASSERT_EQ(cnt, std::size_t{2});
+    view.each([e0](const auto entt) {
+        ASSERT_EQ(entt, e0);
+    });
 }
 
 TEST(RuntimeView, EachWithHoles) {
     entt::registry registry;
+    entt::runtime_view view{};
 
     const auto e0 = registry.create();
     const auto e1 = registry.create();
@@ -167,54 +176,16 @@ TEST(RuntimeView, EachWithHoles) {
     registry.emplace<int>(e0, 0);
     registry.emplace<int>(e2, 2);
 
-    entt::id_type types[] = {entt::type_hash<int>::value(), entt::type_hash<char>::value()};
-    auto view = registry.runtime_view(std::begin(types), std::end(types));
+    view.iterate(registry.storage<int>()).iterate(registry.storage<char>());
 
     view.each([e0](auto entity) {
         ASSERT_EQ(e0, entity);
     });
 }
 
-TEST(RuntimeView, MissingPool) {
-    entt::registry registry;
-
-    const auto e0 = registry.create();
-    registry.emplace<int>(e0);
-
-    entt::id_type types[] = {entt::type_hash<int>::value(), entt::type_hash<char>::value()};
-    auto view = registry.runtime_view(std::begin(types), std::end(types));
-
-    ASSERT_EQ(view.size_hint(), 0u);
-
-    registry.emplace<char>(e0);
-
-    ASSERT_EQ(view.size_hint(), 0u);
-    ASSERT_FALSE(view.contains(e0));
-
-    view.each([](auto) { FAIL(); });
-
-    ASSERT_EQ((std::find(view.begin(), view.end(), e0)), view.end());
-}
-
-TEST(RuntimeView, EmptyRange) {
-    entt::registry registry;
-
-    const auto e0 = registry.create();
-    registry.emplace<int>(e0);
-
-    const entt::id_type *ptr = nullptr;
-    auto view = registry.runtime_view(ptr, ptr);
-
-    ASSERT_EQ(view.size_hint(), 0u);
-    ASSERT_FALSE(view.contains(e0));
-
-    view.each([](auto) { FAIL(); });
-
-    ASSERT_EQ((std::find(view.begin(), view.end(), e0)), view.end());
-}
-
 TEST(RuntimeView, ExcludedComponents) {
     entt::registry registry;
+    entt::runtime_view view{};
 
     const auto e0 = registry.create();
     registry.emplace<int>(e0);
@@ -223,9 +194,9 @@ TEST(RuntimeView, ExcludedComponents) {
     registry.emplace<int>(e1);
     registry.emplace<char>(e1);
 
-    entt::id_type components[] = {entt::type_hash<int>::value()};
-    entt::id_type filter[] = {entt::type_hash<char>::value(), entt::type_hash<double>::value()};
-    auto view = registry.runtime_view(std::begin(components), std::end(components), std::begin(filter), std::end(filter));
+    view.iterate(registry.storage<int>())
+        .exclude(registry.storage<char>())
+        .exclude(registry.storage<double>());
 
     ASSERT_TRUE(view.contains(e0));
     ASSERT_FALSE(view.contains(e1));
@@ -237,6 +208,7 @@ TEST(RuntimeView, ExcludedComponents) {
 
 TEST(RuntimeView, StableType) {
     entt::registry registry;
+    entt::runtime_view view{};
 
     const auto e0 = registry.create();
     const auto e1 = registry.create();
@@ -251,8 +223,7 @@ TEST(RuntimeView, StableType) {
 
     registry.remove<stable_type>(e1);
 
-    entt::id_type components[] = {entt::type_hash<int>::value(), entt::type_hash<stable_type>::value()};
-    auto view = registry.runtime_view(std::begin(components), std::end(components));
+    view.iterate(registry.storage<int>()).iterate(registry.storage<stable_type>());
 
     ASSERT_EQ(view.size_hint(), 2u);
     ASSERT_TRUE(view.contains(e0));
@@ -277,6 +248,7 @@ TEST(RuntimeView, StableType) {
 
 TEST(RuntimeView, StableTypeWithExcludedComponent) {
     entt::registry registry;
+    entt::runtime_view view{};
 
     const auto entity = registry.create();
     const auto other = registry.create();
@@ -285,9 +257,7 @@ TEST(RuntimeView, StableTypeWithExcludedComponent) {
     registry.emplace<stable_type>(other, 42);
     registry.emplace<int>(entity);
 
-    entt::id_type components[] = {entt::type_hash<stable_type>::value()};
-    entt::id_type filter[] = {entt::type_hash<int>::value()};
-    auto view = registry.runtime_view(std::begin(components), std::end(components), std::begin(filter), std::end(filter));
+    view.iterate(registry.storage<stable_type>()).exclude(registry.storage<int>());
 
     ASSERT_EQ(view.size_hint(), 2u);
     ASSERT_FALSE(view.contains(entity));