Compare commits

...

15 Commits

Author SHA1 Message Date
Michele Caini
e4ccb878f4 updated single include file 2022-08-02 09:23:51 +02:00
Michele Caini
268723c87b storage: thanks msvc for accepting invalid C++ code :( 2022-08-02 09:23:20 +02:00
Michele Caini
9b8ab3494b updated single include file 2022-08-02 09:11:15 +02:00
Michele Caini
357c850130 ready to cut version 3.10.3 2022-08-02 09:10:36 +02:00
Michele Caini
c09661536b storage: fix cross range-erase can break when using built-in iterators (close #914) 2022-08-02 09:10:10 +02:00
Michele Caini
f8de85b961 update single include file 2022-08-01 10:34:07 +02:00
Michele Caini
90742a72b0 ready to cut version 3.10.2 2022-08-01 10:33:05 +02:00
Michele Caini
7bbd7d82b0 sparse_set: fix cross range-erase can break when using built-in iterators (close #913) 2022-08-01 10:32:20 +02:00
Michele Caini
965c440d86 update single include file 2022-04-28 09:16:13 +02:00
Michele Caini
6b2aff821c ready to cut version 3.10.1 2022-04-28 09:15:38 +02:00
Michele Caini
84d9125ea1 meta: re-added meta_type::remove_pointer (close #878) 2022-04-28 09:14:03 +02:00
Michele Caini
9b969e762c meta (close #872):
* container traits don't really support plain arrays anymore (if they ever did)
* fix an issue with insert/erase of meta sequence containers
* add non-regression tests
2022-04-28 09:13:51 +02:00
Corentin Schreiber
2695d48ba7 registry: overflow of version and max number of entities (#864) 2022-04-28 09:12:25 +02:00
Michele Caini
aba2a6b17d config: decouple ENTT_NOEXCEPT and exceptions handling (close #867) 2022-04-28 09:12:13 +02:00
Michele Caini
7b87d17d22 resource: added more comparison operators for resource handles 2022-04-28 09:11:58 +02:00
17 changed files with 675 additions and 233 deletions

View File

@@ -11,6 +11,7 @@ ceeac
ColinH ColinH
corystegel corystegel
Croydon Croydon
cschreib
cugone cugone
dbacchet dbacchet
dBagrat dBagrat

7
TODO
View File

@@ -8,16 +8,15 @@ EXAMPLES
WIP: WIP:
* view/group: no storage_traits dependency -> use storage instead of components for the definition * view/group: no storage_traits dependency -> use storage instead of components for the definition
* resource<T>::operator</<=/>/>=
* simplify emitter (see uvw), runtime events
* basic_storage::bind for cross-registry setups * basic_storage::bind for cross-registry setups
* uses-allocator construction: any (with allocator support), poly, ... * uses-allocator construction: any (with allocator support), poly, ...
* process scheduler: reviews, use free lists internally * process scheduler: reviews, use free lists internally
* iterator based try_emplace vs try_insert for perf reasons * iterator based try_emplace vs try_insert for perf reasons
* dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model * dedicated entity storage, in-place O(1) release/destroy for non-orphaned entities, out-of-sync model
* entity-only and exclude-only views * entity-only and exclude-only views
* custom allocators all over * custom allocators all over (sigh storage mixin, registry, ...)
* use ENTT_NOEXCEPT_IF as appropriate (ie make compressed_pair conditionally noexcept) * consider removing ENTT_NOEXCEPT, use ENTT_NOEXCEPT_IF (or noexcept(...)) as appropriate in any case (ie make compressed_pair conditionally noexcept)
* add test for maximum number of entities reached
WIP: WIP:
* add user data to type_info * add user data to type_info

File diff suppressed because it is too large Load Diff

View File

@@ -4,19 +4,22 @@
#include "version.h" #include "version.h"
#if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION) #if defined(__cpp_exceptions) && !defined(ENTT_NOEXCEPTION)
# define ENTT_NOEXCEPT noexcept
# define ENTT_NOEXCEPT_IF(expr) noexcept(expr)
# define ENTT_THROW throw # define ENTT_THROW throw
# define ENTT_TRY try # define ENTT_TRY try
# define ENTT_CATCH catch(...) # define ENTT_CATCH catch(...)
#else #else
# define ENTT_NOEXCEPT
# define ENTT_NOEXCEPT_IF(...)
# define ENTT_THROW # define ENTT_THROW
# define ENTT_TRY if(true) # define ENTT_TRY if(true)
# define ENTT_CATCH if(false) # define ENTT_CATCH if(false)
#endif #endif
#ifndef ENTT_NOEXCEPT
# define ENTT_NOEXCEPT noexcept
# define ENTT_NOEXCEPT_IF(expr) noexcept(expr)
# else
# define ENTT_NOEXCEPT_IF(...)
#endif
#ifdef ENTT_USE_ATOMIC #ifdef ENTT_USE_ATOMIC
# include <atomic> # include <atomic>
# define ENTT_MAYBE_ATOMIC(Type) std::atomic<Type> # define ENTT_MAYBE_ATOMIC(Type) std::atomic<Type>

View File

@@ -5,7 +5,7 @@
#define ENTT_VERSION_MAJOR 3 #define ENTT_VERSION_MAJOR 3
#define ENTT_VERSION_MINOR 10 #define ENTT_VERSION_MINOR 10
#define ENTT_VERSION_PATCH 0 #define ENTT_VERSION_PATCH 3
#define ENTT_VERSION \ #define ENTT_VERSION \
ENTT_XSTR(ENTT_VERSION_MAJOR) \ ENTT_XSTR(ENTT_VERSION_MAJOR) \

View File

@@ -302,7 +302,7 @@ class basic_registry {
} }
auto generate_identifier(const std::size_t pos) ENTT_NOEXCEPT { auto generate_identifier(const std::size_t pos) ENTT_NOEXCEPT {
ENTT_ASSERT(pos < entity_traits::to_integral(null), "No entities available"); ENTT_ASSERT(pos < entity_traits::to_entity(null), "No entities available");
return entity_traits::combine(static_cast<typename entity_traits::entity_type>(pos), {}); return entity_traits::combine(static_cast<typename entity_traits::entity_type>(pos), {});
} }

View File

@@ -240,12 +240,14 @@ protected:
*/ */
virtual void swap_and_pop(basic_iterator first, basic_iterator last) { virtual void swap_and_pop(basic_iterator first, basic_iterator last) {
for(; first != last; ++first) { for(; first != last; ++first) {
sparse_ref(packed.back()) = entity_traits::combine(static_cast<typename entity_traits::entity_type>(first.index()), entity_traits::to_integral(packed.back())); auto &self = sparse_ref(*first);
const auto entt = std::exchange(packed[first.index()], packed.back()); const auto entt = entity_traits::to_entity(self);
sparse_ref(packed.back()) = entity_traits::combine(entt, entity_traits::to_integral(packed.back()));
packed[static_cast<size_type>(entt)] = packed.back();
// unnecessary but it helps to detect nasty bugs // unnecessary but it helps to detect nasty bugs
ENTT_ASSERT((packed.back() = tombstone, true), ""); ENTT_ASSERT((packed.back() = tombstone, true), "");
// lazy self-assignment guard // lazy self-assignment guard
sparse_ref(entt) = null; self = null;
packed.pop_back(); packed.pop_back();
} }
} }
@@ -257,8 +259,8 @@ protected:
*/ */
virtual void in_place_pop(basic_iterator first, basic_iterator last) { virtual void in_place_pop(basic_iterator first, basic_iterator last) {
for(; first != last; ++first) { for(; first != last; ++first) {
sparse_ref(*first) = null; const auto entt = entity_traits::to_entity(std::exchange(sparse_ref(*first), null));
packed[first.index()] = std::exchange(free_list, entity_traits::combine(static_cast<typename entity_traits::entity_type>(first.index()), entity_traits::reserved)); packed[static_cast<size_type>(entt)] = std::exchange(free_list, entity_traits::combine(entt, entity_traits::reserved));
} }
} }

View File

@@ -333,9 +333,11 @@ protected:
*/ */
void swap_and_pop(typename underlying_type::basic_iterator first, typename underlying_type::basic_iterator last) override { void swap_and_pop(typename underlying_type::basic_iterator first, typename underlying_type::basic_iterator last) override {
for(; first != last; ++first) { for(; first != last; ++first) {
// cannot use first::index() because it would break with cross iterators
const auto pos = base_type::index(*first);
auto &elem = element_at(base_type::size() - 1u); auto &elem = element_at(base_type::size() - 1u);
// destroying on exit allows reentrant destructors // destroying on exit allows reentrant destructors
[[maybe_unused]] auto unused = std::exchange(element_at(static_cast<size_type>(first.index())), std::move(elem)); [[maybe_unused]] auto unused = std::exchange(element_at(pos), std::move(elem));
std::destroy_at(std::addressof(elem)); std::destroy_at(std::addressof(elem));
base_type::swap_and_pop(first, first + 1u); base_type::swap_and_pop(first, first + 1u);
} }
@@ -348,8 +350,10 @@ protected:
*/ */
void in_place_pop(typename underlying_type::basic_iterator first, typename underlying_type::basic_iterator last) override { void in_place_pop(typename underlying_type::basic_iterator first, typename underlying_type::basic_iterator last) override {
for(; first != last; ++first) { for(; first != last; ++first) {
// cannot use first::index() because it would break with cross iterators
const auto pos = base_type::index(*first);
base_type::in_place_pop(first, first + 1u); base_type::in_place_pop(first, first + 1u);
std::destroy_at(std::addressof(element_at(static_cast<size_type>(first.index())))); std::destroy_at(std::addressof(element_at(pos)));
} }
} }

View File

@@ -55,14 +55,12 @@ struct basic_meta_sequence_container_traits {
} }
[[nodiscard]] static iterator iter(any &container, const bool as_end) { [[nodiscard]] static iterator iter(any &container, const bool as_end) {
using std::begin;
if(auto *const cont = any_cast<Type>(&container); cont) { if(auto *const cont = any_cast<Type>(&container); cont) {
return iterator{begin(*cont), static_cast<typename iterator::difference_type>(as_end * cont->size())}; return iterator{*cont, static_cast<typename iterator::difference_type>(as_end * cont->size())};
} }
const Type &as_const = any_cast<const Type &>(container); const Type &as_const = any_cast<const Type &>(container);
return iterator{begin(as_const), static_cast<typename iterator::difference_type>(as_end * as_const.size())}; return iterator{as_const, static_cast<typename iterator::difference_type>(as_end * as_const.size())};
} }
[[nodiscard]] static iterator insert([[maybe_unused]] any &container, [[maybe_unused]] const std::ptrdiff_t offset, [[maybe_unused]] meta_any &value) { [[nodiscard]] static iterator insert([[maybe_unused]] any &container, [[maybe_unused]] const std::ptrdiff_t offset, [[maybe_unused]] meta_any &value) {
@@ -70,10 +68,9 @@ struct basic_meta_sequence_container_traits {
if(auto *const cont = any_cast<Type>(&container); cont) { if(auto *const cont = any_cast<Type>(&container); cont) {
// this abomination is necessary because only on macos value_type and const_reference are different types for std::vector<bool> // this abomination is necessary because only on macos value_type and const_reference are different types for std::vector<bool>
if(value.allow_cast<typename Type::const_reference>() || value.allow_cast<typename Type::value_type>()) { if(value.allow_cast<typename Type::const_reference>() || value.allow_cast<typename Type::value_type>()) {
using std::begin;
const auto *element = value.try_cast<std::remove_reference_t<typename Type::const_reference>>(); const auto *element = value.try_cast<std::remove_reference_t<typename Type::const_reference>>();
const auto curr = cont->insert(begin(*cont) + offset, element ? *element : value.cast<typename Type::value_type>()); const auto curr = cont->insert(cont->begin() + offset, element ? *element : value.cast<typename Type::value_type>());
return iterator{curr, curr - begin(*cont)}; return iterator{*cont, curr - cont->begin()};
} }
} }
} }
@@ -84,9 +81,8 @@ struct basic_meta_sequence_container_traits {
[[nodiscard]] static iterator erase([[maybe_unused]] any &container, [[maybe_unused]] const std::ptrdiff_t offset) { [[nodiscard]] static iterator erase([[maybe_unused]] any &container, [[maybe_unused]] const std::ptrdiff_t offset) {
if constexpr(is_dynamic_sequence_container<Type>::value) { if constexpr(is_dynamic_sequence_container<Type>::value) {
if(auto *const cont = any_cast<Type>(&container); cont) { if(auto *const cont = any_cast<Type>(&container); cont) {
using std::begin; const auto curr = cont->erase(cont->begin() + offset);
const auto curr = cont->erase(begin(*cont) + offset); return iterator{*cont, curr - cont->begin()};
return iterator{curr, curr - begin(*cont)};
} }
} }
@@ -115,15 +111,12 @@ struct basic_meta_associative_container_traits {
} }
[[nodiscard]] static iterator iter(any &container, const bool as_end) { [[nodiscard]] static iterator iter(any &container, const bool as_end) {
using std::begin;
using std::end;
if(auto *const cont = any_cast<Type>(&container); cont) { if(auto *const cont = any_cast<Type>(&container); cont) {
return iterator{std::integral_constant<bool, key_only>{}, as_end ? end(*cont) : begin(*cont)}; return iterator{std::integral_constant<bool, key_only>{}, as_end ? cont->end() : cont->begin()};
} }
const auto &as_const = any_cast<const Type &>(container); const auto &as_const = any_cast<const Type &>(container);
return iterator{std::integral_constant<bool, key_only>{}, as_end ? end(as_const) : begin(as_const)}; return iterator{std::integral_constant<bool, key_only>{}, as_end ? as_const.end() : as_const.begin()};
} }
[[nodiscard]] static bool insert(any &container, meta_any &key, [[maybe_unused]] meta_any &value) { [[nodiscard]] static bool insert(any &container, meta_any &key, [[maybe_unused]] meta_any &value) {

View File

@@ -1070,6 +1070,15 @@ public:
return !!(node->traits & internal::meta_traits::is_pointer); return !!(node->traits & internal::meta_traits::is_pointer);
} }
/**
* @brief Provides the type for which the pointer is defined.
* @return The type for which the pointer is defined or this type if it
* doesn't refer to a pointer type.
*/
[[nodiscard]] meta_type remove_pointer() const ENTT_NOEXCEPT {
return node->remove_pointer();
}
/** /**
* @brief Checks whether a type is a pointer-like type or not. * @brief Checks whether a type is a pointer-like type or not.
* @return True if the underlying type is a pointer-like one, false * @return True if the underlying type is a pointer-like one, false
@@ -1464,11 +1473,11 @@ public:
offset{}, offset{},
handle{} {} handle{} {}
template<typename It> template<typename Type>
explicit meta_iterator(It iter, const difference_type init) ENTT_NOEXCEPT explicit meta_iterator(Type &cont, const difference_type init) ENTT_NOEXCEPT
: deref{&deref_fn<It>}, : deref{&deref_fn<decltype(cont.begin())>},
offset{init}, offset{init},
handle{std::move(iter)} {} handle{cont.begin()} {}
meta_iterator &operator++() ENTT_NOEXCEPT { meta_iterator &operator++() ENTT_NOEXCEPT {
return ++offset, *this; return ++offset, *this;

View File

@@ -108,6 +108,7 @@ struct meta_type_node {
meta_type_node *next; meta_type_node *next;
meta_prop_node *prop; meta_prop_node *prop;
const size_type size_of; const size_type size_of;
meta_type_node *(*const remove_pointer)() ENTT_NOEXCEPT;
meta_any (*const default_constructor)(); meta_any (*const default_constructor)();
double (*const conversion_helper)(void *, const void *); double (*const conversion_helper)(void *, const void *);
const meta_template_node *const templ; const meta_template_node *const templ;
@@ -180,6 +181,7 @@ public:
nullptr, nullptr,
nullptr, nullptr,
size_of_v<Type>, size_of_v<Type>,
&meta_node<std::remove_cv_t<std::remove_reference_t<std::remove_pointer_t<Type>>>>::resolve,
meta_default_constructor(), meta_default_constructor(),
meta_conversion_helper(), meta_conversion_helper(),
meta_template_info() meta_template_info()

View File

@@ -178,11 +178,65 @@ template<typename Res, typename Other>
* @param rhs A valid handle. * @param rhs A valid handle.
* @return False if both handles refer to the same registry, true otherwise. * @return False if both handles refer to the same registry, true otherwise.
*/ */
template<typename ILhs, typename IRhs> template<typename Res, typename Other>
[[nodiscard]] bool operator!=(const resource<ILhs> &lhs, const resource<IRhs> &rhs) ENTT_NOEXCEPT { [[nodiscard]] bool operator!=(const resource<Res> &lhs, const resource<Other> &rhs) ENTT_NOEXCEPT {
return !(lhs == rhs); return !(lhs == rhs);
} }
/**
* @brief Compares two handles.
* @tparam Res Type of resource managed by the first handle.
* @tparam Other Type of resource managed by the second handle.
* @param lhs A valid handle.
* @param rhs A valid handle.
* @return True if the first handle is less than the second, false otherwise.
*/
template<typename Res, typename Other>
[[nodiscard]] bool operator<(const resource<Res> &lhs, const resource<Other> &rhs) ENTT_NOEXCEPT {
return (std::addressof(*lhs) < std::addressof(*rhs));
}
/**
* @brief Compares two handles.
* @tparam Res Type of resource managed by the first handle.
* @tparam Other Type of resource managed by the second handle.
* @param lhs A valid handle.
* @param rhs A valid handle.
* @return True if the first handle is greater than the second, false otherwise.
*/
template<typename Res, typename Other>
[[nodiscard]] bool operator>(const resource<Res> &lhs, const resource<Other> &rhs) ENTT_NOEXCEPT {
return (std::addressof(*lhs) > std::addressof(*rhs));
}
/**
* @brief Compares two handles.
* @tparam Res Type of resource managed by the first handle.
* @tparam Other Type of resource managed by the second handle.
* @param lhs A valid handle.
* @param rhs A valid handle.
* @return True if the first handle is less than or equal to the second, false
* otherwise.
*/
template<typename Res, typename Other>
[[nodiscard]] bool operator<=(const resource<Res> &lhs, const resource<Other> &rhs) ENTT_NOEXCEPT {
return !(lhs > rhs);
}
/**
* @brief Compares two handles.
* @tparam Res Type of resource managed by the first handle.
* @tparam Other Type of resource managed by the second handle.
* @param lhs A valid handle.
* @param rhs A valid handle.
* @return True if the first handle is greater than or equal to the second,
* false otherwise.
*/
template<typename Res, typename Other>
[[nodiscard]] bool operator>=(const resource<Res> &lhs, const resource<Other> &rhs) ENTT_NOEXCEPT {
return !(lhs < rhs);
}
} // namespace entt } // namespace entt
#endif #endif

View File

@@ -428,6 +428,22 @@ TEST(SparseSetDeathTest, Erase) {
ASSERT_DEATH(set.erase(entt::null), ""); ASSERT_DEATH(set.erase(entt::null), "");
} }
TEST(SparseSet, CrossErase) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set;
entt::sparse_set other;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.insert(std::begin(entities), std::end(entities));
other.emplace(entities[1u]);
set.erase(other.begin(), other.end());
ASSERT_TRUE(set.contains(entities[0u]));
ASSERT_FALSE(set.contains(entities[1u]));
ASSERT_EQ(set.data()[0u], entities[0u]);
}
TEST(SparseSet, StableErase) { TEST(SparseSet, StableErase) {
using traits_type = entt::entt_traits<entt::entity>; using traits_type = entt::entt_traits<entt::entity>;
@@ -552,6 +568,22 @@ TEST(SparseSetDeathTest, StableErase) {
ASSERT_DEATH(set.erase(entt::null), ""); ASSERT_DEATH(set.erase(entt::null), "");
} }
TEST(SparseSet, CrossStableErase) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set{entt::deletion_policy::in_place};
entt::sparse_set other{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.insert(std::begin(entities), std::end(entities));
other.emplace(entities[1u]);
set.erase(other.begin(), other.end());
ASSERT_TRUE(set.contains(entities[0u]));
ASSERT_FALSE(set.contains(entities[1u]));
ASSERT_EQ(set.data()[0u], entities[0u]);
}
TEST(SparseSet, Remove) { TEST(SparseSet, Remove) {
using traits_type = entt::entt_traits<entt::entity>; using traits_type = entt::entt_traits<entt::entity>;
@@ -609,6 +641,22 @@ TEST(SparseSet, Remove) {
ASSERT_EQ(set.remove(entt::null), 0u); ASSERT_EQ(set.remove(entt::null), 0u);
} }
TEST(SparseSet, CrossRemove) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set;
entt::sparse_set other;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.insert(std::begin(entities), std::end(entities));
other.emplace(entities[1u]);
set.remove(other.begin(), other.end());
ASSERT_TRUE(set.contains(entities[0u]));
ASSERT_FALSE(set.contains(entities[1u]));
ASSERT_EQ(set.data()[0u], entities[0u]);
}
TEST(SparseSet, StableRemove) { TEST(SparseSet, StableRemove) {
using traits_type = entt::entt_traits<entt::entity>; using traits_type = entt::entt_traits<entt::entity>;
@@ -739,6 +787,22 @@ TEST(SparseSet, StableRemove) {
ASSERT_EQ(set.remove(entt::null), 0u); ASSERT_EQ(set.remove(entt::null), 0u);
} }
TEST(SparseSet, CrossStableRemove) {
using traits_type = entt::entt_traits<entt::entity>;
entt::sparse_set set{entt::deletion_policy::in_place};
entt::sparse_set other{entt::deletion_policy::in_place};
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
set.insert(std::begin(entities), std::end(entities));
other.emplace(entities[1u]);
set.remove(other.begin(), other.end());
ASSERT_TRUE(set.contains(entities[0u]));
ASSERT_FALSE(set.contains(entities[1u]));
ASSERT_EQ(set.data()[0u], entities[0u]);
}
TEST(SparseSet, Compact) { TEST(SparseSet, Compact) {
entt::sparse_set set{entt::deletion_policy::in_place}; entt::sparse_set set{entt::deletion_policy::in_place};

View File

@@ -345,6 +345,21 @@ TEST(Storage, Erase) {
ASSERT_EQ(*pool.begin(), 1); ASSERT_EQ(*pool.begin(), 1);
} }
TEST(Storage, CrossErase) {
entt::sparse_set set;
entt::storage<int> pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
pool.emplace(entities[0u], 3);
pool.emplace(entities[1u], 42);
set.emplace(entities[1u]);
pool.erase(set.begin(), set.end());
ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_FALSE(pool.contains(entities[1u]));
ASSERT_EQ(pool.raw()[0u][0u], 3);
}
TEST(Storage, StableErase) { TEST(Storage, StableErase) {
entt::storage<stable_type> pool; entt::storage<stable_type> pool;
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}}; entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}};
@@ -439,6 +454,21 @@ TEST(Storage, StableErase) {
ASSERT_EQ(pool.get(entities[2u]).value, 1); ASSERT_EQ(pool.get(entities[2u]).value, 1);
} }
TEST(Storage, CrossStableErase) {
entt::sparse_set set;
entt::storage<stable_type> pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
pool.emplace(entities[0u], 3);
pool.emplace(entities[1u], 42);
set.emplace(entities[1u]);
pool.erase(set.begin(), set.end());
ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_FALSE(pool.contains(entities[1u]));
ASSERT_EQ(pool.raw()[0u][0u].value, 3);
}
TEST(Storage, Remove) { TEST(Storage, Remove) {
entt::storage<int> pool; entt::storage<int> pool;
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}}; entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}};
@@ -473,6 +503,21 @@ TEST(Storage, Remove) {
ASSERT_EQ(*pool.begin(), 1); ASSERT_EQ(*pool.begin(), 1);
} }
TEST(Storage, CrossRemove) {
entt::sparse_set set;
entt::storage<int> pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
pool.emplace(entities[0u], 3);
pool.emplace(entities[1u], 42);
set.emplace(entities[1u]);
pool.remove(set.begin(), set.end());
ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_FALSE(pool.contains(entities[1u]));
ASSERT_EQ(pool.raw()[0u][0u], 3);
}
TEST(Storage, StableRemove) { TEST(Storage, StableRemove) {
entt::storage<stable_type> pool; entt::storage<stable_type> pool;
entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}}; entt::entity entities[3u]{entt::entity{3}, entt::entity{42}, entt::entity{9}};
@@ -570,6 +615,21 @@ TEST(Storage, StableRemove) {
ASSERT_EQ(pool.get(entities[2u]).value, 1); ASSERT_EQ(pool.get(entities[2u]).value, 1);
} }
TEST(Storage, CrossStableRemove) {
entt::sparse_set set;
entt::storage<stable_type> pool;
entt::entity entities[2u]{entt::entity{3}, entt::entity{42}};
pool.emplace(entities[0u], 3);
pool.emplace(entities[1u], 42);
set.emplace(entities[1u]);
pool.remove(set.begin(), set.end());
ASSERT_TRUE(pool.contains(entities[0u]));
ASSERT_FALSE(pool.contains(entities[1u]));
ASSERT_EQ(pool.raw()[0u][0u].value, 3);
}
TEST(Storage, TypeFromBase) { TEST(Storage, TypeFromBase) {
entt::storage<int> pool; entt::storage<int> pool;
entt::sparse_set &base = pool; entt::sparse_set &base = pool;

View File

@@ -147,11 +147,16 @@ TEST_F(MetaContainer, StdVector) {
ASSERT_EQ(view.begin()->cast<int>(), 0); ASSERT_EQ(view.begin()->cast<int>(), 0);
ASSERT_EQ((++view.begin())->cast<int>(), 1); ASSERT_EQ((++view.begin())->cast<int>(), 1);
ret = view.insert(view.end(), 42);
ASSERT_TRUE(ret);
ASSERT_EQ(*ret, 42);
it = view.begin(); it = view.begin();
ret = view.erase(it); ret = view.erase(it);
ASSERT_TRUE(ret); ASSERT_TRUE(ret);
ASSERT_EQ(view.size(), 4u); ASSERT_EQ(view.size(), 5u);
ASSERT_EQ(ret->cast<int>(), 1); ASSERT_EQ(ret->cast<int>(), 1);
ASSERT_TRUE(view.clear()); ASSERT_TRUE(view.clear());

View File

@@ -261,6 +261,13 @@ TEST_F(MetaType, Traits) {
ASSERT_FALSE(entt::resolve<std::vector<int>>().is_associative_container()); ASSERT_FALSE(entt::resolve<std::vector<int>>().is_associative_container());
} }
TEST_F(MetaType, RemovePointer) {
ASSERT_EQ(entt::resolve<void *>().remove_pointer(), entt::resolve<void>());
ASSERT_EQ(entt::resolve<char **>().remove_pointer(), entt::resolve<char *>());
ASSERT_EQ(entt::resolve<int (*)(char, double)>().remove_pointer(), entt::resolve<int(char, double)>());
ASSERT_EQ(entt::resolve<derived_t>().remove_pointer(), entt::resolve<derived_t>());
}
TEST_F(MetaType, TemplateInfo) { TEST_F(MetaType, TemplateInfo) {
ASSERT_FALSE(entt::resolve<int>().is_template_specialization()); ASSERT_FALSE(entt::resolve<int>().is_template_specialization());
ASSERT_EQ(entt::resolve<int>().template_arity(), 0u); ASSERT_EQ(entt::resolve<int>().template_arity(), 0u);

View File

@@ -128,3 +128,17 @@ TEST(Resource, DynamicResourceHandleCast) {
ASSERT_FALSE(cast); ASSERT_FALSE(cast);
ASSERT_EQ(resource.use_count(), 1u); ASSERT_EQ(resource.use_count(), 1u);
} }
TEST(Resource, Comparison) {
entt::resource<derived> resource{std::make_shared<derived>()};
entt::resource<const base> other = resource;
ASSERT_TRUE(resource == other);
ASSERT_FALSE(resource != other);
ASSERT_FALSE(resource < other);
ASSERT_FALSE(resource > other);
ASSERT_TRUE(resource <= other);
ASSERT_TRUE(resource >= other);
}