浏览代码

removed signal + added dependency function(s)

Michele Caini 8 年之前
父节点
当前提交
92048ac17b

+ 58 - 118
README.md

@@ -25,6 +25,8 @@
          * [Continuous loader](#continuous-loader)
          * [Continuous loader](#continuous-loader)
          * [Archives](#archives)
          * [Archives](#archives)
          * [One example to rule them all](#one-example-to-rule-them-all)
          * [One example to rule them all](#one-example-to-rule-them-all)
+      * [Helpers](#helpers)
+         * [Dependency function](#dependency-function)
    * [View: to persist or not to persist?](#view-to-persist-or-not-to-persist)
    * [View: to persist or not to persist?](#view-to-persist-or-not-to-persist)
       * [Standard View](#standard-view)
       * [Standard View](#standard-view)
          * [Single component standard view](#single-component-standard-view)
          * [Single component standard view](#single-component-standard-view)
@@ -103,7 +105,8 @@ Here is a brief list of what it offers today:
 * The smallest and most basic implementation of a service locator ever seen.
 * The smallest and most basic implementation of a service locator ever seen.
 * A cooperative scheduler for processes of any type.
 * A cooperative scheduler for processes of any type.
 * All what is needed for resource management (cache, loaders, handles).
 * All what is needed for resource management (cache, loaders, handles).
-* Signal handlers of any type, delegates and a tiny event dispatcher.
+* Delegates, signal handlers (with built-in support for collectors) and a tiny
+  event dispatcher.
 * A general purpose event emitter, that is a CRTP idiom based class template.
 * A general purpose event emitter, that is a CRTP idiom based class template.
 * An event dispatcher for immediate and delayed events to integrate in loops.
 * An event dispatcher for immediate and delayed events to integrate in loops.
 * ...
 * ...
@@ -961,6 +964,33 @@ the best way to do it. However, feel free to use it at your own risk.
 The basic idea is to store everything in a group of queues in memory, then bring
 The basic idea is to store everything in a group of queues in memory, then bring
 everything back to the registry with different loaders.
 everything back to the registry with different loaders.
 
 
+### Helpers
+
+The so called _helpers_ are small classes and functions mainly designed to offer
+built-in support for the most basic functionalities.<br/>
+The list of helpers will grow longer as time passes and new ideas come out.
+
+#### Dependency function
+
+A _dependency function_ is a predefined listener, actually a function template
+to use to automatically assign components to an entity when a type has a
+dependency on some other types.<br/>
+The following adds components `AType` and `AnotherType` whenever `MyType` is
+assigned to an entity:
+
+```cpp
+entt::dependency<AType, AnotherType>(registry.construction<MyType>());
+```
+
+A component is assigned to an entity and thus default initialized only in case
+the entity itself hasn't it yet. It means that already existent components won't
+be overriden.<br/>
+A dependency can easily be broken by means of the same function template:
+
+```cpp
+entt::dependency<AType, AnotherType>(entt::break_op_t{}, registry.construction<MyType>());
+```
+
 ## View: to persist or not to persist?
 ## View: to persist or not to persist?
 
 
 First of all, it is worth answering an obvious question: why views?<br/>
 First of all, it is worth answering an obvious question: why views?<br/>
@@ -1984,107 +2014,26 @@ offers a full set of classes to solve completely different problems.
 
 
 ## Signals
 ## Signals
 
 
-There are two types of signal handlers in `EnTT`, internally called _managed_
-and _unmanaged_.<br/>
-They differ in the way they work around the tradeoff between performance, memory
-usage and safety. Managed listeners must be wrapped in an `std::shared_ptr` and
-the signal will take care of disconnecting them whenever they die. Unmanaged
-listeners can be any kind of objects and the client is in charge of connecting
-and disconnecting them from a signal to avoid crashes due to different
-lifetimes.<br/>
-Both solutions follow the same pattern to allow users to use a signal as a
-private data member and therefore not expose any publish functionality to the
-clients of their classes. The basic idea is to impose a clear separation between
-the signal itself and its _sink_ class, that is a tool to be used to connect and
-disconnect listeners on the fly.
-
-### Managed signal handler
-
-A managed signal handler works with weak pointers to classes and pointers to
-member functions as well as pointers to free functions. References are
-automatically removed when the instances to which they point are freed.<br/>
-In other terms, users can simply connect a listener and forget about it, thus
-getting rid of the burden of controlling its lifetime. The drawback is that
-listeners must be allocated on the dynamic storage and wrapped into an
-`std::shared_ptr`. Performance and memory management can suffer from this in
-real world softwares.
-
-To create an instance of this type of handler, the function type is all what is
-needed:
-
-```cpp
-entt::Signal<void(int, char)> signal;
-```
-
-From now on, free functions and member functions that respect the given
-signature can be easily connected to and disconnected from the signal by means
-of a sink:
-
-```cpp
-void foo(int, char) { /* ... */ }
-
-struct S {
-    void bar(int, char) { /* ... */ }
-};
-
-// ...
-
-auto instance = std::make_shared<S>();
-
-signal.sink().connect<&foo>();
-signal.sink().connect<S, &S::bar>(instance);
-
-// ...
-
-// disconnects a free function
-signal.sink().disconnect<&foo>();
-
-// disconnects a specific member function of an instance ...
-signal.sink().disconnect<S, &S::bar>(instance);
-
-// ... or an instance as a whole
-signal.sink().disconnect(instance);
-
-// discards all the listeners at once
-signal.sink().disconnect();
-```
-
-Once listeners are attached (or even if there are no listeners at all), events
-and data in general can be published through a signal by means of the `publish`
-member function:
-
-```cpp
-signal.publish(42, 'c');
-```
-
-This is more or less all what a managed signal handler has to offer.<br/>
-A bunch of other member functions are exposed actually. As an example, there is
-a method to use to know how many listeners a managed signal handler contains
-(`size`) or if it contains at least a listener (`empty`) and even to swap two
-handlers (`swap`).<br/>
-Refer to the [official documentation](https://skypjack.github.io/entt/) for all
-the details.
-
-### Unmanaged signal handler
-
-An unmanaged signal handler works with naked pointers to classes and pointers to
-member functions as well as pointers to free functions. Removing references when
-the instances to which they point are freed is in charge to the users.<br/>
-In other terms, users must explicitly disconnect a listener before to delete the
-class to which it belongs, thus taking care of the lifetime of each instance. On
-the other side, performance shouldn't be affected that much by the presence of
-such a signal handler.
-
-The API of an unmanaged signal handler is similar to the one of a managed signal
-handler.<br/>
-The most important difference is that it comes in two forms: with and without a
-collector. In case it is associated with a collector, all the values returned by
-the listeners can be literally _collected_ and used later by the caller.<br/>
+Signal handlers work with naked pointers, function pointers and pointers to
+member functions. Listeners can be any kind of objects and the user is in charge
+of connecting and disconnecting them from a signal to avoid crashes due to
+different lifetimes. On the other side, performance shouldn't be affected that
+much by the presence of such a signal handler.<br/>
+A signal handler can be used as a private data member without exposing any
+_publish_ functionality to the clients of a class. The basic idea is to impose a
+clear separation between the signal itself and its _sink_ class, that is a tool
+to be used to connect and disconnect listeners on the fly.
+
+The API of a signal handler is straightforward. The most important thing is that
+it comes in two forms: with and without a collector. In case a signal is
+associated with a collector, all the values returned by the listeners can be
+literally _collected_ and used later by the caller. Otherwise it works just like
+a plain signal that emits events from time to time.<br/>
 
 
 **Note**: collectors are allowed only in case of function types whose the return
 **Note**: collectors are allowed only in case of function types whose the return
 type isn't `void` for obvious reasons.
 type isn't `void` for obvious reasons.
 
 
-To create instances of this type of handler there exist mainly two ways:
+To create instances of signal handlers there exist mainly two ways:
 
 
 ```cpp
 ```cpp
 // no collector type
 // no collector type
@@ -2094,9 +2043,9 @@ entt::SigH<void(int, char)> signal;
 entt::SigH<void(int, char), MyCollector<bool>> collector;
 entt::SigH<void(int, char), MyCollector<bool>> collector;
 ```
 ```
 
 
-As expected, an unmanaged signal handler offers all the basic functionalities
-required to know how many listeners it contains (`size`) or if it contains at
-least a listener (`empty`) and even to swap two handlers (`swap`).
+As expected, they offer all the basic functionalities required to know how many
+listeners they contain (`size`) or if they contain at least a listener (`empty`)
+and even to swap two signal handlers (`swap`).
 
 
 Besides them, there are member functions to use both to connect and disconnect
 Besides them, there are member functions to use both to connect and disconnect
 listeners in all their forms by means of a sink::
 listeners in all their forms by means of a sink::
@@ -2231,27 +2180,14 @@ within the framework.
 The event dispatcher class is designed so as to be used in a loop. It allows
 The event dispatcher class is designed so as to be used in a loop. It allows
 users both to trigger immediate events or to queue events to be published all
 users both to trigger immediate events or to queue events to be published all
 together once per tick.<br/>
 together once per tick.<br/>
-Internally it uses either managed or unmanaged signal handlers, that is why
-there exist both a managed and an unmanaged event dispatcher.
-
-This class shares part of its API with the one of the signals, but it doesn't
-require that all the types of events are specified when declared:
+This class shares part of its API with the one of the signal handler, but it
+doesn't require that all the types of events are specified when declared:
 
 
 ```cpp
 ```cpp
-// define a managed dispatcher that works with std::shared_ptr/std::weak_ptr
-entt::Dispatcher<entt::Signal> managed{};
-
-// define an unmanaged dispatcher that works with naked pointers
-entt::Dispatcher<entt::SigH> unmanaged{};
+// define a general purpose dispatcher that works with naked pointers
+entt::Dispatcher dispatcher{};
 ```
 ```
 
 
-Actually there exist two aliases for the classes shown in the previous example:
-`entt::ManagedDispatcher` and `entt::UnmanagedDispatcher`.
-
-For the sake of brevity, below is described the interface of the sole unmanaged
-dispatcher. The interface of the managed dispatcher is almost the same but for
-the fact that it accepts smart pointers instead of naked pointers.
-
 In order to register an instance of a class to a dispatcher, its type must
 In order to register an instance of a class to a dispatcher, its type must
 expose one or more member functions of which the return types are `void` and the
 expose one or more member functions of which the return types are `void` and the
 argument lists are `const E &`, for each type of event `E`.<br/>
 argument lists are `const E &`, for each type of event `E`.<br/>
@@ -2315,6 +2251,10 @@ Events are stored aside until the `update` member function is invoked, then all
 the messages that are still pending are sent to the listeners at once:
 the messages that are still pending are sent to the listeners at once:
 
 
 ```cpp
 ```cpp
+// emits all the events of the given type at once
+dispatcher.update<MyEvent>();
+
+// emits all the events queued so far at once
 dispatcher.update();
 dispatcher.update();
 ```
 ```
 
 

+ 4 - 1
TODO

@@ -4,6 +4,9 @@
 * debugging tools (#60): the issue online already contains interesting tips on this, look at it
 * debugging tools (#60): the issue online already contains interesting tips on this, look at it
 * define a macro for the noexcept policy, so as to provide users with an easy way to disable exception handling
 * define a macro for the noexcept policy, so as to provide users with an easy way to disable exception handling
 * define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
 * define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
-* blueprint registry - kind of factory to create entitites template for initialization (use signals, it's trivial this way)
+* ease the assignment of tags as string (use a template class with a non-type template parameter behind the scene)
+* turn anonymous namespaces in details:: so as to avoid issues related to internal vs external linkage
+* dictionary based dependency class (templates copied over) + prefabs (shared state/copy-on-write)
 * remove Actor::update (it's application dependent), allow tag instead
 * remove Actor::update (it's application dependent), allow tag instead
+* "singleton mode" for tags (see #66)
 * AOB
 * AOB

+ 86 - 0
src/entt/entity/helper.hpp

@@ -0,0 +1,86 @@
+#ifndef ENTT_ENTITY_HELPER_HPP
+#define ENTT_ENTITY_HELPER_HPP
+
+
+#include "../signal/sigh.hpp"
+#include "registry.hpp"
+#include "utility"
+
+
+namespace entt {
+
+
+/**
+ * @brief Dependency function prototype.
+ *
+ * A _dependency function_ is a built-in listener to use to automatically assign
+ * components to an entity when a type has a dependency on some other types.
+ *
+ * This is a prototype function to use to create dependencies.<br/>
+ * It isn't intended for direct use, even if nothing forbids using it freely.
+ *
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ * @tparam Component Types of components to assign to an entity if triggered.
+ * @param registry A valid reference to a registry.
+ * @param entity A valid entity identifier.
+ */
+template<typename Entity, typename... Component>
+void dependency(Registry<Entity> &registry, Entity entity) {
+    using accumulator_type = int[];
+    accumulator_type accumulator = { ((registry.template has<Component>(entity) ? void() : (registry.template assign<Component>(entity), void())), 0)... };
+    (void)accumulator;
+}
+
+
+/**
+ * @brief Connects a dependency function to the given sink.
+ *
+ * A _dependency function_ is a built-in listener to use to automatically assign
+ * components to an entity when a type has a dependency on some other types.
+ *
+ * The following adds components `AType` and `AnotherType` whenever `MyType` is
+ * assigned to an entity:
+ * @code{.cpp}
+ * entt::DefaultRegistry registry;
+ * entt::dependency<AType, AnotherType>(registry.construction<MyType>());
+ * @endcode
+ *
+ * @tparam Dependency Types of components to assign to an entity if triggered.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ * @param sink A sink object properly initialized.
+ */
+template<typename... Dependency, typename Entity>
+void dependency(Sink<void(Registry<Entity> &, Entity)> sink) {
+    using func_type = void(*)(Registry<Entity> &, Entity);
+    sink.template connect<static_cast<func_type>(&dependency<Entity, Dependency...>)>();
+}
+
+
+/**
+ * @brief Disconnects a dependency function from the given sink.
+ *
+ * A _dependency function_ is a built-in listener to use to automatically assign
+ * components to an entity when a type has a dependency on some other types.
+ *
+ * The following breaks the dependency between the component `MyType` and the
+ * components `AType` and `AnotherType`:
+ * @code{.cpp}
+ * entt::DefaultRegistry registry;
+ * entt::dependency<AType, AnotherType>(entt::break_op_t{}, registry.construction<MyType>());
+ * @endcode
+ *
+ * @tparam Dependency Types of components used to create the dependency.
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ * @param sink A sink object properly initialized.
+ */
+template<typename... Dependency, typename Entity>
+void dependency(break_op_t, Sink<void(Registry<Entity> &, Entity)> sink) {
+    using func_type = void(*)(Registry<Entity> &, Entity);
+    sink.template disconnect<static_cast<func_type>(&dependency<Entity, Dependency...>)>();
+}
+
+
+}
+
+
+#endif // ENTT_ENTITY_HELPER_HPP

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

@@ -75,7 +75,7 @@ class Registry {
 
 
     template<typename Component>
     template<typename Component>
     struct Pool: SparseSet<Entity, Component> {
     struct Pool: SparseSet<Entity, Component> {
-        using sink_type = typename SigH<void(Registry &, Entity)>::Sink;
+        using sink_type = typename SigH<void(Registry &, Entity)>::sink_type;
 
 
         Pool(Registry &registry)
         Pool(Registry &registry)
             : registry{registry}
             : registry{registry}

+ 10 - 1
src/entt/entity/utility.hpp

@@ -9,11 +9,20 @@ namespace entt {
  * @brief Tag class type.
  * @brief Tag class type.
  *
  *
  * An empty class type used to disambiguate the overloads of some member
  * An empty class type used to disambiguate the overloads of some member
- * functions of the registry.
+ * functions.
  */
  */
 struct tag_type_t final {};
 struct tag_type_t final {};
 
 
 
 
+/**
+ * @brief Break type.
+ *
+ * An empty class type used to disambiguate the overloads of some member
+ * functions.
+ */
+struct break_op_t final {};
+
+
 }
 }
 
 
 
 

+ 1 - 1
src/entt/entt.hpp

@@ -3,6 +3,7 @@
 #include "core/ident.hpp"
 #include "core/ident.hpp"
 #include "entity/actor.hpp"
 #include "entity/actor.hpp"
 #include "entity/entt_traits.hpp"
 #include "entity/entt_traits.hpp"
+#include "entity/helper.hpp"
 #include "entity/registry.hpp"
 #include "entity/registry.hpp"
 #include "entity/snapshot.hpp"
 #include "entity/snapshot.hpp"
 #include "entity/sparse_set.hpp"
 #include "entity/sparse_set.hpp"
@@ -18,4 +19,3 @@
 #include "signal/dispatcher.hpp"
 #include "signal/dispatcher.hpp"
 #include "signal/emitter.hpp"
 #include "signal/emitter.hpp"
 #include "signal/sigh.hpp"
 #include "signal/sigh.hpp"
-#include "signal/signal.hpp"

+ 43 - 59
src/entt/signal/dispatcher.hpp

@@ -6,8 +6,9 @@
 #include <memory>
 #include <memory>
 #include <utility>
 #include <utility>
 #include <cstdint>
 #include <cstdint>
+#include <algorithm>
+#include <type_traits>
 #include "../core/family.hpp"
 #include "../core/family.hpp"
-#include "signal.hpp"
 #include "sigh.hpp"
 #include "sigh.hpp"
 
 
 
 
@@ -20,34 +21,40 @@ namespace entt {
  * A dispatcher can be used either to trigger an immediate event or to enqueue
  * A dispatcher can be used either to trigger an immediate event or to enqueue
  * events to be published all together once per tick.<br/>
  * events to be published all together once per tick.<br/>
  * Listeners are provided in the form of member functions. For each event of
  * Listeners are provided in the form of member functions. For each event of
- * type `Event`, listeners must have the following signature:
- * `void(const Event &)`. Member functions named `receive` are automatically
- * detected and registered or unregistered by the dispatcher.
+ * type `Event`, listeners must have the following function type:
+ * @code{.cpp}
+ * void(const Event &)
+ * @endcode
  *
  *
- * @tparam Sig Type of the signal handler to use.
+ * Member functions named `receive` are automatically detected and registered or
+ * unregistered by the dispatcher. The type of the instances is `Class *` (a
+ * naked pointer). It means that users must guarantee that the lifetimes of the
+ * instances overcome the one of the dispatcher itself to avoid crashes.
  */
  */
-template<template<typename...> class Sig>
 class Dispatcher final {
 class Dispatcher final {
     using event_family = Family<struct InternalDispatcherEventFamily>;
     using event_family = Family<struct InternalDispatcherEventFamily>;
 
 
     template<typename Class, typename Event>
     template<typename Class, typename Event>
-    using instance_type = typename Sig<void(const Event &)>::template instance_type<Class>;
+    using instance_type = typename SigH<void(const Event &)>::template instance_type<Class>;
 
 
     struct BaseSignalWrapper {
     struct BaseSignalWrapper {
         virtual ~BaseSignalWrapper() = default;
         virtual ~BaseSignalWrapper() = default;
-        virtual void publish(std::size_t) = 0;
+        virtual void publish() = 0;
     };
     };
 
 
     template<typename Event>
     template<typename Event>
     struct SignalWrapper final: BaseSignalWrapper {
     struct SignalWrapper final: BaseSignalWrapper {
-        using sink_type = typename Sig<void(const Event &)>::Sink;
+        using sink_type = typename SigH<void(const Event &)>::sink_type;
 
 
-        void publish(std::size_t current) override {
+        void publish() override {
             for(const auto &event: events[current]) {
             for(const auto &event: events[current]) {
                 signal.publish(event);
                 signal.publish(event);
             }
             }
 
 
             events[current].clear();
             events[current].clear();
+
+            ++current;
+            current %= std::extent<decltype(events)>::value;
         }
         }
 
 
         inline sink_type sink() noexcept {
         inline sink_type sink() noexcept {
@@ -60,19 +67,16 @@ class Dispatcher final {
         }
         }
 
 
         template<typename... Args>
         template<typename... Args>
-        inline void enqueue(std::size_t current, Args &&... args) {
+        inline void enqueue(Args &&... args) {
             events[current].push_back({ std::forward<Args>(args)... });
             events[current].push_back({ std::forward<Args>(args)... });
         }
         }
 
 
     private:
     private:
-        Sig<void(const Event &)> signal{};
+        SigH<void(const Event &)> signal{};
         std::vector<Event> events[2];
         std::vector<Event> events[2];
+        int current{};
     };
     };
 
 
-    inline static std::size_t buffer(bool mode) {
-        return mode ? 0 : 1;
-    }
-
     template<typename Event>
     template<typename Event>
     SignalWrapper<Event> & wrapper() {
     SignalWrapper<Event> & wrapper() {
         const auto type = event_family::type<Event>();
         const auto type = event_family::type<Event>();
@@ -93,11 +97,6 @@ public:
     template<typename Event>
     template<typename Event>
     using sink_type = typename SignalWrapper<Event>::sink_type;
     using sink_type = typename SignalWrapper<Event>::sink_type;
 
 
-    /*! @brief Default constructor. */
-    Dispatcher() noexcept
-        : wrappers{}, mode{false}
-    {}
-
     /**
     /**
      * @brief Returns a sink object for the given event.
      * @brief Returns a sink object for the given event.
      *
      *
@@ -110,14 +109,13 @@ public:
      *
      *
      * The order of invocation of the listeners isn't guaranteed.
      * The order of invocation of the listeners isn't guaranteed.
      *
      *
-     * @sa Signal::Sink
      * @sa SigH::Sink
      * @sa SigH::Sink
      *
      *
      * @tparam Event Type of event of which to get the sink.
      * @tparam Event Type of event of which to get the sink.
      * @return A temporary sink object.
      * @return A temporary sink object.
      */
      */
     template<typename Event>
     template<typename Event>
-    sink_type<Event> sink() noexcept {
+    inline sink_type<Event> sink() noexcept {
         return wrapper<Event>().sink();
         return wrapper<Event>().sink();
     }
     }
 
 
@@ -132,7 +130,7 @@ public:
      * @param args Arguments to use to construct the event.
      * @param args Arguments to use to construct the event.
      */
      */
     template<typename Event, typename... Args>
     template<typename Event, typename... Args>
-    void trigger(Args &&... args) {
+    inline void trigger(Args &&... args) {
         wrapper<Event>().trigger(std::forward<Args>(args)...);
         wrapper<Event>().trigger(std::forward<Args>(args)...);
     }
     }
 
 
@@ -147,56 +145,42 @@ public:
      * @param args Arguments to use to construct the event.
      * @param args Arguments to use to construct the event.
      */
      */
     template<typename Event, typename... Args>
     template<typename Event, typename... Args>
-    void enqueue(Args &&... args) {
-        wrapper<Event>().enqueue(buffer(mode), std::forward<Args>(args)...);
+    inline void enqueue(Args &&... args) {
+        wrapper<Event>().enqueue(std::forward<Args>(args)...);
     }
     }
 
 
     /**
     /**
-     * @brief Delivers all the pending events.
+     * @brief Delivers all the pending events of the given type.
      *
      *
      * This method is blocking and it doesn't return until all the events are
      * This method is blocking and it doesn't return until all the events are
-     * delivered to the registered listeners. It's responsability of the users
+     * delivered to the registered listeners. It's responsibility of the users
      * to reduce at a minimum the time spent in the bodies of the listeners.
      * to reduce at a minimum the time spent in the bodies of the listeners.
+     *
+     * @tparam Event Type of events to send.
      */
      */
-    void update() {
-        const auto buf = buffer(mode);
-        mode = !mode;
+    template<typename Event>
+    inline void update() {
+        wrapper<Event>().publish();
+    }
 
 
-        for(auto &&wrapper: wrappers) {
-            if(wrapper) {
-                wrapper->publish(buf);
-            }
-        }
+    /**
+     * @brief Delivers all the pending events.
+     *
+     * This method is blocking and it doesn't return until all the events are
+     * delivered to the registered listeners. It's responsibility of the users
+     * to reduce at a minimum the time spent in the bodies of the listeners.
+     */
+    inline void update() {
+        std::for_each(wrappers.begin(), wrappers.end(), [](auto &&wrapper) {
+            return wrapper ? wrapper->publish() : void();
+        });
     }
     }
 
 
 private:
 private:
     std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
     std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
-    bool mode;
 };
 };
 
 
 
 
-/**
- * @brief Managed dispatcher.
- *
- * A managed dispatcher uses the Signal class template as an underlying type.
- * The type of the instances is the one required by the signal handler:
- * `std::shared_ptr<Class>` (a shared pointer).
- */
-using ManagedDispatcher = Dispatcher<Signal>;
-
-
-/**
- * @brief Unmanaged dispatcher.
- *
- * An unmanaged dispatcher uses the SigH class template as an underlying type.
- * The type of the instances is the one required by the signal handler:
- * `Class *` (a naked pointer).<br/>
- * When it comes to work with this kind of dispatcher, users must guarantee that
- * the lifetimes of the instances overcome the one of the dispatcher itself.
- */
-using UnmanagedDispatcher = Dispatcher<SigH>;
-
-
 }
 }
 
 
 
 

+ 131 - 108
src/entt/signal/sigh.hpp

@@ -74,6 +74,18 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
 }
 }
 
 
 
 
+/**
+ * @brief Sink implementation.
+ *
+ * Primary template isn't defined on purpose. All the specializations give a
+ * compile-time error unless the template parameter is a function type.
+ *
+ * @tparam Function A valid function type.
+ */
+template<typename Function>
+class Sink;
+
+
 /**
 /**
  * @brief Unmanaged signal handler declaration.
  * @brief Unmanaged signal handler declaration.
  *
  *
@@ -87,6 +99,122 @@ template<typename Function, typename Collector = DefaultCollectorType<Function>>
 class SigH;
 class SigH;
 
 
 
 
+/**
+ * @brief Sink implementation.
+ *
+ * A sink is an opaque object used to connect listeners to signals.<br/>
+ * The function type for a listener is the one of the signal to which it
+ * belongs.
+ *
+ * The clear separation between a signal and a sink permits to store the
+ * former as private data member without exposing the publish functionality
+ * to the users of a class.
+ *
+ * @tparam Ret Return type of a function type.
+ * @tparam Args Types of arguments of a function type.
+ */
+template<typename Ret, typename... Args>
+class Sink<Ret(Args...)> final {
+    /*! @brief A signal is allowed to create sinks. */
+    template<typename, typename>
+    friend class SigH;
+
+    using proto_type = Ret(*)(void *, Args...);
+    using call_type = std::pair<void *, proto_type>;
+
+    template<Ret(*Function)(Args...)>
+    static Ret proto(void *, Args... args) {
+        return (Function)(args...);
+    }
+
+    template<typename Class, Ret(Class::*Member)(Args... args)>
+    static Ret proto(void *instance, Args... args) {
+        return (static_cast<Class *>(instance)->*Member)(args...);
+    }
+
+    Sink(std::vector<call_type> &calls)
+        : calls{calls}
+    {}
+
+public:
+    /**
+     * @brief Connects a free function to a signal.
+     *
+     * The signal handler performs checks to avoid multiple connections for
+     * free functions.
+     *
+     * @tparam Function A valid free function pointer.
+     */
+    template<Ret(*Function)(Args...)>
+    void connect() {
+        disconnect<Function>();
+        calls.emplace_back(nullptr, &proto<Function>);
+    }
+
+    /**
+     * @brief Connects a member function for a given instance to a signal.
+     *
+     * The signal isn't responsible for the connected object. Users must
+     * guarantee that the lifetime of the instance overcomes the one of the
+     * signal. On the other side, the signal handler performs checks to
+     * avoid multiple connections for the same member function of a given
+     * instance.
+     *
+     * @tparam Class Type of class to which the member function belongs.
+     * @tparam Member Member function to connect to the signal.
+     * @param instance A valid instance of type pointer to `Class`.
+     */
+    template <typename Class, Ret(Class::*Member)(Args...) = &Class::receive>
+    void connect(Class *instance) {
+        disconnect<Class, Member>(instance);
+        calls.emplace_back(instance, &proto<Class, Member>);
+    }
+
+    /**
+     * @brief Disconnects a free function from a signal.
+     * @tparam Function A valid free function pointer.
+     */
+    template<Ret(*Function)(Args...)>
+    void disconnect() {
+        call_type target{nullptr, &proto<Function>};
+        calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
+    }
+
+    /**
+     * @brief Disconnects the given member function from a signal.
+     * @tparam Class Type of class to which the member function belongs.
+     * @tparam Member Member function to connect to the signal.
+     * @param instance A valid instance of type pointer to `Class`.
+     */
+    template<typename Class, Ret(Class::*Member)(Args...)>
+    void disconnect(Class *instance) {
+        call_type target{instance, &proto<Class, Member>};
+        calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
+    }
+
+    /**
+     * @brief Removes all existing connections for the given instance.
+     * @tparam Class Type of class to which the member function belongs.
+     * @param instance A valid instance of type pointer to `Class`.
+     */
+    template<typename Class>
+    void disconnect(Class *instance) {
+        auto func = [instance](const call_type &call) { return call.first == instance; };
+        calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
+    }
+
+    /**
+     * @brief Disconnects all the listeners from a signal.
+     */
+    void disconnect() {
+        calls.clear();
+    }
+
+private:
+    std::vector<call_type> &calls;
+};
+
+
 /**
 /**
  * @brief Unmanaged signal handler definition.
  * @brief Unmanaged signal handler definition.
  *
  *
@@ -119,6 +247,8 @@ public:
     using size_type = typename std::vector<call_type>::size_type;
     using size_type = typename std::vector<call_type>::size_type;
     /*! @brief Collector type. */
     /*! @brief Collector type. */
     using collector_type = Collector;
     using collector_type = Collector;
+    /*! @brief Sink type. */
+    using sink_type = Sink<Ret(Args...)>;
 
 
     /**
     /**
      * @brief Instance type when it comes to connecting member functions.
      * @brief Instance type when it comes to connecting member functions.
@@ -127,113 +257,6 @@ public:
     template<typename Class>
     template<typename Class>
     using instance_type = Class *;
     using instance_type = Class *;
 
 
-   /**
-    * @brief Sink implementation.
-    *
-    * A sink is an opaque object used to connect listeners to signals.<br/>
-    * The function type for a listener is the one of the signal to which it
-    * belongs.
-    *
-    * The clear separation between a signal and a sink permits to store the
-    * former as private data member without exposing the publish functionality
-    * to the users of a class.
-    */
-    class Sink final {
-        /*! @brief A signal is allowed to create sinks. */
-        friend class SigH;
-
-        template<Ret(*Function)(Args...)>
-        static Ret proto(void *, Args... args) {
-            return (Function)(args...);
-        }
-
-        template<typename Class, Ret(Class::*Member)(Args... args)>
-        static Ret proto(void *instance, Args... args) {
-            return (static_cast<Class *>(instance)->*Member)(args...);
-        }
-
-        Sink(std::vector<call_type> &calls)
-            : calls{calls}
-        {}
-
-    public:
-        /**
-         * @brief Connects a free function to a signal.
-         *
-         * The signal handler performs checks to avoid multiple connections for
-         * free functions.
-         *
-         * @tparam Function A valid free function pointer.
-         */
-        template<Ret(*Function)(Args...)>
-        void connect() {
-            disconnect<Function>();
-            calls.emplace_back(nullptr, &proto<Function>);
-        }
-
-        /**
-         * @brief Connects a member function for a given instance to a signal.
-         *
-         * The signal isn't responsible for the connected object. Users must
-         * guarantee that the lifetime of the instance overcomes the one of the
-         * signal. On the other side, the signal handler performs checks to
-         * avoid multiple connections for the same member function of a given
-         * instance.
-         *
-         * @tparam Class Type of class to which the member function belongs.
-         * @tparam Member Member function to connect to the signal.
-         * @param instance A valid instance of type pointer to `Class`.
-         */
-        template <typename Class, Ret(Class::*Member)(Args...) = &Class::receive>
-        void connect(instance_type<Class> instance) {
-            disconnect<Class, Member>(instance);
-            calls.emplace_back(instance, &proto<Class, Member>);
-        }
-
-        /**
-         * @brief Disconnects a free function from a signal.
-         * @tparam Function A valid free function pointer.
-         */
-        template<Ret(*Function)(Args...)>
-        void disconnect() {
-            call_type target{nullptr, &proto<Function>};
-            calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
-        }
-
-        /**
-         * @brief Disconnects the given member function from a signal.
-         * @tparam Class Type of class to which the member function belongs.
-         * @tparam Member Member function to connect to the signal.
-         * @param instance A valid instance of type pointer to `Class`.
-         */
-        template<typename Class, Ret(Class::*Member)(Args...)>
-        void disconnect(instance_type<Class> instance) {
-            call_type target{instance, &proto<Class, Member>};
-            calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
-        }
-
-        /**
-         * @brief Removes all existing connections for the given instance.
-         * @tparam Class Type of class to which the member function belongs.
-         * @param instance A valid instance of type pointer to `Class`.
-         */
-        template<typename Class>
-        void disconnect(instance_type<Class> instance) {
-            auto func = [instance](const call_type &call) { return call.first == instance; };
-            calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
-        }
-
-        /**
-         * @brief Disconnects all the listeners from a signal.
-         */
-        void disconnect() {
-            calls.clear();
-        }
-
-    private:
-        std::vector<call_type> &calls;
-    };
-
     /**
     /**
      * @brief Number of listeners connected to the signal.
      * @brief Number of listeners connected to the signal.
      * @return Number of listeners currently connected.
      * @return Number of listeners currently connected.
@@ -259,7 +282,7 @@ public:
      *
      *
      * @return A temporary sink object.
      * @return A temporary sink object.
      */
      */
-    Sink sink() {
+    sink_type sink() {
         return { calls };
         return { calls };
     }
     }
 
 

+ 0 - 268
src/entt/signal/signal.hpp

@@ -1,268 +0,0 @@
-#ifndef ENTT_SIGNAL_SIGNAL_HPP
-#define ENTT_SIGNAL_SIGNAL_HPP
-
-
-#include <memory>
-#include <vector>
-#include <utility>
-#include <cstdint>
-#include <iterator>
-#include <algorithm>
-
-
-namespace entt {
-
-
-/**
- * @brief Managed signal handler declaration.
- *
- * Primary template isn't defined on purpose. All the specializations give a
- * compile-time error unless the template parameter is a function type.
- */
-template<typename>
-class Signal;
-
-
-/**
- * @brief Managed signal handler definition.
- *
- * Managed signal handler. It works with weak pointers to classes and pointers
- * to member functions as well as pointers to free functions. References are
- * automatically removed when the instances to which they point are freed.
- *
- * This class can be used to create signals used later to notify a bunch of
- * listeners.
- *
- * @tparam Args Types of arguments of a function type.
- */
-template<typename... Args>
-class Signal<void(Args...)> final {
-    using proto_type = bool(*)(std::weak_ptr<void> &, Args...);
-    using call_type = std::pair<std::weak_ptr<void>, proto_type>;
-
-public:
-    /*! @brief Unsigned integer type. */
-    using size_type = std::size_t;
-
-    /**
-     * @brief Instance type when it comes to connecting member functions.
-     * @tparam Class Type of class to which the member function belongs.
-     */
-    template<typename Class>
-    using instance_type = std::shared_ptr<Class>;
-
-    /**
-     * @brief Sink implementation.
-     *
-     * A sink is an opaque object used to connect listeners to signals.<br/>
-     * The function type for a listener is the one of the signal to which it
-     * belongs.
-     *
-     * The clear separation between a signal and a sink permits to store the
-     * former as private data member without exposing the publish functionality
-     * to the users of a class.
-     */
-    class Sink final {
-        /*! @brief A signal is allowed to create sinks. */
-        friend class Signal;
-
-        template<void(*Function)(Args...)>
-        static bool proto(std::weak_ptr<void> &, Args... args) {
-            Function(args...);
-            return true;
-        }
-
-        template<typename Class, void(Class::*Member)(Args...)>
-        static bool proto(std::weak_ptr<void> &wptr, Args... args) {
-            bool ret = false;
-
-            if(!wptr.expired()) {
-                auto ptr = std::static_pointer_cast<Class>(wptr.lock());
-                (ptr.get()->*Member)(args...);
-                ret = true;
-            }
-
-            return ret;
-        }
-
-        Sink(std::vector<call_type> &calls)
-            : calls{calls}
-        {}
-
-    public:
-        /**
-         * @brief Connects a free function to a signal.
-         *
-         * The signal handler performs checks to avoid multiple connections for
-         * free functions.
-         *
-         * @tparam Function A valid free function pointer.
-         */
-        template<void(*Function)(Args...)>
-        void connect() {
-            disconnect<Function>();
-            calls.emplace_back(std::weak_ptr<void>{}, &proto<Function>);
-        }
-
-        /**
-         * @brief Connects a member function for a given instance to a signal.
-         *
-         * The signal handler performs checks to avoid multiple connections for
-         * the same member function of a given instance.
-         *
-         * @tparam Class Type of class to which the member function belongs.
-         * @tparam Member Member function to connect to the signal.
-         * @param instance A valid instance of type pointer to `Class`.
-         */
-        template<typename Class, void(Class::*Member)(Args...) = &Class::receive>
-        void connect(instance_type<Class> instance) {
-            disconnect<Class, Member>(instance);
-            calls.emplace_back(std::move(instance), &proto<Class, Member>);
-        }
-
-        /**
-         * @brief Disconnects a free function from a signal.
-         * @tparam Function A valid free function pointer.
-         */
-        template<void(*Function)(Args...)>
-        void disconnect() {
-            calls.erase(std::remove_if(calls.begin(), calls.end(),
-                [](const call_type &call) { return call.second == &proto<Function> && !call.first.lock(); }
-            ), calls.end());
-        }
-
-        /**
-         * @brief Disconnects the given member function from a signal.
-         * @tparam Class Type of class to which the member function belongs.
-         * @tparam Member Member function to connect to the signal.
-         * @param instance A valid instance of type pointer to `Class`.
-         */
-        template<typename Class, void(Class::*Member)(Args...)>
-        void disconnect(instance_type<Class> instance) {
-            calls.erase(std::remove_if(calls.begin(), calls.end(),
-                [instance{std::move(instance)}](const call_type &call) { return call.second == &proto<Class, Member> && call.first.lock() == instance; }
-            ), calls.end());
-        }
-
-        /**
-         * @brief Removes all existing connections for the given instance.
-         * @tparam Class Type of class to which the member function belongs.
-         * @param instance A valid instance of type pointer to `Class`.
-         */
-        template<typename Class>
-        void disconnect(instance_type<Class> instance) {
-            calls.erase(std::remove_if(calls.begin(), calls.end(),
-                [instance{std::move(instance)}](const call_type &call) { return call.first.lock() == instance; }
-            ), calls.end());
-        }
-
-        /**
-         * @brief Disconnects all the listeners from a signal.
-         */
-        void disconnect() {
-            calls.clear();
-        }
-
-    private:
-        std::vector<call_type> &calls;
-    };
-
-    /**
-     * @brief Number of listeners connected to the signal.
-     * @return Number of listeners currently connected.
-     */
-    size_type size() const noexcept {
-        return calls.size();
-    }
-
-    /**
-     * @brief Returns false if at least a listener is connected to the signal.
-     * @return True if the signal has no listeners connected, false otherwise.
-     */
-    bool empty() const noexcept {
-        return calls.empty();
-    }
-
-    /**
-     * @brief Returns a sink object for the given signal.
-     *
-     * A sink is an opaque object used to connect listeners to signals.<br/>
-     * The function type for a listener is the one of the signal to which it
-     * belongs. The order of invocation of the listeners isn't guaranteed.
-     *
-     * @return A temporary sink object.
-     */
-    Sink sink() {
-        return { calls };
-    }
-
-    /**
-     * @brief Triggers a signal.
-     *
-     * All the listeners are notified. Order isn't guaranteed.
-     *
-     * @param args Arguments to use to invoke listeners.
-     */
-    void publish(Args... args) {
-        std::vector<call_type> next;
-
-        for(auto &&call: calls) {
-            if((call.second)(call.first, args...)) {
-                next.push_back(call);
-            }
-        }
-
-        calls.swap(next);
-    }
-
-    /**
-     * @brief Swaps listeners between the two signals.
-     * @param lhs A valid signal object.
-     * @param rhs A valid signal object.
-     */
-    friend void swap(Signal &lhs, Signal &rhs) {
-        using std::swap;
-        swap(lhs.calls, rhs.calls);
-    }
-
-    /**
-     * @brief Checks if the contents of the two signals are identical.
-     *
-     * Two signals are identical if they have the same size and the same
-     * listeners registered exactly in the same order.
-     *
-     * @param other Signal with which to compare.
-     * @return True if the two signals are identical, false otherwise.
-     */
-    bool operator==(const Signal &other) const noexcept {
-        return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend(), [](const auto &lhs, const auto &rhs) {
-            return (lhs.second == rhs.second) && (lhs.first.lock() == rhs.first.lock());
-        });
-    }
-
-private:
-    std::vector<call_type> calls;
-};
-
-
-/**
- * @brief Checks if the contents of the two signals are different.
- *
- * Two signals are identical if they have the same size and the same
- * listeners registered exactly in the same order.
- *
- * @tparam Args Types of arguments of a function type.
- * @param lhs A valid signal object.
- * @param rhs A valid signal object.
- * @return True if the two signals are different, false otherwise.
- */
-template<typename... Args>
-bool operator!=(const Signal<void(Args...)> &lhs, const Signal<void(Args...)> &rhs) noexcept {
-    return !(lhs == rhs);
-}
-
-
-}
-
-
-#endif // ENTT_SIGNAL_SIGNAL_HPP

+ 1 - 1
test/CMakeLists.txt

@@ -73,6 +73,7 @@ add_executable(
     entity
     entity
     $<TARGET_OBJECTS:odr>
     $<TARGET_OBJECTS:odr>
     entt/entity/actor.cpp
     entt/entity/actor.cpp
+    entt/entity/helper.cpp
     entt/entity/registry.cpp
     entt/entity/registry.cpp
     entt/entity/snapshot.cpp
     entt/entity/snapshot.cpp
     entt/entity/sparse_set.cpp
     entt/entity/sparse_set.cpp
@@ -121,7 +122,6 @@ add_executable(
     entt/signal/dispatcher.cpp
     entt/signal/dispatcher.cpp
     entt/signal/emitter.cpp
     entt/signal/emitter.cpp
     entt/signal/sigh.cpp
     entt/signal/sigh.cpp
-    entt/signal/signal.cpp
 )
 )
 target_link_libraries(signal PRIVATE gtest_main Threads::Threads)
 target_link_libraries(signal PRIVATE gtest_main Threads::Threads)
 add_test(NAME signal COMMAND signal)
 add_test(NAME signal COMMAND signal)

+ 49 - 0
test/entt/entity/helper.cpp

@@ -0,0 +1,49 @@
+#include <gtest/gtest.h>
+#include <entt/entity/helper.hpp>
+#include <entt/entity/registry.hpp>
+
+TEST(Dependency, Functionalities) {
+    entt::DefaultRegistry registry;
+    const auto entity = registry.create();
+    entt::dependency<double, float>(registry.construction<int>());
+
+    ASSERT_FALSE(registry.has<double>(entity));
+    ASSERT_FALSE(registry.has<float>(entity));
+
+    registry.assign<char>(entity);
+
+    ASSERT_FALSE(registry.has<double>(entity));
+    ASSERT_FALSE(registry.has<float>(entity));
+
+    registry.assign<int>(entity);
+
+    ASSERT_TRUE(registry.has<double>(entity));
+    ASSERT_TRUE(registry.has<float>(entity));
+    ASSERT_EQ(registry.get<double>(entity), .0);
+    ASSERT_EQ(registry.get<float>(entity), .0f);
+
+    registry.get<double>(entity) = .3;
+    registry.get<float>(entity) = .1f;
+    registry.remove<int>(entity);
+    registry.assign<int>(entity);
+
+    ASSERT_EQ(registry.get<double>(entity), .3);
+    ASSERT_EQ(registry.get<float>(entity), .1f);
+
+    registry.remove<int>(entity);
+    registry.remove<float>(entity);
+    registry.assign<int>(entity);
+
+    ASSERT_TRUE(registry.has<float>(entity));
+    ASSERT_EQ(registry.get<double>(entity), .3);
+    ASSERT_EQ(registry.get<float>(entity), .0f);
+
+    registry.remove<int>(entity);
+    registry.remove<double>(entity);
+    registry.remove<float>(entity);
+    entt::dependency<double, float>(entt::break_op_t{}, registry.construction<int>());
+    registry.assign<int>(entity);
+
+    ASSERT_FALSE(registry.has<double>(entity));
+    ASSERT_FALSE(registry.has<float>(entity));
+}

+ 22 - 29
test/entt/signal/dispatcher.cpp

@@ -2,46 +2,39 @@
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <entt/signal/dispatcher.hpp>
 #include <entt/signal/dispatcher.hpp>
 
 
-struct Event {};
+struct AnEvent {};
+struct AnotherEvent {};
 
 
 struct Receiver {
 struct Receiver {
-    void receive(const Event &) { ++cnt; }
+    void receive(const AnEvent &) { ++cnt; }
     void reset() { cnt = 0; }
     void reset() { cnt = 0; }
-    std::size_t cnt{0};
+    int cnt{0};
 };
 };
 
 
-template<typename Dispatcher, typename Rec>
-void testDispatcher(Rec receiver) {
-    Dispatcher dispatcher;
+TEST(Dispatcher, Functionalities) {
+    entt::Dispatcher dispatcher;
+    Receiver receiver;
 
 
-    dispatcher.template sink<Event>().connect(receiver);
-    dispatcher.template trigger<Event>();
-    dispatcher.template enqueue<Event>();
+    dispatcher.template sink<AnEvent>().connect(&receiver);
+    dispatcher.template trigger<AnEvent>();
+    dispatcher.template enqueue<AnEvent>();
+    dispatcher.template enqueue<AnotherEvent>();
+    dispatcher.update<AnotherEvent>();
 
 
-    ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(1));
+    ASSERT_EQ(receiver.cnt, 1);
 
 
-    dispatcher.update();
-    dispatcher.update();
-    dispatcher.template trigger<Event>();
+    dispatcher.update<AnEvent>();
+    dispatcher.template trigger<AnEvent>();
 
 
-    ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(3));
+    ASSERT_EQ(receiver.cnt, 3);
 
 
-    receiver->reset();
+    receiver.reset();
 
 
-    dispatcher.template sink<Event>().disconnect(receiver);
-    dispatcher.template trigger<Event>();
-    dispatcher.template enqueue<Event>();
+    dispatcher.template sink<AnEvent>().disconnect(&receiver);
+    dispatcher.template trigger<AnEvent>();
+    dispatcher.template enqueue<AnEvent>();
     dispatcher.update();
     dispatcher.update();
-    dispatcher.template trigger<Event>();
-
-    ASSERT_EQ(receiver->cnt, static_cast<decltype(receiver->cnt)>(0));
-}
-
-TEST(ManagedDispatcher, Basics) {
-    testDispatcher<entt::ManagedDispatcher>(std::make_shared<Receiver>());
-}
+    dispatcher.template trigger<AnEvent>();
 
 
-TEST(UnmanagedDispatcher, Basics) {
-    auto ptr = std::make_unique<Receiver>();
-    testDispatcher<entt::UnmanagedDispatcher>(ptr.get());
+    ASSERT_EQ(receiver.cnt, 0);
 }
 }

+ 0 - 179
test/entt/signal/signal.cpp

@@ -1,179 +0,0 @@
-#include <memory>
-#include <utility>
-#include <gtest/gtest.h>
-#include <entt/signal/signal.hpp>
-
-struct S {
-    static void f(const int &j) { k = j; }
-
-    void g() {}
-    void h() {}
-
-    void i(const int &j) { k = j; }
-    void l(const int &) {}
-
-    static int k;
-};
-
-int S::k = 0;
-
-TEST(Signal, Lifetime) {
-    using signal = entt::Signal<void(void)>;
-
-    ASSERT_NO_THROW(signal{});
-
-    signal src{}, other{};
-
-    ASSERT_NO_THROW(signal{src});
-    ASSERT_NO_THROW(signal{std::move(other)});
-    ASSERT_NO_THROW(src = other);
-    ASSERT_NO_THROW(src = std::move(other));
-
-    ASSERT_NO_THROW(delete new signal{});
-}
-
-TEST(Signal, Comparison) {
-    entt::Signal<void()> sig1;
-    entt::Signal<void()> sig2;
-
-    auto s1 = std::make_shared<S>();
-    auto s2 = std::make_shared<S>();
-
-    sig1.sink().connect<S, &S::g>(s1);
-    sig2.sink().connect<S, &S::g>(s2);
-
-    ASSERT_FALSE(sig1 == sig2);
-    ASSERT_TRUE(sig1 != sig2);
-
-    sig1.sink().disconnect<S, &S::g>(s1);
-    sig2.sink().disconnect<S, &S::g>(s2);
-
-    sig1.sink().connect<S, &S::g>(s1);
-    sig2.sink().connect<S, &S::h>(s1);
-
-    ASSERT_FALSE(sig1 == sig2);
-    ASSERT_TRUE(sig1 != sig2);
-
-    sig1.sink().disconnect<S, &S::g>(s1);
-    sig2.sink().disconnect<S, &S::h>(s1);
-
-    ASSERT_TRUE(sig1 == sig2);
-    ASSERT_FALSE(sig1 != sig2);
-
-    sig1.sink().connect<S, &S::g>(s1);
-    sig1.sink().connect<S, &S::h>(s1);
-    sig2.sink().connect<S, &S::g>(s1);
-    sig2.sink().connect<S, &S::h>(s1);
-
-    ASSERT_TRUE(sig1 == sig2);
-
-    sig1.sink().disconnect<S, &S::g>(s1);
-    sig1.sink().disconnect<S, &S::h>(s1);
-    sig2.sink().disconnect<S, &S::g>(s1);
-    sig2.sink().disconnect<S, &S::h>(s1);
-
-    sig1.sink().connect<S, &S::g>(s1);
-    sig1.sink().connect<S, &S::h>(s1);
-    sig2.sink().connect<S, &S::h>(s1);
-    sig2.sink().connect<S, &S::g>(s1);
-
-    ASSERT_FALSE(sig1 == sig2);
-}
-
-TEST(Signal, Clear) {
-    entt::Signal<void(const int &)> signal;
-    signal.sink().connect<&S::f>();
-
-    ASSERT_FALSE(signal.empty());
-
-    signal.sink().disconnect();
-
-    ASSERT_TRUE(signal.empty());
-}
-
-TEST(Signal, Swap) {
-    entt::Signal<void(const int &)> sig1;
-    entt::Signal<void(const int &)> sig2;
-
-    sig1.sink().connect<&S::f>();
-
-    ASSERT_FALSE(sig1.empty());
-    ASSERT_TRUE(sig2.empty());
-
-    std::swap(sig1, sig2);
-
-    ASSERT_TRUE(sig1.empty());
-    ASSERT_FALSE(sig2.empty());
-}
-
-TEST(Signal, Functions) {
-    entt::Signal<void(const int &)> signal;
-    auto val = (S::k = 0) + 1;
-
-    signal.sink().connect<&S::f>();
-    signal.publish(val);
-
-    ASSERT_FALSE(signal.empty());
-    ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
-    ASSERT_EQ(S::k, val);
-
-    signal.sink().disconnect<&S::f>();
-    signal.publish(val+1);
-
-    ASSERT_TRUE(signal.empty());
-    ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
-    ASSERT_EQ(S::k, val);
-}
-
-TEST(Signal, Members) {
-    entt::Signal<void(const int &)> signal;
-    auto ptr = std::make_shared<S>();
-    auto val = (S::k = 0) + 1;
-
-    signal.sink().connect<S, &S::i>(ptr);
-    signal.publish(val);
-
-    ASSERT_FALSE(signal.empty());
-    ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
-    ASSERT_EQ(S::k, val);
-
-    signal.sink().disconnect<S, &S::i>(ptr);
-    signal.publish(val+1);
-
-    ASSERT_TRUE(signal.empty());
-    ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
-    ASSERT_EQ(S::k, val);
-
-    ++val;
-
-    signal.sink().connect<S, &S::i>(ptr);
-    signal.sink().connect<S, &S::l>(ptr);
-    signal.publish(val);
-
-    ASSERT_FALSE(signal.empty());
-    ASSERT_EQ(entt::Signal<void(const int &)>::size_type{2}, signal.size());
-    ASSERT_EQ(S::k, val);
-
-    signal.sink().disconnect(ptr);
-    signal.publish(val+1);
-
-    ASSERT_TRUE(signal.empty());
-    ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
-    ASSERT_EQ(S::k, val);
-}
-
-TEST(Signal, Cleanup) {
-    entt::Signal<void(const int &)> signal;
-    auto ptr = std::make_shared<S>();
-    signal.sink().connect<S, &S::i>(ptr);
-    auto val = (S::k = 0);
-    ptr = nullptr;
-
-    ASSERT_FALSE(signal.empty());
-    ASSERT_EQ(S::k, val);
-
-    signal.publish(val);
-
-    ASSERT_TRUE(signal.empty());
-    ASSERT_EQ(S::k, val);
-}