Explorar el Código

view: chunked iteration for multi-component views (close #462)

Michele Caini hace 5 años
padre
commit
0e7e36f80d
Se han modificado 4 ficheros con 182 adiciones y 0 borrados
  1. 1 0
      TODO
  2. 7 0
      docs/md/entity.md
  3. 63 0
      src/entt/entity/view.hpp
  4. 111 0
      test/entt/entity/view.cpp

+ 1 - 0
TODO

@@ -27,3 +27,4 @@ Next:
  - page size 0 -> page less mode
  - test meta_associative_container::meta_iterator::value fro non-key-only, const value
  - add ::reach and rev iterators to views and groups for raw, faster and unsafe iterations
+ - add example: 64 bit ids with 32 bits reserved for users' purposes

+ 7 - 0
docs/md/entity.md

@@ -1229,6 +1229,13 @@ during iterations.<br/>
 Since they aren't explicitly instantiated, empty components aren't returned in
 any case.
 
+There is also a third method for iterating over entities and components for
+multi component views. It's a chunk based iteration and is made available by
+means of the `chunked` member function.<br/>
+Since this is a particular iteration method with fairly specific purposes, I
+recommend referring to the official documentation for more details and I won't
+further investigate the topic here.
+
 As a side note, in the case of single component views, `get` accepts but doesn't
 strictly require a template parameter, since the type is implicitly defined:
 

+ 63 - 0
src/entt/entity/view.hpp

@@ -294,6 +294,31 @@ class basic_view<Entity, exclude_t<Exclude...>, Component...> {
         }
     }
 
+    template<typename Func, typename... Type>
+    void iterate(Func func, type_list<Type...>) const {
+        const auto &view = candidate();
+        const auto last = view.data() + view.size();
+        auto first = view.data();
+
+        while(first != last) {
+            if((std::get<pool_type<Component> *>(pools)->contains(*first) && ...) && std::none_of(filter.cbegin(), filter.cend(), [entt = *first](const sparse_set<Entity> *cpool) { return cpool->contains(entt); })) {
+                const auto base = *(first++);
+                const auto chunk = std::min({ (std::get<pool_type<Component> *>(pools)->size() - std::get<pool_type<Component> *>(pools)->index(base))... });
+                size_type length{};
+
+                for(++length;
+                    length < chunk
+                        && ((*(std::get<pool_type<Component> *>(pools)->data() + std::get<pool_type<Component> *>(pools)->index(base) + length) == *first) && ...)
+                        && std::none_of(filter.cbegin(), filter.cend(), [&first](const sparse_set<Entity> *cpool) { return cpool->contains(*first); });
+                    ++length, ++first);
+
+                func(view.data() + view.index(base), (std::get<pool_type<Type> *>(pools)->raw() + std::get<pool_type<Type> *>(pools)->index(base))..., length);
+            } else {
+                ++first;
+            }
+        }
+    }
+
 public:
     /*! @brief Underlying entity identifier. */
     using entity_type = Entity;
@@ -579,6 +604,44 @@ public:
         return view_range{iterator{view, unchecked(view), filter, view.begin()}, iterator{view, unchecked(view), filter, view.end()}, pools};
     }
 
