Просмотр исходного кода

Fewer allocations, faster destroy (#41)

Overall improvement of the registry.
Michele Caini 8 лет назад
Родитель
Сommit
936db30e58
3 измененных файлов с 93 добавлено и 61 удалено
  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 |
 |-----------|-------------|-------------|
-| 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, 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 |
@@ -454,8 +454,8 @@ each entity that has it:
   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
   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
 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
 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
 that have a determinate set of components. A view is usually faster than
 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.
      */
     size_type size() const noexcept {
-        return entities.size() - available.size();
+        return entities.size() - available;
     }
 
     /**
@@ -257,7 +257,6 @@ public:
      */
     void reserve(size_type 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.
      */
     bool empty() const noexcept {
-        return entities.size() == available.size();
+        return entities.size() == available;
     }
 
     /**
@@ -408,14 +407,18 @@ public:
     entity_type create() noexcept {
         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());
             assert(entity < traits_type::entity_mask);
-            assert((entity >> traits_type::entity_shift) == entity_type{});
             entities.push_back(entity);
-        } else {
-            entity = available.back();
-            available.pop_back();
         }
 
         return entity;
@@ -439,11 +442,12 @@ public:
     void destroy(entity_type entity) {
         assert(valid(entity));
         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) {
             if(cpool && cpool->has(entity)) {
@@ -879,11 +883,11 @@ public:
         if(managed<Component>()) {
             auto &cpool = pool<Component>();
 
-            for(auto entity: entities) {
+            each([&cpool](auto entity) {
                 if(cpool.has(entity)) {
                     cpool.destroy(entity);
                 }
-            }
+            });
         }
     }
 
@@ -891,41 +895,20 @@ public:
      * @brief Resets a whole registry.
      *
      * 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
      * to know if they are still valid.
      */
     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.
      *
-     * 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:
      *
      * @code{.cpp}
@@ -941,8 +924,19 @@ public:
      */
     template<typename Func>
     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>>> pools;
     std::vector<std::unique_ptr<Attachee>> tags;
-    std::vector<entity_type> available;
     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>());
 }
 
+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) {
     entt::DefaultRegistry registry;
     entt::DefaultRegistry::size_type tot;
     entt::DefaultRegistry::size_type match;
 
+    registry.create();
     registry.create<int>();
+    registry.create();
     registry.create<int>();
+    registry.create();
 
     tot = 0u;
     match = 0u;
@@ -147,19 +165,22 @@ TEST(DefaultRegistry, Each) {
         ++tot;
     });
 
-    ASSERT_EQ(tot, 2u);
+    ASSERT_EQ(tot, 5u);
     ASSERT_EQ(match, 2u);
 
     tot = 0u;
     match = 0u;
 
     registry.each([&](auto entity) {
-        if(registry.has<int>(entity)) { ++match; }
-        registry.destroy(entity);
+        if(registry.has<int>(entity)) {
+            registry.destroy(entity);
+            ++match;
+        }
+
         ++tot;
     });
 
-    ASSERT_EQ(tot, 4u);
+    ASSERT_EQ(tot, 10u);
     ASSERT_EQ(match, 2u);
 
     tot = 0u;
@@ -167,11 +188,14 @@ TEST(DefaultRegistry, Each) {
 
     registry.each([&](auto entity) {
         if(registry.has<int>(entity)) { ++match; }
+        registry.destroy(entity);
         ++tot;
     });
 
-    ASSERT_EQ(tot, 4u);
+    ASSERT_EQ(tot, 8u);
     ASSERT_EQ(match, 0u);
+
+    registry.each([&](auto) { FAIL(); });
 }
 
 
@@ -187,14 +211,30 @@ TEST(DefaultRegistry, Types) {
 
 TEST(DefaultRegistry, CreateDestroyEntities) {
     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_TRUE(registry.valid(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));
 }