Просмотр исходного кода

entity:
* review of entt_traits design
* added static constexpr member function entt_traits::to_integral
* added static constexpr member function entt_traits::to_entity
* added static constexpr member function entt_traits::to_version
* added static constexpr member function entt_traits::to_type
* custom class identifiers must expose member type entity_field
* it's no longer required to specialize entt_traits (breaking change)

Michele Caini 4 лет назад
Родитель
Сommit
3e7dc7af29

+ 1 - 0
TODO

@@ -12,6 +12,7 @@ WIP:
 * page size: split sparse/packed sizes, reduce comp page size, add per-pool size, allow for 0 sizes (old fully packed array)
 * make it possible to register externally managed pools with the registry (allow for system centric mode)
 * registry: switch to the udata/mixin model and get rid of poly storage, use pointer to sparse set only for pools, discard pool_data type.
+* it's now possible to have 0 as null entity/version, so we can finally switch to it
 * make pools available (registry/view/group), review operator| for views
 * compressed pair to exploit ebo in sparse set and the others
 * isolate view iterator, unwrap iterators in registry ::remove/::erase/::destroy to use the faster solution for non-view iterators

+ 2 - 5
docs/md/entity.md

@@ -179,11 +179,8 @@ the alias `entt::registry` for `entt::basic_registry<entt::entity>`.
 
 Entities are represented by _entity identifiers_. An entity identifier carries
 information about the entity itself and its version.<br/>
-User defined identifiers can be introduced by means of enum classes and custom
-types for which a specialization of `entt_traits` exists. For this purpose,
-`entt_traits` is also defined as a _sfinae-friendly_ class template. In theory,
-integral types can also be used as entity identifiers, even though this may
-break in future and isn't recommended in general.
+User defined identifiers can be introduced through enum classes and class types
+that define an `entity_type` member of type `std::uint32_t` or `std::uint64_t`.
 
 A registry is used both to construct and to destroy entities:
 

+ 5 - 7
docs/md/faq.md

@@ -101,21 +101,19 @@ performance from this component.
 
 Custom entity identifiers are definitely a good idea in two cases at least:
 
-* If `std::uint32_t` is too large or isn't large enough for your purposes, since
-  this is the underlying type of `entt::entity`.
+* If `std::uint32_t` isn't large enough for your purposes, since this is the
+  underlying type of `entt::entity`.
 * If you want to avoid conflicts when using multiple registries.
 
-Identifiers can be defined through enum classes and custom types for which a
-specialization of `entt_traits` exists. For this purpose, `entt_traits` is also
-defined as a _sfinae-friendly_ class template.<br/>
+Identifiers can be defined through enum classes and class types that define an
+`entity_type` member of type `std::uint32_t` or `std::uint64_t`.<br/>
 In fact, this is a definition equivalent to that of `entt::entity`:
 
 ```cpp
 enum class entity: std::uint32_t {};
 ```
 
-In theory, integral types can also be used as entity identifiers, even though
-this may break in future and isn't recommended in general.
+There is no limit to the number of identifiers that can be defined.
 
 ## Warning C4307: integral constant overflow
 

+ 82 - 46
src/entt/entity/entity.hpp

