Files
entt/test/entt/entity/storage.cpp

1927 lines
64 KiB
C++

#include <algorithm>
#include <array>
#include <cstddef>
#include <memory>
#include <tuple>
#include <type_traits>
#include <unordered_set>
#include <utility>
#include <gtest/gtest.h>
#include <entt/core/iterator.hpp>
#include <entt/core/type_info.hpp>
#include <entt/entity/component.hpp>
#include <entt/entity/entity.hpp>
#include <entt/entity/storage.hpp>
#include "../../common/aggregate.h"
#include "../../common/config.h"
#include "../../common/linter.hpp"
#include "../../common/new_delete.h"
#include "../../common/pointer_stable.h"
#include "../../common/throwing_allocator.hpp"
#include "../../common/throwing_type.hpp"
#include "../../common/tracked_memory_resource.hpp"
struct update_from_destructor {
update_from_destructor(entt::storage<update_from_destructor> &ref, entt::entity other)
: storage{&ref},
target{other} {}
update_from_destructor(const update_from_destructor &) = delete;
update_from_destructor &operator=(const update_from_destructor &) = delete;
update_from_destructor(update_from_destructor &&other) noexcept
: storage{std::exchange(other.storage, nullptr)},
target{std::exchange(other.target, entt::null)} {}
update_from_destructor &operator=(update_from_destructor &&other) noexcept {
storage = std::exchange(other.storage, nullptr);
target = std::exchange(other.target, entt::null);
return *this;
}
~update_from_destructor() {
if(target != entt::null && storage->contains(target)) {
storage->erase(target);
}
}
private:
entt::storage<update_from_destructor> *storage{};
entt::entity target{entt::null};
};
struct create_from_constructor {
create_from_constructor(entt::storage<create_from_constructor> &ref, entt::entity other)
: child{other} {
if(child != entt::null) {
ref.emplace(child, ref, entt::null);
}
}
entt::entity child;
};
template<>
struct entt::component_traits_deprecated<std::unordered_set<char>> {
static constexpr auto in_place_delete = true;
static constexpr auto page_size = 4u;
};
template<>
struct entt::component_traits_deprecated<int> {
static constexpr auto in_place_delete = false;
static constexpr auto page_size = 128u;
};
template<typename Type>
struct Storage: testing::Test {
static_assert(entt::component_traits_deprecated<Type>::page_size != 0u, "Empty type not allowed");
using type = Type;
};
template<typename Type>
using StorageDeathTest = Storage<Type>;
using StorageTypes = ::testing::Types<int, test::pointer_stable>;
TYPED_TEST_SUITE(Storage, StorageTypes, );
TYPED_TEST_SUITE(StorageDeathTest, StorageTypes, );
TYPED_TEST(Storage, Constructors) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
ASSERT_EQ(pool.policy(), entt::deletion_policy{traits_type::in_place_delete});
ASSERT_NO_THROW([[maybe_unused]] auto alloc = pool.get_allocator());
ASSERT_EQ(pool.type(), entt::type_id<value_type>());
pool = entt::storage<value_type>{std::allocator<value_type>{}};
ASSERT_EQ(pool.policy(), entt::deletion_policy{traits_type::in_place_delete});
ASSERT_NO_THROW([[maybe_unused]] auto alloc = pool.get_allocator());
ASSERT_EQ(pool.type(), entt::type_id<value_type>());
}
TYPED_TEST(Storage, Move) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
const std::array entity{entt::entity{3}, entt::entity{2}};
pool.emplace(entity[0u], 3);
static_assert(std::is_move_constructible_v<decltype(pool)>, "Move constructible type required");
static_assert(std::is_move_assignable_v<decltype(pool)>, "Move assignable type required");
entt::storage<value_type> other{std::move(pool)};
test::is_initialized(pool);
ASSERT_TRUE(pool.empty());
ASSERT_FALSE(other.empty());
ASSERT_EQ(other.type(), entt::type_id<value_type>());
ASSERT_EQ(other.index(entity[0u]), 0u);
ASSERT_EQ(other.get(entity[0u]), value_type{3});
entt::storage<value_type> extended{std::move(other), std::allocator<value_type>{}};
test::is_initialized(other);
ASSERT_TRUE(other.empty());
ASSERT_FALSE(extended.empty());
ASSERT_EQ(extended.type(), entt::type_id<value_type>());
ASSERT_EQ(extended.index(entity[0u]), 0u);
ASSERT_EQ(extended.get(entity[0u]), value_type{3});
pool = std::move(extended);
test::is_initialized(extended);
ASSERT_FALSE(pool.empty());
ASSERT_TRUE(other.empty());
ASSERT_TRUE(extended.empty());
ASSERT_EQ(pool.type(), entt::type_id<value_type>());
ASSERT_EQ(pool.index(entity[0u]), 0u);
ASSERT_EQ(pool.get(entity[0u]), value_type{3});
other = entt::storage<value_type>{};
other.emplace(entity[1u], 2);
other = std::move(pool);
test::is_initialized(pool);
ASSERT_FALSE(pool.empty());
ASSERT_FALSE(other.empty());
ASSERT_EQ(other.type(), entt::type_id<value_type>());
ASSERT_EQ(other.index(entity[0u]), 0u);
ASSERT_EQ(other.get(entity[0u]), value_type{3});
}
TYPED_TEST(Storage, Swap) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
entt::storage<value_type> other;
ASSERT_EQ(pool.type(), entt::type_id<value_type>());
ASSERT_EQ(other.type(), entt::type_id<value_type>());
pool.emplace(entt::entity{4}, 1);
other.emplace(entt::entity{2}, 2);
other.emplace(entt::entity{1}, 3);
other.erase(entt::entity{2});
ASSERT_EQ(pool.size(), 1u);
ASSERT_EQ(other.size(), 1u + traits_type::in_place_delete);
pool.swap(other);
ASSERT_EQ(pool.type(), entt::type_id<value_type>());
ASSERT_EQ(other.type(), entt::type_id<value_type>());
ASSERT_EQ(pool.size(), 1u + traits_type::in_place_delete);
ASSERT_EQ(other.size(), 1u);
ASSERT_EQ(pool.index(entt::entity{1}), traits_type::in_place_delete);
ASSERT_EQ(other.index(entt::entity{4}), 0u);
ASSERT_EQ(pool.get(entt::entity{1}), value_type{3});
ASSERT_EQ(other.get(entt::entity{4}), value_type{1});
}
TYPED_TEST(Storage, Capacity) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
pool.reserve(64);
ASSERT_EQ(pool.capacity(), traits_type::page_size);
ASSERT_TRUE(pool.empty());
pool.reserve(0);
ASSERT_EQ(pool.capacity(), traits_type::page_size);
ASSERT_TRUE(pool.empty());
}
TYPED_TEST(Storage, ShrinkToFit) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
for(std::size_t next{}; next < traits_type::page_size; ++next) {
pool.emplace(entt::entity(next));
}
pool.emplace(entt::entity{traits_type::page_size});
pool.erase(entt::entity{traits_type::page_size});
pool.compact();
ASSERT_EQ(pool.capacity(), 2 * traits_type::page_size);
ASSERT_EQ(pool.size(), traits_type::page_size);
pool.shrink_to_fit();
ASSERT_EQ(pool.capacity(), traits_type::page_size);
ASSERT_EQ(pool.size(), traits_type::page_size);
pool.clear();
ASSERT_EQ(pool.capacity(), traits_type::page_size);
ASSERT_EQ(pool.size(), 0u);
pool.shrink_to_fit();
ASSERT_EQ(pool.capacity(), 0u);
ASSERT_EQ(pool.size(), 0u);
}
TYPED_TEST(Storage, Raw) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
pool.emplace(entt::entity{1}, 1);
pool.emplace(entt::entity{3}, 3);
ASSERT_EQ(pool.raw()[0u][0u], value_type{1});
ASSERT_EQ(std::as_const(pool).raw()[0u][1u], value_type{3});
}
TYPED_TEST(Storage, Iterator) {
using value_type = typename TestFixture::type;
using iterator = typename entt::storage<value_type>::iterator;
testing::StaticAssertTypeEq<typename iterator::value_type, value_type>();
testing::StaticAssertTypeEq<typename iterator::pointer, value_type *>();
testing::StaticAssertTypeEq<typename iterator::reference, value_type &>();
entt::storage<value_type> pool;
pool.emplace(entt::entity{1}, 2);
iterator end{pool.begin()};
iterator begin{};
begin = pool.end();
std::swap(begin, end);
ASSERT_EQ(begin, pool.begin());
ASSERT_EQ(end, pool.end());
ASSERT_NE(begin, end);
ASSERT_EQ(begin.index(), 0);
ASSERT_EQ(end.index(), -1);
ASSERT_EQ(begin++, pool.begin());
ASSERT_EQ(begin--, pool.end());
ASSERT_EQ(begin + 1, pool.end());
ASSERT_EQ(end - 1, pool.begin());
ASSERT_EQ(++begin, pool.end());
ASSERT_EQ(--begin, pool.begin());
ASSERT_EQ(begin += 1, pool.end());
ASSERT_EQ(begin -= 1, pool.begin());
ASSERT_EQ(begin + (end - begin), pool.end());
ASSERT_EQ(begin - (begin - end), pool.end());
ASSERT_EQ(end - (end - begin), pool.begin());
ASSERT_EQ(end + (begin - end), pool.begin());
ASSERT_EQ(begin[0u], *pool.begin().operator->());
ASSERT_LT(begin, end);
ASSERT_LE(begin, pool.begin());
ASSERT_GT(end, begin);
ASSERT_GE(end, pool.end());
ASSERT_EQ(begin.index(), 0);
ASSERT_EQ(end.index(), -1);
pool.emplace(entt::entity{3}, 4);
begin = pool.begin();
ASSERT_EQ(begin.index(), 1);
ASSERT_EQ(end.index(), -1);
ASSERT_EQ(begin[0u], value_type{4});
ASSERT_EQ(begin[1u], value_type{2});
}
TYPED_TEST(Storage, ConstIterator) {
using value_type = typename TestFixture::type;
using iterator = typename entt::storage<value_type>::const_iterator;
testing::StaticAssertTypeEq<typename iterator::value_type, value_type>();
testing::StaticAssertTypeEq<typename iterator::pointer, const value_type *>();
testing::StaticAssertTypeEq<typename iterator::reference, const value_type &>();
entt::storage<value_type> pool;
pool.emplace(entt::entity{1}, 2);
iterator cend{pool.cbegin()};
iterator cbegin{};
cbegin = pool.cend();
std::swap(cbegin, cend);
ASSERT_EQ(cbegin, std::as_const(pool).begin());
ASSERT_EQ(cend, std::as_const(pool).end());
ASSERT_EQ(cbegin, pool.cbegin());
ASSERT_EQ(cend, pool.cend());
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin.index(), 0);
ASSERT_EQ(cend.index(), -1);
ASSERT_EQ(cbegin++, pool.cbegin());
ASSERT_EQ(cbegin--, pool.cend());
ASSERT_EQ(cbegin + 1, pool.cend());
ASSERT_EQ(cend - 1, pool.cbegin());
ASSERT_EQ(++cbegin, pool.cend());
ASSERT_EQ(--cbegin, pool.cbegin());
ASSERT_EQ(cbegin += 1, pool.cend());
ASSERT_EQ(cbegin -= 1, pool.cbegin());
ASSERT_EQ(cbegin + (cend - cbegin), pool.cend());
ASSERT_EQ(cbegin - (cbegin - cend), pool.cend());
ASSERT_EQ(cend - (cend - cbegin), pool.cbegin());
ASSERT_EQ(cend + (cbegin - cend), pool.cbegin());
ASSERT_EQ(cbegin[0u], *pool.cbegin().operator->());
ASSERT_LT(cbegin, cend);
ASSERT_LE(cbegin, pool.cbegin());
ASSERT_GT(cend, cbegin);
ASSERT_GE(cend, pool.cend());
ASSERT_EQ(cbegin.index(), 0);
ASSERT_EQ(cend.index(), -1);
pool.emplace(entt::entity{3}, 4);
cbegin = pool.cbegin();
ASSERT_EQ(cbegin.index(), 1);
ASSERT_EQ(cend.index(), -1);
ASSERT_EQ(cbegin[0u], value_type{4});
ASSERT_EQ(cbegin[1u], value_type{2});
}
TYPED_TEST(Storage, ReverseIterator) {
using value_type = typename TestFixture::type;
using reverse_iterator = typename entt::storage<value_type>::reverse_iterator;
testing::StaticAssertTypeEq<typename reverse_iterator::value_type, value_type>();
testing::StaticAssertTypeEq<typename reverse_iterator::pointer, value_type *>();
testing::StaticAssertTypeEq<typename reverse_iterator::reference, value_type &>();
entt::storage<value_type> pool;
pool.emplace(entt::entity{1}, 2);
reverse_iterator end{pool.rbegin()};
reverse_iterator begin{};
begin = pool.rend();
std::swap(begin, end);
ASSERT_EQ(begin, pool.rbegin());
ASSERT_EQ(end, pool.rend());
ASSERT_NE(begin, end);
ASSERT_EQ(begin.base().index(), -1);
ASSERT_EQ(end.base().index(), 0);
ASSERT_EQ(begin++, pool.rbegin());
ASSERT_EQ(begin--, pool.rend());
ASSERT_EQ(begin + 1, pool.rend());
ASSERT_EQ(end - 1, pool.rbegin());
ASSERT_EQ(++begin, pool.rend());
ASSERT_EQ(--begin, pool.rbegin());
ASSERT_EQ(begin += 1, pool.rend());
ASSERT_EQ(begin -= 1, pool.rbegin());
ASSERT_EQ(begin + (end - begin), pool.rend());
ASSERT_EQ(begin - (begin - end), pool.rend());
ASSERT_EQ(end - (end - begin), pool.rbegin());
ASSERT_EQ(end + (begin - end), pool.rbegin());
ASSERT_EQ(begin[0u], *pool.rbegin().operator->());
ASSERT_LT(begin, end);
ASSERT_LE(begin, pool.rbegin());
ASSERT_GT(end, begin);
ASSERT_GE(end, pool.rend());
ASSERT_EQ(begin.base().index(), -1);
ASSERT_EQ(end.base().index(), 0);
pool.emplace(entt::entity{3}, 4);
begin = pool.rbegin();
end = pool.rend();
ASSERT_EQ(begin.base().index(), -1);
ASSERT_EQ(end.base().index(), 1);
ASSERT_EQ(begin[0u], value_type{2});
ASSERT_EQ(begin[1u], value_type{4});
}
TYPED_TEST(Storage, ConstReverseIterator) {
using value_type = typename TestFixture::type;
using const_reverse_iterator = typename entt::storage<value_type>::const_reverse_iterator;
testing::StaticAssertTypeEq<typename const_reverse_iterator::value_type, value_type>();
testing::StaticAssertTypeEq<typename const_reverse_iterator::pointer, const value_type *>();
testing::StaticAssertTypeEq<typename const_reverse_iterator::reference, const value_type &>();
entt::storage<value_type> pool;
pool.emplace(entt::entity{1}, 2);
const_reverse_iterator cend{pool.crbegin()};
const_reverse_iterator cbegin{};
cbegin = pool.crend();
std::swap(cbegin, cend);
ASSERT_EQ(cbegin, std::as_const(pool).rbegin());
ASSERT_EQ(cend, std::as_const(pool).rend());
ASSERT_EQ(cbegin, pool.crbegin());
ASSERT_EQ(cend, pool.crend());
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin.base().index(), -1);
ASSERT_EQ(cend.base().index(), 0);
ASSERT_EQ(cbegin++, pool.crbegin());
ASSERT_EQ(cbegin--, pool.crend());
ASSERT_EQ(cbegin + 1, pool.crend());
ASSERT_EQ(cend - 1, pool.crbegin());
ASSERT_EQ(++cbegin, pool.crend());
ASSERT_EQ(--cbegin, pool.crbegin());
ASSERT_EQ(cbegin += 1, pool.crend());
ASSERT_EQ(cbegin -= 1, pool.crbegin());
ASSERT_EQ(cbegin + (cend - cbegin), pool.crend());
ASSERT_EQ(cbegin - (cbegin - cend), pool.crend());
ASSERT_EQ(cend - (cend - cbegin), pool.crbegin());
ASSERT_EQ(cend + (cbegin - cend), pool.crbegin());
ASSERT_EQ(cbegin[0u], *pool.crbegin().operator->());
ASSERT_LT(cbegin, cend);
ASSERT_LE(cbegin, pool.crbegin());
ASSERT_GT(cend, cbegin);
ASSERT_GE(cend, pool.crend());
ASSERT_EQ(cbegin.base().index(), -1);
ASSERT_EQ(cend.base().index(), 0);
pool.emplace(entt::entity{3}, 4);
cbegin = pool.crbegin();
cend = pool.crend();
ASSERT_EQ(cbegin.base().index(), -1);
ASSERT_EQ(cend.base().index(), 1);
ASSERT_EQ(cbegin[0u], value_type{2});
ASSERT_EQ(cbegin[1u], value_type{4});
}
TYPED_TEST(Storage, IteratorConversion) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
pool.emplace(entt::entity{1}, 2);
const typename entt::storage<value_type>::iterator it = pool.begin();
typename entt::storage<value_type>::const_iterator cit = it;
testing::StaticAssertTypeEq<decltype(*it), value_type &>();
testing::StaticAssertTypeEq<decltype(*cit), const value_type &>();
ASSERT_EQ(*it.operator->(), value_type{2});
ASSERT_EQ(*it.operator->(), *cit);
ASSERT_EQ(it - cit, 0);
ASSERT_EQ(cit - it, 0);
ASSERT_LE(it, cit);
ASSERT_LE(cit, it);
ASSERT_GE(it, cit);
ASSERT_GE(cit, it);
ASSERT_EQ(it, cit);
ASSERT_NE(++cit, it);
}
TYPED_TEST(Storage, IteratorPageSizeAwareness) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
static_assert(!std::is_same_v<value_type, int> || (traits_type::page_size != entt::component_traits_deprecated<value_type *>::page_size), "Different page size required");
for(unsigned int next{}; next < traits_type::page_size; ++next) {
pool.emplace(entt::entity{next});
}
pool.emplace(entt::entity{traits_type::page_size});
// test the proper use of component traits by the storage iterator
ASSERT_EQ(&pool.begin()[0], pool.raw()[1u]);
ASSERT_EQ(&pool.begin()[traits_type::page_size], pool.raw()[0u]);
}
TYPED_TEST(Storage, Getters) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
const entt::entity entity{1};
pool.emplace(entity, 3);
testing::StaticAssertTypeEq<decltype(pool.get({})), value_type &>();
testing::StaticAssertTypeEq<decltype(std::as_const(pool).get({})), const value_type &>();
testing::StaticAssertTypeEq<decltype(pool.get_as_tuple({})), std::tuple<value_type &>>();
testing::StaticAssertTypeEq<decltype(std::as_const(pool).get_as_tuple({})), std::tuple<const value_type &>>();
ASSERT_EQ(pool.get(entity), value_type{3});
ASSERT_EQ(std::as_const(pool).get(entity), value_type{3});
ASSERT_EQ(pool.get_as_tuple(entity), std::make_tuple(value_type{3}));
ASSERT_EQ(std::as_const(pool).get_as_tuple(entity), std::make_tuple(value_type{3}));
}
ENTT_DEBUG_TYPED_TEST(StorageDeathTest, Getters) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
const entt::entity entity{4};
ASSERT_DEATH([[maybe_unused]] const auto &value = pool.get(entity), "");
ASSERT_DEATH([[maybe_unused]] const auto &value = std::as_const(pool).get(entity), "");
ASSERT_DEATH([[maybe_unused]] const auto value = pool.get_as_tuple(entity), "");
ASSERT_DEATH([[maybe_unused]] const auto value = std::as_const(pool).get_as_tuple(entity), "");
}
TYPED_TEST(Storage, Value) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
const entt::entity entity{2};
pool.emplace(entity);
ASSERT_EQ(pool.value(entity), &pool.get(entity));
}
ENTT_DEBUG_TYPED_TEST(StorageDeathTest, Value) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
ASSERT_DEATH([[maybe_unused]] const void *value = pool.value(entt::entity{2}), "");
}
TYPED_TEST(Storage, Emplace) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
testing::StaticAssertTypeEq<decltype(pool.emplace({})), value_type &>();
ASSERT_EQ(pool.emplace(entt::entity{3}), value_type{});
ASSERT_EQ(pool.emplace(entt::entity{1}, 2), value_type{2});
}
TEST(Storage, EmplaceAggregate) {
entt::storage<test::aggregate> pool;
testing::StaticAssertTypeEq<decltype(pool.emplace({})), test::aggregate &>();
// aggregate types with no args enter the non-aggregate path
ASSERT_EQ(pool.emplace(entt::entity{3}), test::aggregate{});
// aggregate types with args work despite the lack of support in the standard library
ASSERT_EQ(pool.emplace(entt::entity{1}, 2), test::aggregate{2});
}
TEST(Storage, EmplaceSelfMoveSupport) {
// see #37 - this test shouldn't crash, that's all
entt::storage<std::unordered_set<int>> pool;
const entt::entity entity{1};
ASSERT_EQ(pool.policy(), entt::deletion_policy::swap_and_pop);
pool.emplace(entity).insert(2);
pool.erase(entity);
ASSERT_FALSE(pool.contains(entity));
}
TEST(Storage, EmplaceSelfMoveSupportInPlaceDelete) {
// see #37 - this test shouldn't crash, that's all
entt::storage<std::unordered_set<char>> pool;
const entt::entity entity{1};
ASSERT_EQ(pool.policy(), entt::deletion_policy::in_place);
pool.emplace(entity).insert(2);
pool.erase(entity);
ASSERT_FALSE(pool.contains(entity));
}
TYPED_TEST(Storage, TryEmplace) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
entt::sparse_set &base = pool;
const std::array entity{entt::entity{1}, entt::entity{3}};
value_type instance{4};
ASSERT_NE(base.push(entity[0u], &instance), base.end());
ASSERT_EQ(pool.size(), 1u);
ASSERT_EQ(base.index(entity[0u]), 0u);
ASSERT_EQ(base.value(entity[0u]), &pool.get(entity[0u]));
ASSERT_EQ(pool.get(entity[0u]), value_type{4});
base.erase(entity[0u]);
ASSERT_NE(base.push(entity.begin(), entity.end()), base.end());
if constexpr(traits_type::in_place_delete) {
ASSERT_EQ(pool.size(), 3u);
ASSERT_EQ(base.index(entity[0u]), 1u);
ASSERT_EQ(base.index(entity[1u]), 2u);
} else {
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(base.index(entity[0u]), 0u);
ASSERT_EQ(base.index(entity[1u]), 1u);
}
ASSERT_EQ(pool.get(entity[0u]), value_type{});
ASSERT_EQ(pool.get(entity[1u]), value_type{});
base.erase(entity.begin(), entity.end());
ASSERT_NE(base.push(entity.rbegin(), entity.rend()), base.end());
if constexpr(traits_type::in_place_delete) {
ASSERT_EQ(pool.size(), 5u);
ASSERT_EQ(base.index(entity[0u]), 4u);
ASSERT_EQ(base.index(entity[1u]), 3u);
} else {
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(base.index(entity[0u]), 1u);
ASSERT_EQ(base.index(entity[1u]), 0u);
}
ASSERT_EQ(pool.get(entity[0u]), value_type{});
ASSERT_EQ(pool.get(entity[1u]), value_type{});
}
TEST(Storage, TryEmplaceNonDefaultConstructible) {
using value_type = std::pair<int &, int &>;
static_assert(!std::is_default_constructible_v<value_type>, "Default constructible types not allowed");
entt::storage<value_type> pool;
entt::sparse_set &base = pool;
const std::array entity{entt::entity{1}, entt::entity{3}};
ASSERT_EQ(pool.type(), entt::type_id<value_type>());
ASSERT_EQ(pool.type(), base.type());
ASSERT_FALSE(pool.contains(entity[0u]));
ASSERT_FALSE(pool.contains(entity[1u]));
ASSERT_EQ(base.push(entity[0u]), base.end());
ASSERT_FALSE(pool.contains(entity[0u]));
ASSERT_FALSE(pool.contains(entity[1u]));
ASSERT_EQ(base.find(entity[0u]), base.end());
ASSERT_TRUE(pool.empty());
int value = 4;
value_type instance{value, value};
ASSERT_NE(base.push(entity[0u], &instance), base.end());
ASSERT_TRUE(pool.contains(entity[0u]));
ASSERT_FALSE(pool.contains(entity[1u]));
base.erase(entity[0u]);
ASSERT_TRUE(pool.empty());
ASSERT_FALSE(pool.contains(entity[0u]));
ASSERT_EQ(base.push(entity.begin(), entity.end()), base.end());
ASSERT_FALSE(pool.contains(entity[0u]));
ASSERT_FALSE(pool.contains(entity[1u]));
ASSERT_EQ(base.find(entity[0u]), base.end());
ASSERT_EQ(base.find(entity[1u]), base.end());
ASSERT_TRUE(pool.empty());
}
TEST(Storage, TryEmplaceNonCopyConstructible) {
using value_type = std::unique_ptr<int>;
static_assert(!std::is_copy_constructible_v<value_type>, "Copy constructible types not allowed");
entt::storage<value_type> pool;
entt::sparse_set &base = pool;
const std::array entity{entt::entity{1}, entt::entity{3}};
ASSERT_EQ(pool.type(), entt::type_id<value_type>());
ASSERT_EQ(pool.type(), base.type());
ASSERT_FALSE(pool.contains(entity[0u]));
ASSERT_FALSE(pool.contains(entity[1u]));
ASSERT_NE(base.push(entity[0u]), base.end());
ASSERT_TRUE(pool.contains(entity[0u]));
ASSERT_FALSE(pool.contains(entity[1u]));
ASSERT_NE(base.find(entity[0u]), base.end());
ASSERT_FALSE(pool.empty());
value_type instance = std::make_unique<int>(4);
ASSERT_EQ(base.push(entity[1u], &instance), base.end());
ASSERT_TRUE(pool.contains(entity[0u]));
ASSERT_FALSE(pool.contains(entity[1u]));
base.erase(entity[0u]);
ASSERT_TRUE(pool.empty());
ASSERT_FALSE(pool.contains(entity[0u]));
ASSERT_NE(base.push(entity.begin(), entity.end()), base.end());
ASSERT_TRUE(pool.contains(entity[0u]));
ASSERT_TRUE(pool.contains(entity[1u]));
ASSERT_NE(base.find(entity[0u]), base.end());
ASSERT_NE(base.find(entity[1u]), base.end());
ASSERT_FALSE(pool.empty());
}
TYPED_TEST(Storage, Patch) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
const entt::entity entity{2};
auto callback = [](auto &&elem) {
if constexpr(std::is_class_v<std::remove_reference_t<decltype(elem)>>) {
++elem.value;
} else {
++elem;
}
};
pool.emplace(entity, 0);
ASSERT_EQ(pool.get(entity), value_type{0});
pool.patch(entity);
pool.patch(entity, callback);
pool.patch(entity, callback, callback);
ASSERT_EQ(pool.get(entity), value_type{3});
}
ENTT_DEBUG_TYPED_TEST(StorageDeathTest, Patch) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
ASSERT_DEATH(pool.patch(entt::null), "");
}
TYPED_TEST(Storage, Insert) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
const std::array entity{entt::entity{1}, entt::entity{3}};
typename entt::storage<value_type>::iterator it{};
it = pool.insert(entity.begin(), entity.end(), value_type{4});
ASSERT_EQ(it, pool.cbegin());
ASSERT_TRUE(pool.contains(entity[0u]));
ASSERT_TRUE(pool.contains(entity[1u]));
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.get(entity[0u]), value_type{4});
ASSERT_EQ(pool.get(entity[1u]), value_type{4});
ASSERT_EQ(*it++.operator->(), value_type{4});
ASSERT_EQ(*it.operator->(), value_type{4});
const std::array value{value_type{3}, value_type{1}};
pool.erase(entity.begin(), entity.end());
it = pool.insert(entity.rbegin(), entity.rend(), value.begin());
ASSERT_EQ(it, pool.cbegin());
if constexpr(traits_type::in_place_delete) {
ASSERT_EQ(pool.size(), 4u);
ASSERT_EQ(pool.index(entity[0u]), 3u);
ASSERT_EQ(pool.index(entity[1u]), 2u);
} else {
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.index(entity[0u]), 1u);
ASSERT_EQ(pool.index(entity[1u]), 0u);
}
ASSERT_EQ(pool.get(entity[0u]), value_type{1});
ASSERT_EQ(pool.get(entity[1u]), value_type{3});
ASSERT_EQ(*it++.operator->(), value_type{1});
ASSERT_EQ(*it.operator->(), value_type{3});
}
TYPED_TEST(Storage, Erase) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
const std::array entity{entt::entity{1}, entt::entity{3}, entt::entity{2}};
const std::array value{value_type{1}, value_type{2}, value_type{4}};
pool.insert(entity.begin(), entity.end(), value.begin());
pool.erase(entity.begin(), entity.end());
if constexpr(traits_type::in_place_delete) {
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.data()[2u] == entt::tombstone);
} else {
ASSERT_EQ(pool.size(), 0u);
}
pool.insert(entity.begin(), entity.end(), value.begin());
pool.erase(entity.begin(), entity.begin() + 2u);
ASSERT_EQ(*pool.begin(), value[2u]);
if constexpr(traits_type::in_place_delete) {
ASSERT_EQ(pool.size(), 6u);
ASSERT_EQ(pool.index(entity[2u]), 5u);
} else {
ASSERT_EQ(pool.size(), 1u);
}
pool.erase(entity[2u]);
if constexpr(traits_type::in_place_delete) {
ASSERT_EQ(pool.size(), 6u);
ASSERT_TRUE(pool.data()[5u] == entt::tombstone);
} else {
ASSERT_EQ(pool.size(), 0u);
}
}
TYPED_TEST(Storage, CrossErase) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
entt::sparse_set set;
const std::array entity{entt::entity{1}, entt::entity{3}};
pool.emplace(entity[0u], 1);
pool.emplace(entity[1u], 3);
set.push(entity[1u]);
pool.erase(set.begin(), set.end());
ASSERT_TRUE(pool.contains(entity[0u]));
ASSERT_FALSE(pool.contains(entity[1u]));
ASSERT_EQ(pool.raw()[0u][0u], value_type{1});
}
TYPED_TEST(Storage, Remove) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
const std::array entity{entt::entity{1}, entt::entity{3}, entt::entity{2}};
const std::array value{value_type{1}, value_type{2}, value_type{4}};
pool.insert(entity.begin(), entity.end(), value.begin());
ASSERT_EQ(pool.remove(entity.begin(), entity.end()), 3u);
ASSERT_EQ(pool.remove(entity.begin(), entity.end()), 0u);
if constexpr(traits_type::in_place_delete) {
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.data()[2u] == entt::tombstone);
} else {
ASSERT_EQ(pool.size(), 0u);
}
pool.insert(entity.begin(), entity.end(), value.begin());
ASSERT_EQ(pool.remove(entity.begin(), entity.begin() + 2u), 2u);
ASSERT_EQ(*pool.begin(), value[2u]);
if constexpr(traits_type::in_place_delete) {
ASSERT_EQ(pool.size(), 6u);
ASSERT_EQ(pool.index(entity[2u]), 5u);
} else {
ASSERT_EQ(pool.size(), 1u);
}
ASSERT_TRUE(pool.remove(entity[2u]));
ASSERT_FALSE(pool.remove(entity[2u]));
if constexpr(traits_type::in_place_delete) {
ASSERT_EQ(pool.size(), 6u);
ASSERT_TRUE(pool.data()[5u] == entt::tombstone);
} else {
ASSERT_EQ(pool.size(), 0u);
}
}
TYPED_TEST(Storage, CrossRemove) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
entt::sparse_set set;
const std::array entity{entt::entity{1}, entt::entity{3}};
pool.emplace(entity[0u], 1);
pool.emplace(entity[1u], 3);
set.push(entity[1u]);
pool.remove(set.begin(), set.end());
ASSERT_TRUE(pool.contains(entity[0u]));
ASSERT_FALSE(pool.contains(entity[1u]));
ASSERT_EQ(pool.raw()[0u][0u], value_type{1});
}
TYPED_TEST(Storage, Clear) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
const std::array entity{entt::entity{1}, entt::entity{3}, entt::entity{2}};
pool.insert(entity.begin(), entity.end());
ASSERT_EQ(pool.size(), 3u);
pool.clear();
ASSERT_EQ(pool.size(), 0u);
pool.insert(entity.begin(), entity.end());
pool.erase(entity[2u]);
ASSERT_EQ(pool.size(), 2u + traits_type::in_place_delete);
pool.clear();
ASSERT_EQ(pool.size(), 0u);
}
TYPED_TEST(Storage, Compact) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
ASSERT_TRUE(pool.empty());
pool.compact();
ASSERT_TRUE(pool.empty());
pool.emplace(entt::entity{0}, value_type{0});
pool.compact();
ASSERT_EQ(pool.size(), 1u);
pool.emplace(entt::entity{4}, value_type{4});
pool.erase(entt::entity{0});
ASSERT_EQ(pool.size(), 1u + traits_type::in_place_delete);
ASSERT_EQ(pool.index(entt::entity{4}), traits_type::in_place_delete);
ASSERT_EQ(pool.get(entt::entity{4}), value_type{4});
pool.compact();
ASSERT_EQ(pool.size(), 1u);
ASSERT_EQ(pool.index(entt::entity{4}), 0u);
ASSERT_EQ(pool.get(entt::entity{4}), value_type{4});
pool.emplace(entt::entity{0}, value_type{0});
pool.compact();
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.index(entt::entity{4}), 0u);
ASSERT_EQ(pool.index(entt::entity{0}), 1u);
ASSERT_EQ(pool.get(entt::entity{4}), value_type{4});
ASSERT_EQ(pool.get(entt::entity{0}), value_type{0});
pool.erase(entt::entity{0});
pool.erase(entt::entity{4});
pool.compact();
ASSERT_TRUE(pool.empty());
}
TYPED_TEST(Storage, SwapElements) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
pool.emplace(entt::entity{1}, 1);
pool.emplace(entt::entity{2}, 3);
pool.emplace(entt::entity{4}, 8);
pool.erase(entt::entity{2});
ASSERT_EQ(pool.get(entt::entity{1}), value_type{1});
ASSERT_EQ(pool.get(entt::entity{4}), value_type{8});
ASSERT_EQ(pool.index(entt::entity{1}), 0u);
ASSERT_EQ(pool.index(entt::entity{4}), 1u + traits_type::in_place_delete);
pool.swap_elements(entt::entity{1}, entt::entity{4});
ASSERT_EQ(pool.get(entt::entity{1}), value_type{1});
ASSERT_EQ(pool.get(entt::entity{4}), value_type{8});
ASSERT_EQ(pool.index(entt::entity{1}), 1u + traits_type::in_place_delete);
ASSERT_EQ(pool.index(entt::entity{4}), 0u);
}
TYPED_TEST(Storage, Iterable) {
using value_type = typename TestFixture::type;
using iterator = typename entt::storage<value_type>::iterable::iterator;
testing::StaticAssertTypeEq<typename iterator::value_type, std::tuple<entt::entity, value_type &>>();
testing::StaticAssertTypeEq<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity, value_type &>>>();
testing::StaticAssertTypeEq<typename iterator::reference, typename iterator::value_type>();
entt::storage<value_type> pool;
const entt::sparse_set &base = pool;
pool.emplace(entt::entity{1}, 2);
pool.emplace(entt::entity{3}, 4);
auto iterable = pool.each();
iterator end{iterable.begin()};
iterator begin{};
begin = iterable.end();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.begin());
ASSERT_EQ(end, iterable.end());
ASSERT_NE(begin, end);
ASSERT_EQ(begin.base(), base.begin());
ASSERT_EQ(end.base(), base.end());
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{3});
ASSERT_EQ(std::get<1>(*begin.operator->().operator->()), value_type{4});
ASSERT_EQ(std::get<0>(*begin), entt::entity{3});
ASSERT_EQ(std::get<1>(*begin), value_type{4});
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin.base(), ++base.begin());
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(begin.base(), base.end());
for(auto [entity, element]: iterable) {
testing::StaticAssertTypeEq<decltype(entity), entt::entity>();
testing::StaticAssertTypeEq<decltype(element), value_type &>();
ASSERT_TRUE(entity != entt::entity{1} || element == value_type{2});
ASSERT_TRUE(entity != entt::entity{3} || element == value_type{4});
}
}
TYPED_TEST(Storage, ConstIterable) {
using value_type = typename TestFixture::type;
using iterator = typename entt::storage<value_type>::const_iterable::iterator;
testing::StaticAssertTypeEq<typename iterator::value_type, std::tuple<entt::entity, const value_type &>>();
testing::StaticAssertTypeEq<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity, const value_type &>>>();
testing::StaticAssertTypeEq<typename iterator::reference, typename iterator::value_type>();
entt::storage<value_type> pool;
const entt::sparse_set &base = pool;
pool.emplace(entt::entity{1}, 2);
pool.emplace(entt::entity{3}, 4);
auto iterable = std::as_const(pool).each();
iterator end{iterable.cbegin()};
iterator begin{};
begin = iterable.cend();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.cbegin());
ASSERT_EQ(end, iterable.cend());
ASSERT_NE(begin, end);
ASSERT_EQ(begin.base(), base.begin());
ASSERT_EQ(end.base(), base.end());
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{3});
ASSERT_EQ(std::get<1>(*begin.operator->().operator->()), value_type{4});
ASSERT_EQ(std::get<0>(*begin), entt::entity{3});
ASSERT_EQ(std::get<1>(*begin), value_type{4});
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin.base(), ++base.begin());
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(begin.base(), base.end());
for(auto [entity, element]: iterable) {
testing::StaticAssertTypeEq<decltype(entity), entt::entity>();
testing::StaticAssertTypeEq<decltype(element), const value_type &>();
ASSERT_TRUE(entity != entt::entity{1} || element == value_type{2});
ASSERT_TRUE(entity != entt::entity{3} || element == value_type{4});
}
}
TYPED_TEST(Storage, IterableIteratorConversion) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
pool.emplace(entt::entity{3}, 1);
const typename entt::storage<value_type>::iterable::iterator it = pool.each().begin();
typename entt::storage<value_type>::const_iterable::iterator cit = it;
testing::StaticAssertTypeEq<decltype(*it), std::tuple<entt::entity, value_type &>>();
testing::StaticAssertTypeEq<decltype(*cit), std::tuple<entt::entity, const value_type &>>();
ASSERT_EQ(it, cit);
ASSERT_NE(++cit, it);
}
TYPED_TEST(Storage, IterableAlgorithmCompatibility) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
pool.emplace(entt::entity{3}, 1);
const auto iterable = pool.each();
const auto it = std::find_if(iterable.begin(), iterable.end(), [](auto args) { return std::get<0>(args) == entt::entity{3}; });
ASSERT_EQ(std::get<0>(*it), entt::entity{3});
}
TYPED_TEST(Storage, ReverseIterable) {
using value_type = typename TestFixture::type;
using iterator = typename entt::storage<value_type>::reverse_iterable::iterator;
testing::StaticAssertTypeEq<typename iterator::value_type, std::tuple<entt::entity, value_type &>>();
testing::StaticAssertTypeEq<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity, value_type &>>>();
testing::StaticAssertTypeEq<typename iterator::reference, typename iterator::value_type>();
entt::storage<value_type> pool;
const entt::sparse_set &base = pool;
pool.emplace(entt::entity{1}, 2);
pool.emplace(entt::entity{3}, 4);
auto iterable = pool.reach();
iterator end{iterable.begin()};
iterator begin{};
begin = iterable.end();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.begin());
ASSERT_EQ(end, iterable.end());
ASSERT_NE(begin, end);
ASSERT_EQ(begin.base(), base.rbegin());
ASSERT_EQ(end.base(), base.rend());
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{1});
ASSERT_EQ(std::get<1>(*begin.operator->().operator->()), value_type{2});
ASSERT_EQ(std::get<0>(*begin), entt::entity{1});
ASSERT_EQ(std::get<1>(*begin), value_type{2});
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin.base(), ++base.rbegin());
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(begin.base(), base.rend());
for(auto [entity, element]: iterable) {
testing::StaticAssertTypeEq<decltype(entity), entt::entity>();
testing::StaticAssertTypeEq<decltype(element), value_type &>();
ASSERT_TRUE(entity != entt::entity{1} || element == value_type{2});
ASSERT_TRUE(entity != entt::entity{3} || element == value_type{4});
}
}
TYPED_TEST(Storage, ConstReverseIterable) {
using value_type = typename TestFixture::type;
using iterator = typename entt::storage<value_type>::const_reverse_iterable::iterator;
testing::StaticAssertTypeEq<typename iterator::value_type, std::tuple<entt::entity, const value_type &>>();
testing::StaticAssertTypeEq<typename iterator::pointer, entt::input_iterator_pointer<std::tuple<entt::entity, const value_type &>>>();
testing::StaticAssertTypeEq<typename iterator::reference, typename iterator::value_type>();
entt::storage<value_type> pool;
const entt::sparse_set &base = pool;
pool.emplace(entt::entity{1}, 2);
pool.emplace(entt::entity{3}, 4);
auto iterable = std::as_const(pool).reach();
iterator end{iterable.cbegin()};
iterator begin{};
begin = iterable.cend();
std::swap(begin, end);
ASSERT_EQ(begin, iterable.cbegin());
ASSERT_EQ(end, iterable.cend());
ASSERT_NE(begin, end);
ASSERT_EQ(begin.base(), base.rbegin());
ASSERT_EQ(end.base(), base.rend());
ASSERT_EQ(std::get<0>(*begin.operator->().operator->()), entt::entity{1});
ASSERT_EQ(std::get<1>(*begin.operator->().operator->()), value_type{2});
ASSERT_EQ(std::get<0>(*begin), entt::entity{1});
ASSERT_EQ(std::get<1>(*begin), value_type{2});
ASSERT_EQ(begin++, iterable.begin());
ASSERT_EQ(begin.base(), ++base.rbegin());
ASSERT_EQ(++begin, iterable.end());
ASSERT_EQ(begin.base(), base.rend());
for(auto [entity, element]: iterable) {
testing::StaticAssertTypeEq<decltype(entity), entt::entity>();
testing::StaticAssertTypeEq<decltype(element), const value_type &>();
ASSERT_TRUE(entity != entt::entity{1} || element == value_type{2});
ASSERT_TRUE(entity != entt::entity{3} || element == value_type{4});
}
}
TYPED_TEST(Storage, ReverseIterableIteratorConversion) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
pool.emplace(entt::entity{3}, 1);
const typename entt::storage<value_type>::reverse_iterable::iterator it = pool.reach().begin();
typename entt::storage<value_type>::const_reverse_iterable::iterator cit = it;
testing::StaticAssertTypeEq<decltype(*it), std::tuple<entt::entity, value_type &>>();
testing::StaticAssertTypeEq<decltype(*cit), std::tuple<entt::entity, const value_type &>>();
ASSERT_EQ(it, cit);
ASSERT_NE(++cit, it);
}
TYPED_TEST(Storage, ReverseIterableAlgorithmCompatibility) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
pool.emplace(entt::entity{3}, 1);
const auto iterable = pool.reach();
const auto it = std::find_if(iterable.begin(), iterable.end(), [](auto args) { return std::get<0>(args) == entt::entity{3}; });
ASSERT_EQ(std::get<0>(*it), entt::entity{3});
}
TYPED_TEST(Storage, SortOrdered) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
const std::array entity{entt::entity{8}, entt::entity{16}, entt::entity{2}, entt::entity{1}, entt::entity{4}};
const std::array value{value_type{8}, value_type{4}, value_type{2}, value_type{1}, value_type{0}};
pool.insert(entity.begin(), entity.end(), value.begin());
pool.sort([&pool](auto lhs, auto rhs) { return pool.get(lhs) < pool.get(rhs); });
ASSERT_TRUE(std::equal(entity.rbegin(), entity.rend(), pool.entt::sparse_set::begin(), pool.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(value.rbegin(), value.rend(), pool.begin(), pool.end()));
}
TYPED_TEST(Storage, SortReverse) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
const std::array entity{entt::entity{8}, entt::entity{16}, entt::entity{2}, entt::entity{1}, entt::entity{4}};
const std::array value{value_type{0}, value_type{1}, value_type{2}, value_type{4}, value_type{8}};
pool.insert(entity.begin(), entity.end(), value.begin());
pool.sort([&pool](auto lhs, auto rhs) { return pool.get(lhs) < pool.get(rhs); });
ASSERT_TRUE(std::equal(entity.begin(), entity.end(), pool.entt::sparse_set::begin(), pool.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(value.begin(), value.end(), pool.begin(), pool.end()));
}
TYPED_TEST(Storage, SortUnordered) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
const std::array entity{entt::entity{8}, entt::entity{16}, entt::entity{2}, entt::entity{1}, entt::entity{4}};
const std::array value{value_type{2}, value_type{1}, value_type{0}, value_type{4}, value_type{8}};
pool.insert(entity.begin(), entity.end(), value.begin());
pool.sort([&pool](auto lhs, auto rhs) { return pool.get(lhs) < pool.get(rhs); });
auto begin = pool.begin();
auto end = pool.end();
ASSERT_EQ(*(begin++), value[2u]);
ASSERT_EQ(*(begin++), value[1u]);
ASSERT_EQ(*(begin++), value[0u]);
ASSERT_EQ(*(begin++), value[3u]);
ASSERT_EQ(*(begin++), value[4u]);
ASSERT_EQ(begin, end);
ASSERT_EQ(pool.data()[0u], entity[4u]);
ASSERT_EQ(pool.data()[1u], entity[3u]);
ASSERT_EQ(pool.data()[2u], entity[0u]);
ASSERT_EQ(pool.data()[3u], entity[1u]);
ASSERT_EQ(pool.data()[4u], entity[2u]);
}
TYPED_TEST(Storage, SortN) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
const std::array entity{entt::entity{8}, entt::entity{16}, entt::entity{2}, entt::entity{1}, entt::entity{4}};
const std::array value{value_type{1}, value_type{2}, value_type{0}, value_type{4}, value_type{8}};
pool.insert(entity.begin(), entity.end(), value.begin());
pool.sort_n(0u, [&pool](auto lhs, auto rhs) { return pool.get(lhs) < pool.get(rhs); });
ASSERT_TRUE(std::equal(entity.rbegin(), entity.rend(), pool.entt::sparse_set::begin(), pool.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(value.rbegin(), value.rend(), pool.begin(), pool.end()));
pool.sort_n(2u, [&pool](auto lhs, auto rhs) { return pool.get(lhs) < pool.get(rhs); });
ASSERT_EQ(pool.raw()[0u][0u], value[1u]);
ASSERT_EQ(pool.raw()[0u][1u], value[0u]);
ASSERT_EQ(pool.raw()[0u][2u], value[2u]);
ASSERT_EQ(pool.data()[0u], entity[1u]);
ASSERT_EQ(pool.data()[1u], entity[0u]);
ASSERT_EQ(pool.data()[2u], entity[2u]);
const auto length = 5u;
pool.sort_n(length, [&pool](auto lhs, auto rhs) { return pool.get(lhs) < pool.get(rhs); });
auto begin = pool.begin();
auto end = pool.end();
ASSERT_EQ(*(begin++), value[2u]);
ASSERT_EQ(*(begin++), value[0u]);
ASSERT_EQ(*(begin++), value[1u]);
ASSERT_EQ(*(begin++), value[3u]);
ASSERT_EQ(*(begin++), value[4u]);
ASSERT_EQ(begin, end);
ASSERT_EQ(pool.data()[0u], entity[4u]);
ASSERT_EQ(pool.data()[1u], entity[3u]);
ASSERT_EQ(pool.data()[2u], entity[1u]);
ASSERT_EQ(pool.data()[3u], entity[0u]);
ASSERT_EQ(pool.data()[4u], entity[2u]);
}
TYPED_TEST(Storage, SortAsDisjoint) {
using value_type = typename TestFixture::type;
entt::storage<value_type> lhs;
const entt::storage<value_type> rhs;
const std::array entity{entt::entity{1}, entt::entity{2}, entt::entity{4}};
const std::array value{value_type{0}, value_type{1}, value_type{2}};
lhs.insert(entity.begin(), entity.end(), value.begin());
ASSERT_TRUE(std::equal(entity.rbegin(), entity.rend(), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(value.rbegin(), value.rend(), lhs.begin(), lhs.end()));
lhs.sort_as(rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end());
ASSERT_TRUE(std::equal(entity.rbegin(), entity.rend(), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(value.rbegin(), value.rend(), lhs.begin(), lhs.end()));
}
TYPED_TEST(Storage, SortAsOverlap) {
using value_type = typename TestFixture::type;
entt::storage<value_type> lhs;
entt::storage<value_type> rhs;
const std::array lhs_entity{entt::entity{1}, entt::entity{2}, entt::entity{4}};
const std::array lhs_value{value_type{0}, value_type{1}, value_type{2}};
lhs.insert(lhs_entity.begin(), lhs_entity.end(), lhs_value.begin());
const std::array rhs_entity{entt::entity{2}};
const std::array rhs_value{value_type{1}};
rhs.insert(rhs_entity.begin(), rhs_entity.end(), rhs_value.begin());
ASSERT_TRUE(std::equal(lhs_entity.rbegin(), lhs_entity.rend(), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(lhs_value.rbegin(), lhs_value.rend(), lhs.begin(), lhs.end()));
ASSERT_TRUE(std::equal(rhs_entity.rbegin(), rhs_entity.rend(), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(rhs_value.rbegin(), rhs_value.rend(), rhs.begin(), rhs.end()));
lhs.sort_as(rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end());
auto begin = lhs.begin();
auto end = lhs.end();
ASSERT_EQ(*(begin++), lhs_value[1u]);
ASSERT_EQ(*(begin++), lhs_value[2u]);
ASSERT_EQ(*(begin++), lhs_value[0u]);
ASSERT_EQ(begin, end);
ASSERT_EQ(lhs.data()[0u], lhs_entity[0u]);
ASSERT_EQ(lhs.data()[1u], lhs_entity[2u]);
ASSERT_EQ(lhs.data()[2u], lhs_entity[1u]);
}
TYPED_TEST(Storage, SortAsOrdered) {
using value_type = typename TestFixture::type;
entt::storage<value_type> lhs;
entt::storage<value_type> rhs;
const std::array lhs_entity{entt::entity{1}, entt::entity{2}, entt::entity{4}, entt::entity{8}, entt::entity{16}};
const std::array lhs_value{value_type{0}, value_type{1}, value_type{2}, value_type{4}, value_type{8}};
lhs.insert(lhs_entity.begin(), lhs_entity.end(), lhs_value.begin());
const std::array rhs_entity{entt::entity{32}, entt::entity{1}, entt::entity{2}, entt::entity{4}, entt::entity{8}, entt::entity{16}};
const std::array rhs_value{value_type{16}, value_type{0}, value_type{1}, value_type{2}, value_type{4}, value_type{8}};
rhs.insert(rhs_entity.begin(), rhs_entity.end(), rhs_value.begin());
ASSERT_TRUE(std::equal(lhs_entity.rbegin(), lhs_entity.rend(), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(lhs_value.rbegin(), lhs_value.rend(), lhs.begin(), lhs.end()));
ASSERT_TRUE(std::equal(rhs_entity.rbegin(), rhs_entity.rend(), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(rhs_value.rbegin(), rhs_value.rend(), rhs.begin(), rhs.end()));
rhs.sort_as(lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end());
ASSERT_TRUE(std::equal(rhs_entity.rbegin(), rhs_entity.rend(), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(rhs_value.rbegin(), rhs_value.rend(), rhs.begin(), rhs.end()));
}
TYPED_TEST(Storage, SortAsReverse) {
using value_type = typename TestFixture::type;
entt::storage<value_type> lhs;
entt::storage<value_type> rhs;
const std::array lhs_entity{entt::entity{1}, entt::entity{2}, entt::entity{4}, entt::entity{8}, entt::entity{16}};
const std::array lhs_value{value_type{0}, value_type{1}, value_type{2}, value_type{4}, value_type{8}};
lhs.insert(lhs_entity.begin(), lhs_entity.end(), lhs_value.begin());
const std::array rhs_entity{entt::entity{16}, entt::entity{8}, entt::entity{4}, entt::entity{2}, entt::entity{1}, entt::entity{32}};
const std::array rhs_value{value_type{8}, value_type{4}, value_type{2}, value_type{1}, value_type{0}, value_type{16}};
rhs.insert(rhs_entity.begin(), rhs_entity.end(), rhs_value.begin());
ASSERT_TRUE(std::equal(lhs_entity.rbegin(), lhs_entity.rend(), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(lhs_value.rbegin(), lhs_value.rend(), lhs.begin(), lhs.end()));
ASSERT_TRUE(std::equal(rhs_entity.rbegin(), rhs_entity.rend(), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(rhs_value.rbegin(), rhs_value.rend(), rhs.begin(), rhs.end()));
rhs.sort_as(lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end());
auto begin = rhs.begin();
auto end = rhs.end();
ASSERT_EQ(*(begin++), rhs_value[0u]);
ASSERT_EQ(*(begin++), rhs_value[1u]);
ASSERT_EQ(*(begin++), rhs_value[2u]);
ASSERT_EQ(*(begin++), rhs_value[3u]);
ASSERT_EQ(*(begin++), rhs_value[4u]);
ASSERT_EQ(*(begin++), rhs_value[5u]);
ASSERT_EQ(begin, end);
ASSERT_EQ(rhs.data()[0u], rhs_entity[5u]);
ASSERT_EQ(rhs.data()[1u], rhs_entity[4u]);
ASSERT_EQ(rhs.data()[2u], rhs_entity[3u]);
ASSERT_EQ(rhs.data()[3u], rhs_entity[2u]);
ASSERT_EQ(rhs.data()[4u], rhs_entity[1u]);
ASSERT_EQ(rhs.data()[5u], rhs_entity[0u]);
}
TYPED_TEST(Storage, SortAsUnordered) {
using value_type = typename TestFixture::type;
entt::storage<value_type> lhs;
entt::storage<value_type> rhs;
const std::array lhs_entity{entt::entity{1}, entt::entity{2}, entt::entity{4}, entt::entity{8}, entt::entity{16}};
const std::array lhs_value{value_type{0}, value_type{1}, value_type{2}, value_type{4}, value_type{8}};
lhs.insert(lhs_entity.begin(), lhs_entity.end(), lhs_value.begin());
const std::array rhs_entity{entt::entity{4}, entt::entity{2}, entt::entity{32}, entt::entity{1}, entt::entity{8}, entt::entity{16}};
const std::array rhs_value{value_type{2}, value_type{1}, value_type{16}, value_type{0}, value_type{4}, value_type{8}};
rhs.insert(rhs_entity.begin(), rhs_entity.end(), rhs_value.begin());
ASSERT_TRUE(std::equal(lhs_entity.rbegin(), lhs_entity.rend(), lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(lhs_value.rbegin(), lhs_value.rend(), lhs.begin(), lhs.end()));
ASSERT_TRUE(std::equal(rhs_entity.rbegin(), rhs_entity.rend(), rhs.entt::sparse_set::begin(), rhs.entt::sparse_set::end()));
ASSERT_TRUE(std::equal(rhs_value.rbegin(), rhs_value.rend(), rhs.begin(), rhs.end()));
rhs.sort_as(lhs.entt::sparse_set::begin(), lhs.entt::sparse_set::end());
auto begin = rhs.begin();
auto end = rhs.end();
ASSERT_EQ(*(begin++), rhs_value[5u]);
ASSERT_EQ(*(begin++), rhs_value[4u]);
ASSERT_EQ(*(begin++), rhs_value[0u]);
ASSERT_EQ(*(begin++), rhs_value[1u]);
ASSERT_EQ(*(begin++), rhs_value[3u]);
ASSERT_EQ(*(begin++), rhs_value[2u]);
ASSERT_EQ(begin, end);
ASSERT_EQ(rhs.data()[0u], rhs_entity[2u]);
ASSERT_EQ(rhs.data()[1u], rhs_entity[3u]);
ASSERT_EQ(rhs.data()[2u], rhs_entity[1u]);
ASSERT_EQ(rhs.data()[3u], rhs_entity[0u]);
ASSERT_EQ(rhs.data()[4u], rhs_entity[4u]);
ASSERT_EQ(rhs.data()[5u], rhs_entity[5u]);
}
TEST(Storage, MoveOnlyComponent) {
using value_type = std::unique_ptr<int>;
static_assert(!std::is_copy_assignable_v<value_type>, "Copy assignable types not allowed");
static_assert(std::is_move_assignable_v<value_type>, "Move assignable type required");
// the purpose is to ensure that move only types are always accepted
[[maybe_unused]] const entt::storage<value_type> pool;
}
TEST(Storage, NonMovableComponent) {
using value_type = std::pair<const int, const int>;
static_assert(!std::is_move_assignable_v<value_type>, "Move assignable types not allowed");
// the purpose is to ensure that non-movable types are always accepted
[[maybe_unused]] const entt::storage<value_type> pool;
}
ENTT_DEBUG_TEST(StorageDeathTest, NonMovableComponent) {
entt::storage<std::pair<const int, const int>> pool;
const entt::entity entity{0};
const entt::entity destroy{1};
const entt::entity other{2};
pool.emplace(entity);
pool.emplace(destroy);
pool.emplace(other);
pool.erase(destroy);
ASSERT_DEATH(pool.swap_elements(entity, other), "");
ASSERT_DEATH(pool.compact(), "");
ASSERT_DEATH(pool.sort([](auto &&lhs, auto &&rhs) { return lhs < rhs; }), "");
}
TYPED_TEST(Storage, CanModifyDuringIteration) {
using value_type = typename TestFixture::type;
using traits_type = entt::component_traits_deprecated<value_type>;
entt::storage<value_type> pool;
auto *ptr = &pool.emplace(entt::entity{0}, 2);
ASSERT_EQ(pool.capacity(), traits_type::page_size);
const auto it = pool.cbegin();
pool.reserve(traits_type::page_size + 1u);
ASSERT_EQ(pool.capacity(), 2 * traits_type::page_size);
ASSERT_EQ(&pool.get(entt::entity{0}), ptr);
// this should crash with asan enabled if we break the constraint
[[maybe_unused]] const auto &value = *it;
}
TYPED_TEST(Storage, ReferencesGuaranteed) {
using value_type = typename TestFixture::type;
entt::storage<value_type> pool;
pool.emplace(entt::entity{0}, 0);
pool.emplace(entt::entity{1}, 1);
ASSERT_EQ(pool.get(entt::entity{0}), value_type{0});
ASSERT_EQ(pool.get(entt::entity{1}), value_type{1});
for(auto &&elem: pool) {
if(!(elem == value_type{})) {
elem = value_type{4};
}
}
ASSERT_EQ(pool.get(entt::entity{0}), value_type{0});
ASSERT_EQ(pool.get(entt::entity{1}), value_type{4});
auto begin = pool.begin();
while(begin != pool.end()) {
*(begin++) = value_type{3};
}
ASSERT_EQ(pool.get(entt::entity{0}), value_type{3});
ASSERT_EQ(pool.get(entt::entity{1}), value_type{3});
}
TEST(Storage, UpdateFromDestructor) {
constexpr auto size = 10u;
const std::array entity{entt::entity{4u}, entt::entity{2u}, entt::entity{0u}};
for(auto target: entity) {
entt::storage<update_from_destructor> pool;
for(std::size_t next{}; next < size; ++next) {
const auto other = entt::entity(next);
pool.emplace(other, pool, other == entt::entity(size / 2) ? target : other);
}
pool.erase(entt::entity(size / 2));
ASSERT_EQ(pool.size(), size - 1u - (target != entt::null));
ASSERT_FALSE(pool.contains(entt::entity(size / 2)));
ASSERT_FALSE(pool.contains(target));
pool.clear();
ASSERT_TRUE(pool.empty());
for(std::size_t next{}; next < size; ++next) {
ASSERT_FALSE(pool.contains(entt::entity(next)));
}
}
}
TEST(Storage, CreateFromConstructor) {
entt::storage<create_from_constructor> pool;
const entt::entity entity{0u};
const entt::entity other{1u};
pool.emplace(entity, pool, other);
ASSERT_EQ(pool.get(entity).child, other);
ASSERT_EQ(pool.get(other).child, static_cast<entt::entity>(entt::null));
}
TEST(Storage, ClassLevelNewDelete) {
entt::storage<test::new_delete> pool;
const entt::entity entity{0u};
// yeah, that's for code coverage purposes only :)
pool.emplace(entity, *std::make_unique<test::new_delete>(test::new_delete{3}));
ASSERT_EQ(pool.get(entity).value, 3);
}
TYPED_TEST(Storage, CustomAllocator) {
using value_type = typename TestFixture::type;
const test::throwing_allocator<entt::entity> allocator{};
entt::basic_storage<value_type, entt::entity, test::throwing_allocator<value_type>> pool{allocator};
pool.reserve(1u);
ASSERT_NE(pool.capacity(), 0u);
pool.emplace(entt::entity{0});
pool.emplace(entt::entity{1});
decltype(pool) other{std::move(pool), allocator};
test::is_initialized(pool);
ASSERT_TRUE(pool.empty());
ASSERT_FALSE(other.empty());
ASSERT_NE(other.capacity(), 0u);
ASSERT_EQ(other.size(), 2u);
pool = std::move(other);
test::is_initialized(other);
ASSERT_FALSE(pool.empty());
ASSERT_TRUE(other.empty());
ASSERT_NE(pool.capacity(), 0u);
ASSERT_EQ(pool.size(), 2u);
other = {};
pool.swap(other);
pool = std::move(other);
test::is_initialized(other);
ASSERT_FALSE(pool.empty());
ASSERT_TRUE(other.empty());
ASSERT_NE(pool.capacity(), 0u);
ASSERT_EQ(pool.size(), 2u);
pool.clear();
ASSERT_NE(pool.capacity(), 0u);
ASSERT_EQ(pool.size(), 0u);
}
TYPED_TEST(Storage, ThrowingAllocator) {
using value_type = typename TestFixture::type;
entt::basic_storage<value_type, entt::entity, test::throwing_allocator<value_type>> pool{};
typename std::decay_t<decltype(pool)>::base_type &base = pool;
constexpr auto packed_page_size = entt::component_traits_deprecated<value_type>::page_size;
constexpr auto sparse_page_size = entt::entt_traits<entt::entity>::page_size;
pool.get_allocator().template throw_counter<value_type>(0u);
ASSERT_THROW(pool.reserve(1u), test::throwing_allocator_exception);
ASSERT_EQ(pool.capacity(), 0u);
pool.get_allocator().template throw_counter<value_type>(1u);
ASSERT_THROW(pool.reserve(2 * packed_page_size), test::throwing_allocator_exception);
ASSERT_EQ(pool.capacity(), packed_page_size);
pool.shrink_to_fit();
ASSERT_EQ(pool.capacity(), 0u);
pool.get_allocator().template throw_counter<entt::entity>(0u);
ASSERT_THROW(pool.emplace(entt::entity{0}, 0), test::throwing_allocator_exception);
ASSERT_FALSE(pool.contains(entt::entity{0}));
ASSERT_TRUE(pool.empty());
pool.get_allocator().template throw_counter<entt::entity>(0u);
ASSERT_THROW(base.push(entt::entity{0}), test::throwing_allocator_exception);
ASSERT_FALSE(base.contains(entt::entity{0}));
ASSERT_TRUE(base.empty());
pool.get_allocator().template throw_counter<value_type>(0u);
ASSERT_THROW(pool.emplace(entt::entity{0}, 0), test::throwing_allocator_exception);
ASSERT_FALSE(pool.contains(entt::entity{0}));
ASSERT_NO_THROW(pool.compact());
ASSERT_TRUE(pool.empty());
pool.emplace(entt::entity{0}, 0);
const std::array entity{entt::entity{1}, entt::entity{sparse_page_size}};
pool.get_allocator().template throw_counter<entt::entity>(1u);
ASSERT_THROW(pool.insert(entity.begin(), entity.end(), value_type{0}), test::throwing_allocator_exception);
ASSERT_TRUE(pool.contains(entity[0u]));
ASSERT_FALSE(pool.contains(entity[1u]));
pool.erase(entity[0u]);
const std::array component{value_type{1}, value_type{sparse_page_size}};
pool.get_allocator().template throw_counter<entt::entity>(0u);
pool.compact();
ASSERT_THROW(pool.insert(entity.begin(), entity.end(), component.begin()), test::throwing_allocator_exception);
ASSERT_TRUE(pool.contains(entity[0u]));
ASSERT_FALSE(pool.contains(entity[1u]));
}
TEST(Storage, ThrowingComponent) {
entt::storage<test::throwing_type> pool;
const std::array entity{entt::entity{4}, entt::entity{1}};
const std::array value{test::throwing_type{true}, test::throwing_type{false}};
// strong exception safety
ASSERT_THROW(pool.emplace(entity[0u], value[0u]), test::throwing_type_exception);
ASSERT_TRUE(pool.empty());
// basic exception safety
ASSERT_THROW(pool.insert(entity.begin(), entity.end(), value[0u]), test::throwing_type_exception);
ASSERT_EQ(pool.size(), 0u);
ASSERT_FALSE(pool.contains(entity[1u]));
// basic exception safety
ASSERT_THROW(pool.insert(entity.begin(), entity.end(), value.begin()), test::throwing_type_exception);
ASSERT_EQ(pool.size(), 0u);
ASSERT_FALSE(pool.contains(entity[1u]));
// basic exception safety
ASSERT_THROW(pool.insert(entity.rbegin(), entity.rend(), value.rbegin()), test::throwing_type_exception);
ASSERT_EQ(pool.size(), 1u);
ASSERT_TRUE(pool.contains(entity[1u]));
ASSERT_EQ(pool.get(entity[1u]), value[1u]);
pool.clear();
pool.emplace(entity[1u], value[0u].throw_on_copy());
pool.emplace(entity[0u], value[1u].throw_on_copy());
// basic exception safety
ASSERT_THROW(pool.erase(entity[1u]), test::throwing_type_exception);
ASSERT_EQ(pool.size(), 2u);
ASSERT_TRUE(pool.contains(entity[0u]));
ASSERT_TRUE(pool.contains(entity[1u]));
ASSERT_EQ(pool.index(entity[0u]), 1u);
ASSERT_EQ(pool.index(entity[1u]), 0u);
ASSERT_EQ(pool.get(entity[0u]), value[1u]);
// the element may have been moved but it's still there
ASSERT_EQ(pool.get(entity[1u]), value[0u]);
pool.get(entity[1u]).throw_on_copy(false);
pool.erase(entity[1u]);
ASSERT_EQ(pool.size(), 1u);
ASSERT_TRUE(pool.contains(entity[0u]));
ASSERT_FALSE(pool.contains(entity[1u]));
ASSERT_EQ(pool.index(entity[0u]), 0u);
ASSERT_EQ(pool.get(entity[0u]), value[1u]);
}
#if defined(ENTT_HAS_TRACKED_MEMORY_RESOURCE)
TYPED_TEST(Storage, NoUsesAllocatorConstruction) {
using value_type = typename TestFixture::type;
test::tracked_memory_resource memory_resource{};
entt::basic_storage<value_type, entt::entity, std::pmr::polymorphic_allocator<value_type>> pool{&memory_resource};
const entt::entity entity{2};
pool.emplace(entity);
pool.erase(entity);
memory_resource.reset();
pool.emplace(entity, 0);
ASSERT_TRUE(pool.get_allocator().resource()->is_equal(memory_resource));
ASSERT_EQ(memory_resource.do_allocate_counter(), 0u);
ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u);
}
TEST(Storage, UsesAllocatorConstruction) {
using string_type = typename test::tracked_memory_resource::string_type;
test::tracked_memory_resource memory_resource{};
entt::basic_storage<string_type, entt::entity, std::pmr::polymorphic_allocator<string_type>> pool{&memory_resource};
const entt::entity entity{2};
pool.emplace(entity);
pool.erase(entity);
memory_resource.reset();
pool.emplace(entity, test::tracked_memory_resource::default_value);
ASSERT_TRUE(pool.get_allocator().resource()->is_equal(memory_resource));
ASSERT_GT(memory_resource.do_allocate_counter(), 0u);
ASSERT_EQ(memory_resource.do_deallocate_counter(), 0u);
}
#endif