Przeglądaj źródła

handle: full review with test coverage - close #1129

Michele Caini 2 lat temu
rodzic
commit
6bc8f4cfa4
2 zmienionych plików z 457 dodań i 171 usunięć
  1. 44 44
      src/entt/entity/handle.hpp
  2. 413 127
      test/entt/entity/handle.cpp

+ 44 - 44
src/entt/entity/handle.hpp

@@ -5,6 +5,7 @@
 #include <tuple>
 #include <type_traits>
 #include <utility>
+#include "../config/config.h"
 #include "../core/iterator.hpp"
 #include "../core/type_traits.hpp"
 #include "entity.hpp"
@@ -95,6 +96,11 @@ template<typename ILhs, typename IRhs>
  */
 template<typename Registry, typename... Scope>
 class basic_handle {
+    auto &owner_or_assert() const noexcept {
+        ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
+        return static_cast<Registry &>(*owner);
+    }
+
 public:
     /*! @brief Type of registry accepted by the handle. */
     using registry_type = Registry;
@@ -132,30 +138,13 @@ public:
      * @return An iterable object to use to _visit_ the handle.
      */
     [[nodiscard]] iterable storage() const noexcept {
-        auto underlying = owner->storage();
+        auto underlying = owner_or_assert().storage();
         return iterable{{entt, underlying.begin(), underlying.end()}, {entt, underlying.end(), underlying.end()}};
     }
 
-    /**
-     * @brief Constructs a const handle from a non-const one.
-     * @tparam Other A valid entity type.
-     * @tparam Args Scope of the handle to construct.
-     * @return A const handle referring to the same registry and the same
-     * entity.
-     */
-    template<typename Other, typename... Args>
-    operator basic_handle<Other, Args...>() const noexcept {
-        static_assert(std::is_same_v<Other, Registry> || std::is_same_v<std::remove_const_t<Other>, Registry>, "Invalid conversion between different handles");
-        static_assert((sizeof...(Scope) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Scope)) && ... && (type_list_contains_v<type_list<Scope...>, Args>))), "Invalid conversion between different handles");
-        return owner ? basic_handle<Other, Args...>{*owner, entt} : basic_handle<Other, Args...>{};
-    }
-
-    /**
-     * @brief Converts a handle to its underlying entity.
-     * @return The contained identifier.
-     */
-    [[nodiscard]] operator entity_type() const noexcept {
-        return entity();
+    /*! @copydoc valid */
+    [[nodiscard]] explicit operator bool() const noexcept {
+        return owner && owner->valid(entt);
     }
 
     /**
@@ -163,16 +152,8 @@ public:
      * @return True if the handle refers to non-null registry and entity, false
      * otherwise.
      */
-    [[nodiscard]] explicit operator bool() const noexcept {
-        return owner && owner->valid(entt);
-    }
-
-    /**
-     * @brief Checks if a handle refers to a valid entity or not.
-     * @return True if the handle refers to a valid entity, false otherwise.
-     */
     [[nodiscard]] bool valid() const {
-        return owner->valid(entt);
+        return static_cast<bool>(*this);
     }
 
     /**
@@ -191,9 +172,14 @@ public:
         return entt;
     }
 
+    /*! @copydoc entity */
+    [[nodiscard]] operator entity_type() const noexcept {
+        return entity();
+    }
+
     /*! @brief Destroys the entity associated with a handle. */
     void destroy() {
-        owner->destroy(std::exchange(entt, null));
+        owner_or_assert().destroy(std::exchange(entt, null));
     }
 
     /**
@@ -201,7 +187,7 @@ public:
      * @param version A desired version upon destruction.
      */
     void destroy(const version_type version) {
-        owner->destroy(std::exchange(entt, null), version);
+        owner_or_assert().destroy(std::exchange(entt, null), version);
     }
 
     /**
@@ -214,7 +200,7 @@ public:
     template<typename Type, typename... Args>
     decltype(auto) emplace(Args &&...args) const {
         static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type");
-        return owner->template emplace<Type>(entt, std::forward<Args>(args)...);
+        return owner_or_assert().template emplace<Type>(entt, std::forward<Args>(args)...);
     }
 
     /**
@@ -227,7 +213,7 @@ public:
     template<typename Type, typename... Args>
     decltype(auto) emplace_or_replace(Args &&...args) const {
         static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type");
-        return owner->template emplace_or_replace<Type>(entt, std::forward<Args>(args)...);
+        return owner_or_assert().template emplace_or_replace<Type>(entt, std::forward<Args>(args)...);
     }
 
     /**
@@ -240,7 +226,7 @@ public:
     template<typename Type, typename... Func>
     decltype(auto) patch(Func &&...func) const {
         static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type");
-        return owner->template patch<Type>(entt, std::forward<Func>(func)...);
+        return owner_or_assert().template patch<Type>(entt, std::forward<Func>(func)...);
     }
 
     /**
@@ -253,7 +239,7 @@ public:
     template<typename Type, typename... Args>
     decltype(auto) replace(Args &&...args) const {
         static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type");
-        return owner->template replace<Type>(entt, std::forward<Args>(args)...);
+        return owner_or_assert().template replace<Type>(entt, std::forward<Args>(args)...);
     }
 
     /**
@@ -264,7 +250,7 @@ public:
     template<typename... Type>
     size_type remove() const {
         static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type");
-        return owner->template remove<Type...>(entt);
+        return owner_or_assert().template remove<Type...>(entt);
     }
 
     /**
@@ -274,7 +260,7 @@ public:
     template<typename... Type>
     void erase() const {
         static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type");
-        owner->template erase<Type...>(entt);
+        owner_or_assert().template erase<Type...>(entt);
     }
 
     /**
@@ -284,7 +270,7 @@ public:
      */
     template<typename... Type>
     [[nodiscard]] decltype(auto) all_of() const {
-        return owner->template all_of<Type...>(entt);
+        return owner_or_assert().template all_of<Type...>(entt);
     }
 
     /**
@@ -295,7 +281,7 @@ public:
      */
     template<typename... Type>
     [[nodiscard]] decltype(auto) any_of() const {
-        return owner->template any_of<Type...>(entt);
+        return owner_or_assert().template any_of<Type...>(entt);
     }
 
     /**
@@ -306,7 +292,7 @@ public:
     template<typename... Type>
     [[nodiscard]] decltype(auto) get() const {
         static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type");
-        return owner->template get<Type...>(entt);
+        return owner_or_assert().template get<Type...>(entt);
     }
 
     /**
@@ -319,7 +305,7 @@ public:
     template<typename Type, typename... Args>
     [[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const {
         static_assert(((sizeof...(Scope) == 0) || ... || std::is_same_v<Type, Scope>), "Invalid type");
-        return owner->template get_or_emplace<Type>(entt, std::forward<Args>(args)...);
+        return owner_or_assert().template get_or_emplace<Type>(entt, std::forward<Args>(args)...);
     }
 
     /**
@@ -330,7 +316,7 @@ public:
     template<typename... Type>
     [[nodiscard]] auto try_get() const {
         static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type");
-        return owner->template try_get<Type...>(entt);
+        return owner_or_assert().template try_get<Type...>(entt);
     }
 
     /**
@@ -338,7 +324,21 @@ public:
      * @return True if the handle has no elements assigned, false otherwise.
      */
     [[nodiscard]] bool orphan() const {
-        return owner->orphan(entt);
+        return owner_or_assert().orphan(entt);
+    }
+
+    /**
+     * @brief Returns a const handle from a non-const one.
+     * @tparam Other A valid entity type.
+     * @tparam Args Scope of the handle to construct.
+     * @return A const handle referring to the same registry and the same
+     * entity.
+     */
+    template<typename Other, typename... Args>
+    [[nodiscard]] operator basic_handle<Other, Args...>() const noexcept {
+        static_assert(std::is_same_v<Other, Registry> || std::is_same_v<std::remove_const_t<Other>, Registry>, "Invalid conversion between different handles");
+        static_assert((sizeof...(Scope) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Scope)) && ... && (type_list_contains_v<type_list<Scope...>, Args>))), "Invalid conversion between different handles");
+        return owner ? basic_handle<Other, Args...>{*owner, entt} : basic_handle<Other, Args...>{};
     }
 
 private:

