sparse_set/storage:

* added component traits (component.hpp)
* support for in-place delete (tombstones)
This commit is contained in:
Michele Caini
2021-06-21 12:56:33 +02:00
parent a621b36389
commit d49e7ba4b2
10 changed files with 973 additions and 517 deletions
+29
View File
@@ -0,0 +1,29 @@
#ifndef ENTT_ENTITY_COMPONENT_HPP
#define ENTT_ENTITY_COMPONENT_HPP
#include <type_traits>
#include "../config/config.h"
namespace entt {
/**
* @brief Common way to access various properties of components
* @tparam Type Type of component.
*/
template<typename Type, typename = void>
struct component_traits {
static_assert(std::is_same_v<std::decay_t<Type>, Type>, "Type is not decayed");
/*! @brief Pointer stable component, default is `std::false_type`. */
using in_place_delete = std::false_type;
/*! @brief Empty type optimization, default is `ENTT_IGNORE_IF_EMPTY`. */
using ignore_if_empty = ENTT_IGNORE_IF_EMPTY;
};
}
#endif
+2 -2
View File
@@ -13,8 +13,8 @@ template<typename Entity, typename = std::allocator<Entity>>
class basic_sparse_set;
template<typename, typename Type, typename = std::allocator<Type>, typename = void>
class basic_storage;
template<typename, typename Type, typename = std::allocator<Type>>
struct basic_storage;
template<typename>
+171 -106
View File
@@ -17,6 +17,15 @@
namespace entt {
/*! @brief Sparse set deletion policy. */
enum class deletion_policy: std::uint8_t {
/*! @brief Swap-and-pop deletion policy. */
swap_and_pop = 0u,
/*! @brief In-place deletion policy. */
in_place = 1u
};
/**
* @brief Basic sparse set implementation.
*
@@ -57,17 +66,8 @@ class basic_sparse_set {
static_assert(alloc_traits::propagate_on_container_move_assignment::value);
static_assert(bucket_alloc_traits::propagate_on_container_move_assignment::value);
class sparse_set_iterator final {
friend class basic_sparse_set<Entity, Allocator>;
using index_type = typename traits_type::difference_type;
sparse_set_iterator(const alloc_const_pointer *ref, const index_type idx) ENTT_NOEXCEPT
: packed{ref}, index{idx}
{}
public:
using difference_type = index_type;
struct sparse_set_iterator final {
using difference_type = typename traits_type::difference_type;
using value_type = Entity;
using pointer = const value_type *;
using reference = const value_type &;
@@ -75,6 +75,11 @@ 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},
index{idx}
{}
sparse_set_iterator & operator++() ENTT_NOEXCEPT {
return --index, *this;
}
@@ -155,7 +160,7 @@ class basic_sparse_set {
private:
const alloc_const_pointer *packed;
index_type index;
difference_type index;
};
[[nodiscard]] static auto page(const Entity entt) ENTT_NOEXCEPT {
@@ -163,7 +168,7 @@ class basic_sparse_set {
}
[[nodiscard]] static auto offset(const Entity entt) ENTT_NOEXCEPT {
return size_type{traits_type::to_integral(entt) & (sparse_page - 1)};
return size_type{traits_type::to_entity(entt) & (sparse_page - 1)};
}
[[nodiscard]] auto assure_page(const std::size_t idx) {
@@ -171,19 +176,8 @@ class basic_sparse_set {
const size_type sz = idx + 1u;
const auto mem = bucket_alloc_traits::allocate(bucket_allocator, sz);
ENTT_TRY {
std::uninitialized_value_construct(mem + bucket, mem + sz);
ENTT_TRY {
std::uninitialized_copy(sparse, sparse + bucket, mem);
} ENTT_CATCH {
std::destroy(mem + bucket, mem + sz);
ENTT_THROW;
}
} ENTT_CATCH {
bucket_alloc_traits::deallocate(bucket_allocator, mem, sz);
ENTT_THROW;
}
std::uninitialized_value_construct(mem + bucket, mem + sz);
std::uninitialized_copy(sparse, sparse + bucket, mem);
std::destroy(sparse, sparse + bucket);
bucket_alloc_traits::deallocate(bucket_allocator, sparse, bucket);
@@ -193,16 +187,8 @@ class basic_sparse_set {
}
if(!sparse[idx]) {
const auto mem = alloc_traits::allocate(allocator, sparse_page);
ENTT_TRY {
std::uninitialized_fill(mem, mem + sparse_page, null);
} ENTT_CATCH {
alloc_traits::deallocate(allocator, mem, sparse_page);
ENTT_THROW;
}
sparse[idx] = mem;
sparse[idx] = alloc_traits::allocate(allocator, sparse_page);
std::uninitialized_fill(sparse[idx], sparse[idx] + sparse_page, null);
}
return sparse[idx];
@@ -212,14 +198,10 @@ class basic_sparse_set {
ENTT_ASSERT((req != reserved) && !(req < count), "Invalid request");
const auto mem = alloc_traits::allocate(allocator, req);
ENTT_TRY {
std::uninitialized_copy(packed, packed + count, mem);
} ENTT_CATCH {
alloc_traits::deallocate(allocator, mem, req);
ENTT_THROW;
}
std::uninitialized_copy(packed, packed + count, mem);
std::uninitialized_fill(mem + count, mem + req, tombstone);
std::destroy(packed, packed + count);
std::destroy(packed, packed + reserved);
alloc_traits::deallocate(allocator, packed, reserved);
packed = mem;
@@ -235,46 +217,55 @@ class basic_sparse_set {
}
}
std::destroy(packed, packed + count);
std::destroy(packed, packed + reserved);
std::destroy(sparse, sparse + bucket);
alloc_traits::deallocate(allocator, packed, reserved);
bucket_alloc_traits::deallocate(bucket_allocator, sparse, bucket);
}
}
void push_back(const Entity entt) {
ENTT_ASSERT(count != reserved, "No more space left");
alloc_traits::construct(allocator, std::addressof(packed[count]), entt);
ENTT_TRY {
assure_page(page(entt))[offset(entt)] = entity_type{static_cast<typename traits_type::entity_type>(count)};
} ENTT_CATCH {
alloc_traits::destroy(allocator, std::addressof(packed[count]));
ENTT_THROW;
}
++count;
}
void pop(const Entity entt, void *ud) {
ENTT_ASSERT(contains(entt), "Set does not contain entity");
void stable_erase(const Entity entt, void *ud = nullptr) {
// last chance to use the entity for derived classes and mixins, if any
about_to_erase(entt, ud);
about_to_pop(entt, ud);
auto &ref = sparse[page(entt)][offset(entt)];
const auto pos = size_type{traits_type::to_integral(ref)};
auto &last = packed[count - 1u];
ENTT_ASSERT(packed[pos] == entt, "Invalid entity identifier");
// basic no-leak guarantee (with invalid state) if copy assignment operators throw
std::swap(sparse[page(last)][offset(last)], ref);
// swapping with entt isn't strictly required but it prevents nasty bugs
std::swap(packed[pos], last);
// no risks when pos == count, accessing packed is no longer required
packed[pos] = std::exchange(free_list, (tombstone | entity_type{static_cast<typename traits_type::entity_type>(pos)}));
ref = null;
// don't expect exceptions here, instead allow for nosy destructors
alloc_traits::destroy(allocator, std::addressof(packed[--count]));
swap_and_pop(pos);
// strong exception guarantee
in_place_pop(pos);
}
void unstable_erase(const Entity entt, void *ud = nullptr) {
// last chance to use the entity for derived classes and mixins, if any
about_to_pop(entt, ud);
auto &ref = sparse[page(entt)][offset(entt)];
const auto pos = size_type{traits_type::to_integral(ref)};
ENTT_ASSERT(packed[pos] == entt, "Invalid entity identifier");
auto &last = packed[count - 1u];
packed[pos] = last;
sparse[page(last)][offset(last)] = ref;
// lazy self-assignment guard
ref = null;
// unnecessary but it helps to detect nasty bugs
ENTT_ASSERT((last = tombstone, true), "");
--count;
ENTT_TRY {
// strong exception guarantee
swap_and_pop(pos);
} ENTT_CATCH {
last = std::exchange(packed[pos], entt);
ref = std::exchange(sparse[page(last)][offset(last)], entity_type{static_cast<typename traits_type::entity_type>(count++)});
ENTT_THROW;
}
}
protected:
@@ -285,18 +276,31 @@ protected:
*/
virtual void swap_at([[maybe_unused]] const std::size_t lhs, [[maybe_unused]] const std::size_t rhs) {}
/**
* @brief Attempts to move an entity in the internal packed array.
* @param from A valid position of an entity within storage.
* @param to A valid position of an entity within storage.
*/
virtual void move_and_pop([[maybe_unused]] const std::size_t from, [[maybe_unused]] const std::size_t to) {}
/**
* @brief Attempts to erase an entity from the internal packed array.
* @param pos A valid position of an entity within storage.
*/
virtual void swap_and_pop([[maybe_unused]] const std::size_t pos) {}
/**
* @brief Attempts to erase an entity from the internal packed array.
* @param pos A valid position of an entity within storage.
*/
virtual void in_place_pop([[maybe_unused]] const std::size_t pos) {}
/**
* @brief Last chance to use an entity that is about to be erased.
* @param entity A valid entity identifier.
* @param ud Optional user data that are forwarded as-is to derived classes.
*/
virtual void about_to_erase([[maybe_unused]] const Entity entity, [[maybe_unused]] void *ud) {}
virtual void about_to_pop([[maybe_unused]] const Entity entity, [[maybe_unused]] void *ud) {}
public:
/*! @brief Allocator type. */
@@ -310,20 +314,31 @@ public:
/*! @brief Random access iterator type. */
using iterator = sparse_set_iterator;
/*! @brief Reverse iterator type. */
using reverse_iterator = pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
/**
* @brief Default constructor.
* @brief Constructs an empty container with the given policy and allocator.
* @param pol Type of deletion policy.
* @param alloc Allocator to use (possibly default-constructed).
*/
explicit basic_sparse_set(const allocator_type &alloc = {})
explicit basic_sparse_set(deletion_policy pol, const allocator_type &alloc = {})
: allocator{alloc},
bucket_allocator{alloc},
sparse{bucket_alloc_traits::allocate(bucket_allocator, 0u)},
packed{alloc_traits::allocate(allocator, 0u)},
bucket{0u},
count{0u},
reserved{0u}
reserved{0u},
free_list{tombstone},
policy{pol}
{}
/**
* @brief Constructs an empty container with the given allocator.
* @param alloc Allocator to use (possibly default-constructed).
*/
explicit basic_sparse_set(const allocator_type &alloc = {})
: basic_sparse_set{deletion_policy::swap_and_pop, alloc}
{}
/**
@@ -337,7 +352,9 @@ public:
packed{std::exchange(other.packed, alloc_pointer{})},
bucket{std::exchange(other.bucket, 0u)},
count{std::exchange(other.count, 0u)},
reserved{std::exchange(other.reserved, 0u)}
reserved{std::exchange(other.reserved, 0u)},
free_list{std::exchange(other.free_list, tombstone)},
policy{other.policy}
{}
/*! @brief Default destructor. */
@@ -360,6 +377,8 @@ public:
bucket = std::exchange(other.bucket, 0u);
count = std::exchange(other.count, 0u);
reserved = std::exchange(other.reserved, 0u);
free_list = std::exchange(other.free_list, tombstone);
policy = other.policy;
return *this;
}
@@ -476,7 +495,7 @@ public:
* array.
*/
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
return data();
return std::make_reverse_iterator(end());
}
/**
@@ -490,7 +509,7 @@ public:
* reversed internal packed array.
*/
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
return rbegin() + count;
return std::make_reverse_iterator(begin());
}
/**
@@ -499,7 +518,7 @@ public:
* @return An iterator to the given entity if it's found, past the end
* iterator otherwise.
*/
[[nodiscard]] iterator find(const entity_type entt) const {
[[nodiscard]] iterator find(const entity_type entt) const ENTT_NOEXCEPT {
return contains(entt) ? --(end() - index(entt)) : end();
}
@@ -508,9 +527,9 @@ public:
* @param entt A valid entity identifier.
* @return True if the sparse set contains the entity, false otherwise.
*/
[[nodiscard]] bool contains(const entity_type entt) const {
[[nodiscard]] bool contains(const entity_type entt) const ENTT_NOEXCEPT {
const auto curr = page(entt);
// testing against null permits to avoid accessing the packed array
// testing versions permits to avoid accessing the packed array
return (curr < bucket && sparse[curr] && sparse[curr][offset(entt)] != null);
}
@@ -524,7 +543,7 @@ public:
* @param entt A valid entity identifier.
* @return The position of the entity in the sparse set.
*/
[[nodiscard]] size_type index(const entity_type entt) const {
[[nodiscard]] size_type index(const entity_type entt) const ENTT_NOEXCEPT {
ENTT_ASSERT(contains(entt), "Set does not contain entity");
return size_type{traits_type::to_integral(sparse[page(entt)][offset(entt)])};
}
@@ -534,7 +553,7 @@ public:
* @param pos The position for which to return the entity.
* @return The entity at specified location if any, a null entity otherwise.
*/
[[nodiscard]] entity_type at(const size_type pos) const {
[[nodiscard]] entity_type at(const size_type pos) const ENTT_NOEXCEPT {
return pos < count ? packed[pos] : null;
}
@@ -543,7 +562,7 @@ public:
* @param pos The position for which to return the entity.
* @return The entity at specified location.
*/
[[nodiscard]] entity_type operator[](const size_type pos) const {
[[nodiscard]] entity_type operator[](const size_type pos) const ENTT_NOEXCEPT {
ENTT_ASSERT(pos < count, "Position is out of bounds");
return packed[pos];
}
@@ -560,12 +579,20 @@ public:
void emplace(const entity_type entt) {
ENTT_ASSERT(!contains(entt), "Set already contains entity");
if(count == reserved) {
const size_type sz = static_cast<size_type>(reserved * growth_factor);
resize_packed(sz + !(sz > reserved));
}
if(free_list == null) {
if(count == reserved) {
const size_type sz = static_cast<size_type>(reserved * growth_factor);
resize_packed(sz + !(sz > reserved));
}
push_back(entt);
assure_page(page(entt))[offset(entt)] = entity_type{static_cast<typename traits_type::entity_type>(count)};
packed[count++] = entt;
} else {
const auto pos = size_type{traits_type::to_entity(free_list)};
move_and_pop(count, pos);
sparse[page(entt)][offset(entt)] = entity_type{static_cast<typename traits_type::entity_type>(pos)};
free_list = std::exchange(packed[pos], entt);
}
}
/**
@@ -584,8 +611,10 @@ public:
reserve(count + std::distance(first, last));
for(; first != last; ++first) {
ENTT_ASSERT(!contains(*first), "Set already contains entity");
push_back(*first);
const auto entt = *first;
ENTT_ASSERT(!contains(entt), "Set already contains entity");
assure_page(page(entt))[offset(entt)] = entity_type{static_cast<typename traits_type::entity_type>(count)};
packed[count++] = entt;
}
}
@@ -600,11 +629,15 @@ public:
* @param ud Optional user data that are forwarded as-is to derived classes.
*/
void erase(const entity_type entt, void *ud = nullptr) {
pop(entt, ud);
ENTT_ASSERT(contains(entt), "Set does not contain entity");
(policy == deletion_policy::in_place) ? stable_erase(entt, ud) : unstable_erase(entt, ud);
}
/**
* @brief Erases multiple entities from a set.
* @brief Erases entities from a set.
*
* @sa erase
*
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
@@ -613,7 +646,7 @@ public:
template<typename It>
void erase(It first, It last, void *ud = nullptr) {
for(; first != last; ++first) {
pop(*first, ud);
erase(*first, ud);
}
}
@@ -624,11 +657,16 @@ public:
* @return True if the entity is actually removed, false otherwise.
*/
bool remove(const entity_type entt, void *ud = nullptr) {
return contains(entt) ? (pop(entt, ud), true) : false;
if(contains(entt)) {
erase(entt, ud);
return true;
}
return false;
}
/**
* @brief Removes multiple entities from a sparse set if they exist.
* @brief Removes entities from a sparse set if they exist.
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
@@ -646,6 +684,26 @@ public:
return found;
}
/*! @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(auto *it = &free_list; *it != null && next; it = std::addressof(packed[traits_type::to_entity(*it)])) {
if(const size_type pos = traits_type::to_entity(*it); pos < next) {
--next;
move_and_pop(next, pos);
std::swap(packed[next], packed[pos]);
sparse[page(packed[pos])][offset(packed[pos])] = entity_type{static_cast<const typename traits_type::entity_type>(pos)};
*it = (tombstone | entity_type{static_cast<typename traits_type::entity_type>(next)});
for(; next && packed[next - 1u] == tombstone; --next);
}
}
free_list = tombstone;
count = next;
}
/**
* @copybrief swap_at
*
@@ -703,6 +761,8 @@ public:
void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&... args) {
// basic no-leak guarantee (with invalid state) if sorting throws
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)...);
for(size_type pos{}; pos < length; ++pos) {
@@ -715,9 +775,7 @@ public:
swap_at(next, idx);
sparse[page(entt)][offset(entt)] = entity_type{static_cast<typename traits_type::entity_type>(curr)};
curr = next;
next = idx;
curr = std::exchange(next, idx);
}
}
}
@@ -755,12 +813,12 @@ public:
* @param other The sparse sets that imposes the order of the entities.
*/
void respect(const basic_sparse_set &other) {
compact();
const auto to = other.end();
auto from = other.begin();
size_type pos = count - 1;
while(pos && from != to) {
for(size_type pos = count - 1; pos && from != to; ++from) {
if(contains(*from)) {
if(*from != packed[pos]) {
// basic no-leak guarantee (with invalid state) if swapping throws
@@ -769,8 +827,6 @@ public:
--pos;
}
++from;
}
}
@@ -778,8 +834,15 @@ public:
* @brief Clears a sparse set.
* @param ud Optional user data that are forwarded as-is to derived classes.
*/
void clear(void *ud = nullptr) ENTT_NOEXCEPT {
erase(begin(), end(), ud);
void clear(void *ud = nullptr) {
for(auto &&entity: *this) {
if(entity != tombstone) {
stable_erase(entity, ud);
}
}
free_list = tombstone;
count = 0u;
}
private:
@@ -790,6 +853,8 @@ private:
std::size_t bucket;
std::size_t count;
std::size_t reserved;
entity_type free_list;
deletion_policy policy;
};
+82 -64
View File
@@ -13,6 +13,7 @@
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
#include "../signal/sigh.hpp"
#include "component.hpp"
#include "entity.hpp"
#include "fwd.hpp"
#include "sparse_set.hpp"
@@ -47,14 +48,14 @@ namespace entt {
* @tparam Type Type of objects assigned to the entities.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Entity, typename Type, typename Allocator, typename>
class basic_storage: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
static_assert(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>, "The managed type must be at least move constructible and assignable");
template<typename Entity, typename Type, typename Allocator, typename = void>
class basic_storage_impl: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
static constexpr auto packed_page = ENTT_PACKED_PAGE;
using comp_traits = component_traits<Type>;
using underlying_type = basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>>;
using traits_type = entt_traits<Entity>;
using difference_type = typename entt_traits<Entity>::difference_type;
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Type>;
using alloc_pointer = typename alloc_traits::pointer;
@@ -70,15 +71,8 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
static_assert(bucket_alloc_traits::propagate_on_container_move_assignment::value);
template<typename Value>
class storage_iterator final {
friend class basic_storage<Entity, Type, Allocator>;
storage_iterator(bucket_alloc_pointer const *ref, const typename traits_type::difference_type idx) ENTT_NOEXCEPT
: packed{ref}, index{idx}
{}
public:
using difference_type = typename traits_type::difference_type;
struct storage_iterator final {
using difference_type = typename basic_storage_impl::difference_type;
using value_type = Value;
using pointer = value_type *;
using reference = value_type &;
@@ -86,6 +80,11 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
storage_iterator() ENTT_NOEXCEPT = default;
storage_iterator(bucket_alloc_pointer const *ref, const typename basic_storage_impl::difference_type idx) ENTT_NOEXCEPT
: packed{ref},
index{idx}
{}
storage_iterator & operator++() ENTT_NOEXCEPT {
return --index, *this;
}
@@ -179,13 +178,10 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
void release_memory() {
if(packed) {
for(size_type pos{}; pos < bucket; ++pos) {
if(auto length = underlying_type::size(); length) {
const auto sz = length > packed_page ? packed_page : length;
std::destroy(packed[pos], packed[pos] + sz);
length -= sz;
}
// no-throw stable erase iteration
underlying_type::clear();
for(size_type pos{}; pos < bucket; ++pos) {
alloc_traits::deallocate(allocator, packed[pos], packed_page);
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(packed[pos]));
}
@@ -201,31 +197,20 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
const auto mem = bucket_alloc_traits::allocate(bucket_allocator, length);
if(bucket > length) {
ENTT_TRY {
std::uninitialized_copy(packed, packed + length, mem);
} ENTT_CATCH {
bucket_alloc_traits::deallocate(bucket_allocator, mem, length);
ENTT_THROW;
}
std::uninitialized_copy(packed, packed + length, mem);
for(auto pos = length; pos < bucket; ++pos) {
alloc_traits::deallocate(allocator, packed[pos], packed_page);
}
} else {
std::uninitialized_copy(packed, packed + bucket, mem);
size_type pos{};
ENTT_TRY {
std::uninitialized_copy(packed, packed + bucket, mem);
for(pos = bucket; pos < length; ++pos) {
auto pg = alloc_traits::allocate(allocator, packed_page);
ENTT_TRY {
bucket_alloc_traits::construct(bucket_allocator, std::addressof(mem[pos]), pg);
} ENTT_CATCH {
alloc_traits::deallocate(allocator, pg, packed_page);
ENTT_THROW;
}
bucket_alloc_traits::construct(bucket_allocator, std::addressof(mem[pos]), pg);
}
} ENTT_CATCH {
for(auto next = bucket; next < pos; ++next) {
@@ -247,7 +232,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
}
template<typename... Args>
Type & push_back(Args &&... args) {
auto & push_back(Args &&... args) {
const auto length = underlying_type::size();
ENTT_ASSERT(length < (bucket * packed_page), "No more space left");
auto *instance = std::addressof(packed[page(length)][offset(length)]);
@@ -271,24 +256,31 @@ protected:
std::swap(packed[page(lhs)][offset(lhs)], packed[page(rhs)][offset(rhs)]);
}
/*! @copydoc basic_sparse_set::move_and_pop */
void move_and_pop(const std::size_t from, const std::size_t to) final {
auto *instance = std::addressof(packed[page(to)][offset(to)]);
auto *other = std::addressof(packed[page(from)][offset(from)]);
alloc_traits::construct(allocator, instance, std::move(*other));
alloc_traits::destroy(allocator, other);
}
/*! @copydoc basic_sparse_set::swap_and_pop */
void swap_and_pop(const std::size_t pos) final {
const auto length = underlying_type::size();
auto &&elem = packed[page(pos)][offset(pos)];
auto &&last = packed[page(length)][offset(length)];
ENTT_TRY {
[[maybe_unused]] auto other = std::move(elem);
elem = std::move(last);
} ENTT_CATCH {
// basic no-leak guarantee (with invalid state) if swapping throws
alloc_traits::destroy(allocator, std::addressof(last));
ENTT_THROW;
}
// support for nosy destructors
[[maybe_unused]] auto unused = std::move(elem);
elem = std::move(last);
alloc_traits::destroy(allocator, std::addressof(last));
}
/*! @copydoc basic_sparse_set::in_place_pop */
void in_place_pop(const std::size_t pos) final {
alloc_traits::destroy(allocator, std::addressof(packed[page(pos)][offset(pos)]));
}
public:
/*! @brief Allocator type. */
using allocator_type = typename alloc_traits::allocator_type;
@@ -303,9 +295,9 @@ public:
/*! @brief Constant pointer type to contained elements. */
using const_pointer = bucket_alloc_const_pointer;
/*! @brief Random access iterator type. */
using iterator = storage_iterator<Type>;
using iterator = storage_iterator<value_type>;
/*! @brief Constant random access iterator type. */
using const_iterator = storage_iterator<const Type>;
using const_iterator = storage_iterator<const value_type>;
/*! @brief Reverse iterator type. */
using reverse_iterator = std::reverse_iterator<iterator>;
/*! @brief Constant reverse iterator type. */
@@ -315,11 +307,11 @@ public:
* @brief Default constructor.
* @param alloc Allocator to use (possibly default-constructed).
*/
explicit basic_storage(const allocator_type &alloc = {})
: underlying_type{alloc},
explicit basic_storage_impl(const allocator_type &alloc = {})
: underlying_type{deletion_policy{comp_traits::in_place_delete::value}, alloc},
allocator{alloc},
bucket_allocator{alloc},
packed{},
packed{bucket_alloc_traits::allocate(bucket_allocator, 0u)},
bucket{}
{}
@@ -327,7 +319,7 @@ public:
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_storage(basic_storage &&other) ENTT_NOEXCEPT
basic_storage_impl(basic_storage_impl &&other) ENTT_NOEXCEPT
: underlying_type{std::move(other)},
allocator{std::move(other.allocator)},
bucket_allocator{std::move(other.bucket_allocator)},
@@ -336,7 +328,7 @@ public:
{}
/*! @brief Default destructor. */
~basic_storage() override {
~basic_storage_impl() override {
release_memory();
}
@@ -345,7 +337,7 @@ public:
* @param other The instance to move from.
* @return This sparse set.
*/
basic_storage & operator=(basic_storage &&other) ENTT_NOEXCEPT {
basic_storage_impl & operator=(basic_storage_impl &&other) ENTT_NOEXCEPT {
release_memory();
underlying_type::operator=(std::move(other));
@@ -411,7 +403,7 @@ public:
* @return An iterator to the first instance of the internal array.
*/
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
const typename traits_type::difference_type pos = underlying_type::size();
const difference_type pos = underlying_type::size();
return const_iterator{std::addressof(packed), pos};
}
@@ -422,7 +414,7 @@ public:
/*! @copydoc begin */
[[nodiscard]] iterator begin() ENTT_NOEXCEPT {
const typename traits_type::difference_type pos = underlying_type::size();
const difference_type pos = underlying_type::size();
return iterator{std::addressof(packed), pos};
}
@@ -507,13 +499,13 @@ public:
* @param entt A valid entity identifier.
* @return The object assigned to the entity.
*/
[[nodiscard]] const value_type & get(const entity_type entt) const {
[[nodiscard]] const value_type & get(const entity_type entt) const ENTT_NOEXCEPT {
const auto idx = underlying_type::index(entt);
return packed[page(idx)][offset(idx)];
}
/*! @copydoc get */
[[nodiscard]] value_type & get(const entity_type entt) {
[[nodiscard]] value_type & get(const entity_type entt) ENTT_NOEXCEPT {
return const_cast<value_type &>(std::as_const(*this).get(entt));
}
@@ -696,12 +688,18 @@ private:
};
/*! @copydoc basic_storage */
/*! @copydoc basic_storage_impl */
template<typename Entity, typename Type, typename Allocator>
class basic_storage<Entity, Type, Allocator, std::enable_if_t<std::conjunction_v<ENTT_IGNORE_IF_EMPTY, std::is_empty<Type>>>>: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
class basic_storage_impl<Entity, Type, Allocator, std::enable_if_t<component_traits<Type>::ignore_if_empty::value && std::is_empty_v<Type>>>
: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>>
{
using comp_traits = component_traits<Type>;
using underlying_type = basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>>;
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Type>;
public:
/*! @brief Allocator type. */
using allocator_type = typename alloc_traits::allocator_type;
/*! @brief Type of the objects assigned to entities. */
using value_type = Type;
/*! @brief Underlying entity identifier. */
@@ -709,6 +707,14 @@ public:
/*! @brief Unsigned integer type. */
using size_type = std::size_t;
/**
* @brief Default constructor.
* @param alloc Allocator to use (possibly default-constructed).
*/
explicit basic_storage_impl(const allocator_type &alloc = {})
: underlying_type{deletion_policy{comp_traits::in_place_delete::value}, alloc}
{}
/**
* @brief Fake get function.
*
@@ -718,7 +724,7 @@ public:
*
* @param entt A valid entity identifier.
*/
void get([[maybe_unused]] const entity_type entt) const {
void get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
ENTT_ASSERT(underlying_type::contains(entt), "Storage does not contain entity");
}
@@ -832,11 +838,11 @@ struct storage_adapter_mixin: Type {
*/
template<typename Type>
class sigh_storage_mixin final: public Type {
/*! @copydoc basic_sparse_set::about_to_erase */
void about_to_erase(const typename Type::entity_type entity, void *ud) final {
/*! @copydoc basic_sparse_set::about_to_pop */
void about_to_pop(const typename Type::entity_type entity, void *ud) final {
ENTT_ASSERT(ud != nullptr, "Invalid pointer to registry");
destruction.publish(*static_cast<basic_registry<typename Type::entity_type> *>(ud), entity);
Type::about_to_erase(entity, ud);
Type::about_to_pop(entity, ud);
}
public:
@@ -973,9 +979,21 @@ private:
/**
* @brief Defines the component-to-storage conversion.
* @brief Storage implementation dispatcher.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Type Type of objects assigned to the entities.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Entity, typename Type, typename Allocator>
struct basic_storage: basic_storage_impl<Entity, Type, Allocator> {
using basic_storage_impl<Entity, Type, Allocator>::basic_storage_impl;
};
/**
* @brief Provides a common way to access certain properties of storage types.
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Type Type of objects managed by the storage class.
*/
template<typename Entity, typename Type, typename = void>
struct storage_traits {
+1
View File
@@ -9,6 +9,7 @@
#include "core/type_info.hpp"
#include "core/type_traits.hpp"
#include "core/utility.hpp"
#include "entity/component.hpp"
#include "entity/entity.hpp"
#include "entity/group.hpp"
#include "entity/handle.hpp"
+324 -77
View File
@@ -5,10 +5,10 @@
#include <functional>
#include <type_traits>
#include <gtest/gtest.h>
#include <entt/entity/entity.hpp>
#include <entt/entity/sparse_set.hpp>
#include <entt/entity/fwd.hpp>
#include "throwing_allocator.hpp"
#include "throwing_entity.hpp"
struct empty_type {};
struct boxed_int { int value; };
@@ -73,6 +73,50 @@ TEST(SparseSet, Functionalities) {
ASSERT_FALSE(set.contains(entt::entity{42}));
}
TEST(SparseSet, Contains) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set{entt::deletion_policy::in_place};
ASSERT_FALSE(set.contains(entt::null));
ASSERT_FALSE(set.contains(entt::tombstone));
set.emplace(entt::entity{0});
set.emplace(entt::entity{3});
set.emplace(entt::entity{42});
set.emplace(entt::entity{99});
set.emplace(entt::entity{1});
ASSERT_TRUE(set.contains(entt::entity{0}));
ASSERT_TRUE(set.contains(entt::entity{3}));
ASSERT_TRUE(set.contains(entt::entity{42}));
ASSERT_TRUE(set.contains(entt::entity{99}));
ASSERT_TRUE(set.contains(entt::entity{1}));
set.erase(entt::entity{0});
set.erase(entt::entity{3});
set.remove(entt::entity{42});
set.remove(entt::entity{99});
ASSERT_FALSE(set.contains(entt::entity{0}));
ASSERT_FALSE(set.contains(entt::entity{3}));
ASSERT_FALSE(set.contains(entt::entity{42}));
ASSERT_FALSE(set.contains(entt::entity{99}));
ASSERT_TRUE(set.contains(entt::entity{1}));
ASSERT_FALSE(set.contains(entt::null));
ASSERT_FALSE(set.contains(entt::tombstone));
// tombstones and null entities can trigger false positives
ASSERT_TRUE(set.contains(traits_type::to_type(entt::entity{1}, entt::null)));
ASSERT_TRUE(set.contains(traits_type::to_type(entt::entity{1}, entt::tombstone)));
ASSERT_FALSE(set.contains(traits_type::to_type(entt::null, entt::entity{1})));
ASSERT_FALSE(set.contains(traits_type::to_type(entt::tombstone, entt::entity{1})));
}
TEST(SparseSet, Move) {
entt::sparse_set set;
set.emplace(entt::entity{42});
@@ -141,18 +185,18 @@ TEST(SparseSet, Pagination) {
TEST(SparseSet, Insert) {
entt::sparse_set set;
entt::entity entities[2];
entt::entity entities[2u];
entities[0] = entt::entity{3};
entities[1] = entt::entity{42};
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
set.emplace(entt::entity{12});
set.insert(std::end(entities), std::end(entities));
set.insert(std::begin(entities), std::end(entities));
set.emplace(entt::entity{24});
ASSERT_TRUE(set.contains(entities[0]));
ASSERT_TRUE(set.contains(entities[1]));
ASSERT_TRUE(set.contains(entities[0u]));
ASSERT_TRUE(set.contains(entities[1u]));
ASSERT_FALSE(set.contains(entt::entity{0}));
ASSERT_FALSE(set.contains(entt::entity{9}));
ASSERT_TRUE(set.contains(entt::entity{12}));
@@ -161,27 +205,27 @@ TEST(SparseSet, Insert) {
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 4u);
ASSERT_EQ(set.index(entt::entity{12}), 0u);
ASSERT_EQ(set.index(entities[0]), 1u);
ASSERT_EQ(set.index(entities[1]), 2u);
ASSERT_EQ(set.index(entities[0u]), 1u);
ASSERT_EQ(set.index(entities[1u]), 2u);
ASSERT_EQ(set.index(entt::entity{24}), 3u);
ASSERT_EQ(set.data()[set.index(entt::entity{12})], entt::entity{12});
ASSERT_EQ(set.data()[set.index(entities[0])], entities[0]);
ASSERT_EQ(set.data()[set.index(entities[1])], entities[1]);
ASSERT_EQ(set.data()[set.index(entities[0u])], entities[0u]);
ASSERT_EQ(set.data()[set.index(entities[1u])], entities[1u]);
ASSERT_EQ(set.data()[set.index(entt::entity{24})], entt::entity{24});
}
TEST(SparseSet, Erase) {
entt::sparse_set set;
entt::entity entities[3];
entt::entity entities[3u];
entities[0] = entt::entity{3};
entities[1] = entt::entity{42};
entities[2] = entt::entity{9};
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
ASSERT_TRUE(set.empty());
ASSERT_DEATH(set.erase(std::begin(entities), std::end(entities)), "");
ASSERT_DEATH(set.erase(entities[1]), "");
ASSERT_DEATH(set.erase(entities[1u]), "");
ASSERT_TRUE(set.empty());
@@ -194,33 +238,122 @@ TEST(SparseSet, Erase) {
set.erase(entities, entities + 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(*set.begin(), entt::entity{9});
ASSERT_EQ(*set.begin(), entities[2u]);
set.erase(entities[2]);
set.erase(entities[2u]);
ASSERT_DEATH(set.erase(entities[2]), "");
ASSERT_DEATH(set.erase(entities[2u]), "");
ASSERT_TRUE(set.empty());
set.insert(std::begin(entities), std::end(entities));
std::swap(entities[1], entities[2]);
std::swap(entities[1u], entities[2u]);
set.erase(entities, entities + 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(*set.begin(), entt::entity{42});
ASSERT_EQ(*set.begin(), entities[2u]);
}
TEST(SparseSet, StableErase) {
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[3u];
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_DEATH(set.erase(std::begin(entities), std::end(entities)), "");
ASSERT_DEATH(set.erase(entities[1u]), "");
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
set.insert(std::begin(entities), std::end(entities));
set.erase(set.begin(), set.end());
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 3u);
ASSERT_TRUE(set.at(0u) == entt::tombstone);
ASSERT_TRUE(set.at(1u) == entt::tombstone);
ASSERT_TRUE(set.at(2u) == entt::tombstone);
set.insert(std::begin(entities), std::end(entities));
set.erase(entities, entities + 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 6u);
ASSERT_EQ(*set.begin(), entities[2u]);
ASSERT_TRUE(set.at(3u) == entt::tombstone);
ASSERT_TRUE(set.at(4u) == entt::tombstone);
set.erase(entities[2u]);
ASSERT_DEATH(set.erase(entities[2u]), "");
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 6u);
set.insert(std::begin(entities), std::end(entities));
std::swap(entities[1u], entities[2u]);
set.erase(entities, entities + 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 9u);
ASSERT_TRUE(set.at(6u) == entt::tombstone);
ASSERT_EQ(set.at(7u), entities[2u]);
ASSERT_EQ(*++set.begin(), entities[2u]);
ASSERT_TRUE(set.at(8u) == entt::tombstone);
set.compact();
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u);
ASSERT_EQ(*set.begin(), entities[2u]);
set.clear();
ASSERT_EQ(set.size(), 0u);
set.insert(std::begin(entities), std::end(entities));
set.erase(entities[2u]);
ASSERT_DEATH(set.erase(entities[2u]), "");
set.erase(entities[0u]);
set.erase(entities[1u]);
ASSERT_DEATH(set.erase(entities, entities + 2u), "");
ASSERT_EQ(set.size(), 3u);
ASSERT_TRUE(*set.begin() == entt::tombstone);
set.emplace(entities[0u]);
ASSERT_EQ(*++set.begin(), entities[0u]);
set.emplace(entities[1u]);
set.emplace(entities[2u]);
set.emplace(entt::entity{0});
ASSERT_EQ(set.size(), 4u);
ASSERT_EQ(*set.begin(), entt::entity{0});
ASSERT_EQ(set.at(0u), entities[1u]);
ASSERT_EQ(set.at(1u), entities[0u]);
ASSERT_EQ(set.at(2u), entities[2u]);
}
TEST(SparseSet, Remove) {
entt::sparse_set set;
entt::entity entities[3];
entt::entity entities[3u];
entities[0] = entt::entity{3};
entities[1] = entt::entity{42};
entities[2] = entt::entity{9};
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u);
ASSERT_EQ(set.remove(entities[1]), 0u);
ASSERT_EQ(set.remove(entities[1u]), 0u);
ASSERT_TRUE(set.empty());
@@ -233,10 +366,10 @@ TEST(SparseSet, Remove) {
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(*set.begin(), entt::entity{9});
ASSERT_EQ(*set.begin(), entities[2u]);
ASSERT_EQ(set.remove(entities[2]), 1u);
ASSERT_EQ(set.remove(entities[2]), 0u);
ASSERT_EQ(set.remove(entities[2u]), 1u);
ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_TRUE(set.empty());
set.insert(entities, entities + 2u);
@@ -245,11 +378,152 @@ TEST(SparseSet, Remove) {
ASSERT_TRUE(set.empty());
set.insert(std::begin(entities), std::end(entities));
std::swap(entities[1], entities[2]);
std::swap(entities[1u], entities[2u]);
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(*set.begin(), entt::entity{42});
ASSERT_EQ(*set.begin(), entities[2u]);
}
TEST(SparseSet, StableRemove) {
entt::sparse_set set{entt::deletion_policy::in_place};
entt::entity entities[3u];
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 0u);
ASSERT_EQ(set.remove(entities[1u]), 0u);
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
set.insert(std::begin(entities), std::end(entities));
ASSERT_EQ(set.remove(set.begin(), set.end()), 3u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 3u);
ASSERT_TRUE(set.at(0u) == entt::tombstone);
ASSERT_TRUE(set.at(1u) == entt::tombstone);
ASSERT_TRUE(set.at(2u) == entt::tombstone);
set.insert(std::begin(entities), std::end(entities));
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 6u);
ASSERT_EQ(*set.begin(), entt::entity{9});
ASSERT_TRUE(set.at(3u) == entt::tombstone);
ASSERT_TRUE(set.at(4u) == entt::tombstone);
ASSERT_EQ(set.remove(entities[2u]), 1u);
ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 6u);
ASSERT_TRUE(*set.begin() == entt::tombstone);
set.insert(entities, entities + 2u);
ASSERT_EQ(set.remove(std::begin(entities), std::end(entities)), 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 8u);
ASSERT_TRUE(set.at(6u) == entt::tombstone);
ASSERT_TRUE(set.at(7u) == entt::tombstone);
set.insert(std::begin(entities), std::end(entities));
std::swap(entities[1u], entities[2u]);
ASSERT_EQ(set.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 11u);
ASSERT_TRUE(set.at(8u) == entt::tombstone);
ASSERT_EQ(set.at(9u), entities[2u]);
ASSERT_EQ(*++set.begin(), entities[2u]);
ASSERT_TRUE(set.at(10u) == entt::tombstone);
set.compact();
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u);
ASSERT_EQ(*set.begin(), entities[2u]);
set.clear();
ASSERT_EQ(set.size(), 0u);
set.insert(std::begin(entities), std::end(entities));
ASSERT_EQ(set.remove(entities[2u]), 1u);
ASSERT_EQ(set.remove(entities[2u]), 0u);
ASSERT_EQ(set.remove(entities[0u]), 1u);
ASSERT_EQ(set.remove(entities[1u]), 1u);
ASSERT_EQ(set.remove(entities, entities + 2u), 0u);
ASSERT_EQ(set.size(), 3u);
ASSERT_TRUE(*set.begin() == entt::tombstone);
set.emplace(entities[0u]);
ASSERT_EQ(*++set.begin(), entities[0u]);
set.emplace(entities[1u]);
set.emplace(entities[2u]);
set.emplace(entt::entity{0});
ASSERT_EQ(set.size(), 4u);
ASSERT_EQ(*set.begin(), entt::entity{0});
ASSERT_EQ(set.at(0u), entities[1u]);
ASSERT_EQ(set.at(1u), entities[0u]);
ASSERT_EQ(set.at(2u), entities[2u]);
}
TEST(SparseSet, Compact) {
entt::sparse_set set{entt::deletion_policy::in_place};
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
set.compact();
ASSERT_TRUE(set.empty());
ASSERT_EQ(set.size(), 0u);
set.emplace(entt::entity{0});
set.compact();
ASSERT_FALSE(set.empty());
ASSERT_EQ(set.size(), 1u);
set.emplace(entt::entity{42});
set.erase(entt::entity{0});
ASSERT_EQ(set.size(), 2u);
ASSERT_EQ(set.index(entt::entity{42}), 1u);
set.compact();
ASSERT_EQ(set.size(), 1u);
ASSERT_EQ(set.index(entt::entity{42}), 0u);
set.emplace(entt::entity{0});
set.compact();
ASSERT_EQ(set.size(), 2u);
ASSERT_EQ(set.index(entt::entity{42}), 0u);
ASSERT_EQ(set.index(entt::entity{0}), 1u);
set.erase(entt::entity{0});
set.erase(entt::entity{42});
set.compact();
ASSERT_TRUE(set.empty());
}
TEST(SparseSet, Clear) {
@@ -299,7 +573,7 @@ TEST(SparseSet, Iterator) {
ASSERT_EQ(end - (end - begin), set.begin());
ASSERT_EQ(end + (begin - end), set.begin());
ASSERT_EQ(begin[0], *set.begin());
ASSERT_EQ(begin[0u], *set.begin());
ASSERT_LT(begin, end);
ASSERT_LE(begin, set.begin());
@@ -344,7 +618,7 @@ TEST(SparseSet, ReverseIterator) {
ASSERT_EQ(end - (end - begin), set.rbegin());
ASSERT_EQ(end + (begin - end), set.rbegin());
ASSERT_EQ(begin[0], *set.rbegin());
ASSERT_EQ(begin[0u], *set.rbegin());
ASSERT_LT(begin, end);
ASSERT_LE(begin, set.rbegin());
@@ -583,28 +857,6 @@ TEST(SparseSet, CanModifyDuringIteration) {
(void)entity;
}
TEST(SparseSet, ThrowingEntity) {
entt::basic_sparse_set<test::throwing_entity> set{};
test::throwing_entity::trigger_on_entity = 0u;
// strong exception safety
ASSERT_THROW(set.emplace(42), typename test::throwing_entity::exception_type);
ASSERT_TRUE(set.empty());
test::throwing_entity::trigger_on_entity = 42u;
const test::throwing_entity entities[2u]{42, 1};
// basic exception safety
ASSERT_THROW(set.insert(std::begin(entities), std::end(entities)), typename test::throwing_entity::exception_type);
ASSERT_EQ(set.size(), 0u);
ASSERT_FALSE(set.contains(1));
// basic exception safety
ASSERT_THROW(set.insert(std::rbegin(entities), std::rend(entities)), typename test::throwing_entity::exception_type);
ASSERT_EQ(set.size(), 1u);
ASSERT_TRUE(set.contains(1));
}
TEST(SparseSet, ThrowingAllocator) {
entt::basic_sparse_set<entt::entity, test::throwing_allocator<entt::entity>> set{};
@@ -615,6 +867,13 @@ TEST(SparseSet, ThrowingAllocator) {
ASSERT_EQ(set.capacity(), 0u);
ASSERT_EQ(set.extent(), 0u);
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
// strong exception safety
ASSERT_THROW(set.emplace(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.capacity(), 0u);
ASSERT_EQ(set.extent(), 0u);
set.emplace(entt::entity{0});
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
@@ -624,36 +883,24 @@ TEST(SparseSet, ThrowingAllocator) {
ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE);
ASSERT_TRUE(set.contains(entt::entity{0}));
test::throwing_allocator<entt::entity>::trigger_on_pointer_copy = true;
entt::entity entities[2u]{entt::entity{1}, entt::entity{ENTT_SPARSE_PAGE}};
test::throwing_allocator<entt::entity>::trigger_after_allocate = true;
// strong exception safety
ASSERT_THROW(set.reserve(2u), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.capacity(), 1u);
ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE);
// basic exception safety
ASSERT_THROW(set.insert(std::begin(entities), std::end(entities)), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.capacity(), 3u);
ASSERT_EQ(set.size(), 2u);
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
ASSERT_TRUE(set.contains(entt::entity{0}));
ASSERT_TRUE(set.contains(entt::entity{1}));
ASSERT_FALSE(set.contains(entt::entity{ENTT_SPARSE_PAGE}));
set.reserve(ENTT_PACKED_PAGE);
test::throwing_allocator<entt::entity>::trigger_on_pointer_copy = true;
set.emplace(entities[1u]);
// strong exception safety
ASSERT_THROW(set.emplace(entt::entity{ENTT_SPARSE_PAGE + 1u}), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.capacity(), ENTT_PACKED_PAGE);
ASSERT_EQ(set.extent(), ENTT_SPARSE_PAGE);
ASSERT_TRUE(set.contains(entt::entity{0}));
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){};
set.reserve(ENTT_PACKED_PAGE);
set.emplace(entt::entity{ENTT_SPARSE_PAGE + 1u});
test::throwing_allocator<entt::entity>::trigger_on_pointer_copy = true;
// strong exception safety
ASSERT_THROW(set.emplace(entt::entity{0}), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_EQ(set.capacity(), ENTT_PACKED_PAGE);
ASSERT_EQ(set.extent(), 2 * ENTT_SPARSE_PAGE);
ASSERT_FALSE(set.contains(entt::entity{0}));
ASSERT_TRUE(set.contains(entt::entity{ENTT_SPARSE_PAGE + 1u}));
}
+355 -103
View File
@@ -5,14 +5,21 @@
#include <type_traits>
#include <unordered_set>
#include <gtest/gtest.h>
#include <entt/entity/storage.hpp>
#include <entt/entity/component.hpp>
#include <entt/entity/fwd.hpp>
#include <entt/entity/storage.hpp>
#include "throwing_allocator.hpp"
#include "throwing_component.hpp"
#include "throwing_entity.hpp"
struct empty_type {};
struct boxed_int { int value; };
struct stable_type { int value; };
template<>
struct entt::component_traits<stable_type> {
using in_place_delete = std::true_type;
using ignore_if_empty = std::true_type;
};
bool operator==(const boxed_int &lhs, const boxed_int &rhs) {
return lhs.value == rhs.value;
@@ -129,32 +136,32 @@ TEST(Storage, EmptyType) {
TEST(Storage, Insert) {
entt::storage<int> pool;
entt::entity entities[2];
entt::entity entities[2u];
entities[0] = entt::entity{3};
entities[1] = entt::entity{42};
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
pool.insert(std::begin(entities), std::end(entities), {});
ASSERT_TRUE(pool.contains(entities[0]));
ASSERT_TRUE(pool.contains(entities[1]));
ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_TRUE(pool.contains(entities[1u]));
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.get(entities[0]), 0);
ASSERT_EQ(pool.get(entities[1]), 0);
ASSERT_EQ(pool.get(entities[0u]), 0);
ASSERT_EQ(pool.get(entities[1u]), 0);
}
TEST(Storage, InsertEmptyType) {
entt::storage<empty_type> pool;
entt::entity entities[2];
entt::entity entities[2u];
entities[0] = entt::entity{3};
entities[1] = entt::entity{42};
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
pool.insert(std::begin(entities), std::end(entities));
ASSERT_TRUE(pool.contains(entities[0]));
ASSERT_TRUE(pool.contains(entities[1]));
ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_TRUE(pool.contains(entities[1u]));
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
@@ -162,81 +169,340 @@ TEST(Storage, InsertEmptyType) {
TEST(Storage, Erase) {
entt::storage<int> pool;
entt::entity entities[3];
entt::entity entities[3u];
entities[0] = entt::entity{3};
entities[1] = entt::entity{42};
entities[2] = entt::entity{9};
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
pool.emplace(entities[0]);
pool.emplace(entities[1]);
pool.emplace(entities[2]);
pool.emplace(entities[0u]);
pool.emplace(entities[1u]);
pool.emplace(entities[2u]);
pool.erase(std::begin(entities), std::end(entities));
ASSERT_DEATH(pool.erase(std::begin(entities), std::end(entities)), "");
ASSERT_TRUE(pool.empty());
pool.emplace(entities[0], 0);
pool.emplace(entities[1], 1);
pool.emplace(entities[2], 2);
pool.emplace(entities[0u], 0);
pool.emplace(entities[1u], 1);
pool.emplace(entities[2u], 2);
pool.erase(entities, entities + 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(*pool.begin(), 2);
pool.erase(entities[2]);
pool.erase(entities[2u]);
ASSERT_DEATH(pool.erase(entities[2]), "");
ASSERT_DEATH(pool.erase(entities[2u]), "");
ASSERT_TRUE(pool.empty());
pool.emplace(entities[0], 0);
pool.emplace(entities[1], 1);
pool.emplace(entities[2], 2);
std::swap(entities[1], entities[2]);
pool.emplace(entities[0u], 0);
pool.emplace(entities[1u], 1);
pool.emplace(entities[2u], 2);
std::swap(entities[1u], entities[2u]);
pool.erase(entities, entities + 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(*pool.begin(), 1);
}
TEST(Storage, StableErase) {
entt::storage<stable_type> pool;
entt::entity entities[3u];
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entt::tombstone), "");
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entt::null), "");
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{1});
pool.emplace(entities[2u], stable_type{2});
pool.erase(std::begin(entities), std::end(entities));
ASSERT_DEATH(pool.erase(std::begin(entities), std::end(entities)), "");
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.at(2u) == entt::tombstone);
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entt::tombstone), "");
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entt::null), "");
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entities[1u]), "");
pool.emplace(entities[2u], stable_type{2});
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{1});
ASSERT_EQ(pool.get(entities[0u]).value, 0);
ASSERT_EQ(pool.get(entities[1u]).value, 1);
ASSERT_EQ(pool.get(entities[2u]).value, 2);
ASSERT_EQ(pool.begin()->value, 2);
ASSERT_EQ(pool.index(entities[0u]), 1u);
ASSERT_EQ(pool.index(entities[1u]), 0u);
ASSERT_EQ(pool.index(entities[2u]), 2u);
pool.erase(entities, entities + 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_EQ(pool.begin()->value, 2);
ASSERT_EQ(pool.index(entities[2u]), 2u);
pool.erase(entities[2u]);
ASSERT_DEATH(pool.erase(entities[2u]), "");
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_FALSE(pool.contains(entities[0u]));
ASSERT_FALSE(pool.contains(entities[1u]));
ASSERT_FALSE(pool.contains(entities[2u]));
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{1});
pool.emplace(entities[2u], stable_type{2});
std::swap(entities[1u], entities[2u]);
pool.erase(entities, entities + 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.contains(entities[2u]));
ASSERT_EQ(pool.index(entities[2u]), 0u);
ASSERT_EQ(pool.get(entities[2u]).value, 1);
pool.compact();
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 1u);
ASSERT_EQ(pool.begin()->value, 1);
pool.clear();
ASSERT_EQ(pool.size(), 0u);
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{2});
pool.emplace(entities[2u], stable_type{1});
pool.erase(entities[2u]);
ASSERT_DEATH(pool.erase(entities[2u]), "");
pool.erase(entities[0u]);
pool.erase(entities[1u]);
ASSERT_DEATH(pool.erase(entities, entities + 2u), "");
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.at(2u) == entt::tombstone);
pool.emplace(entities[0u], stable_type{99});
ASSERT_EQ((++pool.begin())->value, 99);
pool.emplace(entities[1u], stable_type{2});
pool.emplace(entities[2u], stable_type{1});
pool.emplace(entt::entity{0}, stable_type{7});
ASSERT_EQ(pool.size(), 4u);
ASSERT_EQ(pool.begin()->value, 7);
ASSERT_EQ(pool.at(0u), entities[1u]);
ASSERT_EQ(pool.at(1u), entities[0u]);
ASSERT_EQ(pool.at(2u), entities[2u]);
ASSERT_EQ(pool.get(entities[0u]).value, 99);
ASSERT_EQ(pool.get(entities[1u]).value, 2);
ASSERT_EQ(pool.get(entities[2u]).value, 1);
}
TEST(Storage, Remove) {
entt::storage<int> pool;
entt::entity entities[3];
entt::entity entities[3u];
entities[0] = entt::entity{3};
entities[1] = entt::entity{42};
entities[2] = entt::entity{9};
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
pool.emplace(entities[0]);
pool.emplace(entities[1]);
pool.emplace(entities[2]);
pool.emplace(entities[0u]);
pool.emplace(entities[1u]);
pool.emplace(entities[2u]);
ASSERT_EQ(pool.remove(std::begin(entities), std::end(entities)), 3u);
ASSERT_EQ(pool.remove(std::begin(entities), std::end(entities)), 0u);
ASSERT_TRUE(pool.empty());
pool.emplace(entities[0], 0);
pool.emplace(entities[1], 1);
pool.emplace(entities[2], 2);
pool.emplace(entities[0u], 0);
pool.emplace(entities[1u], 1);
pool.emplace(entities[2u], 2);
ASSERT_EQ(pool.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(*pool.begin(), 2);
ASSERT_EQ(pool.remove(entities[2]), 1u);
ASSERT_EQ(pool.remove(entities[2]), 0u);
ASSERT_EQ(pool.remove(entities[2u]), 1u);
ASSERT_EQ(pool.remove(entities[2u]), 0u);
ASSERT_TRUE(pool.empty());
pool.emplace(entities[0], 0);
pool.emplace(entities[1], 1);
pool.emplace(entities[2], 2);
std::swap(entities[1], entities[2]);
pool.emplace(entities[0u], 0);
pool.emplace(entities[1u], 1);
pool.emplace(entities[2u], 2);
std::swap(entities[1u], entities[2u]);
ASSERT_EQ(pool.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(*pool.begin(), 1);
}
TEST(Storage, StableRemove) {
entt::storage<stable_type> pool;
entt::entity entities[3u];
entities[0u] = entt::entity{3};
entities[1u] = entt::entity{42};
entities[2u] = entt::entity{9};
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{1});
pool.emplace(entities[2u], stable_type{2});
ASSERT_EQ(pool.remove(std::begin(entities), std::end(entities)), 3u);
ASSERT_EQ(pool.remove(std::begin(entities), std::end(entities)), 0u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.at(2u) == entt::tombstone);
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entt::tombstone), "");
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entt::null), "");
ASSERT_DEATH([[maybe_unused]] auto &&value = pool.get(entities[1u]), "");
pool.emplace(entities[2u], stable_type{2});
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{1});
ASSERT_EQ(pool.get(entities[0u]).value, 0);
ASSERT_EQ(pool.get(entities[1u]).value, 1);
ASSERT_EQ(pool.get(entities[2u]).value, 2);
ASSERT_EQ(pool.begin()->value, 2);
ASSERT_EQ(pool.index(entities[0u]), 1u);
ASSERT_EQ(pool.index(entities[1u]), 0u);
ASSERT_EQ(pool.index(entities[2u]), 2u);
ASSERT_EQ(pool.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_EQ(pool.begin()->value, 2);
ASSERT_EQ(pool.index(entities[2u]), 2u);
ASSERT_EQ(pool.remove(entities[2u]), 1u);
ASSERT_EQ(pool.remove(entities[2u]), 0u);
ASSERT_EQ(pool.remove(entities[2u]), 0u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_FALSE(pool.contains(entities[0u]));
ASSERT_FALSE(pool.contains(entities[1u]));
ASSERT_FALSE(pool.contains(entities[2u]));
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{1});
pool.emplace(entities[2u], stable_type{2});
std::swap(entities[1u], entities[2u]);
ASSERT_EQ(pool.remove(entities, entities + 2u), 2u);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.contains(entities[2u]));
ASSERT_EQ(pool.index(entities[2u]), 0u);
ASSERT_EQ(pool.get(entities[2u]).value, 1);
pool.compact();
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 1u);
ASSERT_EQ(pool.begin()->value, 1);
pool.clear();
ASSERT_EQ(pool.size(), 0u);
pool.emplace(entities[0u], stable_type{0});
pool.emplace(entities[1u], stable_type{2});
pool.emplace(entities[2u], stable_type{1});
ASSERT_EQ(pool.remove(entities[2u]), 1u);
ASSERT_EQ(pool.remove(entities[2u]), 0u);
ASSERT_EQ(pool.remove(entities[0u]), 1u);
ASSERT_EQ(pool.remove(entities[1u]), 1u);
ASSERT_EQ(pool.remove(entities, entities + 2u), 0u);
ASSERT_EQ(pool.size(), 3u);
ASSERT_TRUE(pool.at(2u) == entt::tombstone);
pool.emplace(entities[0u], stable_type{99});
ASSERT_EQ((++pool.begin())->value, 99);
pool.emplace(entities[1u], stable_type{2});
pool.emplace(entities[2u], stable_type{1});
pool.emplace(entt::entity{0}, stable_type{7});
ASSERT_EQ(pool.size(), 4u);
ASSERT_EQ(pool.begin()->value, 7);
ASSERT_EQ(pool.at(0u), entities[1u]);
ASSERT_EQ(pool.at(1u), entities[0u]);
ASSERT_EQ(pool.at(2u), entities[2u]);
ASSERT_EQ(pool.get(entities[0u]).value, 99);
ASSERT_EQ(pool.get(entities[1u]).value, 2);
ASSERT_EQ(pool.get(entities[2u]).value, 1);
}
TEST(Storage, Compact) {
entt::storage<stable_type> pool;
ASSERT_TRUE(pool.empty());
ASSERT_EQ(pool.size(), 0u);
pool.compact();
ASSERT_TRUE(pool.empty());
ASSERT_EQ(pool.size(), 0u);
pool.emplace(entt::entity{0}, stable_type{0});
pool.compact();
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 1u);
pool.emplace(entt::entity{42}, stable_type{42});
pool.erase(entt::entity{0});
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.index(entt::entity{42}), 1u);
ASSERT_EQ(pool.get(entt::entity{42}).value, 42);
pool.compact();
ASSERT_EQ(pool.size(), 1u);
ASSERT_EQ(pool.index(entt::entity{42}), 0u);
ASSERT_EQ(pool.get(entt::entity{42}).value, 42);
pool.emplace(entt::entity{0}, stable_type{0});
pool.compact();
ASSERT_EQ(pool.size(), 2u);
ASSERT_EQ(pool.index(entt::entity{42}), 0u);
ASSERT_EQ(pool.index(entt::entity{0}), 1u);
ASSERT_EQ(pool.get(entt::entity{42}).value, 42);
ASSERT_EQ(pool.get(entt::entity{0}).value, 0);
pool.erase(entt::entity{0});
pool.erase(entt::entity{42});
pool.compact();
ASSERT_TRUE(pool.empty());
}
TEST(Storage, ShrinkToFit) {
entt::storage<int> pool;
@@ -312,7 +578,7 @@ TEST(Storage, Iterator) {
ASSERT_EQ(end - (end - begin), pool.begin());
ASSERT_EQ(end + (begin - end), pool.begin());
ASSERT_EQ(begin[0].value, pool.begin()->value);
ASSERT_EQ(begin[0u].value, pool.begin()->value);
ASSERT_LT(begin, end);
ASSERT_LE(begin, pool.begin());
@@ -354,7 +620,7 @@ TEST(Storage, ConstIterator) {
ASSERT_EQ(cend - (cend - cbegin), pool.cbegin());
ASSERT_EQ(cend + (cbegin - cend), pool.cbegin());
ASSERT_EQ(cbegin[0].value, pool.cbegin()->value);
ASSERT_EQ(cbegin[0u].value, pool.cbegin()->value);
ASSERT_LT(cbegin, cend);
ASSERT_LE(cbegin, pool.cbegin());
@@ -396,7 +662,7 @@ TEST(Storage, ReverseIterator) {
ASSERT_EQ(end - (end - begin), pool.rbegin());
ASSERT_EQ(end + (begin - end), pool.rbegin());
ASSERT_EQ(begin[0].value, pool.rbegin()->value);
ASSERT_EQ(begin[0u].value, pool.rbegin()->value);
ASSERT_LT(begin, end);
ASSERT_LE(begin, pool.rbegin());
@@ -438,7 +704,7 @@ TEST(Storage, ConstReverseIterator) {
ASSERT_EQ(cend - (cend - cbegin), pool.crbegin());
ASSERT_EQ(cend + (cbegin - cend), pool.crbegin());
ASSERT_EQ(cbegin[0].value, pool.crbegin()->value);
ASSERT_EQ(cbegin[0u].value, pool.crbegin()->value);
ASSERT_LT(cbegin, cend);
ASSERT_LE(cbegin, pool.crbegin());
@@ -779,42 +1045,6 @@ TEST(Storage, UpdateFromDestructor) {
test(entt::entity{0u});
}
TEST(Storage, ThrowingEntity) {
entt::basic_storage<test::throwing_entity, int> pool;
test::throwing_entity::trigger_on_entity = 42u;
// strong exception safety
ASSERT_THROW(pool.emplace(42, 0), typename test::throwing_entity::exception_type);
ASSERT_TRUE(pool.empty());
const test::throwing_entity entities[2u]{42, 1};
const int components[2u]{42, 1};
// basic exception safety
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), 1), typename test::throwing_entity::exception_type);
ASSERT_EQ(pool.size(), 0u);
ASSERT_FALSE(pool.contains(1));
// basic exception safety
ASSERT_THROW(pool.insert(std::rbegin(entities), std::rend(entities), 1), typename test::throwing_entity::exception_type);
ASSERT_EQ(pool.size(), 1u);
ASSERT_TRUE(pool.contains(1));
ASSERT_EQ(pool.get(1), 1);
pool.clear();
// basic exception safety
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), typename test::throwing_entity::exception_type);
ASSERT_EQ(pool.size(), 0u);
ASSERT_FALSE(pool.contains(1));
// basic exception safety
ASSERT_THROW(pool.insert(std::rbegin(entities), std::rend(entities), std::rbegin(components)), typename test::throwing_entity::exception_type);
ASSERT_EQ(pool.size(), 1u);
ASSERT_TRUE(pool.contains(1));
ASSERT_EQ(pool.get(1), 1);
}
TEST(Storage, ThrowingComponent) {
entt::storage<test::throwing_component> pool;
test::throwing_component::trigger_on_value = 42;
@@ -846,15 +1076,25 @@ TEST(Storage, ThrowingComponent) {
pool.emplace(entt::entity{1}, 1);
pool.emplace(entt::entity{42}, 42);
// basic exception safety
ASSERT_THROW(pool.erase(entt::entity{1}), typename test::throwing_component::exception_type);
ASSERT_FALSE(pool.empty());
ASSERT_EQ(pool.size(), 2u);
ASSERT_TRUE(pool.contains(entt::entity{42}));
ASSERT_TRUE(pool.contains(entt::entity{1}));
ASSERT_EQ(pool.at(0u), entt::entity{1});
ASSERT_EQ(pool.at(1u), entt::entity{42});
ASSERT_EQ(pool.get(entt::entity{42}), 42);
// the element may have been moved but it's still there
ASSERT_EQ(pool.get(entt::entity{1}), test::throwing_component::moved_from_value);
test::throwing_component::trigger_on_value = 99;
pool.erase(entt::entity{1});
ASSERT_EQ(pool.size(), 1u);
ASSERT_TRUE(pool.contains(entt::entity{42}));
ASSERT_FALSE(pool.contains(entt::entity{1}));
ASSERT_EQ(pool.at(0u), entt::entity{42});
ASSERT_EQ(pool.at(1u), static_cast<entt::entity>(entt::null));
// basice exception safety: no-leak guarantee, stored data contain valid values which may differ from the original values
ASSERT_EQ(pool.get(entt::entity{42}), 1);
ASSERT_EQ(pool.get(entt::entity{42}), 42);
}
TEST(Storage, ThrowingAllocator) {
@@ -872,19 +1112,6 @@ TEST(Storage, ThrowingAllocator) {
ASSERT_THROW(pool.reserve(2 * ENTT_PACKED_PAGE), test::throwing_allocator<int>::exception_type);
ASSERT_EQ(pool.capacity(), 0u);
test::throwing_allocator<int>::trigger_on_pointer_copy = true;
// strong exception safety
ASSERT_THROW(pool.reserve(1u), test::throwing_allocator<int>::exception_type);
ASSERT_EQ(pool.capacity(), 0u);
pool.reserve(2 * ENTT_PACKED_PAGE);
test::throwing_allocator<test::throwing_allocator<int>::pointer>::trigger_on_pointer_copy = true;
// strong exception safety
ASSERT_THROW(pool.shrink_to_fit(), test::throwing_allocator<test::throwing_allocator<int>::pointer>::exception_type);
ASSERT_EQ(pool.capacity(), 2 * ENTT_PACKED_PAGE);
pool.shrink_to_fit();
test::throwing_allocator<int>::trigger_on_allocate = true;
@@ -892,4 +1119,29 @@ TEST(Storage, ThrowingAllocator) {
ASSERT_THROW(pool.emplace(entt::entity{0}, 0), test::throwing_allocator<int>::exception_type);
ASSERT_FALSE(pool.contains(entt::entity{0}));
ASSERT_TRUE(pool.empty());
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
// strong exception safety
ASSERT_THROW(pool.emplace(entt::entity{0}, 0), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_FALSE(pool.contains(entt::entity{0}));
ASSERT_TRUE(pool.empty());
pool.emplace(entt::entity{0}, 0);
const entt::entity entities[2u]{entt::entity{1}, entt::entity{ENTT_SPARSE_PAGE}};
test::throwing_allocator<entt::entity>::trigger_after_allocate = true;
// basic exception safety
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), 0), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_TRUE(pool.contains(entt::entity{1}));
ASSERT_FALSE(pool.contains(entt::entity{ENTT_SPARSE_PAGE}));
pool.erase(entt::entity{1});
const int components[2u]{1, ENTT_SPARSE_PAGE};
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
// basic exception safety
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), test::throwing_allocator<entt::entity>::exception_type);
ASSERT_TRUE(pool.contains(entt::entity{1}));
ASSERT_FALSE(pool.contains(entt::entity{ENTT_SPARSE_PAGE}));
}
+6 -113
View File
@@ -3,7 +3,6 @@
#include <cstddef>
#include <iterator>
#include <memory>
#include <type_traits>
@@ -16,123 +15,18 @@ class throwing_allocator {
template<typename Other>
friend class throwing_allocator;
struct fancy_pointer final {
using difference_type = typename std::iterator_traits<Type *>::difference_type;
using element_type = Type;
using value_type = element_type;
using pointer = value_type *;
using reference = value_type &;
using iterator_category = std::random_access_iterator_tag;
fancy_pointer(Type *init = nullptr)
: ptr{init}
{}
fancy_pointer(const fancy_pointer &other)
: ptr{other.ptr}
{
if(throwing_allocator::trigger_on_pointer_copy) {
throwing_allocator::trigger_on_pointer_copy = false;
throw test_exception{};
}
}
fancy_pointer & operator++() {
return ++ptr, *this;
}
fancy_pointer operator++(int) {
auto orig = *this;
return ++(*this), orig;
}
fancy_pointer & operator--() {
return --ptr, *this;
}
fancy_pointer operator--(int) {
auto orig = *this;
return operator--(), orig;
}
fancy_pointer & operator+=(const difference_type value) {
return (ptr += value, *this);
}
fancy_pointer operator+(const difference_type value) const {
auto copy = *this;
return (copy += value);
}
fancy_pointer & operator-=(const difference_type value) {
return (ptr -= value, *this);
}
fancy_pointer operator-(const difference_type value) const {
auto copy = *this;
return (copy -= value);
}
difference_type operator-(const fancy_pointer &other) const {
return ptr - other.ptr;
}
[[nodiscard]] reference operator[](const difference_type value) const {
return ptr[value];
}
[[nodiscard]] bool operator==(const fancy_pointer &other) const {
return other.ptr == ptr;
}
[[nodiscard]] bool operator!=(const fancy_pointer &other) const {
return !(*this == other);
}
[[nodiscard]] bool operator<(const fancy_pointer &other) const {
return ptr > other.ptr;
}
[[nodiscard]] bool operator>(const fancy_pointer &other) const {
return ptr < other.ptr;
}
[[nodiscard]] bool operator<=(const fancy_pointer &other) const {
return !(*this > other);
}
[[nodiscard]] bool operator>=(const fancy_pointer &other) const {
return !(*this < other);
}
explicit operator bool() const {
return (ptr != nullptr);
}
[[nodiscard]] pointer operator->() const {
return ptr;
}
[[nodiscard]] reference operator*() const {
return *ptr;
}
private:
Type *ptr;
};
struct test_exception {};
public:
using value_type = Type;
using pointer = fancy_pointer;
using const_pointer = fancy_pointer;
using void_pointer = fancy_pointer;
using const_void_pointer = fancy_pointer;
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 exception_type = test_exception;
constexpr throwing_allocator() = default;
throwing_allocator() = default;
template<class Other>
throwing_allocator(const throwing_allocator<Other> &other)
@@ -152,12 +46,11 @@ public:
}
void deallocate(pointer mem, std::size_t length) {
allocator.deallocate(mem.operator->(), length);
allocator.deallocate(mem, length);
}
static inline bool trigger_on_allocate{};
static inline bool trigger_after_allocate{};
static inline bool trigger_on_pointer_copy{};
private:
std::allocator<Type> allocator;
+3
View File
@@ -10,6 +10,7 @@ class throwing_component {
public:
using exception_type = test_exception;
static constexpr auto moved_from_value = -1;
throwing_component(int value)
: data{value}
@@ -19,12 +20,14 @@ public:
: data{other.data}
{
if(data == trigger_on_value) {
data = moved_from_value;
throw exception_type{};
}
}
throwing_component & operator=(const throwing_component &other) {
if(other.data == trigger_on_value) {
data = moved_from_value;
throw exception_type{};
}
-52
View File
@@ -1,52 +0,0 @@
#ifndef ENTT_ENTITY_THROWING_ENTITY_HPP
#define ENTT_ENTITY_THROWING_ENTITY_HPP
namespace test {
class throwing_entity {
struct test_exception {};
public:
using entity_type = std::uint32_t;
using exception_type = test_exception;
static constexpr entity_type null = entt::null;
throwing_entity(entity_type value)
: entt{value}
{}
throwing_entity(const throwing_entity &other)
: entt{other.entt}
{
if(entt == trigger_on_entity) {
throw exception_type{};
}
}
throwing_entity & operator=(const throwing_entity &other) {
if(other.entt == trigger_on_entity) {
throw exception_type{};
}
entt = other.entt;
return *this;
}
operator entity_type() const {
return entt;
}
static inline entity_type trigger_on_entity{null};
private:
entity_type entt{};
};
}
#endif