sparse_set:
* (almost) allocator-aware container * unequal containers not supported * copying a sparse set isn't supported
This commit is contained in:
4
TODO
4
TODO
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
{}
|
||||
|
||||
|
||||
57
test/entt/entity/custom_allocator.hpp
Normal file
57
test/entt/entity/custom_allocator.hpp
Normal 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
|
||||
@@ -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){};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user