+ 413 - 127
test/entt/entity/handle.cpp

@@ -8,15 +8,20 @@
 #include <entt/entity/entity.hpp>
 #include <entt/entity/handle.hpp>
 #include <entt/entity/registry.hpp>
+#include "../../common/config.h"
 
 template<typename Type>
 struct BasicHandle: testing::Test {
     using type = Type;
 };
 
+template<typename Type>
+using BasicHandleDeathTest = BasicHandle<Type>;
+
 using BasicHandleTypes = ::testing::Types<entt::handle, entt::const_handle>;
 
 TYPED_TEST_SUITE(BasicHandle, BasicHandleTypes, );
+TYPED_TEST_SUITE(BasicHandleDeathTest, BasicHandleTypes, );
 
 TYPED_TEST(BasicHandle, Assumptions) {
     using handle_type = typename TestFixture::type;
@@ -36,41 +41,113 @@ TYPED_TEST(BasicHandle, Construction) {
     entt::registry registry;
     const auto entity = registry.create();
 
-    const handle_type handle{registry, entity};
+    handle_type handle{};
+
+    ASSERT_FALSE(handle);
+    ASSERT_FALSE(handle.valid());
+
+    ASSERT_TRUE(handle == entt::null);
+    ASSERT_EQ(handle.registry(), nullptr);
+
+    ASSERT_NE(handle, (entt::handle{registry, entity}));
+    ASSERT_NE(handle, (entt::const_handle{registry, entity}));
+
+    handle = handle_type{registry, entity};
 
-    ASSERT_FALSE(entt::null == handle.entity());
-    ASSERT_EQ(entity, handle);
     ASSERT_TRUE(handle);
+    ASSERT_TRUE(handle.valid());
+
+    ASSERT_FALSE(handle == entt::null);
+    ASSERT_EQ(handle.registry(), &registry);
 
     ASSERT_EQ(handle, (entt::handle{registry, entity}));
     ASSERT_EQ(handle, (entt::const_handle{registry, entity}));
 
-    testing::StaticAssertTypeEq<typename handle_type::registry_type *, decltype(handle.registry())>();
+    handle = {};
+
+    ASSERT_FALSE(handle);
+    ASSERT_FALSE(handle.valid());
+
+    ASSERT_TRUE(handle == entt::null);
+    ASSERT_EQ(handle.registry(), nullptr);
+
+    ASSERT_NE(handle, (entt::handle{registry, entity}));
+    ASSERT_NE(handle, (entt::const_handle{registry, entity}));
 }
 
-TYPED_TEST(BasicHandle, Invalidation) {
+TYPED_TEST(BasicHandle, Storage) {
     using handle_type = typename TestFixture::type;
 
-    handle_type handle;
+    entt::registry registry;
+    const auto entity = registry.create();
+    const handle_type handle{registry, entity};
+
+    testing::StaticAssertTypeEq<decltype(*handle.storage().begin()), std::pair<entt::id_type, entt::constness_as_t<entt::sparse_set, typename handle_type::registry_type> &>>();
+
+    ASSERT_EQ(handle.storage().begin(), handle.storage().end());
+
+    registry.storage<double>();
+    registry.emplace<int>(entity);
+
+    ASSERT_NE(handle.storage().begin(), handle.storage().end());
+    ASSERT_EQ(++handle.storage().begin(), handle.storage().end());
+    ASSERT_EQ(handle.storage().begin()->second.type(), entt::type_id<int>());
+}
+
+ENTT_DEBUG_TYPED_TEST(BasicHandleDeathTest, Storage) {
+    using handle_type = typename TestFixture::type;
+    const handle_type handle{};
+
+    ASSERT_DEATH([[maybe_unused]] auto iterable = handle.storage(), "");
+}
+
+TYPED_TEST(BasicHandle, HandleStorageIterator) {
+    using handle_type = typename TestFixture::type;
+
+    entt::registry registry;
+    const auto entity = registry.create();
+
+    registry.emplace<int>(entity);
+    registry.emplace<double>(entity);
+    // required to test the find-first initialization step
+    registry.storage<entt::entity>().erase(entity);
 
+    const handle_type handle{registry, entity};
+    auto iterable = handle.storage();
+
+    ASSERT_FALSE(registry.valid(entity));
     ASSERT_FALSE(handle);
-    ASSERT_EQ(handle.registry(), nullptr);
-    ASSERT_EQ(handle.entity(), entt::entity{entt::null});
+
+    auto end{iterable.begin()};
+    decltype(end) begin{};
+    begin = iterable.end();
+    std::swap(begin, end);
+
+    ASSERT_EQ(begin, iterable.cbegin());
+    ASSERT_EQ(end, iterable.cend());
+    ASSERT_NE(begin, end);
+
+    ASSERT_EQ(begin++, iterable.begin());
+    ASSERT_EQ(++begin, iterable.end());
+}
+
+TYPED_TEST(BasicHandle, Entity) {
+    using handle_type = typename TestFixture::type;
 
     entt::registry registry;
     const auto entity = registry.create();
 
-    handle = {registry, entity};
+    handle_type handle{};
 
-    ASSERT_TRUE(handle);
-    ASSERT_NE(handle.registry(), nullptr);
-    ASSERT_NE(handle.entity(), entt::entity{entt::null});
+    ASSERT_TRUE(handle == entt::null);
+    ASSERT_NE(handle.entity(), entity);
+    ASSERT_NE(handle, entity);
 
-    handle = {};
+    handle = handle_type{registry, entity};
 
-    ASSERT_FALSE(handle);
-    ASSERT_EQ(handle.registry(), nullptr);
-    ASSERT_EQ(handle.entity(), entt::entity{entt::null});
+    ASSERT_FALSE(handle == entt::null);
+    ASSERT_EQ(handle.entity(), entity);
+    ASSERT_EQ(handle, entity);
 }
 
 TEST(BasicHandle, Destruction) {
@@ -81,80 +158,291 @@ TEST(BasicHandle, Destruction) {
     entt::handle handle{registry, entity};
 
     ASSERT_TRUE(handle);
-    ASSERT_TRUE(handle.valid());
-    ASSERT_NE(handle.registry(), nullptr);
+    ASSERT_EQ(handle.registry(), &registry);
     ASSERT_EQ(handle.entity(), entity);
 
     handle.destroy(traits_type::to_version(entity));
 
     ASSERT_FALSE(handle);
-    ASSERT_FALSE(handle.valid());
-    ASSERT_NE(handle.registry(), nullptr);
-    ASSERT_EQ(registry.current(entity), typename entt::registry::version_type{});
+    ASSERT_EQ(handle.registry(), &registry);
     ASSERT_EQ(handle.entity(), entt::entity{entt::null});
+    ASSERT_EQ(registry.current(entity), traits_type::to_version(entity));
 
     handle = entt::handle{registry, registry.create()};
 
     ASSERT_TRUE(handle);
-    ASSERT_TRUE(handle.valid());
-    ASSERT_NE(handle.registry(), nullptr);
+    ASSERT_EQ(handle.registry(), &registry);
     ASSERT_EQ(handle.entity(), entity);
 
     handle.destroy();
 
     ASSERT_FALSE(handle);
-    ASSERT_FALSE(handle.valid());
     ASSERT_NE(handle.registry(), nullptr);
-    ASSERT_NE(registry.current(entity), typename entt::registry::version_type{});
+    ASSERT_NE(registry.current(entity), traits_type::to_version(entity));
     ASSERT_EQ(handle.entity(), entt::entity{entt::null});
 }
 
-TYPED_TEST(BasicHandle, Comparison) {
+ENTT_DEBUG_TEST(BasicHandleDeathTest, Destruction) {
+    using traits_type = entt::entt_traits<entt::entity>;
+
+    entt::handle handle{};
+
+    ASSERT_DEATH(handle.destroy(0u);, "");
+    ASSERT_DEATH(handle.destroy();, "");
+}
+
+TEST(BasicHandle, Emplace) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    const entt::handle handle{registry, entity};
+
+    ASSERT_FALSE(registry.all_of<int>(entity));
+
+    ASSERT_EQ(handle.emplace<int>(3), 3);
+
+    ASSERT_TRUE(registry.all_of<int>(entity));
+    ASSERT_EQ(registry.get<int>(entity), 3);
+}
+
+ENTT_DEBUG_TEST(BasicHandleDeathTest, Emplace) {
+    const entt::handle handle{};
+
+    ASSERT_DEATH(handle.emplace<int>(3);, "");
+}
+
+TEST(BasicHandle, EmplaceOrReplace) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    const entt::handle handle{registry, entity};
+
+    ASSERT_FALSE(registry.all_of<int>(entity));
+
+    ASSERT_EQ(handle.emplace_or_replace<int>(3), 3);
+
+    ASSERT_TRUE(registry.all_of<int>(entity));
+    ASSERT_EQ(registry.get<int>(entity), 3);
+
+    ASSERT_EQ(handle.emplace_or_replace<int>(1), 1);
+
+    ASSERT_EQ(registry.get<int>(entity), 1);
+}
+
+ENTT_DEBUG_TEST(BasicHandleDeathTest, EmplaceOrReplace) {
+    const entt::handle handle{};
+
+    ASSERT_DEATH(handle.emplace_or_replace<int>(3);, "");
+}
+
+TEST(BasicHandle, Patch) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    const entt::handle handle{registry, entity};
+
+    registry.emplace<int>(entity, 3);
+
+    ASSERT_TRUE(handle.all_of<int>());
+    ASSERT_EQ(handle.patch<int>([](auto &comp) { comp = 1; }), 1);
+
+    ASSERT_EQ(registry.get<int>(entity), 1);
+}
+
+ENTT_DEBUG_TEST(BasicHandleDeathTest, Patch) {
+    const entt::handle handle{};
+
+    ASSERT_DEATH(handle.patch<int>([](auto &comp) { comp = 1; });, "");
+}
+
+TEST(BasicHandle, Replace) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    const entt::handle handle{registry, entity};
+
+    registry.emplace<int>(entity, 3);
+
+    ASSERT_TRUE(handle.all_of<int>());
+    ASSERT_EQ(handle.replace<int>(1), 1);
+
+    ASSERT_EQ(registry.get<int>(entity), 1);
+}
+
+ENTT_DEBUG_TEST(BasicHandleDeathTest, Replace) {
+    const entt::handle handle{};
+
+    ASSERT_DEATH(handle.replace<int>(3);, "");
+}
+
+TEST(BasicHandle, Remove) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    const entt::handle handle{registry, entity};
+
+    ASSERT_FALSE(handle.all_of<int>());
+    ASSERT_EQ(handle.remove<int>(), 0u);
+
+    registry.emplace<int>(entity, 3);
+
+    ASSERT_TRUE(handle.all_of<int>());
+    ASSERT_EQ(handle.remove<int>(), 1u);
+
+    ASSERT_FALSE(handle.all_of<int>());
+    ASSERT_EQ(handle.remove<int>(), 0u);
+}
+
+ENTT_DEBUG_TEST(BasicHandleDeathTest, Remove) {
+    const entt::handle handle{};
+
+    ASSERT_DEATH(handle.remove<int>();, "");
+}
+
+TEST(BasicHandle, Erase) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    const entt::handle handle{registry, entity};
+
+    registry.emplace<int>(entity, 3);
+
+    ASSERT_TRUE(handle.all_of<int>());
+
+    handle.erase<int>();
+
+    ASSERT_FALSE(handle.all_of<int>());
+}
+
+ENTT_DEBUG_TEST(BasicHandleDeathTest, Erase) {
+    const entt::handle handle{};
+
+    ASSERT_DEATH(handle.erase<int>();, "");
+}
+
+TYPED_TEST(BasicHandle, AllAnyOf) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    const entt::handle handle{registry, entity};
+
+    ASSERT_FALSE((handle.all_of<int, char>()));
+    ASSERT_FALSE((handle.any_of<int, char>()));
+
+    registry.emplace<char>(entity);
+
+    ASSERT_FALSE((handle.all_of<int, char>()));
+    ASSERT_TRUE((handle.any_of<int, char>()));
+
+    registry.emplace<int>(entity);
+
+    ASSERT_TRUE((handle.all_of<int, char>()));
+    ASSERT_TRUE((handle.any_of<int, char>()));
+}
+
+ENTT_DEBUG_TYPED_TEST(BasicHandleDeathTest, AllAnyOf) {
     using handle_type = typename TestFixture::type;
+    const handle_type handle{};
 
-    handle_type handle{};
+    ASSERT_DEATH([[maybe_unused]] const auto all_of = handle.template all_of<int>(), "");
+    ASSERT_DEATH([[maybe_unused]] const auto any_of = handle.template any_of<int>(), "");
+}
 
