Jelajahi Sumber

storage: fix cross range-erase can break when using built-in iterators (close #914)

Michele Caini 3 tahun lalu
induk
melakukan
e9dbd10db4
2 mengubah file dengan 67 tambahan dan 4 penghapusan
  1. 7 4
      src/entt/entity/storage.hpp
  2. 60 0
      test/entt/entity/storage.cpp

+ 7 - 4
src/entt/entity/storage.hpp

@@ -331,14 +331,17 @@ protected:
      */
     void pop(basic_iterator first, basic_iterator last) override {
         for(; first != last; ++first) {
+            // cannot use first.index() because it would break with cross iterators
+            auto &elem = element_at(base_type::index(*first));
+
             if constexpr(comp_traits::in_place_delete) {
                 base_type::in_place_pop(first);
-                std::destroy_at(std::addressof(element_at(static_cast<size_type>(first.index()))));
+                std::destroy_at(std::addressof(elem));
             } else {
-                auto &elem = element_at(base_type::size() - 1u);
+                auto &other = element_at(base_type::size() - 1u);
                 // destroying on exit allows reentrant destructors
-                [[maybe_unused]] auto unused = std::exchange(element_at(static_cast<size_type>(first.index())), std::move(elem));
-                std::destroy_at(std::addressof(elem));
+                [[maybe_unused]] auto unused = std::exchange(elem, std::move(other));
+                std::destroy_at(std::addressof(other));
                 base_type::swap_and_pop(first);
             }
         }

+ 60 - 0
test/entt/entity/storage.cpp

@@ -364,6 +364,21 @@ TEST(Storage, Erase) {
     ASSERT_EQ(*pool.begin(), 1);
 }
 
+TEST(Storage, CrossErase) {
+    entt::sparse_set set;
+    entt::storage<int> pool;
+    entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
+
+    pool.emplace(entities[0u], 3);
+    pool.emplace(entities[1u], 42);
+    set.emplace(entities[1u]);
+    pool.erase(set.begin(), set.end());
+
+    ASSERT_TRUE(pool.contains(entities[0u]));
+    ASSERT_FALSE(pool.contains(entities[1u]));
+    ASSERT_EQ(pool.raw()[0u][0u], 3);
+}
+
 TEST(Storage, StableErase) {
     entt::storage<stable_type> pool;
     entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}};
@@ -458,6 +473,21 @@ TEST(Storage, StableErase) {
     ASSERT_EQ(pool.get(entities[2u]).value, 1);
 }
 
+TEST(Storage, CrossStableErase) {
+    entt::sparse_set set;
+    entt::storage<stable_type> pool;
+    entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
+
+    pool.emplace(entities[0u], 3);
+    pool.emplace(entities[1u], 42);
+    set.emplace(entities[1u]);
+    pool.erase(set.begin(), set.end());
+
+    ASSERT_TRUE(pool.contains(entities[0u]));
+    ASSERT_FALSE(pool.contains(entities[1u]));
+    ASSERT_EQ(pool.raw()[0u][0u].value, 3);
+}
+
 TEST(Storage, Remove) {
     entt::storage<int> pool;
     entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}};
@@ -492,6 +522,21 @@ TEST(Storage, Remove) {
     ASSERT_EQ(*pool.begin(), 1);
 }
 
+TEST(Storage, CrossRemove) {
+    entt::sparse_set set;
+    entt::storage<int> pool;
+    entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
+
+    pool.emplace(entities[0u], 3);
+    pool.emplace(entities[1u], 42);
+    set.emplace(entities[1u]);
+    pool.remove(set.begin(), set.end());
+
+    ASSERT_TRUE(pool.contains(entities[0u]));
+    ASSERT_FALSE(pool.contains(entities[1u]));
+    ASSERT_EQ(pool.raw()[0u][0u], 3);
+}
+
 TEST(Storage, StableRemove) {
     entt::storage<stable_type> pool;
     entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}};
@@ -589,6 +634,21 @@ TEST(Storage, StableRemove) {
     ASSERT_EQ(pool.get(entities[2u]).value, 1);
 }
 
+TEST(Storage, CrossStableRemove) {
+    entt::sparse_set set;
+    entt::storage<stable_type> pool;
+    entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
+
+    pool.emplace(entities[0u], 3);
+    pool.emplace(entities[1u], 42);
+    set.emplace(entities[1u]);
+    pool.remove(set.begin(), set.end());
+
+    ASSERT_TRUE(pool.contains(entities[0u]));
+    ASSERT_FALSE(pool.contains(entities[1u]));
+    ASSERT_EQ(pool.raw()[0u][0u].value, 3);
+}
+
 TEST(Storage, TypeFromBase) {
     entt::storage<int> pool;
     entt::sparse_set &base = pool;