sparse_set:

* (almost) allocator-aware container
* unequal containers not supported
* copying a sparse set isn't supported
This commit is contained in:
Michele Caini
2021-08-18 23:32:57 +02:00
parent 4e12853d4a
commit c41509151b
6 changed files with 266 additions and 101 deletions

4
TODO
View File

@@ -10,11 +10,11 @@ WIP:
* runtime_view/registry, remove reference to basic_sparse_set<E>
* make pools available (registry/view/group), review operator| for views, make views accept registry to ctor
* make view.lead() or similar available to return leading pool (useful for mt)
* dedicated entity storage, in-place O(1) release/destroy
* general purpose paged vector container
* dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities
* custom allocators all over
WIP:
* general purpose paged vector container
* make sparse_set/storage adhere to AllocatorAwareContainer requirements
* make it possible to register externally managed pools with the registry (allow for system centric mode)
* registry: switch to the udata/mixin model and get rid of poly storage, use pointer to sparse set only for pools, discard pool_data type.

View File

@@ -68,8 +68,7 @@ class basic_sparse_set {
using entity_traits = entt_traits<Entity>;
static_assert(alloc_traits::propagate_on_container_move_assignment::value);
static_assert(alloc_ptr_traits::propagate_on_container_move_assignment::value);
static_assert(alloc_traits::is_always_equal::value, "Unequal allocators not supported");
struct sparse_set_iterator final {
using difference_type = typename entity_traits::difference_type;
@@ -81,7 +80,7 @@ class basic_sparse_set {
sparse_set_iterator() ENTT_NOEXCEPT = default;
sparse_set_iterator(const alloc_const_pointer *ref, const difference_type idx) ENTT_NOEXCEPT
: packed{ref},
: packed_array{ref},
index{idx}
{}
@@ -127,7 +126,7 @@ class basic_sparse_set {
[[nodiscard]] reference operator[](const difference_type value) const {
const auto pos = size_type(index-value-1u);
return (*packed)[pos];
return (*packed_array)[pos];
}
[[nodiscard]] bool operator==(const sparse_set_iterator &other) const ENTT_NOEXCEPT {
@@ -156,7 +155,7 @@ class basic_sparse_set {
[[nodiscard]] pointer operator->() const {
const auto pos = size_type(index-1u);
return std::addressof((*packed)[pos]);
return std::addressof((*packed_array)[pos]);
}
[[nodiscard]] reference operator*() const {
@@ -164,7 +163,7 @@ class basic_sparse_set {
}
private:
const alloc_const_pointer *packed;
const alloc_const_pointer *packed_array;
difference_type index;
};
@@ -184,62 +183,62 @@ class basic_sparse_set {
std::uninitialized_value_construct(mem + bucket, mem + sz);
if(sparse) {
std::uninitialized_copy(sparse, sparse + bucket, mem);
std::destroy(sparse, sparse + bucket);
alloc_ptr_traits::deallocate(allocator_ptr, sparse, bucket);
if(sparse_array) {
std::uninitialized_copy(sparse_array, sparse_array + bucket, mem);
std::destroy(sparse_array, sparse_array + bucket);
alloc_ptr_traits::deallocate(allocator_ptr, sparse_array, bucket);
}
sparse = mem;
sparse_array = mem;
bucket = sz;
}
if(!sparse[idx]) {
sparse[idx] = alloc_traits::allocate(reserved.first(), sparse_page_v);
std::uninitialized_fill(sparse[idx], sparse[idx] + sparse_page_v, null);
if(!sparse_array[idx]) {
sparse_array[idx] = alloc_traits::allocate(reserved.first(), sparse_page_v);
std::uninitialized_fill(sparse_array[idx], sparse_array[idx] + sparse_page_v, null);
}
return sparse[idx];
return sparse_array[idx];
}
void resize_packed(const std::size_t req) {
void resize_packed_array(const std::size_t req) {
auto &&[allocator, len] = reserved;
ENTT_ASSERT((req != len) && !(req < count), "Invalid request");
const auto mem = alloc_traits::allocate(allocator, req);
std::uninitialized_fill(mem + count, mem + req, tombstone);
if(packed) {
std::uninitialized_copy(packed, packed + count, mem);
std::destroy(packed, packed + len);
alloc_traits::deallocate(allocator, packed, len);
if(packed_array) {
std::uninitialized_copy(packed_array, packed_array + count, mem);
std::destroy(packed_array, packed_array + len);
alloc_traits::deallocate(allocator, packed_array, len);
}
packed = mem;
packed_array = mem;
len = req;
}
void release_memory() {
auto &&[allocator, len] = reserved;
if(packed) {
std::destroy(packed, packed + len);
alloc_traits::deallocate(allocator, packed, len);
if(packed_array) {
std::destroy(packed_array, packed_array + len);
alloc_traits::deallocate(allocator, packed_array, len);
}
if(sparse) {
if(sparse_array) {
alloc_ptr allocator_ptr{allocator};
for(size_type pos{}; pos < bucket; ++pos) {
if(sparse[pos]) {
std::destroy(sparse[pos], sparse[pos] + sparse_page_v);
alloc_traits::deallocate(allocator, sparse[pos], sparse_page_v);
if(sparse_array[pos]) {
std::destroy(sparse_array[pos], sparse_array[pos] + sparse_page_v);
alloc_traits::deallocate(allocator, sparse_array[pos], sparse_page_v);
}
alloc_ptr_traits::destroy(allocator_ptr, std::addressof(sparse[pos]));
alloc_ptr_traits::destroy(allocator_ptr, std::addressof(sparse_array[pos]));
}
alloc_ptr_traits::deallocate(allocator_ptr, sparse, bucket);
alloc_ptr_traits::deallocate(allocator_ptr, sparse_array, bucket);
}
}
@@ -255,13 +254,13 @@ protected:
* @param entt A valid identifier.
*/
virtual void swap_and_pop(const Entity entt, void *) {
auto &ref = sparse[page(entt)][offset(entt)];
auto &ref = sparse_array[page(entt)][offset(entt)];
const auto pos = static_cast<size_type>(entity_traits::to_entity(ref));
ENTT_ASSERT(packed[pos] == entt, "Invalid identifier");
auto &last = packed[--count];
ENTT_ASSERT(packed_array[pos] == entt, "Invalid identifier");
auto &last = packed_array[--count];
packed[pos] = last;
auto &elem = sparse[page(last)][offset(last)];
packed_array[pos] = last;
auto &elem = sparse_array[page(last)][offset(last)];
elem = entity_traits::combine(entity_traits::to_integral(ref), entity_traits::to_integral(elem));
// lazy self-assignment guard
ref = null;
@@ -274,11 +273,11 @@ protected:
* @param entt A valid identifier.
*/
virtual void in_place_pop(const Entity entt, void *) {
auto &ref = sparse[page(entt)][offset(entt)];
auto &ref = sparse_array[page(entt)][offset(entt)];
const auto pos = static_cast<size_type>(entity_traits::to_entity(ref));
ENTT_ASSERT(packed[pos] == entt, "Invalid identifier");
ENTT_ASSERT(packed_array[pos] == entt, "Invalid identifier");
packed[pos] = std::exchange(free_list, entity_traits::combine(static_cast<typename entity_traits::entity_type>(pos), entity_traits::reserved));
packed_array[pos] = std::exchange(free_list, entity_traits::combine(static_cast<typename entity_traits::entity_type>(pos), entity_traits::reserved));
// lazy self-assignment guard
ref = null;
}
@@ -315,12 +314,12 @@ public:
/**
* @brief Constructs an empty container with the given policy and allocator.
* @param pol Type of deletion policy.
* @param allocator Allocator to use (possibly default-constructed).
* @param allocator The allocator to use (possibly default-constructed).
*/
explicit basic_sparse_set(deletion_policy pol, const allocator_type &allocator = {})
: reserved{allocator, size_type{}},
sparse{},
packed{},
sparse_array{},
packed_array{},
bucket{},
count{},
free_list{tombstone},
@@ -332,9 +331,24 @@ public:
* @param other The instance to move from.
*/
basic_sparse_set(basic_sparse_set &&other) ENTT_NOEXCEPT
: reserved{std::move(other.reserved)},
sparse{std::exchange(other.sparse, alloc_ptr_pointer{})},
packed{std::exchange(other.packed, alloc_pointer{})},
: reserved{std::move(other.reserved.first()), std::exchange(other.reserved.second(), size_type{})},
sparse_array{std::exchange(other.sparse_array, alloc_ptr_pointer{})},
packed_array{std::exchange(other.packed_array, alloc_pointer{})},
bucket{std::exchange(other.bucket, size_type{})},
count{std::exchange(other.count, size_type{})},
free_list{std::exchange(other.free_list, tombstone)},
mode{other.mode}
{}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_sparse_set(basic_sparse_set &&other, const allocator_type &allocator) ENTT_NOEXCEPT
: reserved{allocator, std::exchange(other.reserved.second(), size_type{})},
sparse_array{std::exchange(other.sparse_array, alloc_ptr_pointer{})},
packed_array{std::exchange(other.packed_array, alloc_pointer{})},
bucket{std::exchange(other.bucket, size_type{})},
count{std::exchange(other.count, size_type{})},
free_list{std::exchange(other.free_list, tombstone)},
@@ -354,9 +368,13 @@ public:
basic_sparse_set & operator=(basic_sparse_set &&other) ENTT_NOEXCEPT {
release_memory();
reserved = std::move(other.reserved);
sparse = std::exchange(other.sparse, alloc_ptr_pointer{});
packed = std::exchange(other.packed, alloc_pointer{});
if constexpr(alloc_traits::propagate_on_container_move_assignment::value) {
reserved.first() = std::move(other.reserved.first());
}
reserved.second() = std::exchange(other.reserved.second(), size_type{});
sparse_array = std::exchange(other.sparse_array, alloc_ptr_pointer{});
packed_array = std::exchange(other.packed_array, alloc_pointer{});
bucket = std::exchange(other.bucket, size_type{});
count = std::exchange(other.count, size_type{});
free_list = std::exchange(other.free_list, tombstone);
@@ -365,6 +383,26 @@ public:
return *this;
}
/**
* @brief Exchanges the contents with those of a given sparse set.
* @param other Sparse set to exchange the content with.
*/
void swap(basic_sparse_set &other) {
using std::swap;
if constexpr(alloc_traits::propagate_on_container_swap::value) {
swap(reserved.first(), other.reserved.first());
}
swap(reserved.second(), other.reserved.second());
swap(sparse_array, other.sparse_array);
swap(packed_array, other.packed_array);
swap(bucket, other.bucket);
swap(count, other.count);
swap(free_list, other.free_list);
swap(mode, other.mode);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
@@ -399,7 +437,7 @@ public:
*/
virtual void reserve(const size_type cap) {
if(cap > reserved.second()) {
resize_packed(cap);
resize_packed_array(cap);
}
}
@@ -415,7 +453,7 @@ public:
/*! @brief Requests the removal of unused capacity. */
virtual void shrink_to_fit() {
if(count < reserved.second()) {
resize_packed(count);
resize_packed_array(count);
}
}
@@ -460,7 +498,7 @@ public:
* @return A pointer to the internal packed array.
*/
[[nodiscard]] pointer data() const ENTT_NOEXCEPT {
return packed;
return packed_array;
}
/**
@@ -473,7 +511,7 @@ public:
* @return An iterator to the first entity of the internal packed array.
*/
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
return iterator{std::addressof(packed), static_cast<typename entity_traits::difference_type>(count)};
return iterator{std::addressof(packed_array), static_cast<typename entity_traits::difference_type>(count)};
}
/**
@@ -487,7 +525,7 @@ public:
* internal packed array.
*/
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
return iterator{std::addressof(packed), {}};
return iterator{std::addressof(packed_array), {}};
}
/**
@@ -534,10 +572,10 @@ public:
* @return True if the sparse set contains the entity, false otherwise.
*/
[[nodiscard]] bool contains(const entity_type entt) const ENTT_NOEXCEPT {
if(const auto curr = page(entt); curr < bucket && sparse[curr]) {
if(const auto curr = page(entt); curr < bucket && sparse_array[curr]) {
constexpr auto cap = entity_traits::to_entity(entt::null);
// testing versions permits to avoid accessing the packed array
return (((~cap & entity_traits::to_integral(entt)) ^ entity_traits::to_integral(sparse[curr][offset(entt)])) < cap);
return (((~cap & entity_traits::to_integral(entt)) ^ entity_traits::to_integral(sparse_array[curr][offset(entt)])) < cap);
}
return false;
@@ -550,8 +588,8 @@ public:
* version otherwise.
*/
[[nodiscard]] version_type current(const entity_type entt) const {
if(const auto curr = page(entt); curr < bucket && sparse[curr]) {
return entity_traits::to_version(sparse[curr][offset(entt)]);
if(const auto curr = page(entt); curr < bucket && sparse_array[curr]) {
return entity_traits::to_version(sparse_array[curr][offset(entt)]);
}
return entity_traits::to_version(tombstone);
@@ -569,7 +607,7 @@ public:
*/
[[nodiscard]] size_type index(const entity_type entt) const ENTT_NOEXCEPT {
ENTT_ASSERT(contains(entt), "Set does not contain entity");
return static_cast<size_type>(entity_traits::to_entity(sparse[page(entt)][offset(entt)]));
return static_cast<size_type>(entity_traits::to_entity(sparse_array[page(entt)][offset(entt)]));
}
/**
@@ -578,7 +616,7 @@ public:
* @return The entity at specified location if any, a null entity otherwise.
*/
[[nodiscard]] entity_type at(const size_type pos) const ENTT_NOEXCEPT {
return pos < count ? packed[pos] : null;
return pos < count ? packed_array[pos] : null;
}
/**
@@ -588,7 +626,7 @@ public:
*/
[[nodiscard]] entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
ENTT_ASSERT(pos < count, "Position is out of bounds");
return packed[pos];
return packed_array[pos];
}
/**
@@ -606,12 +644,12 @@ public:
if(const auto len = reserved.second(); count == len) {
const size_type sz = static_cast<size_type>(len * growth_factor_v);
resize_packed(sz + !(sz > len));
resize_packed_array(sz + !(sz > len));
}
const auto entity = static_cast<typename entity_traits::entity_type>(count);
assure_page(page(entt))[offset(entt)] = entity_traits::combine(entity, entity_traits::to_integral(entt));
packed[count] = entt;
packed_array[count] = entt;
return count++;
}
@@ -632,7 +670,7 @@ public:
ENTT_ASSERT(current(entt) == entity_traits::to_version(tombstone), "Slot not available");
assure_page(page(entt))[offset(entt)] = entity_traits::combine(entity_traits::to_integral(free_list), entity_traits::to_integral(entt));
const auto pos = static_cast<size_type>(entity_traits::to_entity(free_list));
free_list = std::exchange(packed[pos], entt);
free_list = std::exchange(packed_array[pos], entt);
return pos;
}
}
@@ -657,7 +695,7 @@ public:
ENTT_ASSERT(current(entt) == entity_traits::to_version(tombstone), "Slot not available");
const auto entity = static_cast<typename entity_traits::entity_type>(count);
assure_page(page(entt))[offset(entt)] = entity_traits::combine(entity, entity_traits::to_integral(entt));
packed[count++] = entt;
packed_array[count++] = entt;
}
}
@@ -725,17 +763,17 @@ public:
/*! @brief Removes all tombstones from the packed array of a sparse set. */
void compact() {
size_type next = count;
for(; next && packed[next - 1u] == tombstone; --next);
for(; next && packed_array[next - 1u] == tombstone; --next);
for(auto *it = &free_list; *it != null && next; it = std::addressof(packed[entity_traits::to_entity(*it)])) {
for(auto *it = &free_list; *it != null && next; it = std::addressof(packed_array[entity_traits::to_entity(*it)])) {
if(const size_type pos = entity_traits::to_entity(*it); pos < next) {
--next;
move_and_pop(next, pos);
std::swap(packed[next], packed[pos]);
std::swap(packed_array[next], packed_array[pos]);
const auto entity = static_cast<typename entity_traits::entity_type>(pos);
sparse[page(packed[pos])][offset(packed[pos])] = entity_traits::combine(entity, entity_traits::to_integral(packed[pos]));
sparse_array[page(packed_array[pos])][offset(packed_array[pos])] = entity_traits::combine(entity, entity_traits::to_integral(packed_array[pos]));
*it = entity_traits::combine(static_cast<typename entity_traits::entity_type>(next), entity_traits::reserved);
for(; next && packed[next - 1u] == tombstone; --next);
for(; next && packed_array[next - 1u] == tombstone; --next);
}
}
@@ -759,17 +797,17 @@ public:
void swap(const entity_type lhs, const entity_type rhs) {
ENTT_ASSERT(contains(lhs) && contains(rhs), "Set does not contain entities");
auto &entt = sparse[page(lhs)][offset(lhs)];
auto &other = sparse[page(rhs)][offset(rhs)];
auto &entt = sparse_array[page(lhs)][offset(lhs)];
auto &other = sparse_array[page(rhs)][offset(rhs)];
const auto from = entity_traits::to_entity(entt);
const auto to = entity_traits::to_entity(other);
// basic no-leak guarantee (with invalid state) if swapping throws
swap_at(static_cast<size_type>(from), static_cast<size_type>(to));
entt = entity_traits::combine(to, entity_traits::to_integral(packed[from]));
other = entity_traits::combine(from, entity_traits::to_integral(packed[to]));
std::swap(packed[from], packed[to]);
entt = entity_traits::combine(to, entity_traits::to_integral(packed_array[from]));
other = entity_traits::combine(from, entity_traits::to_integral(packed_array[to]));
std::swap(packed_array[from], packed_array[to]);
}
/**
@@ -808,19 +846,19 @@ public:
ENTT_ASSERT(!(length > count), "Length exceeds the number of elements");
compact();
algo(std::make_reverse_iterator(packed + length), std::make_reverse_iterator(packed), std::move(compare), std::forward<Args>(args)...);
algo(std::make_reverse_iterator(packed_array + length), std::make_reverse_iterator(packed_array), std::move(compare), std::forward<Args>(args)...);
for(size_type pos{}; pos < length; ++pos) {
auto curr = pos;
auto next = index(packed[curr]);
auto next = index(packed_array[curr]);
while(curr != next) {
const auto idx = index(packed[next]);
const auto entt = packed[curr];
const auto idx = index(packed_array[next]);
const auto entt = packed_array[curr];
swap_at(next, idx);
const auto entity = static_cast<typename entity_traits::entity_type>(curr);
sparse[page(entt)][offset(entt)] = entity_traits::combine(entity, entity_traits::to_integral(packed[curr]));
sparse_array[page(entt)][offset(entt)] = entity_traits::combine(entity, entity_traits::to_integral(packed_array[curr]));
curr = std::exchange(next, idx);
}
}
@@ -866,9 +904,9 @@ public:
for(size_type pos = count - 1; pos && from != to; ++from) {
if(contains(*from)) {
if(*from != packed[pos]) {
if(*from != packed_array[pos]) {
// basic no-leak guarantee (with invalid state) if swapping throws
swap(packed[pos], *from);
swap(packed_array[pos], *from);
}
--pos;
@@ -893,8 +931,8 @@ public:
private:
compressed_pair<alloc, size_type> reserved;
alloc_ptr_pointer sparse;
alloc_pointer packed;
alloc_ptr_pointer sparse_array;
alloc_pointer packed_array;
size_type bucket;
size_type count;
entity_type free_list;

View File

@@ -348,7 +348,7 @@ public:
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator the allocator to use.
* @param allocator The allocator to use.
*/
explicit basic_storage(const allocator_type &allocator)
: base_type{deletion_policy{comp_traits::in_place_delete::value}, allocator},
@@ -703,11 +703,16 @@ public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/*! @brief Default constructor. */
basic_storage()
: basic_storage{allocator_type{}}
{}
/**
* @brief Default constructor.
* @param allocator Allocator to use (possibly default-constructed).
* @brief Constructs an empty container with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_storage(const allocator_type &allocator = {})
explicit basic_storage(const allocator_type &allocator)
: base_type{deletion_policy{comp_traits::in_place_delete::value}, allocator}
{}

View File

@@ -0,0 +1,57 @@
#ifndef ENTT_ENTITY_CUSTOM_ALLOCATOR_HPP
#define ENTT_ENTITY_CUSTOM_ALLOCATOR_HPP
#include <cstddef>
#include <memory>
#include <type_traits>
namespace test {
template<typename Type>
class custom_allocator: std::allocator<Type> {
template<typename Other>
friend class custom_allocator;
using base = std::allocator<Type>;
public:
using value_type = Type;
using pointer = value_type *;
using const_pointer = const value_type *;
using void_pointer = void *;
using const_void_pointer = const void *;
using propagate_on_container_move_assignment = std::true_type;
using propagate_on_container_swap = std::true_type;
custom_allocator() = default;
template<class Other>
custom_allocator(const custom_allocator<Other> &other)
: base{static_cast<const std::allocator<Other> &>(other)}
{}
pointer allocate(std::size_t length) {
return base::allocate(length);
}
void deallocate(pointer mem, std::size_t length) {
base::deallocate(mem, length);
}
bool operator==(const custom_allocator<Type> &) const {
return true;
}
bool operator!=(const custom_allocator<Type> &) const {
return false;
}
};
}
#endif

View File

@@ -8,6 +8,7 @@
#include <entt/entity/entity.hpp>
#include <entt/entity/sparse_set.hpp>
#include <entt/entity/fwd.hpp>
#include "custom_allocator.hpp"
#include "throwing_allocator.hpp"
struct empty_type {};
@@ -187,6 +188,28 @@ TEST(SparseSet, Move) {
ASSERT_EQ(other.at(0u), entt::entity{42});
}
TEST(SparseSet, Swap) {
entt::sparse_set set;
entt::sparse_set other{entt::deletion_policy::in_place};
set.emplace(entt::entity{42});
other.emplace(entt::entity{9});
other.emplace(entt::entity{3});
other.erase(entt::entity{9});
ASSERT_EQ(set.size(), 1u);
ASSERT_EQ(other.size(), 2u);
set.swap(other);
ASSERT_EQ(set.size(), 2u);
ASSERT_EQ(other.size(), 1u);
ASSERT_EQ(set.at(1u), entt::entity{3});
ASSERT_EQ(other.at(0u), entt::entity{42});
}
TEST(SparseSet, Pagination) {
entt::sparse_set set;
@@ -710,7 +733,7 @@ TEST(SparseSet, Compact) {
ASSERT_TRUE(set.empty());
}
TEST(SparseSet, Swap) {
TEST(SparseSet, SwapEntity) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set;
@@ -1099,6 +1122,54 @@ TEST(SparseSet, CanModifyDuringIteration) {
[[maybe_unused]] const auto entity = *it;
}
TEST(SparseSet, CustomAllocator) {
test::custom_allocator<entt::entity> allocator{};
entt::basic_sparse_set<entt::entity, test::custom_allocator<entt::entity>> set{allocator};
ASSERT_EQ(set.get_allocator(), allocator);
set.reserve(1u);
ASSERT_EQ(set.capacity(), 1u);
set.emplace(entt::entity{0});
set.emplace(entt::entity{1});
entt::basic_sparse_set<entt::entity, test::custom_allocator<entt::entity>> other{std::move(set), allocator};
ASSERT_TRUE(set.empty());
ASSERT_FALSE(other.empty());
ASSERT_EQ(set.capacity(), 0u);
ASSERT_EQ(other.capacity(), 2u);
ASSERT_EQ(other.size(), 2u);
set = std::move(other);
ASSERT_FALSE(set.empty());
ASSERT_TRUE(other.empty());
ASSERT_EQ(other.capacity(), 0u);
ASSERT_EQ(set.capacity(), 2u);
ASSERT_EQ(set.size(), 2u);
set.swap(other);
set = std::move(other);
ASSERT_FALSE(set.empty());
ASSERT_TRUE(other.empty());
ASSERT_EQ(other.capacity(), 0u);
ASSERT_EQ(set.capacity(), 2u);
ASSERT_EQ(set.size(), 2u);
set.clear();
ASSERT_EQ(set.capacity(), 2u);
ASSERT_EQ(set.size(), 0u);
set.shrink_to_fit();
ASSERT_EQ(set.capacity(), 0u);
}
TEST(SparseSet, ThrowingAllocator) {
entt::basic_sparse_set<entt::entity, test::throwing_allocator<entt::entity>> set{};
@@ -1140,9 +1211,4 @@ TEST(SparseSet, ThrowingAllocator) {
set.emplace(entities[1u]);
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
// unnecessary but they test a bit of template machinery :)
set.clear();
set.shrink_to_fit();
set = decltype(set){};
}

View File

@@ -11,10 +11,11 @@ namespace test {
template<typename Type>
class throwing_allocator {
class throwing_allocator: std::allocator<Type> {
template<typename Other>
friend class throwing_allocator;
using base = std::allocator<Type>;
struct test_exception {};
public:
@@ -24,13 +25,14 @@ public:
using void_pointer = void *;
using const_void_pointer = const void *;
using propagate_on_container_move_assignment = std::true_type;
using propagate_on_container_swap = std::true_type;
using exception_type = test_exception;
throwing_allocator() = default;
template<class Other>
throwing_allocator(const throwing_allocator<Other> &other)
: allocator{other.allocator}
: base{other}
{}
pointer allocate(std::size_t length) {
@@ -42,18 +44,15 @@ public:
trigger_on_allocate = trigger_after_allocate;
trigger_after_allocate = false;
return allocator.allocate(length);
return base::allocate(length);
}
void deallocate(pointer mem, std::size_t length) {
allocator.deallocate(mem, length);
base::deallocate(mem, length);
}
static inline bool trigger_on_allocate{};
static inline bool trigger_after_allocate{};
private:
std::allocator<Type> allocator;
};