Bläddra i källkod

signal handler: substitution

Michele Caini 7 år sedan
förälder
incheckning
9fcc1e17f2
4 ändrade filer med 73 tillägg och 29 borttagningar
  1. 5 4
      TODO
  2. 11 5
      docs/md/entity.md
  3. 40 9
      src/entt/entity/registry.hpp
  4. 17 11
      test/entt/entity/registry.cpp

+ 5 - 4
TODO

@@ -10,10 +10,6 @@
 * allow to replace std:: with custom implementations
 * allow to sort groups (::respect can already work with begin/end instead of a whole sparse set)
   -it would ease by far the group trick for hierarchies that requires otherwise more boilerplate
-* events on replace, so that one can track updated components? indagate impact
-  - 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)
-  - from Tommaso on discord view<Health, Transform>().where<Health>([](h) {h > 5}).where<Transform>([](t) {t.inside(aabb)});
 * remove runtime views, welcome reflection and what about snapshot?
 * empty components model allows for shared components and prefabs unity-like
   - each with entity return the shared component multiple times, one per entity that refers to it
@@ -26,3 +22,8 @@
 * review sparse set to allow customization (mix pack in the spec, base is position only)
   - non-owning groups can iterate pages and skip empty ones, this should mitigate the lack of the packed array
 * review 64 bit id: user defined area + dedicated member on the registry to set it
+* events on replace, so that one can track updated components? indagate impact
+  - construction, destruction, substitution -> assigned, removed, replaced
+  - 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)
+  - from Tommaso on discord view<Health, Transform>().where<Health>([](h) {h > 5}).where<Transform>([](t) {t.inside(aabb)});

+ 11 - 5
docs/md/entity.md

@@ -311,9 +311,10 @@ the component owned by an entity if any, a null pointer otherwise.
 
 Because of how the registry works internally, it stores a couple of signal
 handlers for each pool in order to notify some of its data structures on the
-construction and destruction of components.<br/>
-These signal handlers are also exposed and made available to users. This is the
-basic brick to build fancy things like dependencies and reactive systems.
+construction and destruction of components. Moreover, it offers also a signal
+handler to listen for changes on components.<br/>
+These signal handlers are also exposed and made available to users. These are
+the basic bricks to build fancy things like dependencies and reactive systems.
 
 To get a sink to be used to connect and disconnect listeners so as to be
 notified on the creation of a component, use the `construction` member function:
@@ -332,10 +333,12 @@ registry.construction<position>().disconnect<&my_free_function>();
 registry.construction<position>().disconnect<&my_class::member>(&instance);
 ```
 
+The `substitution` member function returns instead a sink object to which to
+connect listeners that are triggered when components are explicitly replaced.
 To be notified when components are destroyed, use the `destruction` member
 function instead.
 
-The function type of a listener is the same in both cases and should be
+The function type of a listener is the same in all cases and should be
 equivalent to:
 
 ```cpp
@@ -346,6 +349,7 @@ In other terms, a listener is provided with the registry that triggered the
 notification and the entity affected by the change. Note also that:
 
 * Listeners are invoked **after** components have been assigned to entities.
+* Listeners are invoked **after** components have been replaced for entities.
 * Listeners are invoked **before** components have been removed from entities.
 * The order of invocation of the listeners isn't guaranteed in any case.
 
@@ -354,13 +358,15 @@ particular:
 
 * Connecting and disconnecting other functions from within the body of a
   listener should be avoided. It can lead to undefined behavior in some cases.
+* Replacing components from within the body of a listener that observes changes
+  on entities should be avoided. Intuitively, it could trigger an infinite loop.
 * Assigning and removing components from within the body of a listener that
   observes the destruction of instances of a given type should be avoided. It
   can lead to undefined behavior in some cases. This type of listeners is
   intended to provide users with an easy way to perform cleanup and nothing
   more.
 
-To a certain extent, these limitations do not apply. However, it is risky to try
+To a certain extent, these limitations don't apply. However, it's risky to try
 to force them and users should respect the limitations unless they know exactly
 what they are doing. Subtle bugs are the price to pay in case of errors
 otherwise.

+ 40 - 9
src/entt/entity/registry.hpp

@@ -90,8 +90,17 @@ class basic_registry {
             sparse_set<Entity, Component>::destroy(entt);
         }
 
