test: exception safety for sparse set and storage
This commit is contained in:
@@ -47,20 +47,18 @@ class basic_sparse_set {
|
||||
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
using alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>;
|
||||
using alloc_traits = std::allocator_traits<alloc_type>;
|
||||
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Entity>;
|
||||
using alloc_pointer = typename alloc_traits::pointer;
|
||||
using alloc_const_pointer = typename alloc_traits::const_pointer;
|
||||
|
||||
using bucket_alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<alloc_pointer>;
|
||||
using bucket_alloc_traits = std::allocator_traits<bucket_alloc_type>;
|
||||
using bucket_alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<alloc_pointer>;
|
||||
using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
|
||||
|
||||
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>;
|
||||
friend class basic_sparse_set<Entity, Allocator>;
|
||||
|
||||
using index_type = typename traits_type::difference_type;
|
||||
|
||||
@@ -189,7 +187,7 @@ class basic_sparse_set {
|
||||
|
||||
std::destroy(sparse, sparse + bucket);
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, sparse, bucket);
|
||||
|
||||
|
||||
sparse = mem;
|
||||
bucket = sz;
|
||||
}
|
||||
@@ -302,7 +300,7 @@ protected:
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = alloc_type;
|
||||
using allocator_type = typename alloc_traits::allocator_type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
@@ -450,7 +448,7 @@ public:
|
||||
* @return An iterator to the first entity of the internal packed array.
|
||||
*/
|
||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
|
||||
return iterator{&packed, static_cast<typename traits_type::difference_type>(count)};
|
||||
return iterator{std::addressof(packed), static_cast<typename traits_type::difference_type>(count)};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -464,7 +462,7 @@ public:
|
||||
* internal packed array.
|
||||
*/
|
||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
|
||||
return iterator{&packed, {}};
|
||||
return iterator{std::addressof(packed), {}};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -785,8 +783,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
alloc_type allocator;
|
||||
bucket_alloc_type bucket_allocator;
|
||||
typename alloc_traits::allocator_type allocator;
|
||||
typename bucket_alloc_traits::allocator_type bucket_allocator;
|
||||
bucket_alloc_pointer sparse;
|
||||
alloc_pointer packed;
|
||||
std::size_t bucket;
|
||||
|
||||
@@ -53,29 +53,25 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
|
||||
|
||||
static constexpr auto packed_page = ENTT_PACKED_PAGE;
|
||||
|
||||
using underlying_type = basic_sparse_set<Entity>;
|
||||
using underlying_type = basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>>;
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
using alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<Type>;
|
||||
using alloc_traits = std::allocator_traits<alloc_type>;
|
||||
using alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<Type>;
|
||||
using alloc_pointer = typename alloc_traits::pointer;
|
||||
using alloc_const_pointer = typename alloc_traits::const_pointer;
|
||||
|
||||
using bucket_alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<alloc_pointer>;
|
||||
using bucket_alloc_traits = std::allocator_traits<bucket_alloc_type>;
|
||||
using bucket_alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<alloc_pointer>;
|
||||
using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
|
||||
|
||||
using bucket_alloc_const_type = typename std::allocator_traits<Allocator>::template rebind_alloc<alloc_const_pointer>;
|
||||
using bucket_alloc_const_pointer = typename std::allocator_traits<bucket_alloc_const_type>::const_pointer;
|
||||
|
||||
using entity_alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>;
|
||||
|
||||
static_assert(alloc_traits::propagate_on_container_move_assignment::value);
|
||||
static_assert(bucket_alloc_traits::propagate_on_container_move_assignment::value);
|
||||
|
||||
template<typename Value>
|
||||
class storage_iterator final {
|
||||
friend class basic_storage<Entity, Type>;
|
||||
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}
|
||||
@@ -211,7 +207,7 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, mem, length);
|
||||
ENTT_THROW;
|
||||
}
|
||||
|
||||
|
||||
for(auto pos = length; pos < bucket; ++pos) {
|
||||
alloc_traits::deallocate(allocator, packed[pos], packed_page);
|
||||
}
|
||||
@@ -295,7 +291,7 @@ protected:
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = alloc_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. */
|
||||
@@ -320,7 +316,7 @@ public:
|
||||
* @param alloc Allocator to use (possibly default-constructed).
|
||||
*/
|
||||
explicit basic_storage(const allocator_type &alloc = {})
|
||||
: basic_sparse_set<entity_type, entity_alloc_type>{alloc},
|
||||
: underlying_type{alloc},
|
||||
allocator{alloc},
|
||||
bucket_allocator{alloc},
|
||||
packed{},
|
||||
@@ -332,7 +328,7 @@ public:
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
basic_storage(basic_storage &&other) ENTT_NOEXCEPT
|
||||
: basic_sparse_set<entity_type, entity_alloc_type>{std::move(other)},
|
||||
: underlying_type{std::move(other)},
|
||||
allocator{std::move(other.allocator)},
|
||||
bucket_allocator{std::move(other.bucket_allocator)},
|
||||
packed{std::exchange(other.packed, bucket_alloc_pointer{})},
|
||||
@@ -352,7 +348,7 @@ public:
|
||||
basic_storage & operator=(basic_storage &&other) ENTT_NOEXCEPT {
|
||||
release_memory();
|
||||
|
||||
basic_sparse_set<entity_type, entity_alloc_type>::operator=(std::move(other));
|
||||
underlying_type::operator=(std::move(other));
|
||||
|
||||
allocator = std::move(other.allocator);
|
||||
bucket_allocator = std::move(other.bucket_allocator);
|
||||
@@ -416,7 +412,7 @@ public:
|
||||
*/
|
||||
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = underlying_type::size();
|
||||
return const_iterator{&packed, pos};
|
||||
return const_iterator{std::addressof(packed), pos};
|
||||
}
|
||||
|
||||
/*! @copydoc cbegin */
|
||||
@@ -427,7 +423,7 @@ public:
|
||||
/*! @copydoc begin */
|
||||
[[nodiscard]] iterator begin() ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = underlying_type::size();
|
||||
return iterator{&packed, pos};
|
||||
return iterator{std::addressof(packed), pos};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -441,7 +437,7 @@ public:
|
||||
* internal array.
|
||||
*/
|
||||
[[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
|
||||
return const_iterator{&packed, {}};
|
||||
return const_iterator{std::addressof(packed), {}};
|
||||
}
|
||||
|
||||
/*! @copydoc cend */
|
||||
@@ -451,7 +447,7 @@ public:
|
||||
|
||||
/*! @copydoc end */
|
||||
[[nodiscard]] iterator end() ENTT_NOEXCEPT {
|
||||
return iterator{&packed, {}};
|
||||
return iterator{std::addressof(packed), {}};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -693,8 +689,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
alloc_type allocator;
|
||||
bucket_alloc_type bucket_allocator;
|
||||
typename alloc_traits::allocator_type allocator;
|
||||
typename bucket_alloc_traits::allocator_type bucket_allocator;
|
||||
bucket_alloc_pointer packed;
|
||||
std::size_t bucket;
|
||||
};
|
||||
@@ -703,7 +699,7 @@ private:
|
||||
/*! @copydoc basic_storage */
|
||||
template<typename Entity, typename Type, typename Allocator>
|
||||
class basic_storage<Entity, Type, Allocator, std::enable_if_t<is_empty_v<Type>>>: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
|
||||
using underlying_type = basic_sparse_set<Entity>;
|
||||
using underlying_type = basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>>;
|
||||
|
||||
public:
|
||||
/*! @brief Type of the objects assigned to entities. */
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <gtest/gtest.h>
|
||||
#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; };
|
||||
@@ -580,3 +582,78 @@ TEST(SparseSet, CanModifyDuringIteration) {
|
||||
const auto entity = *it;
|
||||
(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{};
|
||||
|
||||
test::throwing_allocator<entt::entity>::trigger_on_allocate = true;
|
||||
|
||||
// strong exception safety
|
||||
ASSERT_THROW(set.reserve(1u), 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;
|
||||
|
||||
// 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);
|
||||
ASSERT_TRUE(set.contains(entt::entity{0}));
|
||||
|
||||
test::throwing_allocator<entt::entity>::trigger_on_pointer_copy = 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);
|
||||
ASSERT_TRUE(set.contains(entt::entity{0}));
|
||||
|
||||
set.reserve(ENTT_PACKED_PAGE);
|
||||
test::throwing_allocator<entt::entity>::trigger_on_pointer_copy = true;
|
||||
|
||||
// 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}));
|
||||
|
||||
// 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}));
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/storage.hpp>
|
||||
#include <entt/entity/fwd.hpp>
|
||||
#include "throwing_allocator.hpp"
|
||||
#include "throwing_component.hpp"
|
||||
#include "throwing_entity.hpp"
|
||||
|
||||
struct empty_type {};
|
||||
struct boxed_int { int value; };
|
||||
@@ -15,15 +18,6 @@ bool operator==(const boxed_int &lhs, const boxed_int &rhs) {
|
||||
return lhs.value == rhs.value;
|
||||
}
|
||||
|
||||
struct throwing_component {
|
||||
struct constructor_exception: std::exception {};
|
||||
|
||||
[[noreturn]] throwing_component() { throw constructor_exception{}; }
|
||||
|
||||
// necessary to disable the empty type optimization
|
||||
int data;
|
||||
};
|
||||
|
||||
struct update_from_destructor {
|
||||
~update_from_destructor() {
|
||||
if(target != entt::null) {
|
||||
@@ -760,18 +754,6 @@ TEST(Storage, MoveOnlyComponent) {
|
||||
(void)pool;
|
||||
}
|
||||
|
||||
TEST(Storage, EmplaceStrongExceptionGuarantee) {
|
||||
entt::storage<throwing_component> pool;
|
||||
|
||||
try {
|
||||
pool.emplace(entt::entity{0});
|
||||
} catch (const throwing_component::constructor_exception &) {
|
||||
ASSERT_TRUE(pool.empty());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(pool.empty());
|
||||
}
|
||||
|
||||
TEST(Storage, UpdateFromDestructor) {
|
||||
static constexpr auto size = 10u;
|
||||
|
||||
@@ -796,3 +778,118 @@ TEST(Storage, UpdateFromDestructor) {
|
||||
test(entt::entity(size - 1u));
|
||||
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;
|
||||
|
||||
// strong exception safety
|
||||
ASSERT_THROW(pool.emplace(entt::entity{0}, test::throwing_component{42}), typename test::throwing_component::exception_type);
|
||||
ASSERT_TRUE(pool.empty());
|
||||
|
||||
const entt::entity entities[2u]{entt::entity{42}, entt::entity{1}};
|
||||
const test::throwing_component components[2u]{42, 1};
|
||||
|
||||
// basic exception safety
|
||||
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), test::throwing_component{42}), typename test::throwing_component::exception_type);
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_FALSE(pool.contains(entt::entity{1}));
|
||||
|
||||
// basic exception safety
|
||||
ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), typename test::throwing_component::exception_type);
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_FALSE(pool.contains(entt::entity{1}));
|
||||
|
||||
// basic exception safety
|
||||
ASSERT_THROW(pool.insert(std::rbegin(entities), std::rend(entities), std::rbegin(components)), typename test::throwing_component::exception_type);
|
||||
ASSERT_EQ(pool.size(), 1u);
|
||||
ASSERT_TRUE(pool.contains(entt::entity{1}));
|
||||
ASSERT_EQ(pool.get(entt::entity{1}), 1);
|
||||
|
||||
pool.clear();
|
||||
pool.emplace(entt::entity{1}, 1);
|
||||
pool.emplace(entt::entity{42}, 42);
|
||||
|
||||
ASSERT_THROW(pool.erase(entt::entity{1}), typename test::throwing_component::exception_type);
|
||||
ASSERT_FALSE(pool.empty());
|
||||
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);
|
||||
}
|
||||
|
||||
TEST(Storage, ThrowingAllocator) {
|
||||
entt::basic_storage<entt::entity, int, test::throwing_allocator<int>> pool;
|
||||
|
||||
test::throwing_allocator<int>::trigger_on_allocate = true;
|
||||
|
||||
// strong exception safety
|
||||
ASSERT_THROW(pool.reserve(1u), test::throwing_allocator<int>::exception_type);
|
||||
ASSERT_EQ(pool.capacity(), 0u);
|
||||
|
||||
test::throwing_allocator<int>::trigger_after_allocate = true;
|
||||
|
||||
// strong exception safety
|
||||
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;
|
||||
|
||||
// strong exception safety
|
||||
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());
|
||||
}
|
||||
|
||||
170
test/entt/entity/throwing_allocator.hpp
Normal file
170
test/entt/entity/throwing_allocator.hpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#ifndef ENTT_ENTITY_THROWING_ALLOCATOR_HPP
|
||||
#define ENTT_ENTITY_THROWING_ALLOCATOR_HPP
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
|
||||
namespace test {
|
||||
|
||||
|
||||
template<typename Type>
|
||||
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 propagate_on_container_move_assignment = std::true_type;
|
||||
using exception_type = test_exception;
|
||||
|
||||
constexpr throwing_allocator() = default;
|
||||
|
||||
template<class Other>
|
||||
throwing_allocator(const throwing_allocator<Other> &other)
|
||||
: allocator{other.allocator}
|
||||
{}
|
||||
|
||||
pointer allocate(std::size_t length) {
|
||||
if(trigger_on_allocate) {
|
||||
trigger_on_allocate = false;
|
||||
throw test_exception{};
|
||||
}
|
||||
|
||||
trigger_on_allocate = trigger_after_allocate;
|
||||
trigger_after_allocate = false;
|
||||
|
||||
return allocator.allocate(length);
|
||||
}
|
||||
|
||||
void deallocate(pointer mem, std::size_t length) {
|
||||
allocator.deallocate(mem.operator->(), 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;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
49
test/entt/entity/throwing_component.hpp
Normal file
49
test/entt/entity/throwing_component.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef ENTT_ENTITY_THROWING_COMPONENT_HPP
|
||||
#define ENTT_ENTITY_THROWING_COMPONENT_HPP
|
||||
|
||||
|
||||
namespace test {
|
||||
|
||||
|
||||
class throwing_component {
|
||||
struct test_exception {};
|
||||
|
||||
public:
|
||||
using exception_type = test_exception;
|
||||
|
||||
throwing_component(int value)
|
||||
: data{value}
|
||||
{}
|
||||
|
||||
throwing_component(const throwing_component &other)
|
||||
: data{other.data}
|
||||
{
|
||||
if(data == trigger_on_value) {
|
||||
throw exception_type{};
|
||||
}
|
||||
}
|
||||
|
||||
throwing_component & operator=(const throwing_component &other) {
|
||||
if(other.data == trigger_on_value) {
|
||||
throw exception_type{};
|
||||
}
|
||||
|
||||
data = other.data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator int() const {
|
||||
return data;
|
||||
}
|
||||
|
||||
static inline int trigger_on_value{};
|
||||
|
||||
private:
|
||||
int data{};
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
52
test/entt/entity/throwing_entity.hpp
Normal file
52
test/entt/entity/throwing_entity.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#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