Explorar el Código

registry: first (almost) backward compatible version with opaque hidden entity storage

Michele Caini hace 3 años
padre
commit
068b6ed49b

+ 65 - 149
src/entt/entity/registry.hpp

@@ -305,7 +305,7 @@ class basic_registry {
     template<typename Type>
     [[nodiscard]] auto &assure(const id_type id = type_hash<Type>::value()) {
         static_assert(std::is_same_v<Type, std::decay_t<Type>>, "Non-decayed types not allowed");
-        auto &cpool = pools.first()[id];
+        auto &cpool = pools[id];
 
         if(!cpool) {
             using alloc_type = typename storage_for_type<std::remove_const_t<Type>>::allocator_type;
@@ -320,7 +320,7 @@ class basic_registry {
             cpool->bind(forward_as_any(*this));
         }
 
-        ENTT_ASSERT((cpool->type() == type_id<std::conditional_t<std::is_same_v<Type, entity_type>, void, Type>>()), "Unexpected type");
+        ENTT_ASSERT(cpool->type() == type_id<Type>(), "Unexpected type");
         return static_cast<storage_for_type<Type> &>(*cpool);
     }
 
@@ -328,8 +328,8 @@ class basic_registry {
     [[nodiscard]] const auto &assure(const id_type id = type_hash<Type>::value()) const {
         static_assert(std::is_same_v<Type, std::decay_t<Type>>, "Non-decayed types not allowed");
 
-        if(const auto it = pools.first().find(id); it != pools.first().cend()) {
-            ENTT_ASSERT((it->second->type() == type_id<std::conditional_t<std::is_same_v<Type, entity_type>, void, Type>>()), "Unexpected type");
+        if(const auto it = pools.find(id); it != pools.cend()) {
+            ENTT_ASSERT(it->second->type() == type_id<Type>(), "Unexpected type");
             return static_cast<const storage_for_type<Type> &>(*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<typename traits_type::entity_type>(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<Entity>::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`.<br/>
-     * 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<typename It>
     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<typename It>
-    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<version_type>(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_type>(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<typename It>
     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<typename It>
     void destroy(It first, It last) {
-        if constexpr(std::is_same_v<It, typename base_type::iterator>) {
-            base_type *owner = nullptr;
-
-            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);
-                }
-            }
+        const auto len = entities.pack(first, last);
+        auto from = entities.each().cbegin().base();
+        const auto to = from + len;
 
-            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<typename... Type>
     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<typename... Type>
     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<Type>().clear(), ...);
         }
@@ -1211,16 +1136,8 @@ public:
      */
     template<typename Func>
     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<entity_type, allocator_type> epool;
-    compressed_pair<container_type, allocator_type> pools;
+    container_type pools;
+    storage_for_type<entity_type> entities;
     std::vector<group_data, typename alloc_traits::template rebind_alloc<group_data>> groups;
 };
 

+ 11 - 8
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<typename Archive>
     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<entity_type> 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<typename Archive>
     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) {

+ 11 - 9
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::entity>;
+
     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);

+ 7 - 1
test/entt/entity/snapshot.cpp

@@ -96,6 +96,7 @@ TEST(Snapshot, Dump) {
     using archive_type = std::tuple<
         std::queue<typename traits_type::entity_type>,
         std::queue<entt::entity>,
+        std::queue<std::size_t>,
         std::queue<int>,
         std::queue<char>,
         std::queue<double>,
@@ -160,6 +161,7 @@ TEST(Snapshot, Partial) {
     using archive_type = std::tuple<
         std::queue<typename traits_type::entity_type>,
         std::queue<entt::entity>,
+        std::queue<std::size_t>,
         std::queue<int>,
         std::queue<char>,
         std::queue<double>>;
@@ -224,6 +226,7 @@ TEST(Snapshot, Iterator) {
     using archive_type = std::tuple<
         std::queue<typename traits_type::entity_type>,
         std::queue<entt::entity>,
+        std::queue<std::size_t>,
         std::queue<another_component>,
         std::queue<int>>;
 
@@ -259,6 +262,7 @@ TEST(Snapshot, Continuous) {
     using archive_type = std::tuple<
         std::queue<typename traits_type::entity_type>,
         std::queue<entt::entity>,
+        std::queue<std::size_t>,
         std::queue<another_component>,
         std::queue<what_a_component>,
         std::queue<map_component>,
@@ -497,7 +501,8 @@ TEST(Snapshot, MoreOnShrink) {
 
     using archive_type = std::tuple<
         std::queue<typename traits_type::entity_type>,
-        std::queue<entt::entity>>;
+        std::queue<entt::entity>,
+        std::queue<std::size_t>>;
 
     archive_type storage;
     output_archive<archive_type> output{storage};
@@ -525,6 +530,7 @@ TEST(Snapshot, SyncDataMembers) {
     using archive_type = std::tuple<
         std::queue<typename traits_type::entity_type>,
         std::queue<entt::entity>,
+        std::queue<std::size_t>,
         std::queue<what_a_component>,
         std::queue<map_component>>;
 

+ 2 - 0
test/lib/registry/shared/lib.cpp

@@ -2,6 +2,8 @@
 #include <entt/entity/registry.hpp>
 #include "../common/types.h"
 
+template class entt::basic_registry<entt::entity>;
+
 ENTT_API void update_position(entt::registry &registry) {
     registry.view<position, velocity>().each([](auto &pos, auto &vel) {
         pos.x += static_cast<int>(16 * vel.dx);