From 068b6ed49bccf21165fc2d18d7555ee41ba99a53 Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Thu, 26 Jan 2023 09:41:25 +0100 Subject: [PATCH] registry: first (almost) backward compatible version with opaque hidden entity storage --- src/entt/entity/registry.hpp | 214 ++++++++++--------------------- src/entt/entity/snapshot.hpp | 19 +-- test/entt/entity/registry.cpp | 20 +-- test/entt/entity/snapshot.cpp | 8 +- test/lib/registry/shared/lib.cpp | 2 + 5 files changed, 96 insertions(+), 167 deletions(-) diff --git a/src/entt/entity/registry.hpp b/src/entt/entity/registry.hpp index 91c10d84a..ec0d5a2d5 100644 --- a/src/entt/entity/registry.hpp +++ b/src/entt/entity/registry.hpp @@ -305,7 +305,7 @@ class basic_registry { template [[nodiscard]] auto &assure(const id_type id = type_hash::value()) { static_assert(std::is_same_v>, "Non-decayed types not allowed"); - auto &cpool = pools.first()[id]; + auto &cpool = pools[id]; if(!cpool) { using alloc_type = typename storage_for_type>::allocator_type; @@ -320,7 +320,7 @@ class basic_registry { cpool->bind(forward_as_any(*this)); } - ENTT_ASSERT((cpool->type() == type_id, void, Type>>()), "Unexpected type"); + ENTT_ASSERT(cpool->type() == type_id(), "Unexpected type"); return static_cast &>(*cpool); } @@ -328,8 +328,8 @@ class basic_registry { [[nodiscard]] const auto &assure(const id_type id = type_hash::value()) const { static_assert(std::is_same_v>, "Non-decayed types not allowed"); - if(const auto it = pools.first().find(id); it != pools.first().cend()) { - ENTT_ASSERT((it->second->type() == type_id, void, Type>>()), "Unexpected type"); + if(const auto it = pools.find(id); it != pools.cend()) { + ENTT_ASSERT(it->second->type() == type_id(), "Unexpected type"); return static_cast &>(*it->second); } @@ -337,27 +337,10 @@ class basic_registry { return placeholder; } - auto generate_identifier(const std::size_t pos) noexcept { - ENTT_ASSERT(pos < traits_type::to_entity(null), "No entities available"); - return traits_type::combine(static_cast(pos), {}); - } - - auto recycle_identifier() noexcept { - ENTT_ASSERT(free_list != null, "No entities available"); - const auto curr = traits_type::to_entity(free_list); - free_list = traits_type::combine(traits_type::to_integral(epool[curr]), tombstone); - return (epool[curr] = traits_type::combine(curr, traits_type::to_integral(epool[curr]))); - } - - auto release_entity(const Entity entt, const typename entt_traits::version_type version) { - const typename traits_type::version_type vers = version + (version == traits_type::to_version(tombstone)); - epool[traits_type::to_entity(entt)] = traits_type::construct(traits_type::to_integral(free_list), vers); - free_list = traits_type::combine(traits_type::to_integral(entt), tombstone); - return vers; - } - void rebind() { - for(auto &&curr: pools.first()) { + entities.bind(forward_as_any(*this)); + + for(auto &&curr: pools) { curr.second->bind(forward_as_any(*this)); } } @@ -396,11 +379,11 @@ public: */ basic_registry(const size_type count, const allocator_type &allocator = allocator_type{}) : vars{allocator}, - free_list{tombstone}, - epool{allocator}, - pools{allocator, allocator}, + pools{allocator}, + entities{allocator}, groups{allocator} { - pools.first().reserve(count); + pools.reserve(count); + rebind(); } /** @@ -409,9 +392,8 @@ public: */ basic_registry(basic_registry &&other) noexcept : vars{std::move(other.vars)}, - free_list{std::move(other.free_list)}, - epool{std::move(other.epool)}, pools{std::move(other.pools)}, + entities{std::move(other.entities)}, groups{std::move(other.groups)} { ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a registry is not allowed"); @@ -427,9 +409,8 @@ public: ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a registry is not allowed"); vars = std::move(other.vars); - free_list = std::move(other.free_list); - epool = std::move(other.epool); pools = std::move(other.pools); + entities = std::move(other.entities); groups = std::move(other.groups); rebind(); @@ -443,10 +424,10 @@ public: */ void swap(basic_registry &other) { using std::swap; + swap(vars, other.vars); - swap(free_list, other.free_list); - swap(epool, other.epool); swap(pools, other.pools); + swap(entities, other.entities); swap(groups, other.groups); rebind(); @@ -458,7 +439,7 @@ public: * @return The associated allocator. */ [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { - return pools.second(); + return entities.get_allocator(); } /** @@ -470,12 +451,12 @@ public: * @return An iterable object to use to _visit_ the registry. */ [[nodiscard]] auto storage() noexcept { - return iterable_adaptor{internal::registry_storage_iterator{pools.first().begin()}, internal::registry_storage_iterator{pools.first().end()}}; + return iterable_adaptor{internal::registry_storage_iterator{pools.begin()}, internal::registry_storage_iterator{pools.end()}}; } /*! @copydoc storage */ [[nodiscard]] auto storage() const noexcept { - return iterable_adaptor{internal::registry_storage_iterator{pools.first().cbegin()}, internal::registry_storage_iterator{pools.first().cend()}}; + return iterable_adaptor{internal::registry_storage_iterator{pools.cbegin()}, internal::registry_storage_iterator{pools.cend()}}; } /** @@ -493,8 +474,8 @@ public: * @return A pointer to the storage if it exists, a null pointer otherwise. */ [[nodiscard]] const base_type *storage(const id_type id) const { - const auto it = pools.first().find(id); - return it == pools.first().cend() ? nullptr : it->second.get(); + const auto it = pools.find(id); + return it == pools.cend() ? nullptr : it->second.get(); } /** @@ -529,7 +510,7 @@ public: * @return Number of entities created so far. */ [[nodiscard]] size_type size() const noexcept { - return epool.size(); + return entities.size(); } /** @@ -537,13 +518,7 @@ public: * @return Number of entities still in use. */ [[nodiscard]] size_type alive() const { - auto sz = epool.size(); - - for(auto curr = free_list; curr != null; --sz) { - curr = epool[traits_type::to_entity(curr)]; - } - - return sz; + return entities.in_use(); } /** @@ -551,7 +526,7 @@ public: * @param cap Desired capacity. */ void reserve(const size_type cap) { - epool.reserve(cap); + entities.reserve(cap); } /** @@ -560,7 +535,7 @@ public: * @return Capacity of the registry. */ [[nodiscard]] size_type capacity() const noexcept { - return epool.capacity(); + return entities.capacity(); } /** @@ -584,19 +559,15 @@ public: * @return A pointer to the array of entities. */ [[nodiscard]] const entity_type *data() const noexcept { - return epool.data(); + return entities.data(); } /** - * @brief Returns the head of the list of released entities. - * - * This function is intended for use in conjunction with `assign`.
- * The returned entity has an invalid identifier in all cases. - * - * @return The head of the list of released entities. + * @brief Returns the number of released entities. + * @return The number of released entities. */ - [[nodiscard]] entity_type released() const noexcept { - return free_list; + [[nodiscard]] size_type released() const noexcept { + return (entities.size() - entities.in_use()); } /** @@ -605,8 +576,7 @@ public: * @return True if the identifier is valid, false otherwise. */ [[nodiscard]] bool valid(const entity_type entt) const { - const auto pos = size_type(traits_type::to_entity(entt)); - return (pos < epool.size() && epool[pos] == entt); + return entities.contains(entt); } /** @@ -616,8 +586,7 @@ public: * version otherwise. */ [[nodiscard]] version_type current(const entity_type entt) const { - const auto pos = size_type(traits_type::to_entity(entt)); - return traits_type::to_version(pos < epool.size() ? epool[pos] : tombstone); + return entities.current(entt); } /** @@ -625,7 +594,7 @@ public: * @return A valid identifier. */ [[nodiscard]] entity_type create() { - return (free_list == null) ? epool.emplace_back(generate_identifier(epool.size())) : recycle_identifier(); + return entities.spawn(); } /** @@ -638,26 +607,7 @@ public: * @return A valid identifier. */ [[nodiscard]] entity_type create(const entity_type hint) { - const auto length = epool.size(); - - if(hint == null || hint == tombstone) { - return create(); - } else if(const auto req = traits_type::to_entity(hint); !(req < length)) { - epool.resize(size_type(req) + 1u, null); - - for(auto pos = length; pos < req; ++pos) { - release_entity(generate_identifier(pos), {}); - } - - return (epool[req] = hint); - } else if(const auto curr = traits_type::to_entity(epool[req]); req == curr) { - return create(); - } else { - auto *it = &free_list; - for(; traits_type::to_entity(*it) != req; it = &epool[traits_type::to_entity(*it)]) {} - *it = traits_type::combine(curr, traits_type::to_integral(*it)); - return (epool[req] = hint); - } + return entities.spawn(hint); } /** @@ -671,16 +621,7 @@ public: */ template void create(It first, It last) { - for(; free_list != null && first != last; ++first) { - *first = recycle_identifier(); - } - - const auto length = epool.size(); - epool.resize(length + std::distance(first, last), null); - - for(auto pos = length; first != last; ++first, ++pos) { - *first = epool[pos] = generate_identifier(pos); - } + entities.spawn(std::move(first), std::move(last)); } /** @@ -698,13 +639,13 @@ public: * @tparam It Type of input iterator. * @param first An iterator to the first element of the range of entities. * @param last An iterator past the last element of the range of entities. - * @param destroyed The head of the list of destroyed entities. + * @param destroyed The number of released entities. */ template - void assign(It first, It last, const entity_type destroyed) { - ENTT_ASSERT(!alive(), "Entities still alive"); - epool.assign(std::move(first), std::move(last)); - free_list = destroyed; + void assign(It first, It last, const size_type destroyed) { + ENTT_ASSERT(!entities.in_use(), "Non-empty registry"); + entities.push(first, last); + entities.in_use(entities.size() - destroyed); } /** @@ -719,7 +660,9 @@ public: * @return The version of the recycled entity. */ version_type release(const entity_type entt) { - return release(entt, static_cast(traits_type::to_version(entt) + 1u)); + ENTT_ASSERT(std::all_of(pools.cbegin(), pools.cend(), [entt](auto &&curr) { return (curr.second->current(entt) == traits_type::to_version(tombstone)); }), "Non-orphan entity"); + entities.erase(entt); + return entities.current(entt); } /** @@ -735,9 +678,10 @@ public: * @return The version actually assigned to the entity. */ version_type release(const entity_type entt, const version_type version) { - ENTT_ASSERT(valid(entt), "Invalid identifier"); - ENTT_ASSERT(std::all_of(pools.first().cbegin(), pools.first().cend(), [entt](auto &&curr) { return (curr.second->current(entt) == traits_type::to_version(tombstone)); }), "Non-orphan entity"); - return release_entity(entt, version); + release(entt); + const auto vers = static_cast(version + (version == traits_type::to_version(tombstone))); + entities.bump(traits_type::construct(traits_type::to_entity(entt), vers)); + return vers; } /** @@ -751,9 +695,7 @@ public: */ template void release(It first, It last) { - for(; first != last; ++first) { - release(*first); - } + entities.erase(std::move(first), std::move(last)); } /** @@ -786,8 +728,8 @@ public: * @return The version actually assigned to the entity. */ version_type destroy(const entity_type entt, const version_type version) { - for(size_type pos = pools.first().size(); pos; --pos) { - pools.first().begin()[pos - 1u].second->remove(entt); + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[pos - 1u].second->remove(entt); } return release(entt, version); @@ -804,32 +746,15 @@ public: */ template void destroy(It first, It last) { - if constexpr(std::is_same_v) { - base_type *owner = nullptr; + const auto len = entities.pack(first, last); + auto from = entities.each().cbegin().base(); + const auto to = from + len; - for(size_type pos = pools.first().size(); pos; --pos) { - if(pools.first().begin()[pos - 1u].second->data() == first.data()) { - owner = pools.first().begin()[pos - 1u].second.get(); - } else { - pools.first().begin()[pos - 1u].second->remove(first, last); - } - } - - if(owner) { - // waiting to further/completely optimize this with the entity storage - for(; first != last; ++first) { - const auto entt = *first; - owner->remove(entt); - release(entt); - } - } else { - release(std::move(first), std::move(last)); - } - } else { - for(; first != last; ++first) { - destroy(*first); - } + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[pos - 1u].second->remove(from, to); } + + entities.erase(from, to); } /** @@ -1063,7 +988,7 @@ public: template void compact() { if constexpr(sizeof...(Type) == 0) { - for(auto &&curr: pools.first()) { + for(auto &&curr: pools) { curr.second->compact(); } } else { @@ -1185,11 +1110,11 @@ public: template void clear() { if constexpr(sizeof...(Type) == 0) { - for(auto &&curr: pools.first()) { - curr.second->clear(); + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[pos - 1u].second->clear(); } - each([this](const auto entity) { this->release(entity); }); + entities.clear(); } else { (assure().clear(), ...); } @@ -1211,16 +1136,8 @@ public: */ template void each(Func func) const { - if(free_list == null) { - for(auto pos = epool.size(); pos; --pos) { - func(epool[pos - 1]); - } - } else { - for(auto pos = epool.size(); pos; --pos) { - if(const auto entity = epool[pos - 1]; traits_type::to_entity(entity) == (pos - 1)) { - func(entity); - } - } + for(auto [entt]: entities.each()) { + func(entt); } } @@ -1230,7 +1147,7 @@ public: * @return True if the entity has no components assigned, false otherwise. */ [[nodiscard]] bool orphan(const entity_type entt) const { - return std::none_of(pools.first().cbegin(), pools.first().cend(), [entt](auto &&curr) { return curr.second->contains(entt); }); + return std::none_of(pools.cbegin(), pools.cend(), [entt](auto &&curr) { return curr.second->contains(entt); }); } /** @@ -1566,9 +1483,8 @@ public: private: context vars; - entity_type free_list; - std::vector epool; - compressed_pair pools; + container_type pools; + storage_for_type entities; std::vector> groups; }; diff --git a/src/entt/entity/snapshot.hpp b/src/entt/entity/snapshot.hpp index 9e87b8a9a..078b4773c 100644 --- a/src/entt/entity/snapshot.hpp +++ b/src/entt/entity/snapshot.hpp @@ -87,7 +87,7 @@ public: const basic_snapshot &entities(Archive &archive) const { const auto sz = reg->size(); - archive(typename traits_type::entity_type(sz + 1u)); + archive(sz); archive(reg->released()); for(auto first = reg->data(), last = first + sz; first != last; ++first) { @@ -218,16 +218,18 @@ public: */ template const basic_snapshot_loader &entities(Archive &archive) const { - typename traits_type::entity_type length{}; + typename registry_type::size_type length{}; + typename registry_type::size_type released{}; archive(length); + archive(released); std::vector all(length); for(std::size_t pos{}; pos < length; ++pos) { archive(all[pos]); } - reg->assign(++all.cbegin(), all.cend(), all[0u]); + reg->assign(all.cbegin(), all.cend(), released); return *this; } @@ -431,14 +433,15 @@ public: */ template basic_continuous_loader &entities(Archive &archive) { - typename traits_type::entity_type length{}; - entity_type entt{}; + typename registry_type::size_type length{}; + typename registry_type::size_type released{}; archive(length); - // discards the head of the list of destroyed entities - archive(entt); + // discards the number of destroyed entities + archive(released); - for(std::size_t pos{}, last = length - 1u; pos < last; ++pos) { + for(std::size_t pos{}; pos < length; ++pos) { + entity_type entt{entt::null}; archive(entt); if(const auto entity = traits_type::to_entity(entt); entity == pos) { diff --git a/test/entt/entity/registry.cpp b/test/entt/entity/registry.cpp index cf5e50897..36fb09fcc 100644 --- a/test/entt/entity/registry.cpp +++ b/test/entt/entity/registry.cpp @@ -376,13 +376,12 @@ TEST(Registry, Functionalities) { TEST(Registry, Constructors) { entt::registry registry; entt::registry other{42}; - const entt::entity entity = entt::tombstone; ASSERT_TRUE(registry.empty()); ASSERT_TRUE(other.empty()); - ASSERT_EQ(registry.released(), entity); - ASSERT_EQ(other.released(), entity); + ASSERT_EQ(registry.released(), 0u); + ASSERT_EQ(other.released(), 0u); } TEST(Registry, Move) { @@ -486,6 +485,8 @@ TEST(Registry, Identifiers) { } TEST(Registry, Data) { + using traits_type = entt::entt_traits; + entt::registry registry; ASSERT_EQ(std::as_const(registry).data(), nullptr); @@ -497,8 +498,8 @@ TEST(Registry, Data) { const auto other = registry.create(); registry.release(entity); - ASSERT_NE(*std::as_const(registry).data(), entity); - ASSERT_EQ(*(std::as_const(registry).data() + 1u), other); + ASSERT_EQ(*std::as_const(registry).data(), other); + ASSERT_EQ(*(std::as_const(registry).data() + 1u), traits_type::construct(traits_type::to_entity(entity), 1)); } TEST(Registry, CreateManyEntitiesAtOnce) { @@ -560,8 +561,9 @@ TEST(Registry, CreateWithHint) { auto e3 = registry.create(entt::entity{3}); auto e2 = registry.create(entt::entity{3}); - ASSERT_EQ(e2, entt::entity{2}); - ASSERT_FALSE(registry.valid(entt::entity{1})); + ASSERT_EQ(e2, entt::entity{1}); + ASSERT_TRUE(registry.valid(entt::entity{0})); + ASSERT_TRUE(registry.valid(entt::entity{2})); ASSERT_EQ(e3, entt::entity{3}); registry.release(e2); @@ -572,10 +574,10 @@ TEST(Registry, CreateWithHint) { e2 = registry.create(); auto e1 = registry.create(entt::entity{2}); - ASSERT_EQ(traits_type::to_entity(e2), 2u); + ASSERT_EQ(traits_type::to_entity(e2), 1u); ASSERT_EQ(traits_type::to_version(e2), 1u); - ASSERT_EQ(traits_type::to_entity(e1), 1u); + ASSERT_EQ(traits_type::to_entity(e1), 2u); ASSERT_EQ(traits_type::to_version(e1), 0u); registry.release(e1); diff --git a/test/entt/entity/snapshot.cpp b/test/entt/entity/snapshot.cpp index 1132a4edd..a6e196471 100644 --- a/test/entt/entity/snapshot.cpp +++ b/test/entt/entity/snapshot.cpp @@ -96,6 +96,7 @@ TEST(Snapshot, Dump) { using archive_type = std::tuple< std::queue, std::queue, + std::queue, std::queue, std::queue, std::queue, @@ -160,6 +161,7 @@ TEST(Snapshot, Partial) { using archive_type = std::tuple< std::queue, std::queue, + std::queue, std::queue, std::queue, std::queue>; @@ -224,6 +226,7 @@ TEST(Snapshot, Iterator) { using archive_type = std::tuple< std::queue, std::queue, + std::queue, std::queue, std::queue>; @@ -259,6 +262,7 @@ TEST(Snapshot, Continuous) { using archive_type = std::tuple< std::queue, std::queue, + std::queue, std::queue, std::queue, std::queue, @@ -497,7 +501,8 @@ TEST(Snapshot, MoreOnShrink) { using archive_type = std::tuple< std::queue, - std::queue>; + std::queue, + std::queue>; archive_type storage; output_archive output{storage}; @@ -525,6 +530,7 @@ TEST(Snapshot, SyncDataMembers) { using archive_type = std::tuple< std::queue, std::queue, + std::queue, std::queue, std::queue>; diff --git a/test/lib/registry/shared/lib.cpp b/test/lib/registry/shared/lib.cpp index df5520534..96189fda8 100644 --- a/test/lib/registry/shared/lib.cpp +++ b/test/lib/registry/shared/lib.cpp @@ -2,6 +2,8 @@ #include #include "../common/types.h" +template class entt::basic_registry; + ENTT_API void update_position(entt::registry ®istry) { registry.view().each([](auto &pos, auto &vel) { pos.x += static_cast(16 * vel.dx);