sparse_set/storage:
* added component traits (component.hpp) * support for in-place delete (tombstones)
This commit is contained in:
@@ -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
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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}));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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{};
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user