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:
@@ -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{};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user