diff --git a/src/entt/entity/entity.hpp b/src/entt/entity/entity.hpp index 9a71268b6..b622d04f5 100644 --- a/src/entt/entity/entity.hpp +++ b/src/entt/entity/entity.hpp @@ -74,7 +74,7 @@ struct entt_traits { * @tparam Type Type of identifier. */ template -class entt_traits: public internal::entt_traits { +class entt_traits: private internal::entt_traits { using traits_type = internal::entt_traits; 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 [[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT { - return Entity{entt_traits::entity_mask}; + return entt_traits::reserved(); } /** @@ -173,7 +181,7 @@ struct null_t { */ template [[nodiscard]] constexpr bool operator==(const Entity &entity) const ENTT_NOEXCEPT { - return entt_traits::to_entity(entity) == static_cast::entity_type>(*this); + return entt_traits::to_entity(entity) == entt_traits::to_entity(*this); } /** @@ -215,6 +223,84 @@ template } +/*! @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 + [[nodiscard]] constexpr operator Entity() const ENTT_NOEXCEPT { + return entt_traits::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 + [[nodiscard]] constexpr bool operator==(const Entity &entity) const ENTT_NOEXCEPT { + return entt_traits::to_version(entity) == entt_traits::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 + [[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 +[[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 +[[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 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{}; + + } diff --git a/src/entt/entity/registry.hpp b/src/entt/entity/registry.hpp index 59beff39b..10e7f67cb 100644 --- a/src/entt/entity/registry.hpp +++ b/src/entt/entity/registry.hpp @@ -119,29 +119,30 @@ class basic_registry { } template - [[nodiscard]] const storage_type * pool_if_exists() const { + [[nodiscard]] const storage_type * pool_if_exists() const ENTT_NOEXCEPT { static_assert(std::is_same_v>, "Non-decayed types not allowed"); const auto index = type_seq::value(); return (!(index < pools.size()) || !pools[index].pool) ? nullptr : static_cast *>(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(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(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); } /** diff --git a/test/entt/entity/entity.cpp b/test/entt/entity/entity.cpp index 6443bf99d..2ca68f021 100644 --- a/test/entt/entity/entity.cpp +++ b/test/entt/entity/entity.cpp @@ -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; 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(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; + + 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(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); } diff --git a/test/entt/entity/registry.cpp b/test/entt/entity/registry.cpp index f9668e94c..bbefc3b3e 100644 --- a/test/entt/entity/registry.cpp +++ b/test/entt/entity/registry.cpp @@ -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(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::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(registry.create()); registry.emplace(registry.create()); - registry.create(); + static_cast(registry.create()); registry.emplace(registry.create()); - registry.create(); + static_cast(registry.create()); tot = 0u; match = 0u; registry.each([&](auto entity) { if(registry.all_of(entity)) { ++match; } - registry.create(); + static_cast(registry.create()); ++tot; }); diff --git a/test/entt/entity/snapshot.cpp b/test/entt/entity/snapshot.cpp index 41767007f..0f19eca51 100644 --- a/test/entt/entity/snapshot.cpp +++ b/test/entt/entity/snapshot.cpp @@ -286,7 +286,7 @@ TEST(Snapshot, Continuous) { input_archive input{storage}; for(int i = 0; i < 10; ++i) { - src.create(); + static_cast(src.create()); } src.clear(); @@ -549,8 +549,8 @@ TEST(Snapshot, SyncDataMembers) { output_archive output{storage}; input_archive input{storage}; - src.create(); - src.create(); + static_cast(src.create()); + static_cast(src.create()); src.clear();