* 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)
This commit is contained in:
Michele Caini
2021-05-24 18:31:41 +02:00
parent 0b3e3fd19a
commit 3e7dc7af29
9 changed files with 134 additions and 85 deletions

1
TODO
View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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;
};
}
/**
* @brief Converts an entity type to its underlying type.
* Internal details not to be documented.
* @endcond
*/
/**
* @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.
*

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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)])};
}
/**

View File

@@ -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>;

View File

@@ -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{};