-    ASSERT_EQ(handle, entt::handle{});
-    ASSERT_TRUE(handle == entt::handle{});
-    ASSERT_FALSE(handle != entt::handle{});
+TYPED_TEST(BasicHandle, Get) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    const entt::handle handle{registry, entity};
 
-    ASSERT_EQ(handle, entt::const_handle{});
-    ASSERT_TRUE(handle == entt::const_handle{});
-    ASSERT_FALSE(handle != entt::const_handle{});
+    registry.emplace<int>(entity, 3);
+    registry.emplace<char>(entity, 'c');
+
+    ASSERT_EQ(handle.get<int>(), 3);
+    ASSERT_EQ((handle.get<int, const char>()), (std::make_tuple(3, 'c')));
+
+    std::get<0>(handle.get<int, char>()) = 1;
+    std::get<1>(handle.get<int, char>()) = '\0';
+
+    ASSERT_EQ(registry.get<int>(entity), 1);
+    ASSERT_EQ(registry.get<char>(entity), '\0');
+}
+
+ENTT_DEBUG_TYPED_TEST(BasicHandleDeathTest, Get) {
+    using handle_type = typename TestFixture::type;
+    const handle_type handle{};
 
+    ASSERT_DEATH([[maybe_unused]] const auto &elem = handle.template get<int>(), "");
+}
+
+TEST(BasicHandle, GetOrEmplace) {
     entt::registry registry;
     const auto entity = registry.create();
-    handle = handle_type{registry, entity};
+    const entt::handle handle{registry, entity};
 
-    ASSERT_NE(handle, entt::handle{});
-    ASSERT_FALSE(handle == entt::handle{});
-    ASSERT_TRUE(handle != entt::handle{});
+    ASSERT_FALSE(registry.all_of<int>(entity));
 
-    ASSERT_NE(handle, entt::const_handle{});
-    ASSERT_FALSE(handle == entt::const_handle{});
-    ASSERT_TRUE(handle != entt::const_handle{});
+    ASSERT_EQ(handle.get_or_emplace<int>(3), 3);
 
-    handle = {};
+    ASSERT_TRUE(registry.all_of<int>(entity));
+    ASSERT_EQ(registry.get<int>(entity), 3);
 
-    ASSERT_EQ(handle, entt::handle{});
-    ASSERT_TRUE(handle == entt::handle{});
-    ASSERT_FALSE(handle != entt::handle{});
+    ASSERT_EQ(handle.get_or_emplace<int>(1), 3);
+}
 