+    /**
+     * @brief Chunked iteration for entities and components
+     *
+     * Chunked iteration tries to spot chunks in the sets of entities and
+     * components and return them one at a time along with their sizes.<br/>
+     * This type of iteration is intended where it's known a priori that the
+     * creation of entities and components takes place in chunk, which is
+     * actually quite common. In this case, various optimizations can be applied
+     * downstream to obtain even better performances from the views.
+     *
+     * The signature of the function must be equivalent to the following:
+     *
+     * @code{.cpp}
+     * void(const entity_type *, Type *..., size_type);
+     * @endcode
+     *
+     * The arguments are as follows:
+     *
+     * * A pointer to the entities belonging to the chunk.
+     * * Pointers to the components associated with the returned entities.
+     * * The length of the chunk.
+     *
+     * Note that the callback can be invoked 0 or more times and no guarantee is
+     * given on the order of the elements.
+     *
+     * @note
+     * Empty types aren't explicitly instantiated and therefore they are never
+     * returned during iterations.
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    void chunked(Func func) const {
+        using non_empty_type = type_list_cat_t<std::conditional_t<ENTT_IS_EMPTY(Component), type_list<>, type_list<Component>>...>;
+        iterate(std::move(func), non_empty_type{});
+    }
+
 private:
     const std::tuple<pool_type<Component> *...> pools;
     filter_type filter;

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

@@ -729,3 +729,114 @@ TEST(MultiComponentView, FrontBack) {
     ASSERT_EQ(view.front(), e1);
     ASSERT_EQ(view.back(), e0);
 }
+
+TEST(MultiComponentView, ChunkedEmpty) {
+    entt::registry registry;
+    auto view = registry.view<const entt::id_type, const char>();
+
+    view.chunked([](auto...) { FAIL(); });
+
+    registry.emplace<entt::id_type>(registry.create());
+    registry.emplace<char>(registry.create());
+
+    view.chunked([](auto...) { FAIL(); });
+}
+
+TEST(MultiComponentView, ChunkedContiguous) {
+    entt::registry registry;
+    auto view = registry.view<const entt::id_type, const char>();
+
+    for(auto i = 0; i < 5; ++i) {
+        const auto entity = registry.create();
+        registry.emplace<entt::id_type>(entity, entt::to_integral(entity));
+        registry.emplace<char>(entity);
+    }
+
+    view.chunked([](auto *entity, auto *id, auto *, auto sz) {
+        ASSERT_EQ(sz, 5u);
+
+        for(decltype(sz) i{}; i < sz; ++i) {
+            ASSERT_EQ(entt::to_integral(*(entity + i)), *(id + i));
+        }
+    });
+}
+
+TEST(MultiComponentView, ChunkedSpread) {
+    entt::registry registry;
+    auto view = registry.view<const entt::id_type, const char>();
+
+    registry.emplace<entt::id_type>(registry.create());
+
+    for(auto i = 0; i < 3; ++i) {
+        const auto entity = registry.create();
+        registry.emplace<entt::id_type>(entity, entt::to_integral(entity));
+        registry.emplace<char>(entity);
+    }
+
+    registry.emplace<char>(registry.create());
+
+    for(auto i = 0; i < 3; ++i) {
+        const auto entity = registry.create();
+        registry.emplace<entt::id_type>(entity, entt::to_integral(entity));
+        registry.emplace<char>(entity);
+    }
+
+    registry.emplace<entt::id_type>(registry.create());
+    registry.emplace<entt::id_type>(registry.create());
+    registry.emplace<char>(registry.create());
+
+    view.chunked([](auto *entity, auto *id, auto *, auto sz) {
+        ASSERT_EQ(sz, 3u);
+
+        for(decltype(sz) i{}; i < sz; ++i) {
+            ASSERT_EQ(entt::to_integral(*(entity + i)), *(id + i));
+        }
+    });
+}
+
+TEST(MultiComponentView, ChunkedWithExcludedComponents) {
+    entt::registry registry;
+    auto view = registry.view<const entt::id_type, const char>(entt::exclude<double>);
+
+    registry.emplace<entt::id_type>(registry.create());
+
+    for(auto i = 0; i < 3; ++i) {
+        const auto entity = registry.create();
+        registry.emplace<entt::id_type>(entity, entt::to_integral(entity));
+        registry.emplace<char>(entity);
+    }
+
+    registry.emplace<char>(registry.create());
+
+    for(auto i = 0; i < 2; ++i) {
+        const auto entity = registry.create();
+        registry.emplace<entt::id_type>(entity, entt::to_integral(entity));
+        registry.emplace<char>(entity);
+        registry.emplace<double>(entity);
+    }
+
+    for(auto i = 0; i < 3; ++i) {
+        const auto entity = registry.create();
+        registry.emplace<entt::id_type>(entity, entt::to_integral(entity));
+        registry.emplace<char>(entity);
+    }
+
+    for(auto i = 0; i < 2; ++i) {
+        const auto entity = registry.create();
+        registry.emplace<entt::id_type>(entity, entt::to_integral(entity));
+        registry.emplace<char>(entity);
+        registry.emplace<double>(entity);
+    }
+
+    registry.emplace<entt::id_type>(registry.create());
+    registry.emplace<entt::id_type>(registry.create());
+    registry.emplace<char>(registry.create());
+
+    view.chunked([](auto *entity, auto *id, auto *, auto sz) {
+        ASSERT_EQ(sz, 3u);
+
+        for(decltype(sz) i{}; i < sz; ++i) {
+            ASSERT_EQ(entt::to_integral(*(entity + i)), *(id + i));
+        }
+    });
+}