Forráskód Böngészése

updated observer (close #260)

Michele Caini 6 éve
szülő
commit
16a11638a7
3 módosított fájl, 277 hozzáadás és 68 törlés
  1. 46 14
      docs/md/entity.md
  2. 126 52
      src/entt/entity/observer.hpp
  3. 105 2
      test/entt/entity/observer.cpp

+ 46 - 14
docs/md/entity.md

@@ -432,10 +432,10 @@ from the documentation of the library that first introduced this tool,
 >only update the 10 changed units. So efficient.
 
 In `EnTT`, this means to iterating over a reduced set of entities and components
-with respect to what would otherwise be returned from a view or group.<br/>
-On these words, however, the similarities with the proposal of Entitas also end.
-The rules of language and the design of the library obviously impose and allow
-different things.
+with respect to what would otherwise be returned from a view or a group.<br/>
+On these words, however, the similarities with the proposal of `Entitas` also
+end. The rules of language and the design of the library obviously impose and
+allow different things.
 
 An `observer` is initialized with an instance of a registry and a set of rules
 that describe what are the entities to intercept. As an example:
@@ -444,23 +444,24 @@ that describe what are the entities to intercept. As an example:
 entt::observer observer{registry, entt::collector.replace<sprite>()};
 ```
 
-The class also default constructible if required and it can be reconfigured at
-any time by means of the `connect` member function. Moreover, instances can be
+The class is default constructible if required and it can be reconfigured at any
+time by means of the `connect` member function. Moreover, instances can be
 disconnected from the underlying registries through the `disconnect` member
 function.<br/>
 The `observer` offers also some member functions to query its internal state and
 to know if it's empty or how many entities it contains. Moreover, it can return
-a raw pointer to the list of entities it contains.<br/>
+a raw pointer to the list of entities it contains.
+
 However, the most important features of this class are that:
 
 * It's iterable and therefore users can easily walk through the list of entities
-  by means of a range-for loop.
+  by means of a range-for loop or the `each` member function.
 * It's clearable and therefore users can consume the entities and literally
   reset the observer after each iteration.
 
 These aspects make the observer an incredibly powerful tool to know at any time
-what are the entities that have started to respect the given rules since the
-last time one asked:
+what are the entities that matched the given rules since the last time one
+asked:
 
 ```cpp
 for(const auto entity: observer) {
@@ -470,8 +471,20 @@ for(const auto entity: observer) {
 observer.clear();
 ```
 
-The `collector` is an utility to use to generate a list of `matcher`s (the
-actual rules) to use with an `observer`.<br/>
+Note that the snippet above is equivalent to the following:
+
+```cpp
+observer.each([](const auto entity) {
+    // ...
+});
+```
+
+At least as long as the `observer` isn't const. This means that the non-const
+overload of `each` does also reset the underlying data structure before to
+return to the caller, while the const overload does not for obvious reasons.
+
+The `collector` is an utility aimed to generate a list of `matcher`s (the actual
+rules) to use with an `observer` instead.<br/>
 There are two types of `matcher`s:
 
 * Observing matcher: an observer will return at least all the living entities
@@ -479,7 +492,7 @@ There are two types of `matcher`s:
   and not yet destroyed.
 
   ```cpp
-  collector.replace<sprite>();
+  entt::collector.replace<sprite>();
   ```
 
 * Grouping matcher: an observer will return at least all the living entities
@@ -487,7 +500,7 @@ There are two types of `matcher`s:
   not yet left it.
 
   ```cpp
-  collector.group<position, velocity>(entt::exclude<destroyed>);
+  entt::collector.group<position, velocity>(entt::exclude<destroyed>);
   ```
 
   A grouping matcher supports also exclusion lists as well as single components.
@@ -499,6 +512,25 @@ last time one asked.<br/>
 Note that, for a grouping matcher, if an entity already has all the components
 except one and the missing type is assigned to it, it is intercepted.
 
+In addition, a matcher can be filtered with a `when` clause:
+
+```cpp
+entt::collector.replace<sprite>().when<position>(entt::exclude<velocity>);
+```
+
+This clause introduces a way to intercept entities if and only if they are
+already part of a hypothetical group. If they are not, they aren't returned by
+the observer, no matter if they matched the given rule.<br/>
+In the example above, whenever the component `sprite` of an entity is replaced,
+the observer probes the entity itself to verify that it has at least `position`
+and has not `velocity` before to store it aside. If one of the two conditions of
+the filter isn't respected, the entity is discared, no matter what.
+
+A `when` clause accepts a theoretically unlimited number of types as well as
+multiple elements in the exclusion list. Moreover, every matcher can have it's
+own clause and multiple clauses for the same matcher are combined in a single
+one.
+
 ## Runtime components
 
 Defining components at runtime is useful to support plugin systems and mods in

+ 126 - 52
src/entt/entity/observer.hpp

@@ -6,6 +6,8 @@
 #include <cstddef>
 #include <cstdint>
 #include <utility>
+#include <algorithm>
+#include <type_traits>
 #include "../config/config.h"
 #include "../core/type_traits.hpp"
 #include "registry.hpp"
@@ -17,46 +19,19 @@
 namespace entt {
 
 
-/**
- * @brief Matcher.
- *
- * Primary template isn't defined on purpose. All the specializations give a
- * compile-time error, but for a few reasonable cases.
- */
+/*! @brief Grouping matcher. */
 template<typename...>
-struct matcher;
-
-
-/**
- * @brief Observing matcher.
- *
- * An observing matcher contains a type for which changes should be
- * detected.<br/>
- * Because of the rules of the language, not all changes can be easily detected.
- * In order to avoid nasty solutions that could affect performance to an extent,
- * the matcher listens only to the `on_replace` signals emitted by a registry
- * and is therefore triggered whenever an instance of the given component is
- * explicitly replaced.
- *
- * @tparam AnyOf Type of component for which changes should be detected.
- */
-template<typename AnyOf>
-struct matcher<AnyOf> {};
+struct matcher {};
 
 
 /**
- * @brief Grouping matcher.
- *
- * A grouping matcher describes the group to track in terms of accepted and
- * excluded types.<br/>
- * This kind of matcher is triggered whenever an entity _enters_ the desired
- * group because of the components it is assigned.
+ * @brief Collector.
  *
- * @tparam AllOf Types of components tracked by the matcher.
- * @tparam NoneOf Types of components used to filter out entities.
+ * Primary template isn't defined on purpose. All the specializations give a
+ * compile-time error, but for a few reasonable cases.
  */
-template<typename... AllOf, typename... NoneOf>
-struct matcher<type_list<AllOf...>, type_list<NoneOf...>> {};
+template<typename...>
+struct basic_collector;
 
 
 /**
@@ -66,12 +41,39 @@ struct matcher<type_list<AllOf...>, type_list<NoneOf...>> {};
  * entities.<br/>
  * Its main purpose is to generate a descriptor that allows an observer to know
  * how to connect to a registry.
- *
+ */
+template<>
+struct basic_collector<> {
+    /**
+     * @brief Adds a grouping matcher to the collector.
+     * @tparam AllOf Types of components tracked by the matcher.
+     * @tparam NoneOf Types of components used to filter out entities.
+     * @return The updated collector.
+     */
+    template<typename... AllOf, typename... NoneOf>
+    static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
+        return basic_collector<matcher<matcher<type_list<>, type_list<>>, type_list<NoneOf...>, type_list<AllOf...>>>{};
+    }
+
+    /**
+     * @brief Adds an observing matcher to the collector.
+     * @tparam AnyOf Type of component for which changes should be detected.
+     * @return The updated collector.
+     */
+    template<typename AnyOf>
+    static constexpr auto replace() ENTT_NOEXCEPT {
+        return basic_collector<matcher<matcher<type_list<>, type_list<>>, AnyOf>>{};
+    }
+};
+
+/**
+ * @brief Collector.
+ * @copydetails basic_collector<>
  * @tparam AnyOf Types of components for which changes should be detected.
  * @tparam Matcher Types of grouping matchers.
  */
-template<typename... Matcher>
-struct basic_collector {
+template<typename... Reject, typename... Require, typename... Rule, typename... Other>
+struct basic_collector<matcher<matcher<type_list<Reject...>, type_list<Require...>>, Rule...>, Other...> {
     /**
      * @brief Adds a grouping matcher to the collector.
      * @tparam AllOf Types of components tracked by the matcher.
@@ -80,17 +82,30 @@ struct basic_collector {
      */
     template<typename... AllOf, typename... NoneOf>
     static constexpr auto group(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
-        return basic_collector<Matcher..., matcher<type_list<AllOf...>, type_list<NoneOf...>>>{};
+        using first = matcher<matcher<type_list<Reject...>, type_list<Require...>>, Rule...>;
+        return basic_collector<first, Other..., matcher<matcher<type_list<>, type_list<>>, type_list<NoneOf...>, type_list<AllOf...>>>{};
     }
 
     /**
-     * @brief Adds one or more observing matchers to the collector.
-     * @tparam AnyOf Types of components for which changes should be detected.
+     * @brief Adds an observing matcher to the collector.
+     * @tparam AnyOf Type of component for which changes should be detected.
      * @return The updated collector.
      */
-    template<typename... AnyOf>
+    template<typename AnyOf>
     static constexpr auto replace() ENTT_NOEXCEPT {
-        return basic_collector<Matcher..., matcher<AnyOf>...>{};
+        using first = matcher<matcher<type_list<Reject...>, type_list<Require...>>, Rule...>;
+        return basic_collector<first, Other..., matcher<matcher<type_list<>, type_list<>>, AnyOf>>{};
+    }
+
+    /**
+     * @brief Updates the filter of the last added matcher.
+     * @tparam AllOf Types of components required by the matcher.
+     * @tparam NoneOf Types of components used to filter out entities.
+     * @return The updated collector.
+     */
+    template<typename... AllOf, typename... NoneOf>
+    static constexpr auto when(exclude_t<NoneOf...> = {}) ENTT_NOEXCEPT {
+        return basic_collector<matcher<matcher<type_list<Reject..., NoneOf...>, type_list<Require..., AllOf...>>, Rule...>, Other...>{};
     }
 };
 
@@ -120,6 +135,13 @@ constexpr basic_collector<> collector{};
  * If an entity respects the requirements of multiple matchers, it will be
  * returned once and only once by the observer in any case.
  *
+ * Matchers support also filtering by means of a _when_ clause that accepts both
+ * a list of types and an exclusion list.<br/>
+ * Whenever a matcher finds that an entity matches its requirements, the
+ * condition of the filter is verified before to register the entity itself.
+ * Moreover, a registered entity isn't returned by the observer if the condition
+ * set by the filter is broken in the meantime.
+ *
  * @b Important
  *
  * Iterators aren't invalidated if:
@@ -149,10 +171,12 @@ class basic_observer {
     template<std::size_t Index, typename>
     struct matcher_handler;
 
-    template<std::size_t Index, typename AnyOf>
-    struct matcher_handler<Index, matcher<AnyOf>> {
-        static void maybe_valid_if(basic_observer *obs, const basic_registry<Entity> &, const Entity entt) {
-            (obs->view.has(entt) ? obs->view.get(entt) : obs->view.construct(entt)) |= (1 << Index);
+    template<std::size_t Index, typename... Reject, typename... Require, typename AnyOf>
+    struct matcher_handler<Index, matcher<matcher<type_list<Reject...>, type_list<Require...>>, AnyOf>> {
+        static void maybe_valid_if(basic_observer *obs, const basic_registry<Entity> &reg, const Entity entt) {
+            if(reg.template has<Require...>(entt) && !(reg.template has<Reject>(entt) || ...)) {
+                (obs->view.has(entt) ? obs->view.get(entt) : obs->view.construct(entt)) |= (1 << Index);
+            }
         }
 
         static void discard_if(basic_observer *obs, const basic_registry<Entity> &, const Entity entt) {
@@ -162,20 +186,26 @@ class basic_observer {
         }
 
         static void disconnect(basic_registry<Entity> &reg, const basic_observer &obs) {
-            (reg.template on_replace<AnyOf>().disconnect(&obs));
-            (reg.template on_destroy<AnyOf>().disconnect(&obs));
+            reg.template on_replace<AnyOf>().disconnect(&obs);
+            reg.template on_destroy<AnyOf>().disconnect(&obs);
+            (reg.template on_destroy<Require>().disconnect(&obs), ...);
+            (reg.template on_construct<Reject>().disconnect(&obs), ...);
         }
 
         static void connect(basic_observer &obs, basic_registry<Entity> &reg) {
             reg.template on_replace<AnyOf>().template connect<&maybe_valid_if>(&obs);
             reg.template on_destroy<AnyOf>().template connect<&discard_if>(&obs);
+            (reg.template on_destroy<Require>().template connect<&discard_if>(&obs), ...);
+            (reg.template on_construct<Reject>().template connect<&discard_if>(&obs), ...);
         }
     };
 
-    template<std::size_t Index, typename... AllOf, typename... NoneOf>
-    struct matcher_handler<Index, matcher<type_list<AllOf...>, type_list<NoneOf...>>> {
+    template<std::size_t Index, typename... Reject, typename... Require, typename... NoneOf, typename... AllOf>
+    struct matcher_handler<Index, matcher<matcher<type_list<Reject...>, type_list<Require...>>, type_list<NoneOf...>, type_list<AllOf...>>> {
         static void maybe_valid_if(basic_observer *obs, const basic_registry<Entity> &reg, const Entity entt) {
-            if(reg.template has<AllOf...>(entt) && !(reg.template has<NoneOf>(entt) || ...)) {
+            if(reg.template has<AllOf...>(entt) && !(reg.template has<NoneOf>(entt) || ...)
+                    && reg.template has<Require...>(entt) && !(reg.template has<Reject>(entt) || ...))
+            {
                 (obs->view.has(entt) ? obs->view.get(entt) : obs->view.construct(entt)) |= (1 << Index);
             }
         }
@@ -191,6 +221,8 @@ class basic_observer {
             ((reg.template on_destroy<AllOf>().disconnect(&obs)), ...);
             ((reg.template on_construct<NoneOf>().disconnect(&obs)), ...);
             ((reg.template on_destroy<NoneOf>().disconnect(&obs)), ...);
+            (reg.template on_destroy<Require>().disconnect(&obs), ...);
+            (reg.template on_construct<Reject>().disconnect(&obs), ...);
         }
 
         static void connect(basic_observer &obs, basic_registry<Entity> &reg) {
@@ -198,6 +230,8 @@ class basic_observer {
             (reg.template on_destroy<NoneOf>().template connect<&maybe_valid_if>(&obs), ...);
             (reg.template on_destroy<AllOf>().template connect<&discard_if>(&obs), ...);
             (reg.template on_construct<NoneOf>().template connect<&discard_if>(&obs), ...);
+            (reg.template on_destroy<Require>().template connect<&discard_if>(&obs), ...);
+            (reg.template on_construct<Reject>().template connect<&discard_if>(&obs), ...);
         }
     };
 
@@ -341,7 +375,47 @@ public:
 
     /*! @brief Resets the underlying container. */
     void clear() {
-        return view.reset();
+        view.reset();
+    }
+
+    /**
+     * @brief Iterates entities and applies the given function object to them,
+     * then clears the observer.
+     *
+     * The function object is invoked for each entity.<br/>
+     * The signature of the function must be equivalent to the following form:
+     *
+     * @code{.cpp}
+     * void(const entity_type);
+     * @endcode
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    void each(Func func) const {
+        static_assert(std::is_invocable_v<Func, entity_type>);
+        std::for_each(begin(), end(), std::move(func));
+    }
+
+    /**
+     * @brief Iterates entities and applies the given function object to them,
+     * then clears the observer.
+     *
+     * The function object is invoked for each entity.<br/>
+     * The signature of the function must be equivalent to the following form:
+     *
+     * @code{.cpp}
+     * void(const entity_type);
+     * @endcode
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    void each(Func func) {
+        std::as_const(*this).each(std::move(func));
+        clear();
     }
 
 private:

+ 105 - 2
test/entt/entity/observer.cpp

@@ -1,3 +1,5 @@
+#include <tuple>
+#include <type_traits>
 #include <gtest/gtest.h>
 #include <entt/entity/observer.hpp>
 #include <entt/entity/registry.hpp>
@@ -82,9 +84,49 @@ TEST(Observer, AllOf) {
     ASSERT_TRUE(observer.empty());
 }
 
+TEST(Observer, AllOfFiltered) {
+    constexpr auto collector =  entt::collector
+            .group<int>().when<char>(entt::exclude<double>);
+
+    entt::registry registry;
+    entt::observer observer{registry, collector};
+    const auto entity = registry.create();
+
+    ASSERT_TRUE(observer.empty());
+
+    registry.assign<int>(entity);
+
+    ASSERT_EQ(observer.size(), entt::observer::size_type{});
+    ASSERT_TRUE(observer.empty());
+    ASSERT_EQ(observer.data(), nullptr);
+
+    registry.remove<int>(entity);
+    registry.assign<char>(entity);
+    registry.assign<double>(entity);
+    registry.assign<int>(entity);
+
+    ASSERT_TRUE(observer.empty());
+
+    registry.remove<int>(entity);
+    registry.remove<double>(entity);
+    registry.assign<int>(entity);
+
+    ASSERT_EQ(observer.size(), entt::observer::size_type{1});
+    ASSERT_FALSE(observer.empty());
+    ASSERT_EQ(*observer.data(), entity);
+
+    registry.assign<double>(entity);
+
+    ASSERT_TRUE(observer.empty());
+
+    registry.remove<double>(entity);
+
+    ASSERT_TRUE(observer.empty());
+}
+
 TEST(Observer, Observe) {
     entt::registry registry;
-    entt::observer observer{registry, entt::collector.replace<int, char>()};
+    entt::observer observer{registry, entt::collector.replace<int>().replace<char>()};
     const auto entity = registry.create();
 
     ASSERT_TRUE(observer.empty());
@@ -116,6 +158,45 @@ TEST(Observer, Observe) {
     ASSERT_TRUE(observer.empty());
 }
 
+TEST(Observer, ObserveFiltered) {
+    constexpr auto collector =  entt::collector
+            .replace<int>().when<char>(entt::exclude<double>);
+
+    entt::registry registry;
+    entt::observer observer{registry, collector};
+    const auto entity = registry.create();
+
+    ASSERT_TRUE(observer.empty());
+
+    registry.assign<int>(entity);
+    registry.replace<int>(entity);
+
+    ASSERT_EQ(observer.size(), entt::observer::size_type{});
+    ASSERT_TRUE(observer.empty());
+    ASSERT_EQ(observer.data(), nullptr);
+
+    registry.assign<char>(entity);
+    registry.assign<double>(entity);
+    registry.replace<int>(entity);
+
+    ASSERT_TRUE(observer.empty());
+
+    registry.remove<double>(entity);
+    registry.replace<int>(entity);
+
+    ASSERT_EQ(observer.size(), entt::observer::size_type{1});
+    ASSERT_FALSE(observer.empty());
+    ASSERT_EQ(*observer.data(), entity);
+
+    registry.assign<double>(entity);
+
+    ASSERT_TRUE(observer.empty());
+
+    registry.remove<double>(entity);
+
+    ASSERT_TRUE(observer.empty());
+}
+
 TEST(Observer, AllOfObserve) {
     entt::registry registry;
     entt::observer observer{};
@@ -151,7 +232,6 @@ TEST(Observer, AllOfObserve) {
     ASSERT_TRUE(observer.empty());
 }
 
-
 TEST(Observer, CrossRulesCornerCase) {
     entt::registry registry;
     entt::observer observer{registry, entt::collector.group<int>().group<char>()};
@@ -167,3 +247,26 @@ TEST(Observer, CrossRulesCornerCase) {
 
     ASSERT_FALSE(observer.empty());
 }
+
+TEST(Observer, Each) {
+    entt::registry registry;
+    entt::observer observer{registry, entt::collector.group<int>()};
+    const auto entity = std::get<0>(registry.create<int>());
+
+    ASSERT_FALSE(observer.empty());
+    ASSERT_EQ(observer.size(), entt::observer::size_type{1});
+
+    std::as_const(observer).each([entity](const auto entt) {
+        ASSERT_EQ(entity, entt);
+    });
+
+    ASSERT_FALSE(observer.empty());
+    ASSERT_EQ(observer.size(), entt::observer::size_type{1});
+
+    observer.each([entity](const auto entt) {
+        ASSERT_EQ(entity, entt);
+    });
+
+    ASSERT_TRUE(observer.empty());
+    ASSERT_EQ(observer.size(), entt::observer::size_type{});
+}