-    ASSERT_EQ(handle, entt::const_handle{});
-    ASSERT_TRUE(handle == entt::const_handle{});
-    ASSERT_FALSE(handle != entt::const_handle{});
+ENTT_DEBUG_TEST(BasicHandleDeathTest, GetOrEmplace) {
+    const entt::handle handle{};
 
-    entt::registry diff;
-    handle = {registry, entity};
-    const handle_type other = {diff, diff.create()};
+    ASSERT_DEATH([[maybe_unused]] auto &&elem = handle.template get_or_emplace<int>(3), "");
+}
 
-    ASSERT_NE(handle, other);
-    ASSERT_FALSE(other == handle);
-    ASSERT_TRUE(other != handle);
-    ASSERT_EQ(handle.entity(), other.entity());
-    ASSERT_NE(handle.registry(), other.registry());
+TYPED_TEST(BasicHandle, TryGet) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    const entt::handle handle{registry, entity};
+
+    ASSERT_EQ((handle.try_get<int, const char>()), (std::make_tuple(nullptr, nullptr)));
+
+    registry.emplace<int>(entity, 3);
+
+    ASSERT_NE(handle.try_get<int>(), nullptr);
+    ASSERT_EQ(handle.try_get<char>(), nullptr);
+
+    ASSERT_EQ((*std::get<0>(handle.try_get<int, const char>())), 3);
+    ASSERT_EQ((std::get<1>(handle.try_get<int, const char>())), nullptr);
+
+    *std::get<0>(handle.try_get<int, const char>()) = 1;
+
+    ASSERT_EQ(registry.get<int>(entity), 1);
 }
 
