ソースを参照

support for filtered persistent views

Michele Caini 7 年 前
コミット
73e5a9f45b

+ 4 - 3
TODO

@@ -7,12 +7,10 @@
 * define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
 * define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
 * runner proposal: https://en.wikipedia.org/wiki/Fork%E2%80%93join_model https://slide-rs.github.io/specs/03_dispatcher.html
-* optimize for empty components, it would be a mid improvement in terms of memory usage (see std::is_empty)
 * deep copy of a registry (or use the snapshot stuff to copy components and keep intact ids at least)
 * is it possible to iterate all the components assigned to an entity through a common base class?
 * can we do more for shared libraries? who knows... see #144
 * work stealing job system (see #100)
-* composable looper so as to pack erased systems, compose runners at different rates and run them at once in the loop
 * meta: sort of meta view based on meta stuff to iterate entities, void * and meta info objects
 * meta: move-to-head optimization when searching by name on parts (data, func, etc)
 * hashed string: add implicit check on construction for uniqueness (optional)
@@ -22,4 +20,7 @@
 * add on-the-fly sort functionality (is it possible?)
 * write/show how to work with missing components using and-strategies
 * write/show how to create an archetype based model on top of EnTT
-* mention hunter and conan in the readme file, section packaging tools
+* mention hunter in the readme file, section packaging tools
+* monostate: make constraint trivially copyable due to atomic optional
+* monostate: use template variable to avoid {}
+* travis + windows is now available, try it

+ 22 - 6
docs/entity.md

@@ -839,6 +839,8 @@ All of them have pros and cons to take in consideration. In particular:
     they don't have any type of initialization.
   * They are the best tool for iterating multiple components when most entities
     have them all.
+  * They are also the only type of views that supports filters without incurring
+    in a loss of performance during iterations.
 
   Cons:
 
@@ -895,8 +897,9 @@ To sum up and as a rule of thumb:
 * Use a persistent view when you want to iterate multiple components and each
   component is assigned to a great number of entities but the intersection
   between the sets of entities is small.
-* Use a persistent view in all the cases where a standard view wouldn't fit well
-  otherwise.
+* Use a persistent view when you want to set more complex filters that would
+  translate otherwise in a bunch of `if`s within a loop.
+* Use a persistent view in all the cases where a standard view doesn't fit well.
 * Finally, in case you don't know at compile-time what are the components to
   use, choose a runtime view and set them during execution.
 
@@ -1019,17 +1022,30 @@ mind that it works only with the components of the view itself.
 ## Persistent View
 
 A persistent view returns all the entities and only the entities that have at
-least the given components. Moreover, it's guaranteed that the entity list is
-tightly packed in memory for fast iterations.<br/>
+least the given components and respect the given filters. Moreover, it's
+guaranteed that the entity list is tightly packed in memory for fast
+iterations.<br/>
 In general, persistent views don't stay true to the order of any set of
 components unless users explicitly sort them.
 
-Persistent views can be used only to iterate multiple components at once:
+Persistent views are used mainly to iterate multiple components at once:
 
 ```cpp
 auto view = registry.persistent_view<position, velocity>();
 ```
 
+Moreover, filters can be applied to a persistent view to some extents:
+
+```cpp
+auto view = registry.persistent_view<position, velocity>(entt::type_list<renderable>);
+```
+
+In this case, the view will return all the entities that have both components
+`position` and `velocity` but don't have component `renderable`.<br/>
+Exclusive filters (ie the entities that have either `position` or `velocity`)
+aren't supported for performance reasons. Similarly, a filter cannot be applied
+to a persistent view with an empty template parameters list.
+
 There is no need to store views around for they are extremely cheap to
 construct, even though they can be copied without problems and reused freely. In
 fact, they return newly created and correctly initialized iterators whenever
@@ -1180,7 +1196,7 @@ views.
 # Types: const, non-const and all in between
 
 The `registry` class offers two overloads for most of the member functions used
-to construct views: a const one and a non-const one. The former accepts both
+to construct views: a const version and a non-const one. The former accepts both
 const and non-const types as template parameters, the latter accepts only const
 types instead.<br/>
 It means that views can be constructed also from a const registry and they

+ 16 - 0
src/entt/core/type_traits.hpp

@@ -0,0 +1,16 @@
+#ifndef ENTT_CORE_TYPE_TRAITS_HPP
+#define ENTT_CORE_TYPE_TRAITS_HPP
+
+
+namespace entt {
+
+
+/*! @brief A class to use to push around lists of types, nothing more. */
+template<typename...>
+struct type_list {};
+
+
+}
+
+
+#endif // ENTT_CORE_TYPE_TRAITS_HPP

+ 1 - 1
src/entt/entity/actor.hpp

@@ -32,7 +32,7 @@ struct actor {
      * @brief Constructs an actor by using the given registry.
      * @param reg An entity-component system properly initialized.
      */
-    actor(registry<Entity> &reg)
+    actor(registry_type &reg)
         : reg{&reg}, entt{reg.create()}
     {}
 

+ 79 - 76
src/entt/entity/registry.hpp

@@ -17,6 +17,7 @@
 #include "../config/config.h"
 #include "../core/algorithm.hpp"
 #include "../core/family.hpp"
+#include "../core/type_traits.hpp"
 #include "../signal/sigh.hpp"
 #include "entity.hpp"
 #include "entt_traits.hpp"
@@ -43,12 +44,9 @@ class registry {
     using component_family = family<struct internal_registry_component_family>;
     using handler_family = family<struct internal_registry_handler_family>;
     using component_signal_type = sigh<void(registry &, const Entity)>;
-    using pool_signal_type = sigh<void(const typename component_family::family_type)>;
+    using pool_signal_type = sigh<void(registry &, const typename component_family::family_type)>;
     using traits_type = entt_traits<Entity>;
 
-    template<std::size_t N>
-    using handler_type = sparse_set<Entity, std::array<typename sparse_set<Entity>::size_type, N>>;
-
     template<typename Component>
     struct component_pool: sparse_set<Entity, Component> {
         component_pool(registry *reg) ENTT_NOEXCEPT
@@ -81,27 +79,61 @@ class registry {
         registry *reg;
     };
 
-    template<typename... Component>
-    static void creating(registry &reg, const Entity entity) {
-        if((reg.pool<Component>().has(entity) && ...)) {
-            auto *handler = static_cast<handler_type<sizeof...(Component)> *>(reg.handlers[handler_family::type<Component...>].get());
-            handler->construct(entity, reg.pools[component_family::type<Component>]->get(entity)...);
+    template<typename, typename>
+    struct handler_pool;
+
+    template<typename... Component, typename... Exclude>
+    struct handler_pool<type_list<Component...>, type_list<Exclude...>>: sparse_set<Entity, std::array<typename sparse_set<Entity>::size_type, sizeof...(Component)>> {
+        void candidate(registry &reg, const Entity entity) {
+            if((reg.pool<Component>().has(entity) && ...) && !(reg.pool<Exclude>().has(entity) || ...)) {
+                handler_pool::construct(entity, reg.pools[component_family::type<Component>]->get(entity)...);
+            }
         }
-    }
 
-    template<typename Comp, std::size_t Index, typename... Component>
-    static void destroying(registry &reg, const Entity entity) {
-        auto *handler = static_cast<handler_type<sizeof...(Component)> *>(reg.handlers[handler_family::type<Component...>].get());
-        const sparse_set<Entity> &cpool = reg.pool<Comp>();
-        const auto last = *cpool.begin();
+        template<typename Comp, std::size_t Index>
+        void check(registry &reg, const Entity entity) {
+            const sparse_set<Entity> &cpool = reg.pool<Comp>();
+            const auto last = *cpool.begin();
+
+            if(handler_pool::has(last)) {
+                handler_pool::get(last)[Index] = cpool.get(entity);
+            }
+
+            if(handler_pool::has(entity)) {
+                handler_pool::destroy(entity);
+            }
+        }
 
-        if(handler->has(last)) {
-            handler->get(last)[Index] = cpool.get(entity);
+        void discard(registry &, const Entity entity) {
+            if(handler_pool::has(entity)) {
+                handler_pool::destroy(entity);
+            }
         }
 
-        if(handler->has(entity)) {
-            handler->destroy(entity);
+        void rebuild(registry &reg, const typename component_family::family_type ctype) {
+            auto index = sizeof...(Component);
+            decltype(index) cnt{};
+
+            ((index = (component_family::type<Component> == ctype) ? cnt++ : (static_cast<void>(++cnt), index)), ...);
+
+            if(index != sizeof...(Component)) {
+                auto begin = sparse_set<Entity>::begin();
+                const auto &cpool = *reg.pools[ctype];
+
+                for(auto &&indexes: *this) {
+                    indexes[index] = cpool.get(*(begin++));
+                }
+            }
         }
+    };
+
+    template<typename... Component, typename... Exclude, std::size_t... Indexes>
+    void connect(handler_pool<type_list<Component...>, type_list<Exclude...>> *handler, std::index_sequence<Indexes...>) {
+        using handler_type = handler_pool<type_list<Component...>, type_list<Exclude...>>;
+        (pool<Component>().construction().template connect<&handler_type::candidate>(handler), ...);
+        (pool<Exclude>().construction().template connect<&handler_type::discard>(handler), ...);
+        (pool<Component>().destruction().template connect<&handler_type::template check<Component, Indexes>>(handler), ...);
+        (pool<Exclude>().destruction().template connect<&handler_type::candidate>(handler), ...);
     }
 
     template<typename Component>
@@ -121,33 +153,6 @@ class registry {
         return const_cast<component_pool<std::decay_t<Component>> &>(std::as_const(*this).template pool<Component>());
     }
 
-    template<typename... Component, std::size_t... Indexes>
-    void connect(std::index_sequence<Indexes...>) {
-        (pool<Component>().construction().template connect<&registry::creating<Component...>>(), ...);
-        (pool<Component>().destruction().template connect<&registry::destroying<Component, Indexes, Component...>>(), ...);
-    }
-
-    template<typename... Component, std::size_t... Indexes>
-    void rebuild(const typename component_family::family_type ctype, std::index_sequence<Indexes...>) {
-        auto index = sizeof...(Indexes);
-        ((index = (component_family::type<Component> == ctype) ? Indexes : index), ...);
-
-        if(index != sizeof...(Indexes)) {
-            auto *handler = static_cast<handler_type<sizeof...(Component)> *>(handlers[handler_family::type<Component...>].get());
-            auto begin = handler->sparse_set<Entity>::begin();
-            const auto &cpool = *pools[ctype];
-
-            for(auto &&indexes: *handler) {
-                indexes[index] = cpool.get(*(begin++));
-            }
-        }
-    }
-
-    template<typename... Component>
-    void refresh(const typename component_family::family_type ctype) {
-        rebuild<Component...>(ctype, std::make_index_sequence<sizeof...(Component)>{});
-    }
-
     template<typename Component>
     void assure() {
         const auto ctype = component_family::type<Component>;
@@ -920,7 +925,7 @@ public:
     void sort(Compare compare, Sort sort = Sort{}, Args &&... args) {
         assure<Component>();
         pool<Component>().sort(std::move(compare), std::move(sort), std::forward<Args>(args)...);
-        invalidate.publish(component_family::type<Component>);
+        invalidate.publish(*this, component_family::type<Component>);
     }
 
     /**
@@ -958,7 +963,7 @@ public:
         assure<To>();
         assure<From>();
         pool<To>().respect(pool<From>());
-        invalidate.publish(component_family::type<To>);
+        invalidate.publish(*this, component_family::type<To>);
     }
 
     /**
@@ -1202,8 +1207,9 @@ public:
      *
      * Persistent views are the right choice to iterate entities when the number
      * of components grows up and the most of the entities have all the given
-     * components.<br/>
-     * However they have also drawbacks:
+     * components. They are also the only type of views that supports filters
+     * without incurring in a loss of performance during iterations.<br/>
+     * However, persistent views have also drawbacks:
      *
      * * Each kind of persistent view requires a dedicated data structure that
      *   is allocated within the registry and it increases memory pressure.
@@ -1214,12 +1220,6 @@ public:
      * That being said, persistent views are an incredibly powerful tool if used
      * with care and offer a boost of performance undoubtedly.
      *
-     * @note
-     * Consider to use the `prepare` member function to initialize the internal
-     * data structures used by persistent views when the registry is still
-     * empty. Initialization could be a costly operation otherwise and it will
-     * be performed the very first time each view is created.
-     *
      * @sa view
      * @sa view<Entity, Component>
      * @sa persistent_view
@@ -1227,11 +1227,13 @@ public:
      * @sa runtime_view
      *
      * @tparam Component Types of components used to construct the view.
+     * @tparam Exclude Types of components used to filter the view.
      * @return A newly created persistent view.
      */
-    template<typename... Component>
-    entt::persistent_view<Entity, Component...> persistent_view() {
-        static_assert(sizeof...(Component) > 1);
+    template<typename... Component, typename... Exclude>
+    entt::persistent_view<Entity, Component...> persistent_view(type_list<Exclude...> = type_list<>{}) {
+        static_assert(sizeof...(Component));
+        using handler_type = handler_pool<type_list<Component...>, type_list<Exclude...>>;
         const auto htype = handler_family::type<Component...>;
 
         if(!(htype < handlers.size())) {
@@ -1240,19 +1242,24 @@ public:
 
         if(!handlers[htype]) {
             (assure<Component>(), ...);
-            connect<Component...>(std::make_index_sequence<sizeof...(Component)>{});
-            invalidate.sink().template connect<&registry::refresh<Component...>>(this);
+            (assure<Exclude>(), ...);
 
-            handlers[htype] = std::make_unique<handler_type<sizeof...(Component)>>();
-            auto *handler = static_cast<handler_type<sizeof...(Component)> *>(handlers[htype].get());
+            auto handler = std::make_unique<handler_type>();
+
+            connect(handler.get(), std::make_index_sequence<sizeof...(Component)>{});
+            invalidate.sink().template connect<&handler_type::rebuild>(handler.get());
 
             for(const auto entity: view<Component...>()) {
-                handler->construct(entity, pools[component_family::type<Component>]->get(entity)...);
+                if(!(pool<Exclude>().has(entity) || ...)) {
+                    handler->construct(entity, pools[component_family::type<Component>]->get(entity)...);
+                }
             }
+
+            handlers[htype] = std::move(handler);
         }
 
         return {
-            static_cast<handler_type<sizeof...(Component)> *>(handlers[htype].get()),
+            static_cast<handler_type *>(handlers[htype].get()),
             &pool<Component>()...
         };
     }
@@ -1272,8 +1279,9 @@ public:
      *
      * Persistent views are the right choice to iterate entities when the number
      * of components grows up and the most of the entities have all the given
-     * components.<br/>
-     * However they have also drawbacks:
+     * components. They are also the only type of views that supports filters
+     * without incurring in a loss of performance during iterations.<br/>
+     * However, persistent views have also drawbacks:
      *
      * * Each kind of persistent view requires a dedicated data structure that
      *   is allocated within the registry and it increases memory pressure.
@@ -1284,12 +1292,6 @@ public:
      * That being said, persistent views are an incredibly powerful tool if used
      * with care and offer a boost of performance undoubtedly.
      *
-     * @note
-     * Consider to use the `prepare` member function to initialize the internal
-     * data structures used by persistent views when the registry is still
-     * empty. Initialization could be a costly operation otherwise and it will
-     * be performed the very first time each view is created.
-     *
      * @sa view
      * @sa view<Entity, Component>
      * @sa persistent_view
@@ -1297,12 +1299,13 @@ public:
      * @sa runtime_view
      *
      * @tparam Component Types of components used to construct the view.
+     * @tparam Exclude Types of components used to filter the view.
      * @return A newly created persistent view.
      */
-    template<typename... Component>
-    inline entt::persistent_view<Entity, Component...> persistent_view() const {
+    template<typename... Component, typename... Exclude>
+    inline entt::persistent_view<Entity, Component...> persistent_view(type_list<Exclude...> = type_list<>{}) const {
         static_assert(std::conjunction_v<std::is_const<Component>...>);
-        return const_cast<registry *>(this)->persistent_view<Component...>();
+        return const_cast<registry *>(this)->persistent_view<Component...>(type_list<Exclude...>{});
     }
 
     /**

+ 1 - 1
src/entt/entity/view.hpp

@@ -70,7 +70,7 @@ class registry;
  */
 template<typename Entity, typename... Component>
 class persistent_view final {
-    static_assert(sizeof...(Component) > 1);
+    static_assert(sizeof...(Component));
 
     /*! @brief A registry is allowed to create views. */
     friend class registry<Entity>;

+ 1 - 0
src/entt/entt.hpp

@@ -3,6 +3,7 @@
 #include "core/hashed_string.hpp"
 #include "core/ident.hpp"
 #include "core/monostate.hpp"
+#include "core/type_traits.hpp"
 #include "core/utility.hpp"
 #include "entity/actor.hpp"
 #include "entity/entity.hpp"

+ 66 - 0
test/entt/entity/view.cpp

@@ -1,5 +1,6 @@
 #include <utility>
 #include <iterator>
+#include <algorithm>
 #include <gtest/gtest.h>
 #include <entt/entity/registry.hpp>
 #include <entt/entity/view.hpp>
@@ -251,6 +252,71 @@ TEST(PersistentView, Find) {
     ASSERT_EQ(++view.find(e0), view.end());
 }
 
+TEST(PersistentView, SingleComponent) {
+    entt::registry<> registry;
+    const auto view = registry.persistent_view<const int>();
+
+    registry.assign<int>(registry.create());
+
+    const auto entity = registry.create();
+    registry.assign<int>(entity);
+
+    registry.assign<int>(registry.create());
+
+    registry.destroy(entity);
+    registry.assign<int>(registry.create());
+
+    ASSERT_TRUE(std::equal(view.begin(), view.end(), registry.view<int>().begin()));
+}
+
+TEST(PersistentView, ExcludedComponents) {
+    entt::registry<> registry;
+
+    const auto e0 = registry.create();
+    registry.assign<int>(e0, 0);
+
+    const auto e1 = registry.create();
+    registry.assign<int>(e1, 1);
+    registry.assign<char>(e1);
+
+    const auto view = registry.persistent_view<int>(entt::type_list<char>{});
+
+    const auto e2 = registry.create();
+    registry.assign<int>(e2, 2);
+
+    const auto e3 = registry.create();
+    registry.assign<int>(e3, 3);
+    registry.assign<char>(e3);
+
+    for(const auto entity: view) {
+        if(entity == e0) {
+            ASSERT_EQ(view.get<int>(e0), 0);
+        } else if(entity == e2) {
+            ASSERT_EQ(view.get<int>(e2), 2);
+        } else {
+            FAIL();
+        }
+    }
+
+    registry.assign<char>(e0);
+    registry.assign<char>(e2);
+
+    ASSERT_TRUE(view.empty());
+
+    registry.remove<char>(e1);
+    registry.remove<char>(e3);
+
+    for(const auto entity: view) {
+        if(entity == e1) {
+            ASSERT_EQ(view.get<int>(e1), 1);
+        } else if(entity == e3) {
+            ASSERT_EQ(view.get<int>(e3), 3);
+        } else {
+            FAIL();
+        }
+    }
+}
+
 TEST(SingleComponentView, Functionalities) {
     entt::registry<> registry;
     auto view = registry.view<char>();