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

sigh_storage_mixin: allocator support

Michele Caini пре 3 година
родитељ
комит
80c700b1f8
3 измењених фајлова са 409 додато и 19 уклоњено
  1. 2 1
      TODO
  2. 71 6
      src/entt/entity/sigh_storage_mixin.hpp
  3. 336 12
      test/entt/entity/sigh_storage_mixin.cpp

+ 2 - 1
TODO

@@ -11,6 +11,7 @@ DOC:
 * examples (and credits) from @alanjfs :)
 * examples (and credits) from @alanjfs :)
 
 
 WIP:
 WIP:
+* remove storage::base_type, make views extract the sparse set directly
 * make non-const registry::get use const assure or the like
 * make non-const registry::get use const assure or the like
 * emitter: runtime handlers, allocator support (ready for both already)
 * emitter: runtime handlers, allocator support (ready for both already)
 * view/group: no storage_traits dependency -> use storage instead of components for the definition
 * view/group: no storage_traits dependency -> use storage instead of components for the definition
@@ -20,7 +21,7 @@ WIP:
 * iterator based try_emplace vs try_insert for perf reasons
 * iterator based try_emplace vs try_insert for perf reasons
 * dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
 * dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
 * entity-only and exclude-only views
 * entity-only and exclude-only views
-* custom allocators all over (sigh storage mixin, registry, ...)
+* custom allocators all over (registry, ...)
 * consider removing ENTT_NOEXCEPT, use ENTT_NOEXCEPT_IF (or noexcept(...)) as appropriate in any case (ie make compressed_pair conditionally noexcept)
 * consider removing ENTT_NOEXCEPT, use ENTT_NOEXCEPT_IF (or noexcept(...)) as appropriate in any case (ie make compressed_pair conditionally noexcept)
 * add test for maximum number of entities reached
 * add test for maximum number of entities reached
 * add user data to type_info
 * add user data to type_info

+ 71 - 6
src/entt/entity/sigh_storage_mixin.hpp