+ENTT_DEBUG_TYPED_TEST(BasicHandleDeathTest, TryGet) {
+    using handle_type = typename TestFixture::type;
+    const handle_type handle{};
+
+    ASSERT_DEATH([[maybe_unused]] const auto *elem = handle.template try_get<int>(), "");
+}
+
+TYPED_TEST(BasicHandle, Orphan) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    const entt::handle handle{registry, entity};
+
+    ASSERT_TRUE(handle.orphan());
+
+    registry.emplace<int>(entity);
+    registry.emplace<char>(entity);
+
+    ASSERT_FALSE(handle.orphan());
+
+    registry.erase<char>(entity);
+
+    ASSERT_FALSE(handle.orphan());
+
+    registry.erase<int>(entity);
+
+    ASSERT_TRUE(handle.orphan());
+}
+
+ENTT_DEBUG_TYPED_TEST(BasicHandleDeathTest, Orphan) {
+    using handle_type = typename TestFixture::type;
+    const handle_type handle{};
+
+    ASSERT_DEATH([[maybe_unused]] const auto result = handle.orphan(), "");
+}
+
+/*
 TEST(BasicHandle, Component) {
     entt::registry registry;
     const auto entity = registry.create();
@@ -198,45 +486,9 @@ TEST(BasicHandle, Component) {
     ASSERT_EQ(nullptr, handle.try_get<char>());
     ASSERT_EQ(nullptr, std::get<1>(handle.try_get<int, char, double>()));
 }
+*/
 
