Jelajahi Sumber

Fewer allocations, faster destroy (#41)

Overall improvement of the registry.
Michele Caini 8 tahun lalu
induk
melakukan
936db30e58
3 mengubah file dengan 93 tambahan dan 61 penghapusan
  1. 7 10
      README.md
  2. 38 43
      src/entt/entity/registry.hpp
  3. 48 8
      test/entt/entity/registry.cpp

+ 7 - 10
README.md

@@ -155,8 +155,8 @@ Dell XPS 13 out of the mid 2014):
 
 
 | Benchmark | EntityX (compile-time) | EnTT |
 | Benchmark | EntityX (compile-time) | EnTT |
 |-----------|-------------|-------------|
 |-----------|-------------|-------------|
-| Create 10M entities | 0.1289s | **0.0388s** |
-| Destroy 10M entities | **0.0531s** | 0.0828s |
+| Create 10M entities | 0.1289s | **0.0423s** |
+| Destroy 10M entities | 0.0531s | **0.0221s** |
 | Standard view, 10M entities, one component | 0.0107s | **7.8e-08s** |
 | Standard view, 10M entities, one component | 0.0107s | **7.8e-08s** |
 | Standard view, 10M entities, two components | **0.0113s** | 0.0244s |
 | Standard view, 10M entities, two components | **0.0113s** | 0.0244s |
 | Standard view, 10M entities, two components<br/>Half of the entities have all the components | **0.0078s** | 0.0129s |
 | Standard view, 10M entities, two components<br/>Half of the entities have all the components | **0.0078s** | 0.0129s |
@@ -454,8 +454,8 @@ each entity that has it:
   registry.reset<Position>();
   registry.reset<Position>();
   ```
   ```
 
 
-* If neither the entity nor the component are specified, all the entities and
-their components are destroyed:
+* If neither the entity nor the component are specified, all the entities still
+in use and their components are destroyed:
 
 
   ```cpp
   ```cpp
   registry.reset();
   registry.reset();
@@ -855,8 +855,9 @@ mind that it works only with the components of the view itself.
 
 
 Views are narrow windows on the entire list of entities. They work by filtering
 Views are narrow windows on the entire list of entities. They work by filtering
 entities according to their components.<br/>
 entities according to their components.<br/>
