فهرست منبع

entity/registry:
* added entt_traits<T>::reserved()
* added tombstone_t type and tombstone constant
* create-with-hint also accepts null entities (discarded)
* registry skips over tombstone versions and rejects them

Michele Caini 4 سال پیش
والد
کامیت
e2e433480b
5فایلهای تغییر یافته به همراه190 افزوده شده و 43 حذف شده
  1. 102 6
      src/entt/entity/entity.hpp
  2. 26 21
      src/entt/entity/registry.hpp
  3. 35 6
      test/entt/entity/entity.cpp
  4. 24 7
      test/entt/entity/registry.cpp
  5. 3 3
      test/entt/entity/snapshot.cpp

+ 102 - 6
src/entt/entity/entity.hpp

@@ -74,7 +74,7 @@ struct entt_traits<std::uint64_t> {
  * @tparam Type Type of identifier.
  */
 template<typename Type>
-class entt_traits: public internal::entt_traits<Type> {
+class entt_traits: private internal::entt_traits<Type> {
     using traits_type = internal::entt_traits<Type>;
 
 public:
@@ -99,7 +99,7 @@ public:
      * @param value The value to convert.
      * @return The integral representation of the entity part.
      */
-    [[nodiscard]] static constexpr auto to_entity(const Type value) {
+    [[nodiscard]] static constexpr auto to_entity(const Type value) ENTT_NOEXCEPT {
         return (to_integral(value) & traits_type::entity_mask);
     }
 
@@ -108,7 +108,7 @@ public:
      * @param value The value to convert.
      * @return The integral representation of the version part.
      */
-    [[nodiscard]] static constexpr auto to_version(const Type value) {
+    [[nodiscard]] static constexpr auto to_version(const Type value) ENTT_NOEXCEPT {
         constexpr auto mask = (traits_type::version_mask << traits_type::entity_shift);
         return ((to_integral(value) & mask) >> traits_type::entity_shift);
     }
@@ -119,9 +119,17 @@ public:
      * @param version The version part of the identifier.
      * @return A properly constructed identifier.
      */
-    [[nodiscard]] static constexpr auto to_type(const entity_type entity, const version_type version = {}) {
+    [[nodiscard]] static constexpr auto to_type(const entity_type entity, const version_type version) ENTT_NOEXCEPT {
         return Type{(entity & traits_type::entity_mask) | (version << traits_type::entity_shift)};
     }
+
+    /**
+     * @brief Returns the reserved identifer.
+     * @return The reserved identifier.
+     */
+    [[nodiscard]] static constexpr auto reserved() ENTT_NOEXCEPT {
+        return to_type(traits_type::entity_mask, traits_type::version_mask);
+    }
 };
 
 
@@ -146,7 +154,7 @@ struct null_t {
      */
     template<typename Entity>
     [[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT {
-        return Entity{entt_traits<Entity>::entity_mask};
+        return entt_traits<Entity>::reserved();
     }
 
     /**
@@ -173,7 +181,7 @@ struct null_t {
      */
     template<typename Entity>
     [[nodiscard]] constexpr bool operator==(const Entity &entity) const ENTT_NOEXCEPT {
-        return entt_traits<Entity>::to_entity(entity) == static_cast<typename entt_traits<Entity>::entity_type>(*this);
+        return entt_traits<Entity>::to_entity(entity) == entt_traits<Entity>::to_entity(*this);
     }
 
     /**
@@ -215,6 +223,84 @@ template<typename Entity>
 }
 
 
+/*! @brief Tombstone object for all entity identifiers.  */
+struct tombstone_t {
+    /**
+     * @brief Converts the tombstone object to identifiers of any type.
+     * @tparam Entity Type of entity identifier.
+     * @return The tombstone representation for the given identifier.
+     */
+    template<typename Entity>
+    [[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT {
+        return entt_traits<Entity>::reserved();
+    }
+
+    /**
+     * @brief Compares two tombstone objects.
+     * @return True in all cases.
+     */
+    [[nodiscard]] constexpr bool operator==(const tombstone_t &) const ENTT_NOEXCEPT {
+        return true;
+    }
+
+    /**
+     * @brief Compares two tombstone objects.
+     * @return False in all cases.
+     */
+    [[nodiscard]] constexpr bool operator!=(const tombstone_t &) const ENTT_NOEXCEPT {
+        return false;
+    }
+
+    /**
+     * @brief Compares a null tombstone and an entity identifier of any type.
+     * @tparam Entity Type of entity identifier.
+     * @param entity Entity identifier with which to compare.
+     * @return False if the two elements differ, true otherwise.
+     */
+    template<typename Entity>
+    [[nodiscard]] constexpr bool operator==(const Entity &entity) const ENTT_NOEXCEPT {
+        return entt_traits<Entity>::to_version(entity) == entt_traits<Entity>::to_version(*this);
+    }
+
+    /**
+     * @brief Compares a tombstone object and an entity identifier of any type.
+     * @tparam Entity Type of entity identifier.
+     * @param entity Entity identifier with which to compare.
+     * @return True if the two elements differ, false otherwise.
+     */
+    template<typename Entity>
+        [[nodiscard]] constexpr bool operator!=(const Entity &entity) const ENTT_NOEXCEPT {
+        return !(entity == *this);
+    }
+};
+
+
+/**
+ * @brief Compares a tombstone object and an entity identifier of any type.
+ * @tparam Entity Type of entity identifier.
+ * @param entity Entity identifier with which to compare.
+ * @param other A tombstone object yet to be converted.
+ * @return False if the two elements differ, true otherwise.
+ */
+template<typename Entity>
+[[nodiscard]] constexpr bool operator==(const Entity &entity, const tombstone_t &other) ENTT_NOEXCEPT {
+    return other.operator==(entity);
+}
+
+
+/**
+ * @brief Compares a tombstone object and an entity identifier of any type.
+ * @tparam Entity Type of entity identifier.
+ * @param entity Entity identifier with which to compare.
+ * @param other A tombstone object yet to be converted.
+ * @return True if the two elements differ, false otherwise.
+ */
+template<typename Entity>
+[[nodiscard]] constexpr bool operator!=(const Entity &entity, const tombstone_t &other) ENTT_NOEXCEPT {
+    return !(other == entity);
+}
+
+
 /**
  * @brief Compile-time constant for null entities.
  *
@@ -225,6 +311,16 @@ template<typename Entity>
 inline constexpr null_t null{};
 
 
+/**
+ * @brief Compile-time constant for tombstone entities.
+ *
+ * There exist implicit conversions from this variable to entity identifiers of
+ * any allowed type. Similarly, there exist comparision operators between the
+ * tombstone entity and any other entity identifier.
+ */
+inline constexpr tombstone_t tombstone{};
+
+
 }
 
 

+ 26 - 21
src/entt/entity/registry.hpp

@@ -119,29 +119,30 @@ class basic_registry {
     }
 
     template<typename Component>
-    [[nodiscard]] const storage_type<Component> * pool_if_exists() const {
+    [[nodiscard]] const storage_type<Component> * pool_if_exists() const ENTT_NOEXCEPT {
         static_assert(std::is_same_v<Component, std::decay_t<Component>>, "Non-decayed types not allowed");
         const auto index = type_seq<Component>::value();
         return (!(index < pools.size()) || !pools[index].pool) ? nullptr : static_cast<const storage_type<Component> *>(pools[index].pool.get());
     }
 
-    Entity generate_identifier(const std::size_t pos) {
-        ENTT_ASSERT(pos < traits_type::to_integral(entt::null), "No entities available");
-        return entity_type{static_cast<typename traits_type::entity_type>(pos)};
+    auto generate_identifier(const std::size_t pos) ENTT_NOEXCEPT {
+        ENTT_ASSERT(pos < traits_type::to_integral(null), "No entities available");
+        return traits_type::to_type(static_cast<typename traits_type::entity_type>(pos), {});
     }
 
-    Entity recycle_identifier() {
+    auto recycle_identifier() ENTT_NOEXCEPT {
         ENTT_ASSERT(available != null, "No entities available");
-        const auto curr = traits_type::to_integral(available);
+        const auto curr = traits_type::to_entity(available);
         const auto version = traits_type::to_version(entities[curr]);
-        available = entity_type{traits_type::to_entity(entities[curr])};
+        available = entities[curr];
         return entities[curr] = traits_type::to_type(curr, version);
     }
 
-    void release_entity(const Entity entity, const typename traits_type::version_type version) {
+    auto release_entity(const Entity entity, const typename traits_type::version_type version) {
         const auto entt = traits_type::to_entity(entity);
-        entities[entt] = traits_type::to_type(traits_type::to_integral(available), version);
-        available = entity_type{entt};
+        entities[entt] = traits_type::to_type(traits_type::to_integral(available), version + (traits_type::to_type(null, version) == tombstone));
+        available = traits_type::to_type(entt, {});
+        return traits_type::to_version(entities[entt]);
     }
 
 public:
@@ -160,7 +161,7 @@ public:
      * @return The entity identifier without the version.
      */
     [[nodiscard]] static entity_type entity(const entity_type entity) ENTT_NOEXCEPT {
-        return entity_type{traits_type::to_entity(entity)};
+        return traits_type::to_type(traits_type::to_entity(entity), {});
     }
 
     /**
@@ -387,7 +388,7 @@ public:
      *
      * @return A valid entity identifier.
      */
-    entity_type create() {
+    [[nodiscard]] entity_type create() {
         return (available == null) ? entities.emplace_back(generate_identifier(entities.size())) : recycle_identifier();
     }
 
@@ -397,16 +398,17 @@ public:
      * @sa create
      *
      * If the requested entity isn't in use, the suggested identifier is created
-     * and returned. Otherwise, a new one will be generated for this purpose.
+     * and returned. Otherwise, a new identifier is generated.
      *
-     * @param hint A desired entity identifier.
+     * @param hint Required entity identifier.
      * @return A valid entity identifier.
      */
     [[nodiscard]] entity_type create(const entity_type hint) {
-        ENTT_ASSERT(hint != null, "Null entity not available");
         const auto length = entities.size();
 
-        if(const auto req = traits_type::to_entity(hint); !(req < length)) {
+        if(hint == null || hint == tombstone) {
+            return create();
+        } else if(const auto req = traits_type::to_entity(hint); !(req < length)) {
             entities.resize(size_type(req) + 1u, null);
 
             for(auto pos = length; pos < req; ++pos) {
@@ -484,29 +486,32 @@ public:
      * Attempting to use an invalid entity results in undefined behavior.
      *
      * @param entity A valid entity identifier.
+     * @return The version of the recycled entity.
      */
-    void destroy(const entity_type entity) {
-        destroy(entity, version(entity) + 1u);
+    version_type destroy(const entity_type entity) {
+        return destroy(entity, traits_type::to_version(entity) + 1u);
     }
 
     /**
      * @brief Destroys an entity.
      *
-     * The suggested version is used instead of the implicitly generated one.
+     * The suggested version or the valid version closest to the suggested one
+     * is used instead of the implicitly generated version.
      *
      * @sa destroy
      *
      * @param entity A valid entity identifier.
      * @param version A desired version upon destruction.
+     * @return The version actually assigned to the entity.
      */
-    void destroy(const entity_type entity, const version_type version) {
+    version_type destroy(const entity_type entity, const version_type version) {
         ENTT_ASSERT(valid(entity), "Invalid entity");
 
         for(auto pos = pools.size(); pos; --pos) {
             pools[pos-1].pool && pools[pos-1].pool->remove(entity, this);
         }
 
-        release_entity(entity, version);
+        return release_entity(entity, version);
     }
 
     /**

+ 35 - 6
test/entt/entity/entity.cpp

@@ -23,21 +23,20 @@ TEST(Entity, Traits) {
 
     ASSERT_EQ(traits_type::to_type(traits_type::to_entity(entity), traits_type::to_version(entity)), entity);
     ASSERT_EQ(traits_type::to_type(traits_type::to_entity(other), traits_type::to_version(other)), other);
-    ASSERT_NE(traits_type::to_type(traits_type::to_integral(entity)), entity);
+    ASSERT_NE(traits_type::to_type(traits_type::to_integral(entity), {}), entity);
 }
 
 TEST(Entity, Null) {
     using traits_type = entt::entt_traits<entt::entity>;
 
     ASSERT_FALSE(entt::entity{} == entt::null);
-    ASSERT_TRUE(entt::entity{traits_type::entity_mask} == entt::null);
-    ASSERT_TRUE(entt::entity{~typename traits_type::entity_type{}} == entt::null);
+    ASSERT_TRUE(entt::entity{traits_type::reserved()} == entt::null);
 
     ASSERT_TRUE(entt::null == entt::null);
     ASSERT_FALSE(entt::null != entt::null);
 
     entt::registry registry{};
-    auto entity = registry.create();
+    const auto entity = registry.create();
 
     registry.emplace<int>(entity, 42);
 
@@ -47,6 +46,36 @@ TEST(Entity, Null) {
     ASSERT_TRUE(entity != entt::null);
     ASSERT_TRUE(entt::null != entity);
 
-    ASSERT_FALSE(registry.valid(entt::null));
-    ASSERT_DEATH((entity = registry.create(entt::null)), "");
+    const entt::entity other = entt::null;
+
+    ASSERT_FALSE(registry.valid(other));
+    ASSERT_NE(registry.create(other), other);
+}
+
+TEST(Entity, Tombstone) {
+    using traits_type = entt::entt_traits<entt::entity>;
+
+    ASSERT_FALSE(entt::entity{} == entt::tombstone);
+    ASSERT_TRUE(entt::entity{traits_type::reserved()} == entt::tombstone);
+
+    ASSERT_TRUE(entt::tombstone == entt::tombstone);
+    ASSERT_FALSE(entt::tombstone != entt::tombstone);
+
+    entt::registry registry{};
+    const auto entity = registry.create();
+
+    registry.emplace<int>(entity, 42);
+
+    ASSERT_FALSE(entity == entt::tombstone);
+    ASSERT_FALSE(entt::tombstone == entity);
+
+    ASSERT_TRUE(entity != entt::tombstone);
+    ASSERT_TRUE(entt::tombstone != entity);
+
+    const auto vers = traits_type::to_version(entt::tombstone);
+    const auto other = traits_type::to_type(traits_type::to_entity(entity), vers);
+
+    ASSERT_FALSE(registry.valid(entt::tombstone));
+    ASSERT_NE(registry.destroy(entity, vers), vers);
+    ASSERT_NE(registry.create(other), other);
 }

+ 24 - 7
test/entt/entity/registry.cpp

@@ -535,7 +535,7 @@ TEST(Registry, VersionOverflow) {
     ASSERT_NE(registry.current(entity), registry.version(entity));
     ASSERT_NE(registry.current(entity), typename traits_type::version_type{});
 
-    registry.destroy(registry.create(), typename traits_type::version_type{traits_type::version_mask});
+    registry.destroy(registry.create(), traits_type::to_version(traits_type::reserved()) - 1u);
     registry.destroy(registry.create());
 
     ASSERT_EQ(registry.current(entity), registry.version(entity));
@@ -544,9 +544,26 @@ TEST(Registry, VersionOverflow) {
 
 TEST(Registry, NullEntity) {
     entt::registry registry;
+    const entt::entity entity = entt::null;
 
-    ASSERT_FALSE(registry.valid(entt::null));
-    ASSERT_DEATH(static_cast<void>(registry.create(entt::null)), "");
+    ASSERT_FALSE(registry.valid(entity));
+    ASSERT_NE(registry.create(entity), entity);
+}
+
+TEST(Registry, TombstoneVersion) {
+    using traits_type = entt::entt_traits<entt::entity>;
+
+    entt::registry registry;
+    const entt::entity entity = entt::tombstone;
+
+    ASSERT_FALSE(registry.valid(entity));
+
+    const auto other = registry.create();
+    const auto vers = traits_type::to_version(entity);
+    const auto required = traits_type::to_type(traits_type::to_entity(other), vers);
+
+    ASSERT_NE(registry.destroy(other, vers), vers);
+    ASSERT_NE(registry.create(required), required);
 }
 
 TEST(Registry, Each) {
@@ -554,18 +571,18 @@ TEST(Registry, Each) {
     entt::registry::size_type tot;
     entt::registry::size_type match;
 
-    registry.create();
+    static_cast<void>(registry.create());
     registry.emplace<int>(registry.create());
-    registry.create();
+    static_cast<void>(registry.create());
     registry.emplace<int>(registry.create());
-    registry.create();
+    static_cast<void>(registry.create());
 
     tot = 0u;
     match = 0u;
 
     registry.each([&](auto entity) {
         if(registry.all_of<int>(entity)) { ++match; }
-        registry.create();
+        static_cast<void>(registry.create());
         ++tot;
     });
 

+ 3 - 3
test/entt/entity/snapshot.cpp

@@ -286,7 +286,7 @@ TEST(Snapshot, Continuous) {
     input_archive<storage_type> input{storage};
 
     for(int i = 0; i < 10; ++i) {
-        src.create();
+        static_cast<void>(src.create());
     }
 
     src.clear();
@@ -549,8 +549,8 @@ TEST(Snapshot, SyncDataMembers) {
     output_archive<storage_type> output{storage};
     input_archive<storage_type> input{storage};
 
-    src.create();
-    src.create();
+    static_cast<void>(src.create());
+    static_cast<void>(src.create());
 
     src.clear();