Browse Source

Truly invalid entt::handle (#551)

Vennor 5 years ago
parent
commit
897850c3eb
3 changed files with 167 additions and 26 deletions
  1. 7 2
      docs/md/entity.md
  2. 32 19
      src/entt/entity/handle.hpp
  3. 128 5
      test/entt/entity/handle.cpp

+ 7 - 2
docs/md/entity.md

@@ -674,10 +674,15 @@ the requested method, passing on the arguments if necessary.
 
 ### Handle
 
-A handle is a thin wrapper around an entity and a registry. It provides the same
-functions that the registry offers for working with components, such as
+A handle is a thin wrapper around an entity and a registry. It provides the
+same functions that the registry offers for working with components, such as
 `emplace`, `get`, `patch`, `remove` and so on. The difference being that the
 entity is implicitly passed to the registry.<br/>
+It's default constructible as an invalid handle that contains a null registry
+and a null entity. When it contains a null registry calling functions that
+delegate execution to the registry will cause a crash, so it's recommended to
+check the validity of the handle with implicit cast to `bool` when in doubt.
+<br/>
 A handle is also non-owning, meaning that it can be freely copied and moved
 around without affecting its entity (in fact, handles happen to be trivially
 copyable). An implication of this is that mutability becomes part of the

+ 32 - 19
src/entt/entity/handle.hpp

@@ -27,31 +27,36 @@ struct basic_handle {
         basic_registry<entity_type>
     >;
 
+    /**
+     * @brief Constructs a default invalid handle.
+     */
+    basic_handle() ENTT_NOEXCEPT
+        : reg{nullptr}, entt{null}
+    {}
+
     /**
      * @brief Constructs a handle from a given registry and entity.
      * @param ref An instance of the registry class.
      * @param value An entity identifier.
      */
-    basic_handle(registry_type &ref, entity_type value = null) ENTT_NOEXCEPT
+    basic_handle(registry_type &ref, entity_type value) ENTT_NOEXCEPT
         : reg{&ref}, entt{value}
     {}
 
     /**
-     * @brief Assigns an entity to a handle.
-     * @param value An entity identifier.
-     * @return This handle.
+     * @brief Compares two handles.
+     * @return True if both handles refer to the same registry and the same entity, false otherwise.
      */
-    basic_handle & operator=(const entity_type value) ENTT_NOEXCEPT {
-        entt = value;
-        return *this;
+    bool operator==(const basic_handle<entity_type> &other) const ENTT_NOEXCEPT {
+        return reg == other.registry() && entt == other.entity();
     }
 
     /**
-     * @brief Assigns the null object to a handle.
-     * @return This handle.
+     * @brief Compares two handles.
+     * @return False if both handles refer to the same registry and the same entity, true otherwise.
      */
-    basic_handle & operator=(null_t) ENTT_NOEXCEPT {
-        return (*this = static_cast<entity_type>(null));
+    bool operator!=(const basic_handle<entity_type> &other) const ENTT_NOEXCEPT {
+        return !( *this == other);
     }
 
     /**
@@ -59,7 +64,7 @@ struct basic_handle {
      * @return A const handle referring to the same entity.
      */
     [[nodiscard]] operator basic_handle<const entity_type>() const ENTT_NOEXCEPT {
-        return {*reg, entt};
+        return reg ? basic_handle<const entity_type>{ *reg, entt} : basic_handle<const entity_type>{};
     }
 
     /**
@@ -70,25 +75,33 @@ struct basic_handle {
         return entity();
     }
 
+    /**
+     * @brief Checks if a handle refers to non-null registry pointer and entity.
+     * @return True if the handle refers to non-null registry and entity, false otherwise.
+     */
+    [[nodiscard]] explicit operator bool() const {
+        return reg && entt != null;
+    }
+
     /**
      * @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]] explicit operator bool() const {
+    [[nodiscard]] bool valid() const {
         return reg->valid(entt);
     }
 
     /**
-     * @brief Returns a reference to the underlying registry.
-     * @return A reference to the underlying registry.
+     * @brief Returns a pointer to the underlying registry.
+     * @return A pointer to the underlying registry.
      */
-    [[nodiscard]] registry_type & registry() const ENTT_NOEXCEPT {
-        return *reg;
+    [[nodiscard]] registry_type  * registry() const ENTT_NOEXCEPT {
+        return reg;
     }
 
     /**
      * @brief Returns the entity associated with a handle.
-     * @return The entity associated with the handle.
+     * @param value he entity associated with the handle.
      */
     [[nodiscard]] entity_type entity() const ENTT_NOEXCEPT {
         return entt;
@@ -253,7 +266,7 @@ struct basic_handle {
     }
 
 private:
-    registry_type *reg;
+    registry_type  *reg;
     entity_type entt;
 };
 

+ 128 - 5
test/entt/entity/handle.cpp

@@ -36,18 +36,141 @@ TEST(BasicHandle, Construction) {
 
     ASSERT_EQ(handle, chandle);
 
-    static_assert(std::is_same_v<entt::registry &, decltype(handle.registry())>);
-    static_assert(std::is_same_v<const entt::registry &, decltype(chandle.registry())>);
+    static_assert(std::is_same_v<entt::registry *, decltype(handle.registry())>);
+    static_assert(std::is_same_v<const entt::registry *, decltype(chandle.registry())>);
+}
+
 
-    handle = entt::null;
+TEST(BasicHandle, Invalidation) {
+    entt::handle handle;
 
+    ASSERT_TRUE(nullptr == handle.registry());
     ASSERT_TRUE(entt::null == handle.entity());
-    ASSERT_NE(entity, handle);
     ASSERT_FALSE(handle);
 
-    ASSERT_NE(handle, chandle);
+    entt::registry registry;
+    const auto entity = registry.create();
+
+    handle = {registry, entity};
+
+    ASSERT_FALSE(nullptr == handle.registry());
+    ASSERT_FALSE(entt::null == handle.entity());
+    ASSERT_TRUE(handle);
+
+    handle = {};
+
+    ASSERT_TRUE(nullptr == handle.registry());
+    ASSERT_TRUE(entt::null == handle.entity());
+    ASSERT_FALSE(handle);
+}
+
+
+TEST(BasicHandle, Comparison) {
+    entt::registry registry;
+    const auto entity1 = registry.create();
+    const auto entity2 = registry.create();
+
+    entt::handle handle1{registry, entity1};
+    entt::handle handle2{registry, entity2};
+    entt::const_handle chandle1 = handle1;
+    entt::const_handle chandle2 = handle2;
+
+    ASSERT_NE(handle1, handle2);
+    ASSERT_FALSE(handle1 == handle2);
+    ASSERT_TRUE(handle1 != handle2);
+
+    ASSERT_NE(chandle1, chandle2);
+    ASSERT_FALSE(chandle1 == chandle2);
+    ASSERT_TRUE(chandle1 != chandle2);
+
+    ASSERT_EQ(handle1, chandle1);
+    ASSERT_TRUE(handle1 == chandle1);
+    ASSERT_FALSE(handle1 != chandle1);
+
+    ASSERT_EQ(handle2, chandle2);
+    ASSERT_TRUE(handle2 == chandle2);
+    ASSERT_FALSE(handle2 != chandle2);
+
+    ASSERT_NE(handle1, chandle2);
+    ASSERT_FALSE(handle1 == chandle2);
+    ASSERT_TRUE(handle1 != chandle2);
+
+    handle1 = {};
+    chandle2 = {};
+
+    ASSERT_NE(handle1, handle2);
+    ASSERT_FALSE(handle1 == handle2);
+    ASSERT_TRUE(handle1 != handle2);
+
+    ASSERT_NE(chandle1, chandle2);
+    ASSERT_FALSE(chandle1 == chandle2);
+    ASSERT_TRUE(chandle1 != chandle2);
+
+    ASSERT_NE(handle1, chandle1);
+    ASSERT_FALSE(handle1 == chandle1);
+    ASSERT_TRUE(handle1 != chandle1);
+
+    ASSERT_NE(handle2, chandle2);
+    ASSERT_FALSE(handle2 == chandle2);
+    ASSERT_TRUE(handle2 != chandle2);
+
+    ASSERT_EQ(handle1, chandle2);
+    ASSERT_TRUE(handle1 == chandle2);
+    ASSERT_FALSE(handle1 != chandle2);
+
+    handle2 = {};
+    chandle1 = {};
+
+    ASSERT_EQ(handle1, handle2);
+    ASSERT_TRUE(handle1 == handle2);
+    ASSERT_FALSE(handle1 != handle2);
+
+    ASSERT_EQ(chandle1, chandle2);
+    ASSERT_TRUE(chandle1 == chandle2);
+    ASSERT_FALSE(chandle1 != chandle2);
+
+    ASSERT_EQ(handle1, chandle1);
+    ASSERT_TRUE(handle1 == chandle1);
+    ASSERT_FALSE(handle1 != chandle1);
+
+    ASSERT_EQ(handle2, chandle2);
+    ASSERT_TRUE(handle2 == chandle2);
+    ASSERT_FALSE(handle2 != chandle2);
+
+    ASSERT_EQ(handle1, chandle2);
+    ASSERT_TRUE(handle1 == chandle2);
+    ASSERT_FALSE(handle1 != chandle2);
+
+    entt::registry registry_b;
+    const auto entity_b1 = registry.create();
+
+    handle1 = {registry_b, entity_b1};
+    handle2 = {registry, entity1};
+    chandle1 = handle1;
+    chandle2 = handle2;
+
+    ASSERT_NE(handle1, handle2);
+    ASSERT_FALSE(handle1 == handle2);
+    ASSERT_TRUE(handle1 != handle2);
+
+    ASSERT_NE(chandle1, chandle2);
+    ASSERT_FALSE(chandle1 == chandle2);
+    ASSERT_TRUE(chandle1 != chandle2);
+
+    ASSERT_EQ(handle1, chandle1);
+    ASSERT_TRUE(handle1 == chandle1);
+    ASSERT_FALSE(handle1 != chandle1);
+
+    ASSERT_EQ(handle2, chandle2);
+    ASSERT_TRUE(handle2 == chandle2);
+    ASSERT_FALSE(handle2 != chandle2);
+
+    ASSERT_NE(handle1, chandle2);
+    ASSERT_FALSE(handle1 == chandle2);
+    ASSERT_TRUE(handle1 != chandle2);
 }
 
+
 TEST(BasicHandle, Component) {
     entt::registry registry;
     const auto entity = registry.create();