-In some cases there may be the need to iterate all the entities regardless of
-their components. The registry offers a specific member function to do that:
+In some cases there may be the need to iterate all the entities still in use
+regardless of their components. The registry offers a specific member function
+to do that:
 
 
 ```cpp
 ```cpp
 registry.each([](auto entity) {
 registry.each([](auto entity) {
@@ -864,10 +865,6 @@ registry.each([](auto entity) {
 });
 });
 ```
 ```
 
 
-Each entity ever created is returned, no matter if it's in use or not.<br/>
-Usually, filtering entities that aren't currently in use is more expensive than
-iterating them all and filtering out those in which one isn't interested.
-
 As a rule of thumb, consider using a view if the goal is to iterate entities
 As a rule of thumb, consider using a view if the goal is to iterate entities
 that have a determinate set of components. A view is usually faster than
 that have a determinate set of components. A view is usually faster than
 combining this function with a bunch of custom tests.<br/>
 combining this function with a bunch of custom tests.<br/>

+ 38 - 43
src/entt/entity/registry.hpp

@@ -230,7 +230,7 @@ public:
      * @return Number of entities still in use.
      * @return Number of entities still in use.
      */
      */
     size_type size() const noexcept {
     size_type size() const noexcept {
-        return entities.size() - available.size();
+        return entities.size() - available;
     }
     }
 
 
     /**
     /**
@@ -257,7 +257,6 @@ public:
      */
      */
     void reserve(size_type cap) {
     void reserve(size_type cap) {
         entities.reserve(cap);
         entities.reserve(cap);
-        available.reserve(cap);
     }
     }
 
 
     /**
     /**
@@ -284,7 +283,7 @@ public:
      * @return True if at least an entity is still in use, false otherwise.
      * @return True if at least an entity is still in use, false otherwise.
      */
      */
     bool empty() const noexcept {
     bool empty() const noexcept {
-        return entities.size() == available.size();
+        return entities.size() == available;
     }
     }
 
 
     /**
     /**
@@ -408,14 +407,18 @@ public:
     entity_type create() noexcept {
     entity_type create() noexcept {
         entity_type entity;
         entity_type entity;
 
 
-        if(available.empty()) {
+        if(available) {
+            const auto entt = next;
+            const auto version = entities[entt] & (~traits_type::entity_mask);
+
+            entity = entt | version;
+            next = entities[entt] & traits_type::entity_mask;
+            entities[entt] = entity;
+            --available;
+        } else {
             entity = entity_type(entities.size());
             entity = entity_type(entities.size());
             assert(entity < traits_type::entity_mask);
             assert(entity < traits_type::entity_mask);
-            assert((entity >> traits_type::entity_shift) == entity_type{});
             entities.push_back(entity);
             entities.push_back(entity);
-        } else {
-            entity = available.back();
-            available.pop_back();
         }
         }
 
 
         return entity;
         return entity;
@@ -439,11 +442,12 @@ public:
     void destroy(entity_type entity) {
     void destroy(entity_type entity) {
         assert(valid(entity));
         assert(valid(entity));
         const auto entt = entity & traits_type::entity_mask;
         const auto entt = entity & traits_type::entity_mask;
-        const auto version = version_type{1} + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
-        const auto next = entt | (version << traits_type::entity_shift);
+        const auto version = (entity & (~traits_type::entity_mask)) + (typename traits_type::entity_type{1} << traits_type::entity_shift);
+        const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version;
 
 
-        entities[entt] = next;
-        available.push_back(next);
+        entities[entt] = node;
+        next = entt;
+        ++available;
 
 
         for(auto &&cpool: pools) {
         for(auto &&cpool: pools) {
             if(cpool && cpool->has(entity)) {
             if(cpool && cpool->has(entity)) {
@@ -879,11 +883,11 @@ public:
         if(managed<Component>()) {
         if(managed<Component>()) {
             auto &cpool = pool<Component>();
             auto &cpool = pool<Component>();
 
 
-            for(auto entity: entities) {
+            each([&cpool](auto entity) {
                 if(cpool.has(entity)) {
                 if(cpool.has(entity)) {
                     cpool.destroy(entity);
                     cpool.destroy(entity);
                 }
                 }
-            }
+            });
         }
         }
     }
     }
 
 
@@ -891,41 +895,20 @@ public:
      * @brief Resets a whole registry.
      * @brief Resets a whole registry.
      *
      *
      * Destroys all the entities. After a call to `reset`, all the entities
      * Destroys all the entities. After a call to `reset`, all the entities
-     * previously created are recycled with a new version number. In case entity
+     * still in use are recycled with a new version number. In case entity
      * identifers are stored around, the `current` member function can be used
      * identifers are stored around, the `current` member function can be used
      * to know if they are still valid.
      * to know if they are still valid.
      */
      */
     void reset() {
     void reset() {
-        available.clear();
-
-        for(auto &&entity: entities) {
-            const auto version = version_type{1} + ((entity >> traits_type::entity_shift) & traits_type::version_mask);
-            entity = (entity & traits_type::entity_mask) | (version << traits_type::entity_shift);
-            available.push_back(entity);
-        }
-
-        for(auto &&handler: handlers) {
-            if(handler) {
-                handler->reset();
-            }
-        }
-
-        for(auto &&pool: pools) {
-            if(pool) {
-                pool->reset();
-            }
-        }
-
-        for(auto &&tag: tags) {
-            tag.reset();
-        }
+        each([this](auto entity) {
+            destroy(entity);
+        });
     }
     }
 
 
     /**
     /**
      * @brief Iterate entities and applies them the given function object.
      * @brief Iterate entities and applies them the given function object.
      *
      *
-     * The function object is invoked for each entity, no matter if it's in use
-     * or not.<br/>
+     * The function object is invoked for each entity still in use.<br/>
      * The signature of the function should be equivalent to the following:
      * The signature of the function should be equivalent to the following:
      *
      *
      * @code{.cpp}
      * @code{.cpp}
@@ -941,8 +924,19 @@ public:
      */
      */
     template<typename Func>
     template<typename Func>
     void each(Func func) const {
     void each(Func func) const {
-        for(auto pos = entities.size(); pos > size_type{0}; --pos) {
-            func(entities[pos-1]);
+        if(available) {
+            for(auto pos = entities.size(); pos > size_type{0}; --pos) {
+                const entity_type curr = pos - 1;
+                const auto entt = entities[curr] & traits_type::entity_mask;
+
+                if(curr == entt) {
+                    func(entities[curr]);
+                }
+            }
+        } else {
+            for(auto pos = entities.size(); pos > size_type{0}; --pos) {
+                func(entities[pos-1]);
+            }
         }
         }
     }
     }
 
 
@@ -1091,8 +1085,9 @@ private:
     std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
     std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
     std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
     std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
     std::vector<std::unique_ptr<Attachee>> tags;
     std::vector<std::unique_ptr<Attachee>> tags;
-    std::vector<entity_type> available;
     std::vector<entity_type> entities;
     std::vector<entity_type> entities;
+    size_type available{};
+    entity_type next{};
 };
 };
 
 
 
 