@@ -12,86 +12,128 @@ namespace entt {
 
 
 /**
- * @brief Entity traits.
- *
- * Primary template isn't defined on purpose. All the specializations give a
- * compile-time error unless the template parameter is an accepted entity type.
+ * @cond TURN_OFF_DOXYGEN
+ * Internal details not to be documented.
  */
+
+
+namespace internal {
+
+
 template<typename, typename = void>
 struct entt_traits;
 
 
-/**
- * @brief Entity traits for enumeration types.
- * @tparam Type The type to check.
- */
 template<typename Type>
 struct entt_traits<Type, std::enable_if_t<std::is_enum_v<Type>>>
-        : entt_traits<std::underlying_type_t<Type>>
+    : entt_traits<std::underlying_type_t<Type>>
+{};
+
+
+template<typename Type>
+struct entt_traits<Type, std::enable_if_t<std::is_class_v<Type>>>
+    : entt_traits<typename Type::entity_type>
 {};
 
 
-/**
- * @brief Entity traits for a 32 bits entity identifier.
- *
- * A 32 bits entity identifier guarantees:
- *
- * * 20 bits for the entity number (suitable for almost all the games).
- * * 12 bit for the version (resets in [0-4095]).
- */
 template<>
 struct entt_traits<std::uint32_t> {
-    /*! @brief Underlying entity type. */
     using entity_type = std::uint32_t;
-    /*! @brief Underlying version type. */
     using version_type = std::uint16_t;
-    /*! @brief Difference type. */
     using difference_type = std::int64_t;
 
-    /*! @brief Mask to use to get the entity number out of an identifier. */
     static constexpr entity_type entity_mask = 0xFFFFF;
-    /*! @brief Mask to use to get the version out of an identifier. */
     static constexpr entity_type version_mask = 0xFFF;
-    /*! @brief Extent of the entity number within an identifier. */
     static constexpr std::size_t entity_shift = 20u;
 };
 
 
-/**
- * @brief Entity traits for a 64 bits entity identifier.
- *
- * A 64 bits entity identifier guarantees:
- *
- * * 32 bits for the entity number (an indecently large number).
- * * 32 bit for the version (an indecently large number).
- */
 template<>
 struct entt_traits<std::uint64_t> {
-    /*! @brief Underlying entity type. */
     using entity_type = std::uint64_t;
-    /*! @brief Underlying version type. */
     using version_type = std::uint32_t;
-    /*! @brief Difference type. */
     using difference_type = std::int64_t;
 
-    /*! @brief Mask to use to get the entity number out of an identifier. */
     static constexpr entity_type entity_mask = 0xFFFFFFFF;
-    /*! @brief Mask to use to get the version out of an identifier. */
     static constexpr entity_type version_mask = 0xFFFFFFFF;
-    /*! @brief Extent of the entity number within an identifier. */
     static constexpr std::size_t entity_shift = 32u;
 };
 
 
+}
+
+
+/**
+* Internal details not to be documented.
+* @endcond
+*/
+
+
 /**
- * @brief Converts an entity type to its underlying type.
+ * @brief Entity traits.
+ * @tparam Type Type of identifier.
+ */
+template<typename Type>
+class entt_traits: public internal::entt_traits<Type> {
+    using traits_type = internal::entt_traits<Type>;
+
+public:
+    /*! @brief Underlying entity type. */
+    using entity_type = typename traits_type::entity_type;
+    /*! @brief Underlying version type. */
+    using version_type = typename traits_type::version_type;
+    /*! @brief Difference type. */
+    using difference_type = typename traits_type::difference_type;
+
+    /**
+     * @brief Converts an entity to its underlying type.
+     * @param value The value to convert.
+     * @return The integral representation of the given value.
+     */
+    [[nodiscard]] static constexpr auto to_integral(const Type value) ENTT_NOEXCEPT {
+        return static_cast<entity_type>(value);
+    }
+
+    /**
+     * @brief Returns the entity part once converted to the underlying type.
+     * @param value The value to convert.
+     * @return The integral representation of the entity part.
+     */
+    [[nodiscard]] static constexpr auto to_entity(const Type value) {
+        return (to_integral(value) & traits_type::entity_mask);
+    }
+
+    /**
+     * @brief Returns the version part once converted to the underlying type.
+     * @param value The value to convert.
+     * @return The integral representation of the version part.
+     */
+    [[nodiscard]] static constexpr auto to_version(const Type value) {
+        constexpr auto mask = (traits_type::version_mask << traits_type::entity_shift);
+        return ((to_integral(value) & mask) >> traits_type::entity_shift);
+    }
+
+    /**
+     * @brief Constructs an identifier from its parts.
+     * @param entity The entity part of the identifier.
+     * @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 = {}) {
+        return Type{entity | (version << traits_type::entity_shift)};
+    }
+};
+
+
+/**
+ * @brief Converts an entity to its underlying type.
  * @tparam Entity The value type.
  * @param entity The value to convert.
  * @return The integral representation of the given value.
  */
 template<typename Entity>
 [[nodiscard]] constexpr auto to_integral(const Entity entity) ENTT_NOEXCEPT {
-    return static_cast<typename entt_traits<Entity>::entity_type>(entity);
+    return entt_traits<Entity>::to_integral(entity);
 }
 
 
@@ -131,7 +173,7 @@ struct null_t {
      */
     template<typename Entity>
     [[nodiscard]] constexpr bool operator==(const Entity &entity) const ENTT_NOEXCEPT {
-        return (to_integral(entity) & entt_traits<Entity>::entity_mask) == to_integral(static_cast<Entity>(*this));
+        return entt_traits<Entity>::to_entity(entity) == static_cast<typename entt_traits<Entity>::entity_type>(*this);
     }
 
     /**
@@ -173,12 +215,6 @@ template<typename Entity>
 }
 
 
-/**
- * Internal details not to be documented.
- * @endcond
- */
-
-
 /**
  * @brief Compile-time constant for null entities.
  *

+ 17 - 18
src/entt/entity/registry.hpp

@@ -126,22 +126,21 @@ class basic_registry {
     }
 
     Entity generate_identifier(const std::size_t pos) {
-        // traits_type::entity_mask is reserved to allow for null identifiers
-        ENTT_ASSERT(static_cast<typename traits_type::entity_type>(pos) < traits_type::entity_mask, "No entities available");
+        ENTT_ASSERT(pos < traits_type::to_integral(entt::null), "No entities available");
         return entity_type{static_cast<typename traits_type::entity_type>(pos)};
     }
 
     Entity recycle_identifier() {
         ENTT_ASSERT(available != null, "No entities available");
-        const auto curr = to_integral(available);
-        const auto version = to_integral(entities[curr]) & (traits_type::version_mask << traits_type::entity_shift);
-        available = entity_type{to_integral(entities[curr]) & traits_type::entity_mask};
-        return entities[curr] = entity_type{curr | version};
+        const auto curr = traits_type::to_integral(available);
+        const auto version = traits_type::to_version(entities[curr]);
+        available = entity_type{traits_type::to_entity(entities[curr])};
+        return entities[curr] = traits_type::to_type(curr, version);
     }
 
     void release_entity(const Entity entity, const typename traits_type::version_type version) {
-        const auto entt = to_integral(entity) & traits_type::entity_mask;
-        entities[entt] = entity_type{to_integral(available) | (typename traits_type::entity_type{version} << traits_type::entity_shift)};
+        const auto entt = traits_type::to_entity(entity);
+        entities[entt] = traits_type::to_type(traits_type::to_integral(available), version);
         available = entity_type{entt};
     }
 
@@ -161,7 +160,7 @@ public:
      * @return The entity identifier without the version.
      */
     [[nodiscard]] static entity_type entity(const entity_type entity) ENTT_NOEXCEPT {
-        return entity_type{to_integral(entity) & traits_type::entity_mask};
+        return entity_type{traits_type::to_entity(entity)};
     }
 
     /**
@@ -170,7 +169,7 @@ public:
      * @return The version stored along with the given entity identifier.
      */
     [[nodiscard]] static version_type version(const entity_type entity) ENTT_NOEXCEPT {
-        return version_type(to_integral(entity) >> traits_type::entity_shift);
+        return traits_type::to_version(entity);
     }
 
     /*! @brief Default constructor. */
@@ -236,7 +235,7 @@ public:
         auto sz = entities.size();
 
         for(auto curr = available; curr != null; --sz) {
-            curr = entities[to_integral(curr) & traits_type::entity_mask];
+            curr = entities[traits_type::to_entity(curr)];
         }
 
         return sz;
@@ -357,7 +356,7 @@ public:
      * @return True if the identifier is valid, false otherwise.
      */
     [[nodiscard]] bool valid(const entity_type entity) const {
-        const auto pos = size_type(to_integral(entity) & traits_type::entity_mask);
+        const auto pos = size_type(traits_type::to_entity(entity));
         return (pos < entities.size() && entities[pos] == entity);
     }
 
@@ -373,7 +372,7 @@ public:
      * @return Actual version for the given entity identifier.
      */
     [[nodiscard]] version_type current(const entity_type entity) const {
-        const auto pos = size_type(to_integral(entity) & traits_type::entity_mask);
+        const auto pos = size_type(traits_type::to_entity(entity));
         ENTT_ASSERT(pos < entities.size(), "Entity does not exist");
         return version(entities[pos]);
     }
@@ -407,7 +406,7 @@ public:
         ENTT_ASSERT(hint != null, "Null entity not available");
         const auto length = entities.size();
 
-        if(const auto req = (to_integral(hint) & traits_type::entity_mask); !(req < length)) {
+        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) {
@@ -415,12 +414,12 @@ public:
             }
 
             return (entities[req] = hint);
-        } else if(const auto curr = (to_integral(entities[req]) & traits_type::entity_mask); req == curr) {
+        } else if(const auto curr = traits_type::to_entity(entities[req]); req == curr) {
             return create();
         } else {
             auto *it = &available;
-            for(; (to_integral(*it) & traits_type::entity_mask) != req; it = &entities[to_integral(*it) & traits_type::entity_mask]);
-            *it = entity_type{curr | (to_integral(*it) & (traits_type::version_mask << traits_type::entity_shift))};
+            for(; traits_type::to_entity(*it) != req; it = &entities[traits_type::to_entity(*it)]);
+            *it = traits_type::to_type(curr, traits_type::to_version(*it));
             return (entities[req] = hint);
         }
     }
@@ -949,7 +948,7 @@ public:
             }
         } else {
             for(auto pos = entities.size(); pos; --pos) {
-                if(const auto entity = entities[pos - 1]; (to_integral(entity) & traits_type::entity_mask) == (pos - 1)) {
+                if(const auto entity = entities[pos - 1]; traits_type::to_entity(entity) == (pos - 1)) {
                     func(entity);
                 }
             }

+ 1 - 1
src/entt/entity/snapshot.hpp

@@ -448,7 +448,7 @@ public:
         for(decltype(length) pos{}; pos < length; ++pos) {
             archive(entt);
 
-            if(const auto entity = (to_integral(entt) & traits_type::entity_mask); entity == pos) {
+            if(const auto entity = traits_type::to_entity(entt); entity == pos) {
                 restore(entt);
             } else {
                 destroy(entt);

+ 4 - 4
src/entt/entity/sparse_set.hpp

@@ -161,11 +161,11 @@ class basic_sparse_set {
     };
 
     [[nodiscard]] static auto page(const Entity entt) ENTT_NOEXCEPT {
-        return size_type{(to_integral(entt) & traits_type::entity_mask) / sparse_page};
+        return size_type{traits_type::to_entity(entt) / sparse_page};
     }
 
     [[nodiscard]] static auto offset(const Entity entt) ENTT_NOEXCEPT {
-        return size_type{to_integral(entt) & (sparse_page - 1)};
+        return size_type{traits_type::to_integral(entt) & (sparse_page - 1)};
     }
 
     [[nodiscard]] auto assure_page(const std::size_t idx) {
@@ -236,7 +236,7 @@ class basic_sparse_set {
         about_to_erase(entt, ud);
 
         auto &ref = sparse[page(entt)][offset(entt)];
-        const auto pos = size_type{to_integral(ref)};
+        const auto pos = size_type{traits_type::to_integral(ref)};
 
         const auto last = --count;
         packed[pos] = std::exchange(packed[last], entt);
@@ -498,7 +498,7 @@ public:
      */
     [[nodiscard]] size_type index(const entity_type entt) const {
         ENTT_ASSERT(contains(entt), "Set does not contain entity");
-        return size_type{to_integral(sparse[page(entt)][offset(entt)])};
+        return size_type{traits_type::to_integral(sparse[page(entt)][offset(entt)])};
     }
 
     /**

+ 21 - 0
test/entt/entity/entity.cpp

@@ -4,6 +4,27 @@
 #include <entt/entity/entity.hpp>
 #include <entt/entity/registry.hpp>
 
+TEST(Entity, Traits) {
+    using traits_type = entt::entt_traits<entt::entity>;
+    entt::registry registry{};
+
+    registry.destroy(registry.create());
+    const auto entity = registry.create();
+    const auto other = registry.create();
+
+    ASSERT_EQ(entt::to_integral(entity), traits_type::to_integral(entity));
+    ASSERT_NE(entt::to_integral(entity), entt::to_integral<entt::entity>(entt::null));
+    ASSERT_NE(entt::to_integral(entity), entt::to_integral(entt::entity{}));
+
+    ASSERT_EQ(traits_type::to_entity(entity), 0u);
+    ASSERT_EQ(traits_type::to_version(entity), 1u);
+    ASSERT_EQ(traits_type::to_entity(other), 1u);
+    ASSERT_EQ(traits_type::to_version(other), 0u);
+
+    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);
+}
+
 TEST(Entity, Null) {
     using traits_type = entt::entt_traits<entt::entity>;
 

+ 1 - 4
test/example/custom_identifier.cpp

@@ -4,7 +4,7 @@
 #include <entt/entity/registry.hpp>
 
 struct entity_id {
-    using entity_type = typename entt::entt_traits<entt::entity>::entity_type;
+    using entity_type = std::uint32_t;
     static constexpr auto null = entt::null;
 
     constexpr entity_id(entity_type value = null)
@@ -23,9 +23,6 @@ private:
     entity_type entt;
 };
 
-template<>
-struct entt::entt_traits<entity_id>: entt::entt_traits<entt::entity> {};
-
 TEST(Example, CustomIdentifier) {
     entt::basic_registry<entity_id> registry{};
     entity_id entity{};