Browse Source

removed signal + added dependency function(s)

Michele Caini 8 years ago
parent
commit
92048ac17b

+ 58 - 118
README.md

@@ -25,6 +25,8 @@
          * [Continuous loader](#continuous-loader)
          * [Archives](#archives)
          * [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)
       * [Standard View](#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.
 * A cooperative scheduler for processes of any type.
 * 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.
 * 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
 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?
 
 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
 
-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
 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
 // no collector type
@@ -2094,9 +2043,9 @@ entt::SigH<void(int, char)> signal;
 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
 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
 users both to trigger immediate events or to queue events to be published all
 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
-// 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
 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/>
@@ -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:
 
 ```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();
 ```
 

+ 4 - 1
TODO

@@ -4,6 +4,9 @@
 * 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 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
+* "singleton mode" for tags (see #66)
 * 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>
     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)
             : registry{registry}

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

@@ -9,11 +9,20 @@ namespace entt {
  * @brief Tag class type.
  *
  * An empty class type used to disambiguate the overloads of some member
- * functions of the registry.
+ * functions.
  */
 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 "entity/actor.hpp"
 #include "entity/entt_traits.hpp"
+#include "entity/helper.hpp"
 #include "entity/registry.hpp"
 #include "entity/snapshot.hpp"
 #include "entity/sparse_set.hpp"
@@ -18,4 +19,3 @@
 #include "signal/dispatcher.hpp"
 #include "signal/emitter.hpp"
 #include "signal/sigh.hpp"
-#include "signal/signal.hpp"

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

@@ -6,8 +6,9 @@
 #include <memory>
 #include <utility>
 #include <cstdint>
+#include <algorithm>
+#include <type_traits>
 #include "../core/family.hpp"
-#include "signal.hpp"
 #include "sigh.hpp"
 
 
@@ -20,34 +21,40 @@ namespace entt {
  * A dispatcher can be used either to trigger an immediate event or to enqueue
  * events to be published all together once per tick.<br/>
  * 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 {
     using event_family = Family<struct InternalDispatcherEventFamily>;
 
     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 {
         virtual ~BaseSignalWrapper() = default;
-        virtual void publish(std::size_t) = 0;
+        virtual void publish() = 0;
     };
 
     template<typename Event>
     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]) {
                 signal.publish(event);
             }
 
             events[current].clear();
+
+            ++current;
+            current %= std::extent<decltype(events)>::value;
         }
 
         inline sink_type sink() noexcept {
@@ -60,19 +67,16 @@ class Dispatcher final {
         }
 
         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)... });
         }
 
     private:
-        Sig<void(const Event &)> signal{};
+        SigH<void(const Event &)> signal{};
         std::vector<Event> events[2];
+        int current{};
     };
 
-    inline static std::size_t buffer(bool mode) {
-        return mode ? 0 : 1;
-    }
-
     template<typename Event>
     SignalWrapper<Event> & wrapper() {
         const auto type = event_family::type<Event>();
@@ -93,11 +97,6 @@ public:
     template<typename Event>
     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.
      *
@@ -110,14 +109,13 @@ public:
      *
      * The order of invocation of the listeners isn't guaranteed.
      *
-     * @sa Signal::Sink
      * @sa SigH::Sink
      *
      * @tparam Event Type of event of which to get the sink.
      * @return A temporary sink object.
      */
     template<typename Event>
-    sink_type<Event> sink() noexcept {
+    inline sink_type<Event> sink() noexcept {
         return wrapper<Event>().sink();
     }
 
@@ -132,7 +130,7 @@ public:
      * @param args Arguments to use to construct the event.
      */
     template<typename Event, typename... Args>
-    void trigger(Args &&... args) {
+    inline void trigger(Args &&... args) {
         wrapper<Event>().trigger(std::forward<Args>(args)...);
     }
 
@@ -147,56 +145,42 @@ public:
      * @param args Arguments to use to construct the event.
      */
     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
-     * 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.
+     *
+     * @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:
     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.
  *
@@ -87,6 +99,122 @@ template<typename Function, typename Collector = DefaultCollectorType<Function>>
 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.
  *
@@ -119,6 +247,8 @@ public:
     using size_type = typename std::vector<call_type>::size_type;
     /*! @brief Collector type. */
     using collector_type = Collector;
+    /*! @brief Sink type. */
+    using sink_type = Sink<Ret(Args...)>;
 
     /**
      * @brief Instance type when it comes to connecting member functions.
@@ -127,113 +257,6 @@ public:
     template<typename 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.
      * @return Number of listeners currently connected.
@@ -259,7 +282,7 @@ public:
      *
      * @return A temporary sink object.
      */
-    Sink sink() {
+    sink_type sink() {
         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
     $<TARGET_OBJECTS:odr>
     entt/entity/actor.cpp
+    entt/entity/helper.cpp
     entt/entity/registry.cpp
     entt/entity/snapshot.cpp
     entt/entity/sparse_set.cpp
@@ -121,7 +122,6 @@ add_executable(
     entt/signal/dispatcher.cpp
     entt/signal/emitter.cpp
     entt/signal/sigh.cpp
-    entt/signal/signal.cpp
 )
 target_link_libraries(signal PRIVATE gtest_main Threads::Threads)
 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 <entt/signal/dispatcher.hpp>
 
-struct Event {};
+struct AnEvent {};
+struct AnotherEvent {};
 
 struct Receiver {
-    void receive(const Event &) { ++cnt; }
+    void receive(const AnEvent &) { ++cnt; }
     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.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);
-}