Просмотр исходного кода

sparse_set: test shrink_to_fit and improve corner cases

Michele Caini 1 год назад
Родитель
Сommit
fdb6214879
3 измененных файлов с 105 добавлено и 18 удалено
  1. 0 4
      TODO
  2. 12 12
      src/entt/entity/sparse_set.hpp
  3. 93 2
      test/entt/entity/sparse_set.cpp

+ 0 - 4
TODO

@@ -25,7 +25,6 @@ TODO:
   - documentation for reserved entities
 * storage entity: fast range-push from above
 * table: pop back to support swap and pop, single column access, empty type optimization
-* checkout tools workflow
 * entity based component_traits
 * review cmake warning about FetchContent_Populate (need .28 and EXCLUDE_FROM_ALL for FetchContent)
 * suppress -Wself-move on CI with g++13
@@ -38,6 +37,3 @@ TODO:
 * any cdynamic to support const ownership construction
 * allow passing arguments to meta setter/getter (we can fallback on meta invoke for everything probably)
 * delegate/sigh: forward connect/disconnect from & to *
-* a few more tests on the improved shrink_to_fit for sparse arrays
-* sparse_set/storage/...: difference_type (?)
-* -Wextra

+ 12 - 12
src/entt/entity/sparse_set.hpp

@@ -565,17 +565,18 @@ public:
         other.reserve(len);
 
         for(auto &&elem: std::as_const(packed)) {
-            // also skip tombstones, if any
-            if(const auto page = pos_to_page(entity_to_pos(elem)); page < len && sparse[page] != nullptr) {
-                if(const auto sz = page + 1u; sz > other.size()) {
-                    other.resize(sz, nullptr);
-                }
+            if(elem != tombstone) {
+                if(const auto page = pos_to_page(entity_to_pos(elem)); sparse[page] != nullptr) {
+                    if(const auto sz = page + 1u; sz > other.size()) {
+                        other.resize(sz, nullptr);
+                    }
 
-                other[page] = std::exchange(sparse[page], nullptr);
+                    other[page] = std::exchange(sparse[page], nullptr);
 
-                if(++cnt == len) {
-                    // early exit due to lack of pages
-                    break;
+                    if(++cnt == len) {
+                        // early exit due to lack of pages
+                        break;
+                    }
                 }
             }
         }
@@ -591,9 +592,8 @@ public:
      * @brief Returns the extent of a sparse set.
      *
      * The extent of a sparse set is also the size of the internal sparse array.
-     * There is no guarantee that the internal packed array has the same size.
-     * Usually the size of the internal sparse array is equal or greater than
-     * the one of the internal packed array.
+     * There is no guarantee that all pages have been allocated, nor that the
+     * internal packed array is be the same size.
      *
      * @return Extent of the sparse set.
      */

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

@@ -273,18 +273,109 @@ TYPED_TEST(SparseSet, Capacity) {
     for(const auto policy: this->deletion_policy) {
         sparse_set_type set{policy};
 
-        set.reserve(64);
+        set.reserve(64u);
 
         ASSERT_EQ(set.capacity(), 64u);
         ASSERT_TRUE(set.empty());
 
-        set.reserve(0);
+        set.reserve(0u);
 
         ASSERT_EQ(set.capacity(), 64u);
         ASSERT_TRUE(set.empty());
     }
 }
 
+TYPED_TEST(SparseSet, ShrinkToFit) {
+    using entity_type = typename TestFixture::type;
+    using sparse_set_type = entt::basic_sparse_set<entity_type>;
+    using traits_type = entt::entt_traits<entity_type>;
+
+    for(const auto policy: this->deletion_policy) {
+        sparse_set_type set{policy};
+
+        ASSERT_EQ(set.capacity(), 0u);
+        ASSERT_EQ(set.extent(), 0u);
+
+        set.reserve(8u);
+
+        set.push(entity_type{traits_type::page_size - 1u});
+        set.push(entity_type{traits_type::page_size});
+        set.erase(entity_type{traits_type::page_size - 1u});
+
+        ASSERT_EQ(set.capacity(), 8u);
+        ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
+
+        ASSERT_FALSE(set.contains(entity_type{traits_type::page_size - 1u}));
+        ASSERT_TRUE(set.contains(entity_type{traits_type::page_size}));
+
+        set.shrink_to_fit();
+
+        switch(policy) {
+        case entt::deletion_policy::swap_only:
+        case entt::deletion_policy::in_place: {
+            ASSERT_EQ(set.capacity(), 2u);
+            ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
+        } break;
+        case entt::deletion_policy::swap_and_pop: {
+            ASSERT_EQ(set.capacity(), 1u);
+            ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
+        } break;
+        }
+
+        set.reserve(8u);
+
+        set.push(entity_type{traits_type::page_size - 1u});
+        set.erase(entity_type{traits_type::page_size});
+
+        ASSERT_EQ(set.capacity(), 8u);
+        ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
+
+        ASSERT_TRUE(set.contains(entity_type{traits_type::page_size - 1u}));
+        ASSERT_FALSE(set.contains(entity_type{traits_type::page_size}));
+
+        set.shrink_to_fit();
+
+        switch(policy) {
+        case entt::deletion_policy::in_place: {
+            ASSERT_EQ(set.capacity(), 2u);
+            ASSERT_EQ(set.extent(), traits_type::page_size);
+        } break;
+        case entt::deletion_policy::swap_only: {
+            ASSERT_EQ(set.capacity(), 2u);
+            ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
+        } break;
+        case entt::deletion_policy::swap_and_pop: {
+            ASSERT_EQ(set.capacity(), 1u);
+            ASSERT_EQ(set.extent(), traits_type::page_size);
+        } break;
+        }
+
+        ASSERT_TRUE(set.contains(entity_type{traits_type::page_size - 1u}));
+        ASSERT_FALSE(set.contains(entity_type{traits_type::page_size}));
+
+        set.erase(entity_type{traits_type::page_size - 1u});
+        set.shrink_to_fit();
+
+        switch(policy) {
+        case entt::deletion_policy::in_place: {
+            ASSERT_EQ(set.capacity(), 2u);
+            ASSERT_EQ(set.extent(), 0u);
+        } break;
+        case entt::deletion_policy::swap_only: {
+            ASSERT_EQ(set.capacity(), 2u);
+            ASSERT_EQ(set.extent(), 2 * traits_type::page_size);
+        } break;
+        case entt::deletion_policy::swap_and_pop: {
+            ASSERT_EQ(set.capacity(), 0u);
+            ASSERT_EQ(set.extent(), 0u);
+        } break;
+        }
+
+        ASSERT_FALSE(set.contains(entity_type{traits_type::page_size - 1u}));
+        ASSERT_FALSE(set.contains(entity_type{traits_type::page_size}));
+    }
+}
+
 TYPED_TEST(SparseSet, Pagination) {
     using entity_type = typename TestFixture::type;
     using sparse_set_type = entt::basic_sparse_set<entity_type>;