Explorar o código

sparse_set: better, faster range push

Michele Caini %!s(int64=3) %!d(string=hai) anos
pai
achega
935393aae0

+ 49 - 21
src/entt/entity/sparse_set.hpp

@@ -274,6 +274,27 @@ protected:
         packed[static_cast<size_type>(entt)] = std::exchange(free_list, traits_type::combine(entt, tombstone));
     }
 
+    /**
+     * @brief Assigns an entity to a sparse set.
+     * @param entt A valid identifier.
+     * @param force_back Force back insertion.
+     * @return Iterator pointing to the emplaced element.
+     */
+    basic_iterator try_emplace(const Entity entt, const bool force_back) {
+        ENTT_ASSERT(!contains(entt), "Set already contains entity");
+
+        if(auto &elem = assure_at_least(entt); free_list == null || force_back) {
+            packed.push_back(entt);
+            elem = traits_type::combine(static_cast<typename traits_type::entity_type>(packed.size() - 1u), traits_type::to_integral(entt));
+            return begin();
+        } else {
+            const auto pos = static_cast<size_type>(traits_type::to_entity(free_list));
+            elem = traits_type::combine(traits_type::to_integral(free_list), traits_type::to_integral(entt));
+            free_list = std::exchange(packed[pos], entt);
+            return --(end() - pos);
+        }
+    }
+
 protected:
     /**
      * @brief Erases entities from a sparse set.
@@ -293,24 +314,14 @@ protected:
     }
 
     /**
-     * @brief Assigns an entity to a sparse set.
-     * @param entt A valid identifier.
-     * @param force_back Force back insertion.
-     * @return Iterator pointing to the emplaced element.
+     * @brief Assigns one or more entities to a sparse set.
+     * @param first An iterator to the first element of the range of entities.
+     * @param last An iterator past the last element of the range of entities.
+     * @param value Optional opaque value.
+     * @return Iterator pointing to the emplaced elements.
      */