-TYPED_TEST(BasicHandle, FromEntity) {
-    using handle_type = typename TestFixture::type;
-
-    entt::registry registry;
-    const auto entity = registry.create();
-
-    registry.emplace<int>(entity, 2);
-    registry.emplace<char>(entity, 'c');
-
-    const handle_type handle{registry, entity};
-
-    ASSERT_TRUE(handle);
-    ASSERT_EQ(entity, handle.entity());
-    ASSERT_TRUE((handle.template all_of<int, char>()));
-    ASSERT_EQ(handle.template get<int>(), 2);
-    ASSERT_EQ(handle.template get<char>(), 'c');
-}
-
-TEST(BasicHandle, Lifetime) {
-    entt::registry registry;
-    const auto entity = registry.create();
-    auto handle = std::make_unique<entt::handle>(registry, entity);
-    handle->emplace<int>();
-
-    ASSERT_FALSE(registry.storage<int>().empty());
-    ASSERT_NE(registry.storage<entt::entity>().free_list(), 0u);
-
-    for(auto [entt]: registry.storage<entt::entity>().each()) {
-        ASSERT_EQ(handle->entity(), entt);
-    }
-
-    handle.reset();
-
-    ASSERT_FALSE(registry.storage<int>().empty());
-    ASSERT_NE(registry.storage<entt::entity>().free_list(), 0u);
-}
-
-TEST(BasicHandle, ImplicitConversions) {
+TEST(BasicHandle, ImplicitConversion) {
     entt::registry registry;
     const entt::handle handle{registry, registry.create()};
     const entt::const_handle const_handle = handle;
@@ -251,53 +503,50 @@ TEST(BasicHandle, ImplicitConversions) {
     ASSERT_EQ(const_handle_view.get<int>(), 2);
 }
 
-TYPED_TEST(BasicHandle, Storage) {
+TYPED_TEST(BasicHandle, Comparison) {
     using handle_type = typename TestFixture::type;
 
-    entt::registry registry;
-    const auto entity = registry.create();
-    const handle_type handle{registry, entity};
-
-    testing::StaticAssertTypeEq<decltype(*handle.storage().begin()), std::pair<entt::id_type, entt::constness_as_t<entt::sparse_set, typename handle_type::registry_type> &>>();
-
-    ASSERT_EQ(handle.storage().begin(), handle.storage().end());
-
-    registry.storage<double>();
-    registry.emplace<int>(entity);
+    handle_type handle{};
 
-    ASSERT_NE(handle.storage().begin(), handle.storage().end());
-    ASSERT_EQ(++handle.storage().begin(), handle.storage().end());
-    ASSERT_EQ(handle.storage().begin()->second.type(), entt::type_id<int>());
-}
+    ASSERT_EQ(handle, entt::handle{});
+    ASSERT_TRUE(handle == entt::handle{});
+    ASSERT_FALSE(handle != entt::handle{});
 
-TYPED_TEST(BasicHandle, HandleStorageIterator) {
-    using handle_type = typename TestFixture::type;
+    ASSERT_EQ(handle, entt::const_handle{});
+    ASSERT_TRUE(handle == entt::const_handle{});
+    ASSERT_FALSE(handle != entt::const_handle{});
 
     entt::registry registry;
     const auto entity = registry.create();
+    handle = handle_type{registry, entity};
 
-    registry.emplace<int>(entity);
-    registry.emplace<double>(entity);
-    // required to test the find-first initialization step
-    registry.storage<entt::entity>().erase(entity);
+    ASSERT_NE(handle, entt::handle{});
+    ASSERT_FALSE(handle == entt::handle{});
+    ASSERT_TRUE(handle != entt::handle{});
 
-    const handle_type handle{registry, entity};
-    auto iterable = handle.storage();
+    ASSERT_NE(handle, entt::const_handle{});
+    ASSERT_FALSE(handle == entt::const_handle{});
+    ASSERT_TRUE(handle != entt::const_handle{});
 
-    ASSERT_FALSE(registry.valid(entity));
-    ASSERT_FALSE(handle);
+    handle = {};
 
-    auto end{iterable.begin()};
-    decltype(end) begin{};
-    begin = iterable.end();
-    std::swap(begin, end);
+    ASSERT_EQ(handle, entt::handle{});
+    ASSERT_TRUE(handle == entt::handle{});
+    ASSERT_FALSE(handle != entt::handle{});
 
-    ASSERT_EQ(begin, iterable.cbegin());
-    ASSERT_EQ(end, iterable.cend());
-    ASSERT_NE(begin, end);
+    ASSERT_EQ(handle, entt::const_handle{});
+    ASSERT_TRUE(handle == entt::const_handle{});
+    ASSERT_FALSE(handle != entt::const_handle{});
 
-    ASSERT_EQ(begin++, iterable.begin());
-    ASSERT_EQ(++begin, iterable.end());
+    entt::registry diff;
+    handle = {registry, entity};
+    const handle_type other = {diff, diff.create()};
+
+    ASSERT_NE(handle, other);
+    ASSERT_FALSE(other == handle);
+    ASSERT_TRUE(other != handle);
+    ASSERT_EQ(handle.entity(), other.entity());
+    ASSERT_NE(handle.registry(), other.registry());
 }
 
 TYPED_TEST(BasicHandle, Null) {
@@ -332,3 +581,40 @@ TYPED_TEST(BasicHandle, Null) {
         ASSERT_FALSE(entt::null != handle);
     }
 }
+
+TYPED_TEST(BasicHandle, FromEntity) {
+    using handle_type = typename TestFixture::type;
+
+    entt::registry registry;
+    const auto entity = registry.create();
+
+    registry.emplace<int>(entity, 2);
+    registry.emplace<char>(entity, 'c');
+
+    const handle_type handle{registry, entity};
+
+    ASSERT_TRUE(handle);
+    ASSERT_EQ(entity, handle.entity());
+    ASSERT_TRUE((handle.template all_of<int, char>()));
+    ASSERT_EQ(handle.template get<int>(), 2);
+    ASSERT_EQ(handle.template get<char>(), 'c');
+}
+
+TEST(BasicHandle, Lifetime) {
+    entt::registry registry;
+    const auto entity = registry.create();
+    auto handle = std::make_unique<entt::handle>(registry, entity);
+    handle->emplace<int>();
+
+    ASSERT_FALSE(registry.storage<int>().empty());
+    ASSERT_NE(registry.storage<entt::entity>().free_list(), 0u);
+
+    for(auto [entt]: registry.storage<entt::entity>().each()) {
+        ASSERT_EQ(handle->entity(), entt);
+    }
+
+    handle.reset();
+
+    ASSERT_FALSE(registry.storage<int>().empty());
+    ASSERT_NE(registry.storage<entt::entity>().free_list(), 0u);
+}