+ 48 - 8
test/entt/entity/registry.cpp

@@ -130,13 +130,31 @@ TEST(DefaultRegistry, Functionalities) {
     ASSERT_TRUE(registry.empty<int>());
     ASSERT_TRUE(registry.empty<int>());
 }
 }
 
 
+TEST(DefaultRegistry, CreateDestroyCornerCase) {
+    entt::DefaultRegistry registry;
+
+    auto e0 = registry.create();
+    auto e1 = registry.create();
+
+    registry.destroy(e0);
+    registry.destroy(e1);
+
+    registry.each([](auto) { FAIL(); });
+
+    ASSERT_EQ(registry.current(e0), entt::DefaultRegistry::version_type{1});
+    ASSERT_EQ(registry.current(e1), entt::DefaultRegistry::version_type{1});
+}
+
 TEST(DefaultRegistry, Each) {
 TEST(DefaultRegistry, Each) {
     entt::DefaultRegistry registry;
     entt::DefaultRegistry registry;
     entt::DefaultRegistry::size_type tot;
     entt::DefaultRegistry::size_type tot;
     entt::DefaultRegistry::size_type match;
     entt::DefaultRegistry::size_type match;
 
 
+    registry.create();
     registry.create<int>();
     registry.create<int>();
+    registry.create();
     registry.create<int>();
     registry.create<int>();
+    registry.create();
 
 
     tot = 0u;
     tot = 0u;
     match = 0u;
     match = 0u;
@@ -147,19 +165,22 @@ TEST(DefaultRegistry, Each) {
         ++tot;
         ++tot;
     });
     });
 
 
-    ASSERT_EQ(tot, 2u);
+    ASSERT_EQ(tot, 5u);
     ASSERT_EQ(match, 2u);
     ASSERT_EQ(match, 2u);
 
 
     tot = 0u;
     tot = 0u;
     match = 0u;
     match = 0u;
 
 
     registry.each([&](auto entity) {
     registry.each([&](auto entity) {
-        if(registry.has<int>(entity)) { ++match; }
-        registry.destroy(entity);
+        if(registry.has<int>(entity)) {
+            registry.destroy(entity);
+            ++match;
+        }
+
         ++tot;
         ++tot;
     });
     });
 
 
-    ASSERT_EQ(tot, 4u);
+    ASSERT_EQ(tot, 10u);
     ASSERT_EQ(match, 2u);
     ASSERT_EQ(match, 2u);
 
 
     tot = 0u;
     tot = 0u;
@@ -167,11 +188,14 @@ TEST(DefaultRegistry, Each) {
 
 
     registry.each([&](auto entity) {
     registry.each([&](auto entity) {
         if(registry.has<int>(entity)) { ++match; }
         if(registry.has<int>(entity)) { ++match; }
+        registry.destroy(entity);
         ++tot;
         ++tot;
     });
     });
 
 
-    ASSERT_EQ(tot, 4u);
+    ASSERT_EQ(tot, 8u);
     ASSERT_EQ(match, 0u);
     ASSERT_EQ(match, 0u);
+
+    registry.each([&](auto) { FAIL(); });
 }
 }
 
 
 
 
@@ -187,14 +211,30 @@ TEST(DefaultRegistry, Types) {
 
 
 TEST(DefaultRegistry, CreateDestroyEntities) {
 TEST(DefaultRegistry, CreateDestroyEntities) {
     entt::DefaultRegistry registry;
     entt::DefaultRegistry registry;
+    entt::DefaultRegistry::entity_type pre{}, post{};
+
+    for(int i = 0; i < 10; ++i) {
+        registry.create<double>();
+    }
+
+    registry.reset();
 
 
-    auto pre = registry.create<double>();
-    registry.destroy(pre);
-    auto post = registry.create<double>();
+    for(int i = 0; i < 7; ++i) {
+        auto entity = registry.create<int>();
+        if(i == 3) { pre = entity; }
+    }
+
+    registry.reset();
+
+    for(int i = 0; i < 5; ++i) {
+        auto entity = registry.create();
+        if(i == 3) { post = entity; }
+    }
 
 
     ASSERT_FALSE(registry.valid(pre));
     ASSERT_FALSE(registry.valid(pre));
     ASSERT_TRUE(registry.valid(post));
     ASSERT_TRUE(registry.valid(post));
     ASSERT_NE(registry.version(pre), registry.version(post));
     ASSERT_NE(registry.version(pre), registry.version(post));
+    ASSERT_EQ(registry.version(pre) + 1, registry.version(post));
     ASSERT_EQ(registry.current(pre), registry.current(post));
     ASSERT_EQ(registry.current(pre), registry.current(post));
 }
 }