Explorar o código

improved multi component standard view

Michele Caini %!s(int64=8) %!d(string=hai) anos
pai
achega
a2e243d992
Modificáronse 3 ficheiros con 85 adicións e 32 borrados
  1. 12 9
      README.md
  2. 44 10
      src/entt/entity/sparse_set.hpp
  3. 29 13
      src/entt/entity/view.hpp

+ 12 - 9
README.md

@@ -186,7 +186,7 @@ amazing set of features. And even more, of course.
 
 As it stands right now, `EnTT` is just fast enough for my requirements if
 compared to my first choice (it was already amazingly fast actually).<br/>
-Here is a comparison between the two (both of them compiled with GCC 7.2.0 on a
+Here is a comparison between the two (both of them compiled with GCC 7.3.0 on a
 Dell XPS 13 out of the mid 2014):
 
 | Benchmark | EntityX (compile-time) | EnTT |
@@ -194,18 +194,21 @@ Dell XPS 13 out of the mid 2014):
 | Create 1M entities | 0.0167s | **0.0046s** |
 | Destroy 1M entities | 0.0053s | **0.0022s** |
 | Standard view, 1M entities, one component | 0.0012s | **1.9e-07s** |
-| Standard view, 1M entities, two components | **0.0012s** | 0.0013s |
-| Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **0.0007s** |
-| Standard view, 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.3e-06s** |
+| Standard view, 1M entities, two components | 0.0012s | **0.0010s** |
+| Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **0.0006s** |
+| Standard view, 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
 | Persistent view, 1M entities, two components | 0.0012s | **2.8e-07s** |
-| Standard view, 1M entities, five components | **0.0010s** | 0.0034s |
+| Standard view, 1M entities, five components | **0.0010s** | 0.0024s |
 | Persistent view, 1M entities, five components | 0.0010s | **2.8e-07s** |
-| Standard view, 1M entities, ten components | **0.0011s** | 0.0075s |
-| Standard view, 1M entities, ten components<br/>Half of the entities have all the components | **0.0010s** | 0.0041s |
+| Standard view, 1M entities, ten components | **0.0011s** | 0.0058s |
+| Standard view, 1M entities, ten components<br/>Half of the entities have all the components | **0.0010s** | 0.0032s |
 | Standard view, 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.7e-06s** |
 | Persistent view, 1M entities, ten components | 0.0011s | **3.0e-07s** |
-| Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0040s** |
-| Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0006s** |
+| Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0036s** |
+| Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0005s** |
+
+Note: The default version of `EntityX` (`master` branch) wasn't added to the
+comparison because it's already much slower than its compile-time counterpart.
 
 `EnTT` includes its own tests and benchmarks. See
 [benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp)

+ 44 - 10
src/entt/entity/sparse_set.hpp

@@ -139,6 +139,20 @@ public:
         direct.reserve(cap);
     }
 