-    virtual basic_iterator try_emplace(const Entity entt, const bool force_back, const void * = nullptr) {
-        ENTT_ASSERT(!contains(entt), "Set already contains entity");
-
-        if(auto &elem = assure_at_least(entt); free_list == null || force_back) {
-            packed.push_back(entt);
-            elem = traits_type::combine(static_cast<typename traits_type::entity_type>(packed.size() - 1u), traits_type::to_integral(entt));
-            return begin();
-        } else {
-            const auto pos = static_cast<size_type>(traits_type::to_entity(free_list));
-            elem = traits_type::combine(traits_type::to_integral(free_list), traits_type::to_integral(entt));
-            free_list = std::exchange(packed[pos], entt);
-            return --(end() - pos);
-        }
+    virtual basic_iterator try_insert(basic_iterator first, [[maybe_unused]] basic_iterator last, [[maybe_unused]] const void *value) {
+        return first;
     }
 
 public:
@@ -690,7 +701,8 @@ public:
      * `end()` iterator otherwise.
      */
     iterator push(const entity_type entt, const void *value = nullptr) {
-        return try_emplace(entt, false, value);
+        const auto it = try_emplace(entt, false);
+        return try_insert(it, it + 1u, value);
     }
 
     /**
@@ -708,11 +720,27 @@ public:
      */
     template<typename It>
     iterator push(It first, It last) {
-        for(auto it = first; it != last; ++it) {
-            try_emplace(*it, true);
+        if(first == last) {
+            return end();
+        }
+
+        const auto to = begin();
+        auto from = to;
+
+        ENTT_TRY {
+            for(; first != last; ++first) {
+                from = try_emplace(*first, true);
+            }
+        }
+        ENTT_CATCH {
+            for(; from != to; ++from) {
+                swap_and_pop(from);
+            }
+
+            ENTT_THROW;
         }
 
-        return first == last ? end() : find(*first);
+        return try_insert(from, to, nullptr);
     }
 
     /**

+ 34 - 18
src/entt/entity/storage.hpp

@@ -264,9 +264,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
     }
 
     template<typename... Args>
-    auto emplace_element(const Entity entt, const bool force_back, Args &&...args) {
-        const auto it = base_type::try_emplace(entt, force_back);
-
+    void emplace_element(const underlying_iterator it, Args &&...args) {
         ENTT_TRY {
             auto elem = assure_at_least(static_cast<size_type>(it.index()));
             entt::uninitialized_construct_using_allocator(to_address(elem), get_allocator(), std::forward<Args>(args)...);
@@ -275,8 +273,6 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
             base_type::pop(it, it + 1u);
             ENTT_THROW;
         }
-
-        return it;
     }
 
     void shrink_to_size(const std::size_t sz) {
@@ -350,23 +346,39 @@ protected:
     }
 
     /**
-     * @brief Assigns an entity to a storage.
-     * @param entt A valid identifier.
+     * @brief Assigns one or more entities to a sparse set.
+     * @param first An iterator to the first element of the range of entities.
+     * @param last An iterator past the last element of the range of entities.
      * @param value Optional opaque value.
-     * @param force_back Force back insertion.
-     * @return Iterator pointing to the emplaced element.
+     * @return Iterator pointing to the emplaced elements.
      */
-    underlying_iterator try_emplace([[maybe_unused]] const Entity entt, [[maybe_unused]] const bool force_back, const void *value) override {
+    underlying_iterator try_insert(underlying_iterator first, underlying_iterator last, const void *value = nullptr) override {
         if(value) {
             if constexpr(std::is_copy_constructible_v<value_type>) {
-                return emplace_element(entt, force_back, *static_cast<const value_type *>(value));
+                ENTT_ASSERT(std::next(first) == last, "Opaque emplace does not support ranges");
+                emplace_element(first, *static_cast<const value_type *>(value));
+                return first;
             } else {
+                base_type::pop(first, last);
                 return base_type::end();
             }
         } else {
             if constexpr(std::is_default_constructible_v<value_type>) {
-                return emplace_element(entt, force_back);
+                const auto placeholder = first;
+
+                ENTT_TRY {
+                    for(; first != last; ++first) {
+                        emplace_element(first);
+                    }
+
+                    return placeholder;
+                }
+                ENTT_CATCH {
+                    base_type::pop(++first, last);
+                    ENTT_THROW;
+                }
             } else {
+                base_type::pop(first, last);
                 return base_type::end();
             }
         }
@@ -655,13 +667,15 @@ public:
      */
     template<typename... Args>
     value_type &emplace(const entity_type entt, Args &&...args) {
+        const auto it = base_type::try_emplace(entt, false);
+
         if constexpr(std::is_aggregate_v<value_type>) {
-            const auto it = emplace_element(entt, false, Type{std::forward<Args>(args)...});
-            return element_at(static_cast<size_type>(it.index()));
+            emplace_element(it, Type{std::forward<Args>(args)...});
         } else {
-            const auto it = emplace_element(entt, false, std::forward<Args>(args)...);
-            return element_at(static_cast<size_type>(it.index()));
+            emplace_element(it, std::forward<Args>(args)...);
         }
+
+        return element_at(static_cast<size_type>(it.index()));
     }
 
     /**
@@ -695,7 +709,8 @@ public:
     template<typename It>
     void insert(It first, It last, const value_type &value = {}) {
         for(; first != last; ++first) {
-            emplace_element(*first, true, value);
+            const auto it = base_type::try_emplace(*first, true);
+            emplace_element(it, value);
         }
     }
 
@@ -714,7 +729,8 @@ public:
     template<typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<typename std::iterator_traits<CIt>::value_type, value_type>>>
     void insert(EIt first, EIt last, CIt from) {
         for(; first != last; ++first, ++from) {
-            emplace_element(*first, true, *from);
+            const auto it = base_type::try_emplace(*first, true);
+            emplace_element(it, *from);
         }
     }
 

+ 8 - 3
src/entt/entity/storage_mixin.hpp

@@ -39,10 +39,15 @@ class sigh_mixin final: public Type {
         }
     }
 
-    underlying_iterator try_emplace(const typename Type::entity_type entt, const bool force_back, const void *value) final {
+    underlying_iterator try_insert(underlying_iterator first, underlying_iterator last, const void *value = nullptr) override {
         ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
-        Type::try_emplace(entt, force_back, value);
-        construction.publish(*owner, entt);
+        const auto entt = *first;
+
+        for(; first != last; ++first) {
+            Type::try_insert(first, first + 1u, value);
+            construction.publish(*owner, *first);
+        }
+
         return Type::find(entt);
     }
 

+ 2 - 2
test/entt/entity/sparse_set.cpp

@@ -1417,10 +1417,10 @@ TEST(SparseSet, ThrowingAllocator) {
     ASSERT_THROW(set.push(std::begin(entities), std::end(entities)), test::throwing_allocator<entt::entity>::exception_type);
     ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
     ASSERT_TRUE(set.contains(entt::entity{0}));
-    ASSERT_TRUE(set.contains(entt::entity{1}));
+    ASSERT_FALSE(set.contains(entt::entity{1}));
     ASSERT_FALSE(set.contains(entt::entity{traits_type::page_size}));
     ASSERT_EQ(set.capacity(), 2u);
-    ASSERT_EQ(set.size(), 2u);
+    ASSERT_EQ(set.size(), 1u);
 
     set.push(entities[1u]);