+        template<typename... Args>
+        Component & replace(const Entity entt, Args &&... args) {
+            auto &component = sparse_set<Entity, Component>::get(entt);
+            component = std::decay_t<Component>{std::forward<Args>(args)...};
+            substitution.publish(*owner, entt);
+            return component;
+        }
+
         signal_type construction;
         signal_type destruction;
+        signal_type substitution;
         basic_registry *owner;
     };
 
@@ -861,7 +870,7 @@ public:
      */
     template<typename Component, typename... Args>
     Component & replace(const entity_type entity, Args &&... args) {
-        return (pool<Component>()->get(entity) = std::decay_t<Component>{std::forward<Args>(args)...});
+        return assure<Component>()->replace(entity, std::forward<Args>(args)...);
     }
 
     /**
@@ -893,15 +902,10 @@ public:
     template<typename Component, typename... Args>
     Component & assign_or_replace(const entity_type entity, Args &&... args) {
         auto *cpool = assure<Component>();
-        auto *comp = cpool->try_get(entity);
-
-        if(comp) {
-            *comp = std::decay_t<Component>{std::forward<Args>(args)...};
-        } else {
-            comp = &cpool->construct(entity, std::forward<Args>(args)...);
-        }
 
-        return *comp;
+        return cpool->has(entity)
+                ? cpool->replace(entity, std::forward<Args>(args)...)
+                : cpool->construct(entity, std::forward<Args>(args)...);
     }
 
     /**
@@ -960,6 +964,33 @@ public:
         return assure<Component>()->destruction.sink();
     }
 
+    /**
+     * @brief Returns a sink object for the given component.
+     *
+     * A sink is an opaque object used to connect listeners to components.<br/>
+     * The sink returned by this function can be used to receive notifications
+     * whenever an instance of the given component is explicitly replaced.
+     *
+     * The function type for a listener is equivalent to:
+     * @code{.cpp}
+     * void(registry<Entity> &, Entity);
+     * @endcode
+     *
+     * Listeners are invoked **after** the component has been replaced. The
+     * order of invocation of the listeners isn't guaranteed.<br/>
+     * Note also that the greater the number of listeners, the greater the
+     * performance hit when a component is replaced.
+     *
+     * @sa sink
+     *
+     * @tparam Component Type of component of which to get the sink.
+     * @return A temporary sink object.
+     */
+    template<typename Component>
+    sink_type substitution() ENTT_NOEXCEPT {
+        return assure<Component>()->substitution.sink();
+    }
+
     /**
      * @brief Sorts the pool of entities for the given component.
      *

+ 17 - 11
test/entt/entity/registry.cpp

@@ -856,6 +856,7 @@ TEST(Registry, Signals) {
 
     registry.construction<int>().connect<&listener::incr<int>>(&listener);
     registry.destruction<int>().connect<&listener::decr<int>>(&listener);
+    registry.substitution<int>().connect<&listener::incr<int>>(&listener);
 
     auto e0 = registry.create();
     auto e1 = registry.create();
@@ -902,6 +903,22 @@ TEST(Registry, Signals) {
 
     ASSERT_EQ(listener.counter, 1);
     ASSERT_EQ(listener.last, e1);
+
+    registry.remove<int>(e0);
+    registry.assign_or_replace<int>(e0);
+
+    ASSERT_EQ(listener.counter, 1);
+    ASSERT_EQ(listener.last, e0);
+
+    registry.assign_or_replace<int>(e0);
+
+    ASSERT_EQ(listener.counter, 2);
+    ASSERT_EQ(listener.last, e0);
+
+    registry.replace<int>(e0);
+
+    ASSERT_EQ(listener.counter, 3);
+    ASSERT_EQ(listener.last, e0);
 }
 
 TEST(Registry, DestroyByComponents) {
@@ -952,17 +969,6 @@ TEST(Registry, DestroyByComponents) {
     ASSERT_FALSE(registry.valid(e2));
 }
 
-TEST(Registry, SignalsOnAccommodate) {
-    entt::registry registry;
-    const auto entity = registry.create();
-    const auto group = registry.group<>(entt::get<int, char>);
-
-    registry.assign<int>(entity);
-    registry.assign_or_replace<char>(entity);
-
-    ASSERT_FALSE((group.empty()));
-}
-
 TEST(Registry, CreateManyEntitiesAtOnce) {
     entt::registry registry;
     entt::entity entities[3];