+    /**
+     * @brief Returns the extent of a sparse set.
+     *
+     * The extent of a sparse set is also the size of the internal sparse array.
+     * There is no guarantee that the internal packed array has the same size.
+     * Usually the size of the internal sparse array is equal or greater than
+     * the one of the internal packed array.
+     *
+     * @return Extent of the sparse set.
+     */
+    size_type extent() const noexcept {
+        return reverse.size();
+    }
+
     /**
      * @brief Returns the number of elements in a sparse set.
      *
@@ -219,11 +233,33 @@ public:
      * @return True if the sparse set contains the entity, false otherwise.
      */
     bool has(entity_type entity) const noexcept {
-        using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
-        // explicit promotion to avoid warnings with std::uint16_t
-        const auto entt = promotion_type{entity} & traits_type::entity_mask;
+        const auto pos = size_type(entity & traits_type::entity_mask);
+        // the in-use control bit permits to avoid accessing the direct vector
+        return (pos < reverse.size()) && (reverse[pos] & in_use);
+    }
+
+    /**
+     * @brief Checks if a sparse set contains an entity (unsafe).
+     *
+     * Alternative version of `has`. It accesses the underlying data structures
+     * without bounds checking and thus it's both unsafe and risky to use.<br/>
+     * You should not invoke directly this function unless you know exactly what
+     * you are doing. Prefer the `has` member function instead.
+     *
+     * @warning
+     * Attempting to use an entity that doesn't belong to the sparse set can
+     * result in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case of
+     * bounds violation.
+     *
+     * @param entity A valid entity identifier.
+     * @return True if the sparse set contains the entity, false otherwise.
+     */
+    bool fast(entity_type entity) const noexcept {
+        const auto pos = size_type(entity & traits_type::entity_mask);
+        assert(pos < reverse.size());
         // the in-use control bit permits to avoid accessing the direct vector
-        return (entt < reverse.size()) && (reverse[entt] & in_use);
+        return (reverse[pos] & in_use);
     }
 
     /**
@@ -258,17 +294,15 @@ public:
      */
     void construct(entity_type entity) {
         assert(!has(entity));
-        using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
-        // explicit promotion to avoid warnings with std::uint16_t
-        const auto entt = promotion_type{entity} & traits_type::entity_mask;
+        const auto pos = size_type(entity & traits_type::entity_mask);
 
-        if(!(entt < reverse.size())) {
-            reverse.resize(entt+1, pos_type{});
+        if(!(pos < reverse.size())) {
+            reverse.resize(pos+1, pos_type{});
         }
 
         // we exploit the fact that pos_type is equal to entity_type and pos has
         // traits_type::version_mask bits unused we can use to mark it as in-use
-        reverse[entt] = pos_type(direct.size()) | in_use;
+        reverse[pos] = pos_type(direct.size()) | in_use;
         direct.emplace_back(entity);
     }
 

+ 29 - 13
src/entt/entity/view.hpp

@@ -7,6 +7,7 @@
 #include <utility>
 #include <algorithm>
 #include <type_traits>
+#include "entt_traits.hpp"
 #include "sparse_set.hpp"
 
 
@@ -209,7 +210,7 @@ public:
     template<typename... Comp>
     std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
     get(entity_type entity) const noexcept {
-        return std::tuple<const Comp &...>{ get<Comp>(entity)... };
+        return std::tuple<const Comp &...>{get<Comp>(entity)...};
     }
 
     /**
@@ -232,7 +233,7 @@ public:
     template<typename... Comp>
     std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
     get(entity_type entity) noexcept {
-        return std::tuple<Comp &...>{ get<Comp>(entity)... };
+        return std::tuple<Comp &...>{get<Comp>(entity)...};
     }
 
     /**
@@ -302,7 +303,7 @@ public:
 
 private:
     view_type &view;
-    pattern_type pools;
+    const pattern_type pools;
 };
 
 
@@ -358,11 +359,20 @@ class View final {
     using underlying_iterator_type = typename view_type::iterator_type;
     using unchecked_type = std::array<const view_type *, (sizeof...(Component) - 1)>;
     using pattern_type = std::tuple<pool_type<Component> &...>;
+    using traits_type = entt_traits<Entity>;
 
     class Iterator {
+        using size_type = typename view_type::size_type;
+
         inline bool valid() const noexcept {
+            const auto entity = *begin;
+            const auto sz = size_type(entity & traits_type::entity_mask);
             auto i = unchecked.size();
-            for(const auto entity = *begin; i && unchecked[i-1]->has(entity); --i);
+
+            if(sz < extent) {
+                for(; i && unchecked[i-1]->fast(entity); --i);
+            }
+
             return !i;
         }
 
@@ -370,8 +380,11 @@ class View final {
         using difference_type = typename underlying_iterator_type::difference_type;
         using value_type = typename view_type::entity_type;
 
-        Iterator(unchecked_type unchecked, underlying_iterator_type begin, underlying_iterator_type end) noexcept
-            : unchecked{unchecked}, begin{begin}, end{end}
+        Iterator(unchecked_type unchecked, size_type extent, underlying_iterator_type begin, underlying_iterator_type end) noexcept
+            : unchecked{unchecked},
+              extent{extent},
+              begin{begin},
+              end{end}
         {
             if(begin != end && !valid()) {
                 ++(*this);
@@ -393,7 +406,7 @@ class View final {
         }
 
         Iterator operator+(difference_type value) noexcept {
-            return Iterator{unchecked, begin+value, end};
+            return Iterator{unchecked, extent, begin+value, end};
         }
 
         bool operator==(const Iterator &other) const noexcept {
@@ -409,7 +422,8 @@ class View final {
         }
 
     private:
-        unchecked_type unchecked;
+        const unchecked_type unchecked;
+        const size_type extent;
         underlying_iterator_type begin;
         underlying_iterator_type end;
     };
@@ -451,7 +465,8 @@ public:
      * @return An iterator to the first entity that has the given components.
      */
     iterator_type begin() const noexcept {
-        return Iterator{unchecked, view->begin(), view->end()};
+        const auto extent = std::min({ std::get<pool_type<Component> &>(pools).extent()... });
+        return Iterator{unchecked, extent, view->begin(), view->end()};
     }
 
     /**
@@ -470,7 +485,8 @@ public:
      * given components.
      */
     iterator_type end() const noexcept {
-        return Iterator{unchecked, view->end(), view->end()};
+        const auto extent = std::min({ std::get<pool_type<Component> &>(pools).extent()... });
+        return Iterator{unchecked, extent, view->end(), view->end()};
     }
 
     /**
@@ -537,7 +553,7 @@ public:
     template<typename... Comp>
     std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
     get(entity_type entity) const noexcept {
-        return std::tuple<const Comp &...>{ get<Comp>(entity)... };
+        return std::tuple<const Comp &...>{get<Comp>(entity)...};
     }
 
     /**
@@ -560,7 +576,7 @@ public:
     template<typename... Comp>
     std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
     get(entity_type entity) noexcept {
-        return std::tuple<Comp &...>{ get<Comp>(entity)... };
+        return std::tuple<Comp &...>{get<Comp>(entity)...};
     }
 
     /**
@@ -638,7 +654,7 @@ public:
     }
 
 private:
-    pattern_type pools;
+    const pattern_type pools;
     const view_type *view;
     unchecked_type unchecked;
 };