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)
This commit is contained in:
1
TODO
1
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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)])};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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{};
|
||||
|
||||
Reference in New Issue
Block a user