@@ -24,6 +24,7 @@ namespace entt {
  */
  */
 template<typename Type>
 template<typename Type>
 class sigh_storage_mixin final: public Type {
 class sigh_storage_mixin final: public Type {
+    using sigh_type = sigh<void(basic_registry<typename Type::entity_type> &, const typename Type::entity_type), typename Type::allocator_type>;
     using basic_iterator = typename Type::basic_iterator;
     using basic_iterator = typename Type::basic_iterator;
 
 
     template<typename Func>
     template<typename Func>
@@ -54,11 +55,75 @@ class sigh_storage_mixin final: public Type {
     }
     }
 
 
 public:
 public:
+    /*! @brief Allocator type. */
+    using allocator_type = typename Type::allocator_type;
     /*! @brief Underlying entity identifier. */
     /*! @brief Underlying entity identifier. */
     using entity_type = typename Type::entity_type;
     using entity_type = typename Type::entity_type;
 
 
-    /*! @brief Inherited constructors. */
-    using Type::Type;
+    /*! @brief Default constructor. */
+    sigh_storage_mixin()
+        : sigh_storage_mixin{allocator_type{}} {}
+
+    /**
+     * @brief Constructs an empty storage with a given allocator.
+     * @param allocator The allocator to use.
+     */
+    explicit sigh_storage_mixin(const allocator_type &allocator)
+        : Type{allocator},
+          owner{},
+          construction{},
+          destruction{},
+          update{} {}
+
+    /**
+     * @brief Move constructor.
+     * @param other The instance to move from.
+     */
+    sigh_storage_mixin(sigh_storage_mixin &&other) ENTT_NOEXCEPT
+        : Type{std::move(other)},
+          owner{other.owner},
+          construction{std::move(other.construction)},
+          destruction{std::move(other.destruction)},
+          update{std::move(other.update)} {}
+
+    /**
+     * @brief Allocator-extended move constructor.
+     * @param other The instance to move from.
+     * @param allocator The allocator to use.
+     */
+    sigh_storage_mixin(sigh_storage_mixin &&other, const allocator_type &allocator) ENTT_NOEXCEPT
+        : Type{std::move(other), allocator},
+          owner{other.owner},
+          construction{std::move(other.construction), allocator},
+          destruction{std::move(other.destruction), allocator},
+          update{std::move(other.update), allocator} {}
+
+    /**
+     * @brief Move assignment operator.
+     * @param other The instance to move from.
+     * @return This storage.
+     */
+    sigh_storage_mixin &operator=(sigh_storage_mixin &&other) ENTT_NOEXCEPT {
+        Type::operator=(std::move(other));
+        owner = other.owner;
+        construction = std::move(other.construction);
+        destruction = std::move(other.destruction);
+        update = std::move(other.update);
+        return *this;
+    }
+
+    /**
+     * @brief Exchanges the contents with those of a given storage.
+     * @param other Storage to exchange the content with.
+     */
+    void swap(sigh_storage_mixin &other) {
+        using std::swap;
+        Type::swap(other);
+        swap(owner, other.owner);
+        swap(construction, other.construction);
+        swap(destruction, other.destruction);
+        swap(update, other.update);
+    }
 
 
     /**
     /**
      * @brief Returns a sink object.
      * @brief Returns a sink object.
@@ -166,10 +231,10 @@ public:
     }
     }
 
 
 private:
 private:
-    sigh<void(basic_registry<entity_type> &, const entity_type)> construction{};
-    sigh<void(basic_registry<entity_type> &, const entity_type)> destruction{};
-    sigh<void(basic_registry<entity_type> &, const entity_type)> update{};
-    basic_registry<entity_type> *owner{};
+    basic_registry<entity_type> *owner;
+    sigh_type construction;
+    sigh_type destruction;
+    sigh_type update;
 };
 };
 
 
 } // namespace entt
 } // namespace entt

+ 336 - 12
test/entt/entity/sigh_storage_mixin.cpp

@@ -2,6 +2,8 @@
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <entt/entity/registry.hpp>
 #include <entt/entity/registry.hpp>
 #include <entt/entity/storage.hpp>
 #include <entt/entity/storage.hpp>
+#include "../common/throwing_allocator.hpp"
+#include "../common/throwing_type.hpp"
 
 
 struct empty_type {};
 struct empty_type {};
 
 
@@ -31,13 +33,12 @@ TEST(SighStorageMixin, GenericType) {
     entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
     entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
     entt::sigh_storage_mixin<entt::storage<int>> pool;
     entt::sigh_storage_mixin<entt::storage<int>> pool;
     entt::sparse_set &base = pool;
     entt::sparse_set &base = pool;
-    entt::registry registry{};
-
-    pool.bind(entt::forward_as_any(registry));
+    entt::registry registry;
 
 
     counter on_construct{};
     counter on_construct{};
     counter on_destroy{};
     counter on_destroy{};
 
 
+    pool.bind(entt::forward_as_any(registry));
     pool.on_construct().connect<&listener>(on_construct);
     pool.on_construct().connect<&listener>(on_construct);
     pool.on_destroy().connect<&listener>(on_destroy);
     pool.on_destroy().connect<&listener>(on_destroy);
 
 
@@ -97,13 +98,12 @@ TEST(SighStorageMixin, StableType) {
     entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
     entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
     entt::sigh_storage_mixin<entt::storage<stable_type>> pool;
     entt::sigh_storage_mixin<entt::storage<stable_type>> pool;
     entt::sparse_set &base = pool;
     entt::sparse_set &base = pool;
-    entt::registry registry{};
-
-    pool.bind(entt::forward_as_any(registry));
+    entt::registry registry;
 
 
     counter on_construct{};
     counter on_construct{};
     counter on_destroy{};
     counter on_destroy{};
 
 
+    pool.bind(entt::forward_as_any(registry));
     pool.on_construct().connect<&listener>(on_construct);
     pool.on_construct().connect<&listener>(on_construct);
     pool.on_destroy().connect<&listener>(on_destroy);
     pool.on_destroy().connect<&listener>(on_destroy);
 
 
@@ -163,13 +163,12 @@ TEST(SighStorageMixin, EmptyType) {
     entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
     entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
     entt::sigh_storage_mixin<entt::storage<empty_type>> pool;
     entt::sigh_storage_mixin<entt::storage<empty_type>> pool;
     entt::sparse_set &base = pool;
     entt::sparse_set &base = pool;
-    entt::registry registry{};
-
-    pool.bind(entt::forward_as_any(registry));
+    entt::registry registry;
 
 
     counter on_construct{};
     counter on_construct{};
     counter on_destroy{};
     counter on_destroy{};
 
 
+    pool.bind(entt::forward_as_any(registry));
     pool.on_construct().connect<&listener>(on_construct);
     pool.on_construct().connect<&listener>(on_construct);
     pool.on_destroy().connect<&listener>(on_destroy);
     pool.on_destroy().connect<&listener>(on_destroy);
 
 
@@ -229,13 +228,12 @@ TEST(SighStorageMixin, NonDefaultConstructibleType) {
     entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
     entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
     entt::sigh_storage_mixin<entt::storage<non_default_constructible>> pool;
     entt::sigh_storage_mixin<entt::storage<non_default_constructible>> pool;
     entt::sparse_set &base = pool;
     entt::sparse_set &base = pool;
-    entt::registry registry{};
-
-    pool.bind(entt::forward_as_any(registry));
+    entt::registry registry;
 
 
     counter on_construct{};
     counter on_construct{};
     counter on_destroy{};
     counter on_destroy{};
 
 
+    pool.bind(entt::forward_as_any(registry));
     pool.on_construct().connect<&listener>(on_construct);
     pool.on_construct().connect<&listener>(on_construct);
     pool.on_destroy().connect<&listener>(on_destroy);
     pool.on_destroy().connect<&listener>(on_destroy);
 
 
@@ -281,3 +279,329 @@ TEST(SighStorageMixin, NonDefaultConstructibleType) {
     ASSERT_EQ(on_destroy.value, 3);
     ASSERT_EQ(on_destroy.value, 3);
     ASSERT_TRUE(pool.empty());
     ASSERT_TRUE(pool.empty());
 }
 }
+
+TEST(SighStorageMixin, VoidType) {
+    entt::sigh_storage_mixin<entt::storage<void>> pool;
+    entt::registry registry;
+
+    counter on_construct{};
+    counter on_destroy{};
+
+    pool.bind(entt::forward_as_any(registry));
+    pool.on_construct().connect<&listener>(on_construct);
+    pool.on_destroy().connect<&listener>(on_destroy);
+
+    pool.emplace(entt::entity{99});
+
+    ASSERT_EQ(pool.type(), entt::type_id<void>());
+    ASSERT_TRUE(pool.contains(entt::entity{99}));
+
+    entt::sigh_storage_mixin<entt::storage<void>> other{std::move(pool)};
+
+    ASSERT_FALSE(pool.contains(entt::entity{99}));
+    ASSERT_TRUE(other.contains(entt::entity{99}));
+
+    pool = std::move(other);
+
+    ASSERT_TRUE(pool.contains(entt::entity{99}));
+    ASSERT_FALSE(other.contains(entt::entity{99}));
+
+    pool.clear();
+
+    ASSERT_EQ(on_construct.value, 1);
+    ASSERT_EQ(on_destroy.value, 1);
+}
+
+TEST(SighStorageMixin, Move) {
+    entt::sigh_storage_mixin<entt::storage<int>> pool;
+    entt::registry registry;
+
+    counter on_construct{};
+    counter on_destroy{};
+
+    pool.bind(entt::forward_as_any(registry));
+    pool.on_construct().connect<&listener>(on_construct);
+    pool.on_destroy().connect<&listener>(on_destroy);
+
+    pool.emplace(entt::entity{3}, 3);
+
+    ASSERT_TRUE(std::is_move_constructible_v<decltype(pool)>);
+    ASSERT_TRUE(std::is_move_assignable_v<decltype(pool)>);
+    ASSERT_EQ(pool.type(), entt::type_id<int>());
+
+    entt::sigh_storage_mixin<entt::storage<int>> other{std::move(pool)};
+
+    ASSERT_TRUE(pool.empty());
+    ASSERT_FALSE(other.empty());
+    ASSERT_EQ(other.type(), entt::type_id<int>());
+    ASSERT_EQ(pool.at(0u), static_cast<entt::entity>(entt::null));
+    ASSERT_EQ(other.at(0u), entt::entity{3});
+    ASSERT_EQ(other.get(entt::entity{3}), 3);
+
+    pool = std::move(other);
+
+    ASSERT_FALSE(pool.empty());
+    ASSERT_TRUE(other.empty());
+    ASSERT_EQ(pool.at(0u), entt::entity{3});
+    ASSERT_EQ(pool.get(entt::entity{3}), 3);
+    ASSERT_EQ(other.at(0u), static_cast<entt::entity>(entt::null));
+
+    other = entt::sigh_storage_mixin<entt::storage<int>>{};
+    other.bind(entt::forward_as_any(registry));
+
+    other.emplace(entt::entity{42}, 42);
+    other = std::move(pool);
+
+    ASSERT_TRUE(pool.empty());
+    ASSERT_FALSE(other.empty());
+    ASSERT_EQ(pool.at(0u), static_cast<entt::entity>(entt::null));
+    ASSERT_EQ(other.at(0u), entt::entity{3});
+    ASSERT_EQ(other.get(entt::entity{3}), 3);
+
+    other.clear();
+
+    ASSERT_EQ(on_construct.value, 1);
+    ASSERT_EQ(on_destroy.value, 1);
+}
+
+TEST(SighStorageMixin, Swap) {
+    entt::sigh_storage_mixin<entt::storage<int>> pool;
+    entt::sigh_storage_mixin<entt::storage<int>> other;
+    entt::registry registry;
+
+    counter on_construct{};
+    counter on_destroy{};
+
+    pool.bind(entt::forward_as_any(registry));
+    pool.on_construct().connect<&listener>(on_construct);
+    pool.on_destroy().connect<&listener>(on_destroy);
+
+    other.bind(entt::forward_as_any(registry));
+    other.on_construct().connect<&listener>(on_construct);
+    other.on_destroy().connect<&listener>(on_destroy);
+
+    pool.emplace(entt::entity{42}, 41);
+
+    other.emplace(entt::entity{9}, 8);
+    other.emplace(entt::entity{3}, 2);
+    other.erase(entt::entity{9});
+
+    ASSERT_EQ(pool.size(), 1u);
+    ASSERT_EQ(other.size(), 1u);
+
+    pool.swap(other);
+
+    ASSERT_EQ(pool.type(), entt::type_id<int>());
+    ASSERT_EQ(other.type(), entt::type_id<int>());
+
+    ASSERT_EQ(pool.size(), 1u);
+    ASSERT_EQ(other.size(), 1u);
+
+    ASSERT_EQ(pool.at(0u), entt::entity{3});
+    ASSERT_EQ(pool.get(entt::entity{3}), 2);
+
+    ASSERT_EQ(other.at(0u), entt::entity{42});
+    ASSERT_EQ(other.get(entt::entity{42}), 41);
+
+    pool.clear();
+    other.clear();
+
+    ASSERT_EQ(on_construct.value, 3);
+    ASSERT_EQ(on_destroy.value, 3);
+}
+
+TEST(SighStorageMixin, CustomAllocator) {
+    auto test = [](auto pool, auto alloc) {
+        entt::registry registry;
+
+        counter on_construct{};
+        counter on_destroy{};
+
+        pool.bind(entt::forward_as_any(registry));
+        pool.on_construct().connect<&listener>(on_construct);
+        pool.on_destroy().connect<&listener>(on_destroy);
+
+        pool.reserve(1u);
+
+        ASSERT_NE(pool.capacity(), 0u);
+
+        pool.emplace(entt::entity{0});
+        pool.emplace(entt::entity{1});
+
+        decltype(pool) other{std::move(pool), alloc};
+
+        ASSERT_TRUE(pool.empty());
+        ASSERT_FALSE(other.empty());
+        ASSERT_EQ(pool.capacity(), 0u);
+        ASSERT_NE(other.capacity(), 0u);
+        ASSERT_EQ(other.size(), 2u);
+
+        pool = std::move(other);
+
+        ASSERT_FALSE(pool.empty());
+        ASSERT_TRUE(other.empty());
+        ASSERT_EQ(other.capacity(), 0u);
+        ASSERT_NE(pool.capacity(), 0u);
+        ASSERT_EQ(pool.size(), 2u);
+
+        pool.swap(other);
+        pool = std::move(other);
+
+        ASSERT_FALSE(pool.empty());
+        ASSERT_TRUE(other.empty());
+        ASSERT_EQ(other.capacity(), 0u);
+        ASSERT_NE(pool.capacity(), 0u);
+        ASSERT_EQ(pool.size(), 2u);
+
+        pool.clear();
+
+        ASSERT_NE(pool.capacity(), 0u);
+        ASSERT_EQ(pool.size(), 0u);
+
+        ASSERT_EQ(on_construct.value, 2);
+        ASSERT_EQ(on_destroy.value, 2);
+    };
+
+    test::throwing_allocator<entt::entity> allocator{};
+
+    test(entt::sigh_storage_mixin<entt::storage<int, test::throwing_allocator<int>>>{allocator}, allocator);
+    test(entt::sigh_storage_mixin<entt::storage<std::true_type, test::throwing_allocator<std::true_type>>>{allocator}, allocator);
+    test(entt::sigh_storage_mixin<entt::storage<stable_type, test::throwing_allocator<stable_type>>>{allocator}, allocator);
+}
+
+TEST(SighStorageMixin, ThrowingAllocator) {
+    auto test = [](auto pool) {
+        using pool_allocator_type = typename decltype(pool)::allocator_type;
+        using value_type = typename decltype(pool)::value_type;
+
+        typename std::decay_t<decltype(pool)>::base_type &base = pool;
+        entt::registry registry;
+
+        counter on_construct{};
+        counter on_destroy{};
+
+        pool.bind(entt::forward_as_any(registry));
+        pool.on_construct().connect<&listener>(on_construct);
+        pool.on_destroy().connect<&listener>(on_destroy);
+
+        pool_allocator_type::trigger_on_allocate = true;
+
+        ASSERT_THROW(pool.reserve(1u), typename pool_allocator_type::exception_type);
+        ASSERT_EQ(pool.capacity(), 0u);
+
+        pool_allocator_type::trigger_after_allocate = true;
+
+        ASSERT_THROW(pool.reserve(2 * ENTT_PACKED_PAGE), typename pool_allocator_type::exception_type);
+        ASSERT_EQ(pool.capacity(), ENTT_PACKED_PAGE);
+
+        pool.shrink_to_fit();
+
+        ASSERT_EQ(pool.capacity(), 0u);
+
+        test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
+
+        ASSERT_THROW(pool.emplace(entt::entity{0}, 0), test::throwing_allocator<entt::entity>::exception_type);
+        ASSERT_FALSE(pool.contains(entt::entity{0}));
+        ASSERT_TRUE(pool.empty());
+
+        test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
+
+        ASSERT_THROW(base.emplace(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
+        ASSERT_FALSE(base.contains(entt::entity{0}));
+        ASSERT_TRUE(base.empty());
+
+        pool_allocator_type::trigger_on_allocate = true;
+
+        ASSERT_THROW(pool.emplace(entt::entity{0}, 0), typename pool_allocator_type::exception_type);
+        ASSERT_FALSE(pool.contains(entt::entity{0}));
+        ASSERT_NO_THROW(pool.compact());
+        ASSERT_TRUE(pool.empty());
+
+        pool.emplace(entt::entity{0}, 0);
+        const entt::entity entities[2u]{entt::entity{1}, entt::entity{ENTT_SPARSE_PAGE}};
+        test::throwing_allocator<entt::entity>::trigger_after_allocate = true;
+
+        ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), value_type{0}), test::throwing_allocator<entt::entity>::exception_type);
+        ASSERT_TRUE(pool.contains(entt::entity{1}));
+        ASSERT_FALSE(pool.contains(entt::entity{ENTT_SPARSE_PAGE}));
+
+        pool.erase(entt::entity{1});
+        const value_type components[2u]{value_type{1}, value_type{ENTT_SPARSE_PAGE}};
+        test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
+        pool.compact();
+
+        ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), test::throwing_allocator<entt::entity>::exception_type);
+        ASSERT_TRUE(pool.contains(entt::entity{1}));
+        ASSERT_FALSE(pool.contains(entt::entity{ENTT_SPARSE_PAGE}));
+
+        ASSERT_EQ(on_construct.value, 1);
+        ASSERT_EQ(on_destroy.value, 1);
+    };
+
+    test(entt::sigh_storage_mixin<entt::basic_storage<entt::entity, int, test::throwing_allocator<int>>>{});
+    test(entt::sigh_storage_mixin<entt::basic_storage<entt::entity, stable_type, test::throwing_allocator<stable_type>>>{});
+}
+
+TEST(SighStorageMixin, ThrowingComponent) {
+    entt::sigh_storage_mixin<entt::storage<test::throwing_type>> pool;
+    entt::registry registry;
+
+    counter on_construct{};
+    counter on_destroy{};
+
+    pool.bind(entt::forward_as_any(registry));
+    pool.on_construct().connect<&listener>(on_construct);
+    pool.on_destroy().connect<&listener>(on_destroy);
+
+    test::throwing_type::trigger_on_value = 42;
+
+    // strong exception safety
+    ASSERT_THROW(pool.emplace(entt::entity{0}, test::throwing_type{42}), typename test::throwing_type::exception_type);
+    ASSERT_TRUE(pool.empty());
+
+    const entt::entity entities[2u]{entt::entity{42}, entt::entity{1}};
+    const test::throwing_type components[2u]{42, 1};
+
+    // basic exception safety
+    ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), test::throwing_type{42}), typename test::throwing_type::exception_type);
+    ASSERT_EQ(pool.size(), 0u);
+    ASSERT_FALSE(pool.contains(entt::entity{1}));
+
+    // basic exception safety
+    ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), typename test::throwing_type::exception_type);
+    ASSERT_EQ(pool.size(), 0u);
+    ASSERT_FALSE(pool.contains(entt::entity{1}));
+
+    // basic exception safety
+    ASSERT_THROW(pool.insert(std::rbegin(entities), std::rend(entities), std::rbegin(components)), typename test::throwing_type::exception_type);
+    ASSERT_EQ(pool.size(), 1u);
+    ASSERT_TRUE(pool.contains(entt::entity{1}));
+    ASSERT_EQ(pool.get(entt::entity{1}), 1);
+
+    pool.clear();
+    pool.emplace(entt::entity{1}, 1);
+    pool.emplace(entt::entity{42}, 42);
+
+    // basic exception safety
+    ASSERT_THROW(pool.erase(entt::entity{1}), typename test::throwing_type::exception_type);
+    ASSERT_EQ(pool.size(), 2u);
+    ASSERT_TRUE(pool.contains(entt::entity{42}));
+    ASSERT_TRUE(pool.contains(entt::entity{1}));
+    ASSERT_EQ(pool.at(0u), entt::entity{1});
+    ASSERT_EQ(pool.at(1u), entt::entity{42});
+    ASSERT_EQ(pool.get(entt::entity{42}), 42);
+    // the element may have been moved but it's still there
+    ASSERT_EQ(pool.get(entt::entity{1}), test::throwing_type::moved_from_value);
+
+    test::throwing_type::trigger_on_value = 99;
+    pool.erase(entt::entity{1});
+
+    ASSERT_EQ(pool.size(), 1u);
+    ASSERT_TRUE(pool.contains(entt::entity{42}));
+    ASSERT_FALSE(pool.contains(entt::entity{1}));
+    ASSERT_EQ(pool.at(0u), entt::entity{42});
+    ASSERT_EQ(pool.get(entt::entity{42}), 42);
+
+    ASSERT_EQ(on_construct.value, 2);
+    ASSERT_EQ(on_destroy.value, 3);
+}