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
This commit is contained in:
Michele Caini
2021-05-26 13:33:52 +02:00
parent b8bc3e4e94
commit e2e433480b
5 changed files with 190 additions and 43 deletions

View File

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

View File

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

View File

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

View File

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

View File

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