ソースを参照

signals on component creation/destruction (#62)

Michele Caini 8 年 前
コミット
e99d7e2c3c

+ 92 - 112
README.md

@@ -15,6 +15,7 @@
       * [Pay per use](#pay-per-use)
       * [Pay per use](#pay-per-use)
    * [Vademecum](#vademecum)
    * [Vademecum](#vademecum)
    * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
    * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
+      * [Observe changes](#observe-changes)
       * [Single instance components](#single-instance-components)
       * [Single instance components](#single-instance-components)
       * [Runtime components](#runtime-components)
       * [Runtime components](#runtime-components)
          * [A journey through a plugin](#a-journey-through-a-plugin)
          * [A journey through a plugin](#a-journey-through-a-plugin)
@@ -44,7 +45,6 @@
    * [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache)
    * [The resource, the loader and the cache](#the-resource-the-loader-and-the-cache)
 * [Crash Course: events, signals and everything in between](#crash-course-events-signals-and-everything-in-between)
 * [Crash Course: events, signals and everything in between](#crash-course-events-signals-and-everything-in-between)
    * [Signals](#signals)
    * [Signals](#signals)
-   * [Compile-time event bus](#compile-time-event-bus)
    * [Delegate](#delegate)
    * [Delegate](#delegate)
    * [Event dispatcher](#event-dispatcher)
    * [Event dispatcher](#event-dispatcher)
    * [Event emitter](#event-emitter)
    * [Event emitter](#event-emitter)
@@ -103,7 +103,7 @@ 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 an event bus.
+* Signal handlers of any type, delegates 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.
 * ...
 * ...
@@ -199,7 +199,7 @@ Dell XPS 13 out of the mid 2014):
 | Benchmark | EntityX (compile-time) | EnTT |
 | Benchmark | EntityX (compile-time) | EnTT |
 |-----------|-------------|-------------|
 |-----------|-------------|-------------|
 | Create 1M entities | 0.0167s | **0.0046s** |
 | Create 1M entities | 0.0167s | **0.0046s** |
-| Destroy 1M entities | 0.0053s | **0.0039s** |
+| Destroy 1M entities | 0.0053s | **0.0037s** |
 | Standard view, 1M entities, one component | 0.0012s | **1.9e-07s** |
 | Standard view, 1M entities, one component | 0.0012s | **1.9e-07s** |
 | Standard view, 1M entities, two components | 0.0012s | **3.8e-07s** |
 | Standard view, 1M entities, two components | 0.0012s | **3.8e-07s** |
 | Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
 | Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
@@ -528,6 +528,47 @@ std::tuple<Position &, Velocity &> tup = registry.get<Position, Velocity>(entity
 The `get` member function template gives direct access to the component of an
 The `get` member function template gives direct access to the component of an
 entity stored in the underlying data structures of the registry.
 entity stored in the underlying data structures of the registry.
 
 
+### Observe changes
+
+Because of how the registry works internally, it stores a couple of signal
+handlers for each pool in order to notify some of its data structures on the
+construction and destruction of components.<br/>
+These signal handlers are also exposed and made available to users. This is the
+basic brick to build fancy things like blueprints and reactive systems.
+
+To get a sink to be used to connect and disconnect listeners so as to be
+notified on the creation of a component, use the `construction` member function:
+
+```cpp
+// connects a free function
+registry.construction<Position>().connect<&MyFreeFunction>();
+
+// connects a member function
+registry.construction<Position>().connect<MyClass, &MyClass::member>(&instance);
+
+// disconnects a free function
+registry.construction<Position>().disconnect<&MyFreeFunction>();
+
+// disconnects a member function
+registry.construction<Position>().disconnect<MyClass, &MyClass::member>(&instance);
+```
+
+To be notified when components are destroyed, use the `destruction` member
+function instead.
+
+The function type of a listener is the same in both cases:
+
+```cpp
+void(Registry<Entity> &, Entity);
+```
+
+In other terms, a listener is provided with the registry that triggered the
+notification and the entity affected by the change. Note also that:
+
+* Listeners are invoked **after** components have been assigned to entities.
+* Listeners are invoked **before** components have been removed from entities.
+* The order of invocation of the listeners isn't guaranteed in any case.
+
 ### Single instance components
 ### Single instance components
 
 
 In those cases where all what is needed is a single instance component, tags are
 In those cases where all what is needed is a single instance component, tags are
@@ -937,6 +978,7 @@ All of them have pros and cons to take in consideration. In particular:
 * Standard views:
 * Standard views:
 
 
   Pros:
   Pros:
+
   * They work out-of-the-box and don't require any dedicated data structure.
   * They work out-of-the-box and don't require any dedicated data structure.
   * Creating and destroying them isn't expensive at all because they don't have
   * Creating and destroying them isn't expensive at all because they don't have
     any type of initialization.
     any type of initialization.
@@ -946,18 +988,21 @@ All of them have pros and cons to take in consideration. In particular:
   * They don't affect any other operations of the registry.
   * They don't affect any other operations of the registry.
 
 
   Cons:
   Cons:
+
   * Their performance tend to degenerate when the number of components to
   * Their performance tend to degenerate when the number of components to
     iterate grows up and the most of the entities have all of them.
     iterate grows up and the most of the entities have all of them.
 
 
 * Persistent views:
 * Persistent views:
 
 
   Pros:
   Pros:
+
   * Once prepared, creating and destroying them isn't expensive at all because
   * Once prepared, creating and destroying them isn't expensive at all because
     they don't have any type of initialization.
     they don't have any type of initialization.
   * They are the best tool for iterating entities for mmultiple components and
   * They are the best tool for iterating entities for mmultiple components and
     most entities have them all.
     most entities have them all.
 
 
   Cons:
   Cons:
+
   * They have dedicated data structures and thus affect the memory usage to a
   * They have dedicated data structures and thus affect the memory usage to a
     minimal extent.
     minimal extent.
   * If not previously prepared, the first time they are used they go through an
   * If not previously prepared, the first time they are used they go through an
@@ -969,6 +1014,7 @@ All of them have pros and cons to take in consideration. In particular:
 * Raw views:
 * Raw views:
 
 
   Pros:
   Pros:
+
   * They work out-of-the-box and don't require any dedicated data structure.
   * They work out-of-the-box and don't require any dedicated data structure.
   * Creating and destroying them isn't expensive at all because they don't have
   * Creating and destroying them isn't expensive at all because they don't have
     any type of initialization.
     any type of initialization.
@@ -977,10 +1023,12 @@ All of them have pros and cons to take in consideration. In particular:
   * They don't affect any other operations of the registry.
   * They don't affect any other operations of the registry.
 
 
   Cons:
   Cons:
+
   * They can be used to iterate only one type of component at a time.
   * They can be used to iterate only one type of component at a time.
   * They don't return the entity to which a component belongs to the caller.
   * They don't return the entity to which a component belongs to the caller.
 
 
 To sum up and as a rule of thumb:
 To sum up and as a rule of thumb:
+
 * Use a raw view to iterate components only (no entities) for a given type.
 * Use a raw view to iterate components only (no entities) for a given type.
 * Use a standard view to iterate entities for a single component.
 * Use a standard view to iterate entities for a single component.
 * Use a standard view to iterate entities for multiple components when a
 * Use a standard view to iterate entities for multiple components when a
@@ -1939,9 +1987,15 @@ There are two types of signal handlers in `EnTT`, internally called _managed_
 and _unmanaged_.<br/>
 and _unmanaged_.<br/>
 They differ in the way they work around the tradeoff between performance, memory
 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
 usage and safety. Managed listeners must be wrapped in an `std::shared_ptr` and
-the sink will take care of disconnecting them whenever they die. Unmanaged
+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
 listeners can be any kind of objects and the client is in charge of connecting
-and disconnecting them from a sink to avoid crashes due to different lifetimes.
+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
 ### Managed signal handler
 
 
@@ -1962,7 +2016,8 @@ entt::Signal<void(int, char)> signal;
 ```
 ```
 
 
 From now on, free functions and member functions that respect the given
 From now on, free functions and member functions that respect the given
-signature can be easily connected to and disconnected from the signal:
+signature can be easily connected to and disconnected from the signal by means
+of a sink:
 
 
 ```cpp
 ```cpp
 void foo(int, char) { /* ... */ }
 void foo(int, char) { /* ... */ }
@@ -1975,18 +2030,22 @@ struct S {
 
 
 auto instance = std::make_shared<S>();
 auto instance = std::make_shared<S>();
 
 
-signal.connect<&foo>();
-signal.connect<S, &S::bar>(instance);
+signal.sink().connect<&foo>();
+signal.sink().connect<S, &S::bar>(instance);
 
 
 // ...
 // ...
 
 
-signal.disconnect<&foo>();
+// disconnects a free function
+signal.sink().disconnect<&foo>();
 
 
-// disconnect a specific member function of an instance ...
-signal.disconnect<S, &S::bar>(instance);
+// disconnects a specific member function of an instance ...
+signal.sink().disconnect<S, &S::bar>(instance);
 
 
 // ... or an instance as a whole
 // ... or an instance as a whole
-signal.disconnect(instance);
+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
 Once listeners are attached (or even if there are no listeners at all), events
@@ -2000,8 +2059,8 @@ signal.publish(42, 'c');
 This is more or less all what a managed signal handler has to offer.<br/>
 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 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
 a method to use to know how many listeners a managed signal handler contains
-(`size`) or if it contains at least a listener (`empty`), to reset it to its
-initial state (`clear`) and even to swap two handlers (`swap`).<br/>
+(`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
 Refer to the [official documentation](https://skypjack.github.io/entt/) for all
 the details.
 the details.
 
 
@@ -2036,11 +2095,10 @@ entt::SigH<void(int, char), MyCollector<bool>> collector;
 
 
 As expected, an unmanaged signal handler offers all the basic functionalities
 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
 required to know how many listeners it contains (`size`) or if it contains at
-least a listener (`empty`), to reset it to its initial state (`clear`) and even
-to swap two handlers (`swap`).
+least a listener (`empty`) and even to swap two 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:
+listeners in all their forms by means of a sink::
 
 
 ```cpp
 ```cpp
 void foo(int, char) { /* ... */ }
 void foo(int, char) { /* ... */ }
@@ -2053,18 +2111,22 @@ struct S {
 
 
 S instance;
 S instance;
 
 
-signal.connect<&foo>();
-signal.connect<S, &S::bar>(&instance);
+signal.sink().connect<&foo>();
+signal.sink().connect<S, &S::bar>(&instance);
 
 
 // ...
 // ...
 
 
-signal.disconnect<&foo>();
+// disconnects a free function
+signal.sink().disconnect<&foo>();
 
 
 // disconnect a specific member function of an instance ...
 // disconnect a specific member function of an instance ...
-signal.disconnect<S, &S::bar>(&instance);
+signal.sink().disconnect<S, &S::bar>(&instance);
 
 
 // ... or an instance as a whole
 // ... or an instance as a whole
-signal.disconnect(&instance);
+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
 Once listeners are attached (or even if there are no listeners at all), events
@@ -2095,8 +2157,8 @@ int g() { return 1; }
 
 
 entt::SigH<int(), MyCollector<int>> signal;
 entt::SigH<int(), MyCollector<int>> signal;
 
 
-signal.connect<&f>();
-signal.connect<&g>();
+signal.sink().connect<&f>();
+signal.sink().connect<&g>();
 
 
 MyCollector collector = signal.collect();
 MyCollector collector = signal.collect();
 
 
@@ -2110,89 +2172,6 @@ Moreover, it has to return a boolean value that is false to stop collecting
 data, true otherwise. This way one can avoid calling all the listeners in case
 data, true otherwise. This way one can avoid calling all the listeners in case
 it isn't necessary.
 it isn't necessary.
 
 
-## Compile-time event bus
-
-A bus can be used to create a compile-time backbone for event management.<br/>
-The intended use is as a base class, which is the opposite of what the signals
-are meant for. Internally it uses either managed or unmanaged signal handlers,
-that is why there exist both a managed and an unmanaged event bus.
-
-The API of a bus is a kind of subset of the one of a signal. First of all, it
-requires that all the types of events are specified when the bus is declared:
-
-```cpp
-struct AnEvent { int value; };
-struct AnotherEvent {};
-
-// define a managed bus that works with std::shared_ptr/std::weak_ptr
-entt::ManagedBus<AnEvent, AnotherEvent> managed;
-
-// define an unmanaged bus that works with naked pointers
-entt::UnmanagedBus<AnEvent, AnotherEvent> unmanaged;
-```
-
-For the sake of brevity, below is described the interface of the sole unmanaged
-bus. The interface of the managed bus 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 bus, its type must expose one
-or more member functions named `receive` of which the return types are `void`
-and the argument lists are `const E &`, for each type of event `E`.<br/>
-The `reg` member function is the way to go to register such an instance:
-
-```cpp
-struct Listener
-{
-    void receive(const AnEvent &) { /* ... */ }
-    void receive(const AnotherEvent &) { /* ... */ }
-};
-
-// ...
-
-Listener listener;
-bus.reg(&listener);
-```
-
-To disconnect an instance of a class from a bus, use the `unreg` member
-function instead:
-
-```cpp
-bus.unreg(&listener);
-```
-
-Each function that respects the accepted signature is automatically registered
-and/or unregistered. Note that invoking `unreg` with an instance of a class that
-hasn't been previously registered is a perfectly valid operation.
-
-Free functions can be registered and unregistered as well by means of the
-dedicated member functions, namely `connect` and `disconnect`:
-
-```cpp
-void foo(const AnEvent &) { /* ... */ }
-void bar(const AnotherEvent &) { /* ... */ }
-
-// ...
-
-bus.connect<AnEvent, &foo>();
-bus.connect<AnotherEvent, &bar>();
-
-// ...
-
-bus.disconnect<AnEvent, &foo>();
-bus.disconnect<AnotherEvent, &bar>();
-```
-
-Whenever the need to send an event arises, it can be done through the `publish`
-member function:
-
-```cpp
-bus.publish<AnEvent>(42);
-bus.publish<AnotherEvent>();
-```
-
-Finally, there are another few functions to use to query the internal state of a
-bus like `empty` and `size` whose meaning is quite intuitive.
-
 ## Delegate
 ## Delegate
 
 
 A delegate can be used as general purpose invoker with no memory overhead for
 A delegate can be used as general purpose invoker with no memory overhead for
@@ -2278,7 +2257,8 @@ argument lists are `const E &`, for each type of event `E`.<br/>
 To ease the development, member functions that are named `receive` are
 To ease the development, member functions that are named `receive` are
 automatically detected and have not to be explicitly specified when registered.
 automatically detected and have not to be explicitly specified when registered.
 In all the other cases, the name of the member function aimed to receive the
 In all the other cases, the name of the member function aimed to receive the
-event must be provided to the `connect` member function:
+event must be provided to the `connect` member function of the sink bound to the
+specific event:
 
 
 ```cpp
 ```cpp
 struct AnEvent { int value; };
 struct AnEvent { int value; };
@@ -2293,16 +2273,16 @@ struct Listener
 // ...
 // ...
 
 
 Listener listener;
 Listener listener;
-dispatcher.connect<AnEvent>(&listener);
-dispatcher.connect<AnotherEvent, Listener, &Listener::method>(&listener);
+dispatcher.sink<AnEvent>().connect(&listener);
+dispatcher.sink<AnotherEvent>().connect<Listener, &Listener::method>(&listener);
 ```
 ```
 
 
 The `disconnect` member function follows the same pattern and can be used to
 The `disconnect` member function follows the same pattern and can be used to
 selectively remove listeners:
 selectively remove listeners:
 
 
 ```cpp
 ```cpp
-dispatcher.disconnect<AnEvent>(&listener);
-dispatcher.disconnect<AnotherEvent, Listener, &Listener::method>(&listener);
+dispatcher.sink<AnEvent>().disconnect(&listener);
+dispatcher.sink<AnotherEvent>().disconnect<Listener, &Listener::method>(&listener);
 ```
 ```
 
 
 The `trigger` member function serves the purpose of sending an immediate event
 The `trigger` member function serves the purpose of sending an immediate event

+ 4 - 3
TODO

@@ -1,9 +1,10 @@
 * custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
 * custom allocators and EnTT allocator-aware in general (long term feature, I don't actually need it at the moment) - see #22
-* to analyze, long term feature: systems organizer based on dependency graphs for implicit parallelism (I don't want to think anymore in future :-))
 * scene management (I prefer the concept of spaces, that is a kind of scene anyway)
 * scene management (I prefer the concept of spaces, that is a kind of scene anyway)
-* blueprint registry - kind of factory to create entitites template for initialization (get rid of the extra versions of Registry::create)
 * review doc: separate it in multiple md/dox files, reduce the readme to a minimum and provide users with links to the online documentation on gh-pages
 * review doc: separate it in multiple md/dox files, reduce the readme to a minimum and provide users with links to the online documentation on gh-pages
 * 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
-* signals on component creation/destruction: crtp + internal detection, probably it works - test 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)
+* blueprint registry - kind of factory to create entitites template for initialization (use signals, it's trivial this way)
+* use tag dispatching to disambiguate between standard components and single instance components, then provide users with an common API
+* remove Actor::update (it's application dependent), allow tag instead
 * AOB
 * AOB

+ 2 - 2
src/entt/core/ident.hpp

@@ -10,7 +10,7 @@
 namespace entt {
 namespace entt {
 
 
 
 
-namespace {
+namespace details {
 
 
 
 
 template<typename... Types>
 template<typename... Types>
@@ -87,7 +87,7 @@ private:
  * @tparam Types List of types for which to generate identifiers.
  * @tparam Types List of types for which to generate identifiers.
  */
  */
 template<typename... Types>
 template<typename... Types>
-constexpr auto ident = Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
+constexpr auto ident = details::Identifier<std::decay_t<Types>...>{std::make_index_sequence<sizeof...(Types)>{}};
 
 
 
 
 }
 }

+ 138 - 70
src/entt/entity/registry.hpp

@@ -12,6 +12,7 @@
 #include <algorithm>
 #include <algorithm>
 #include <type_traits>
 #include <type_traits>
 #include "../core/family.hpp"
 #include "../core/family.hpp"
+#include "../signal/sigh.hpp"
 #include "entt_traits.hpp"
 #include "entt_traits.hpp"
 #include "snapshot.hpp"
 #include "snapshot.hpp"
 #include "sparse_set.hpp"
 #include "sparse_set.hpp"
@@ -35,9 +36,27 @@ template<typename Entity>
 class Registry {
 class Registry {
     using tag_family = Family<struct InternalRegistryTagFamily>;
     using tag_family = Family<struct InternalRegistryTagFamily>;
     using component_family = Family<struct InternalRegistryComponentFamily>;
     using component_family = Family<struct InternalRegistryComponentFamily>;
-    using view_family = Family<struct InternalRegistryViewFamily>;
+    using handler_family = Family<struct InternalRegistryHandlerFamily>;
     using traits_type = entt_traits<Entity>;
     using traits_type = entt_traits<Entity>;
 
 
+    template<typename... Component>
+    static void creating(Registry &registry, Entity entity) {
+        if(registry.has<Component...>(entity)) {
+            const auto htype = handler_family::type<Component...>();
+            registry.handlers[htype]->construct(entity);
+        }
+    }
+
+    template<typename... Component>
+    static void destroying(Registry &registry, Entity entity) {
+        const auto htype = handler_family::type<Component...>();
+        auto &handler = registry.handlers[htype];
+
+        if(handler->has(entity)) {
+            handler->destroy(entity);
+        }
+    }
+
     struct Attachee {
     struct Attachee {
         Entity entity;
         Entity entity;
     };
     };
@@ -55,45 +74,38 @@ class Registry {
 
 
     template<typename Component>
     template<typename Component>
     struct Pool: SparseSet<Entity, Component> {
     struct Pool: SparseSet<Entity, Component> {
-        using test_fn_type = bool(Registry::*)(Entity) const;
+        using sink_type = typename SigH<void(Registry &, Entity)>::Sink;
+
+        Pool(Registry &registry)
+            : registry{registry}
+        {}
 
 
         template<typename... Args>
         template<typename... Args>
-        Component & construct(Registry &registry, Entity entity, Args &&... args) {
+        Component & construct(Entity entity, Args &&... args) {
             auto &component = SparseSet<Entity, Component>::construct(entity, std::forward<Args>(args)...);
             auto &component = SparseSet<Entity, Component>::construct(entity, std::forward<Args>(args)...);
-
-            for(auto &&listener: listeners) {
-                if((registry.*listener.second)(entity)) {
-                    listener.first->construct(entity);
-                }
-            }
-
+            listeners.first.publish(registry, entity);
             return component;
             return component;
         }
         }
 
 
         void destroy(Entity entity) override {
         void destroy(Entity entity) override {
+            listeners.second.publish(registry, entity);
             SparseSet<Entity, Component>::destroy(entity);
             SparseSet<Entity, Component>::destroy(entity);
-
-            for(auto &&listener: listeners) {
-                auto *handler = listener.first;
-
-                if(handler->has(entity)) {
-                    handler->destroy(entity);
-                }
-            }
         }
         }
 
 
-        inline void append(SparseSet<Entity> *handler, test_fn_type fn) {
-            listeners.emplace_back(handler, fn);
+        sink_type construction() noexcept {
+            return listeners.first.sink();
         }
         }
 
 
-        inline void remove(SparseSet<Entity> *handler) {
-            listeners.erase(std::remove_if(listeners.begin(), listeners.end(), [handler](auto &listener) {
-                return listener.first == handler;
-            }), listeners.end());
+        sink_type destruction() noexcept {
+            return listeners.second.sink();
         }
         }
 
 
     private:
     private:
-        std::vector<std::pair<SparseSet<Entity> *, test_fn_type>> listeners;
+        Registry &registry;
+        std::pair<
+            SigH<void(Registry &, Entity)>,
+            SigH<void(Registry &, Entity)>
+        > listeners;
     };
     };
 
 
     template<typename Component>
     template<typename Component>
@@ -122,40 +134,12 @@ class Registry {
         }
         }
 
 
         if(!pools[ctype]) {
         if(!pools[ctype]) {
-            pools[ctype] = std::make_unique<Pool<Component>>();
+            pools[ctype] = std::make_unique<Pool<Component>>(*this);
         }
         }
 
 
         return pool<Component>();
         return pool<Component>();
     }
     }
 
 
-    template<typename... Component>
-    SparseSet<Entity> & handler() {
-        static_assert(sizeof...(Component) > 1, "!");
-        const auto vtype = view_family::type<Component...>();
-
-        if(!(vtype < handlers.size())) {
-            handlers.resize(vtype + 1);
-        }
-
-        if(!handlers[vtype]) {
-            using accumulator_type = int[];
-            auto set = std::make_unique<SparseSet<Entity>>();
-
-            for(auto entity: view<Component...>()) {
-                set->construct(entity);
-            }
-
-            accumulator_type accumulator = {
-                (assure<Component>().append(set.get(), &Registry::has<Component...>), 0)...
-            };
-
-            handlers[vtype] = std::move(set);
-            (void)accumulator;
-        }
-
-        return *handlers[vtype];
-    }
-
 public:
 public:
     /*! @brief Underlying entity identifier. */
     /*! @brief Underlying entity identifier. */
     using entity_type = typename traits_type::entity_type;
     using entity_type = typename traits_type::entity_type;
@@ -168,6 +152,10 @@ public:
     /*! @brief Unsigned integer type. */
     /*! @brief Unsigned integer type. */
     using component_type = typename component_family::family_type;
     using component_type = typename component_family::family_type;
 
 
+    /*! @brief Type of sink for the given component. */
+    template<typename Component>
+    using sink_type = typename Pool<Component>::sink_type;
+
     /*! @brief Default constructor. */
     /*! @brief Default constructor. */
     Registry() = default;
     Registry() = default;
 
 
@@ -316,7 +304,6 @@ public:
     bool fast(entity_type entity) const noexcept {
     bool fast(entity_type entity) const noexcept {
         const auto pos = size_type(entity & traits_type::entity_mask);
         const auto pos = size_type(entity & traits_type::entity_mask);
         assert(pos < entities.size());
         assert(pos < entities.size());
-        // the in-use control bit permits to avoid accessing the direct vector
         return (entities[pos] == entity);
         return (entities[pos] == entity);
     }
     }
 
 
@@ -375,7 +362,7 @@ public:
     entity_type create(Component &&... components) noexcept {
     entity_type create(Component &&... components) noexcept {
         using accumulator_type = int[];
         using accumulator_type = int[];
         const auto entity = create();
         const auto entity = create();
-        accumulator_type accumulator = { 0, (assure<std::decay_t<Component>>().construct(*this, entity, std::forward<Component>(components)), 0)... };
+        accumulator_type accumulator = { 0, (assure<std::decay_t<Component>>().construct(entity, std::forward<Component>(components)), 0)... };
         (void)accumulator;
         (void)accumulator;
         return entity;
         return entity;
     }
     }
@@ -402,7 +389,7 @@ public:
     entity_type create() noexcept {
     entity_type create() noexcept {
         using accumulator_type = int[];
         using accumulator_type = int[];
         const auto entity = create();
         const auto entity = create();
-        accumulator_type accumulator = { 0, (assure<Component>().construct(*this, entity), 0)... };
+        accumulator_type accumulator = { 0, (assure<Component>().construct(entity), 0)... };
         (void)accumulator;
         (void)accumulator;
         return entity;
         return entity;
     }
     }
@@ -469,11 +456,11 @@ public:
         next = entt;
         next = entt;
         ++available;
         ++available;
 
 
-        for(auto &&cpool: pools) {
+        std::for_each(pools.begin(), pools.end(), [entity](auto &cpool) {
             if(cpool && cpool->has(entity)) {
             if(cpool && cpool->has(entity)) {
                 cpool->destroy(entity);
                 cpool->destroy(entity);
             }
             }
-        }
+        });
     }
     }
 
 
     /**
     /**
@@ -663,7 +650,7 @@ public:
     template<typename Component, typename... Args>
     template<typename Component, typename... Args>
     Component & assign(entity_type entity, Args &&... args) {
     Component & assign(entity_type entity, Args &&... args) {
         assert(valid(entity));
         assert(valid(entity));
-        return assure<Component>().construct(*this, entity, std::forward<Args>(args)...);
+        return assure<Component>().construct(entity, std::forward<Args>(args)...);
     }
     }
 
 
     /**
     /**
@@ -845,7 +832,59 @@ public:
 
 
         return (cpool.has(entity)
         return (cpool.has(entity)
                 ? (cpool.get(entity) = Component{std::forward<Args>(args)...})
                 ? (cpool.get(entity) = Component{std::forward<Args>(args)...})
-                : cpool.construct(*this, entity, std::forward<Args>(args)...));
+                : cpool.construct(entity, std::forward<Args>(args)...));
+    }
+
+    /**
+     * @brief Returns a sink object for the given component.
+     *
+     * A sink is an opaque object used to connect listeners to components.<br/>
+     * The sink returned by this function can be used to receive notifications
+     * whenever a new instance of the given component is created and assigned to
+     * an entity.
+     *
+     * The function type for a listener is:
+     * @code{.cpp}
+     * void(Registry<Entity> &, Entity);
+     * @endcode
+     *
+     * Listeners are invoked **after** the component has been assigned to the
+     * entity. The order of invocation of the listeners isn't guaranteed.
+     *
+     * @sa SigH::Sink
+     *
+     * @tparam Component Type of component of which to get the sink.
+     * @return A temporary sink object.
+     */
+    template<typename Component>
+    sink_type<Component> construction() noexcept {
+        return assure<Component>().construction();
+    }
+
+    /**
+     * @brief Returns a sink object for the given component.
+     *
+     * A sink is an opaque object used to connect listeners to components.<br/>
+     * The sink returned by this function can be used to receive notifications
+     * whenever an instance of the given component is removed from an entity and
+     * thus destroyed.
+     *
+     * The function type for a listener is:
+     * @code{.cpp}
+     * void(Registry<Entity> &, Entity);
+     * @endcode
+     *
+     * Listeners are invoked **before** the component has been removed from the
+     * entity. The order of invocation of the listeners isn't guaranteed.
+     *
+     * @sa SigH::Sink
+     *
+     * @tparam Component Type of component of which to get the sink.
+     * @return A temporary sink object.
+     */
+    template<typename Component>
+    sink_type<Component> destruction() noexcept {
+        return assure<Component>().destruction();
     }
     }
 
 
     /**
     /**
@@ -1119,7 +1158,30 @@ public:
      */
      */
     template<typename... Component>
     template<typename... Component>
     void prepare() {
     void prepare() {
-        handler<Component...>();
+        static_assert(sizeof...(Component) > 1, "!");
+        const auto htype = handler_family::type<Component...>();
+
+        if(!(htype < handlers.size())) {
+            handlers.resize(htype + 1);
+        }
+
+        if(!handlers[htype]) {
+            using accumulator_type = int[];
+            handlers[htype] = std::make_unique<SparseSet<entity_type>>();
+            auto &handler = handlers[htype];
+
+            for(auto entity: view<Component...>()) {
+                handler->construct(entity);
+            }
+
+            auto connect = [](auto &pool) {
+                pool.construction().template connect<&Registry::creating<Component...>>();
+                pool.destruction().template connect<&Registry::destroying<Component...>>();
+            };
+
+            accumulator_type accumulator = { (connect(assure<Component>()), 0)... };
+            (void)accumulator;
+        }
     }
     }
 
 
     /**
     /**
@@ -1141,11 +1203,16 @@ public:
     void discard() {
     void discard() {
         if(contains<Component...>()) {
         if(contains<Component...>()) {
             using accumulator_type = int[];
             using accumulator_type = int[];
-            const auto vtype = view_family::type<Component...>();
-            auto *set = handlers[vtype].get();
+            const auto htype = handler_family::type<Component...>();
+
+            auto disconnect = [](auto &pool) {
+                pool.construction().template disconnect<&Registry::creating<Component...>>();
+                pool.destruction().template disconnect<&Registry::destroying<Component...>>();
+            };
+
             // if a set exists, pools have already been created for it
             // if a set exists, pools have already been created for it
-            accumulator_type accumulator = { (pool<Component>().remove(set), 0)... };
-            handlers[vtype].reset();
+            accumulator_type accumulator = { (disconnect(pool<Component>()), 0)... };
+            handlers[htype].reset();
             (void)accumulator;
             (void)accumulator;
         }
         }
     }
     }
@@ -1158,8 +1225,8 @@ public:
     template<typename... Component>
     template<typename... Component>
     bool contains() const noexcept {
     bool contains() const noexcept {
         static_assert(sizeof...(Component) > 1, "!");
         static_assert(sizeof...(Component) > 1, "!");
-        const auto vtype = view_family::type<Component...>();
-        return vtype < handlers.size() && handlers[vtype];
+        const auto htype = handler_family::type<Component...>();
+        return (htype < handlers.size() && handlers[htype]);
     }
     }
 
 
     /**
     /**
@@ -1202,8 +1269,9 @@ public:
      */
      */
     template<typename... Component>
     template<typename... Component>
     PersistentView<Entity, Component...> persistent() {
     PersistentView<Entity, Component...> persistent() {
-        // after the calls to handler, pools have already been created
-        return PersistentView<Entity, Component...>{handler<Component...>(), pool<Component>()...};
+        prepare<Component...>();
+        const auto htype = handler_family::type<Component...>();
+        return PersistentView<Entity, Component...>{*handlers[htype], pool<Component>()...};
     }
     }
 
 
     /**
     /**

+ 0 - 1
src/entt/entt.hpp

@@ -13,7 +13,6 @@
 #include "resource/cache.hpp"
 #include "resource/cache.hpp"
 #include "resource/handle.hpp"
 #include "resource/handle.hpp"
 #include "resource/loader.hpp"
 #include "resource/loader.hpp"
-#include "signal/bus.hpp"
 #include "signal/delegate.hpp"
 #include "signal/delegate.hpp"
 #include "signal/dispatcher.hpp"
 #include "signal/dispatcher.hpp"
 #include "signal/emitter.hpp"
 #include "signal/emitter.hpp"

+ 0 - 305
src/entt/signal/bus.hpp

@@ -1,305 +0,0 @@
-#ifndef ENTT_SIGNAL_BUS_HPP
-#define ENTT_SIGNAL_BUS_HPP
-
-
-#include <cstddef>
-#include <utility>
-#include "signal.hpp"
-#include "sigh.hpp"
-
-
-namespace entt {
-
-
-/**
- * @brief Minimal event bus.
- *
- * Primary template isn't defined on purpose. The main reason for which it
- * exists is to work around the doxygen's parsing capabilities. In fact, there
- * is no need to declare it actually.
- */
-template<template<typename...> class, typename...>
-class Bus;
-
-
-/**
- * @brief Event bus specialization for multiple types.
- *
- * The event bus is designed to allow an easy registration of specific member
- * functions to a bunch of signal handlers (either manager or unmanaged).
- * Classes must publicly expose the required member functions to allow the bus
- * to detect them for the purpose of registering and unregistering
- * instances.<br/>
- * In particular, for each event type `E`, a matching member function has the
- * following signature: `void receive(const E &)`. Events will be properly
- * redirected to all the listeners by calling the right member functions, if
- * any.
- *
- * @tparam Sig Type of signal handler to use.
- * @tparam Event The list of events managed by the bus.
- */
-template<template<typename...> class Sig, typename Event, typename... Other>
-class Bus<Sig, Event, Other...>
-        : private Bus<Sig, Event>, private Bus<Sig, Other>...
-{
-public:
-    /*! @brief Unsigned integer type. */
-    using size_type = std::size_t;
-
-    /**
-     * @brief Unregisters all the member functions of an instance.
-     *
-     * A bus is used to convey a certain set of events. This method detects
-     * and unregisters from the bus all the matching member functions of an
-     * instance.<br/>
-     * For each event type `E`, a matching member function has the following
-     * signature: `void receive(const E &)`.
-     *
-     * @tparam Instance Type of instance to unregister.
-     * @param instance A valid instance of the right type.
-     */
-    template<typename Instance>
-    void unreg(Instance instance) {
-        using accumulator_type = int[];
-        accumulator_type accumulator = {
-            (Bus<Sig, Event>::unreg(instance), 0),
-            (Bus<Sig, Other>::unreg(instance), 0)...
-        };
-        return void(accumulator);
-    }
-
-    /**
-     * @brief Registers all the member functions of an instance.
-     *
-     * A bus is used to convey a certain set of events. This method detects
-     * and registers to the bus all the matching member functions of an
-     * instance.<br/>
-     * For each event type `E`, a matching member function has the following
-     * signature: `void receive(const E &)`.
-     *
-     * @tparam Instance Type of instance to register.
-     * @param instance A valid instance of the right type.
-     */
-    template<typename Instance>
-    void reg(Instance instance) {
-        using accumulator_type = int[];
-        accumulator_type accumulator = {
-            (Bus<Sig, Event>::reg(instance), 0),
-            (Bus<Sig, Other>::reg(instance), 0)...
-        };
-        return void(accumulator);
-    }
-
-    /**
-     * @brief Number of listeners connected to the bus.
-     * @return Number of listeners currently connected.
-     */
-    size_type size() const noexcept {
-        using accumulator_type = std::size_t[];
-        std::size_t sz = Bus<Sig, Event>::size();
-        accumulator_type accumulator = { sz, (sz += Bus<Sig, Other>::size())... };
-        return void(accumulator), sz;
-    }
-
-    /**
-     * @brief Returns false if at least a listener is connected to the bus.
-     * @return True if the bus has no listeners connected, false otherwise.
-     */
-    bool empty() const noexcept {
-        using accumulator_type = bool[];
-        bool ret = Bus<Sig, Event>::empty();
-        accumulator_type accumulator = { ret, (ret = ret && Bus<Sig, Other>::empty())... };
-        return void(accumulator), ret;
-    }
-
-    /**
-     * @brief Connects a free function to the bus.
-     * @tparam Type Type of event to which to connect the function.
-     * @tparam Function A valid free function pointer.
-     */
-    template<typename Type, void(*Function)(const Type &)>
-    void connect() {
-        Bus<Sig, Type>::template connect<Function>();
-    }
-
-    /**
-     * @brief Disconnects a free function from the bus.
-     * @tparam Type Type of event from which to disconnect the function.
-     * @tparam Function A valid free function pointer.
-     */
-    template<typename Type, void(*Function)(const Type &)>
-    void disconnect() {
-        Bus<Sig, Type>::template disconnect<Function>();
-    }
-
-    /**
-     * @brief Publishes an event.
-     *
-     * All the listeners are notified. Order isn't guaranteed.
-     *
-     * @tparam Type Type of event to publish.
-     * @tparam Args Types of arguments to use to construct the event.
-     * @param args Arguments to use to construct the event.
-     */
-    template<typename Type, typename... Args>
-    void publish(Args &&... args) {
-        Bus<Sig, Type>::publish(std::forward<Args>(args)...);
-    }
-};
-
-
-/**
- * @brief Event bus specialization for a single type.
- *
- * The event bus is designed to allow an easy registration of a specific member
- * function to a signal handler (either manager or unmanaged).
- * Classes must publicly expose the required member function to allow the bus to
- * detect it for the purpose of registering and unregistering instances.<br/>
- * In particular, a matching member function has the following signature:
- * `void receive(const Event &)`. Events of the given type will be properly
- * redirected to all the listeners by calling the right member function, if any.
- *
- * @tparam Sig Type of signal handler to use.
- * @tparam Event Type of event managed by the bus.
- */
-template<template<typename...> class Sig, typename Event>
-class Bus<Sig, Event> {
-    using signal_type = Sig<void(const Event &)>;
-
-    template<typename Class>
-    using instance_type = typename signal_type::template instance_type<Class>;
-
-    template<typename Class>
-    auto disconnect(int, instance_type<Class> instance)
-    -> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
-        signal.template disconnect<Class, &Class::receive>(std::move(instance));
-    }
-
-    template<typename Class>
-    auto connect(int, instance_type<Class> instance)
-    -> decltype(std::declval<Class>().receive(std::declval<Event>()), void()) {
-        signal.template connect<Class, &Class::receive>(std::move(instance));
-    }
-
-    template<typename Class> void disconnect(char, instance_type<Class>) {}
-    template<typename Class> void connect(char, instance_type<Class>) {}
-
-public:
-    /*! @brief Unsigned integer type. */
-    using size_type = typename signal_type::size_type;
-
-    /**
-     * @brief Unregisters member functions of instances.
-     *
-     * This method tries to detect and unregister from the bus matching member
-     * functions of instances.<br/>
-     * A matching member function has the following signature:
-     * `void receive(const Event &)`.
-     *
-     * @tparam Class Type of instance to unregister.
-     * @param instance A valid instance of the right type.
-     */
-    template<typename Class>
-    void unreg(instance_type<Class> instance) {
-        disconnect(0, std::move(instance));
-    }
-
-    /**
-     * @brief Tries to register an instance.
-     *
-     * This method tries to detect and register to the bus matching member
-     * functions of instances.<br/>
-     * A matching member function has the following signature:
-     * `void receive(const Event &)`.
-     *
-     * @tparam Class Type of instance to register.
-     * @param instance A valid instance of the right type.
-     */
-    template<typename Class>
-    void reg(instance_type<Class> instance) {
-        connect(0, std::move(instance));
-    }
-
-    /**
-     * @brief Number of listeners connected to the bus.
-     * @return Number of listeners currently connected.
-     */
-    size_type size() const noexcept {
-        return signal.size();
-    }
-
-    /**
-     * @brief Returns false if at least a listener is connected to the bus.
-     * @return True if the bus has no listeners connected, false otherwise.
-     */
-    bool empty() const noexcept {
-        return signal.empty();
-    }
-
-    /**
-     * @brief Connects a free function to the bus.
-     * @tparam Function A valid free function pointer.
-     */
-    template<void(*Function)(const Event &)>
-    void connect() {
-        signal.template connect<Function>();
-    }
-
-    /**
-     * @brief Disconnects a free function from the bus.
-     * @tparam Function A valid free function pointer.
-     */
-    template<void(*Function)(const Event &)>
-    void disconnect() {
-        signal.template disconnect<Function>();
-    }
-
-    /**
-     * @brief Publishes an event.
-     *
-     * All the listeners are notified. Order isn't guaranteed.
-     *
-     * @tparam Args Types of arguments to use to construct the event.
-     * @param args Arguments to use to construct the event.
-     */
-    template<typename... Args>
-    void publish(Args &&... args) {
-        signal.publish({ std::forward<Args>(args)... });
-    }
-
-private:
-    signal_type signal;
-};
-
-
-/**
- * @brief Managed event bus.
- *
- * A managed event bus 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).
- *
- * @tparam Event The list of events managed by the bus.
- */
-template<typename... Event>
-using ManagedBus = Bus<Signal, Event...>;
-
-/**
- * @brief Unmanaged event bus.
- *
- * An unmanaged event bus 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 bus, users must guarantee that the
- * lifetimes of the instances overcome the one of the bus itself.
- *
- * @tparam Event The list of events managed by the bus.
- */
-template<typename... Event>
-using UnmanagedBus = Bus<SigH, Event...>;
-
-
-}
-
-
-#endif // ENTT_SIGNAL_BUS_HPP

+ 23 - 45
src/entt/signal/dispatcher.hpp

@@ -40,6 +40,8 @@ class Dispatcher final {
 
 
     template<typename Event>
     template<typename Event>
     struct SignalWrapper final: BaseSignalWrapper {
     struct SignalWrapper final: BaseSignalWrapper {
+        using sink_type = typename Sig<void(const Event &)>::Sink;
+
         void publish(std::size_t current) override {
         void publish(std::size_t current) override {
             for(const auto &event: events[current]) {
             for(const auto &event: events[current]) {
                 signal.publish(event);
                 signal.publish(event);
@@ -48,14 +50,8 @@ class Dispatcher final {
             events[current].clear();
             events[current].clear();
         }
         }
 
 
-        template<typename Class, void(Class::*Member)(const Event &)>
-        inline void connect(instance_type<Class, Event> instance) noexcept {
-            signal.template connect<Class, Member>(std::move(instance));
-        }
-
-        template<typename Class, void(Class::*Member)(const Event &)>
-        inline void disconnect(instance_type<Class, Event> instance) noexcept {
-            signal.template disconnect<Class, Member>(std::move(instance));
+        inline sink_type sink() noexcept {
+            return signal.sink();
         }
         }
 
 
         template<typename... Args>
         template<typename... Args>
@@ -93,52 +89,36 @@ class Dispatcher final {
     }
     }
 
 
 public:
 public:
+    /*! @brief Type of sink for the given event. */
+    template<typename Event>
+    using sink_type = typename SignalWrapper<Event>::sink_type;
+
     /*! @brief Default constructor. */
     /*! @brief Default constructor. */
     Dispatcher() noexcept
     Dispatcher() noexcept
         : wrappers{}, mode{false}
         : wrappers{}, mode{false}
     {}
     {}
 
 
     /**
     /**
-     * @brief Registers a listener given in the form of a member function.
-     *
-     * A matching member function has the following signature:
-     * `void receive(const Event &)`. Member functions named `receive` are
-     * automatically detected and registered if available.
+     * @brief Returns a sink object for the given event.
      *
      *
-     * @warning
-     * Connecting a listener during an update may lead to unexpected behavior.
-     * Register listeners before or after invoking the update if possible.
+     * A sink is an opaque object used to connect listeners to events.
      *
      *
-     * @tparam Event Type of event to which to connect the function.
-     * @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 the right type.
-     */
-    template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
-    void connect(instance_type<Class, Event> instance) noexcept {
-        wrapper<Event>().template connect<Class, Member>(std::move(instance));
-    }
-
-    /**
-     * @brief Unregisters a listener given in the form of a member function.
+     * The function type for a listener is:
+     * @code{.cpp}
+     * void(const Event &)
+     * @endcode
      *
      *
-     * A matching member function has the following signature:
-     * `void receive(const Event &)`. Member functions named `receive` are
-     * automatically detected and unregistered if available.
+     * The order of invocation of the listeners isn't guaranteed.
      *
      *
-     * @warning
-     * Disconnecting a listener during an update may lead to unexpected
-     * behavior. Unregister listeners before or after invoking the update if
-     * possible.
+     * @sa Signal::Sink
+     * @sa SigH::Sink
      *
      *
-     * @tparam Event Type of event from which to disconnect the function.
-     * @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 the right type.
+     * @tparam Event Type of event of which to get the sink.
+     * @return A temporary sink object.
      */
      */
-    template<typename Event, typename Class, void(Class::*Member)(const Event &) = &Class::receive>
-    void disconnect(instance_type<Class, Event> instance) noexcept {
-        wrapper<Event>().template disconnect<Class, Member>(std::move(instance));
+    template<typename Event>
+    sink_type<Event> sink() noexcept {
+        return wrapper<Event>().sink();
     }
     }
 
 
     /**
     /**
@@ -182,9 +162,7 @@ public:
         const auto buf = buffer(mode);
         const auto buf = buffer(mode);
         mode = !mode;
         mode = !mode;
 
 
-        for(auto pos = wrappers.size(); pos; --pos) {
-            auto &wrapper = wrappers[pos-1];
-
+        for(auto &&wrapper: wrappers) {
             if(wrapper) {
             if(wrapper) {
                 wrapper->publish(buf);
                 wrapper->publish(buf);
             }
             }

+ 1 - 1
src/entt/signal/emitter.hpp

@@ -138,7 +138,7 @@ class Emitter {
     }
     }
 
 
 public:
 public:
-    /** @brief Type of listeners accepted for the given type of event. */
+    /** @brief Type of listeners accepted for the given event. */
     template<typename Event>
     template<typename Event>
     using Listener = typename Handler<Event>::listener_type;
     using Listener = typename Handler<Event>::listener_type;
 
 

+ 118 - 82
src/entt/signal/sigh.hpp

@@ -10,7 +10,7 @@
 namespace entt {
 namespace entt {
 
 
 
 
-namespace {
+namespace details {
 
 
 
 
 template<typename, typename>
 template<typename, typename>
@@ -83,7 +83,7 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
  * @tparam Function A valid function type.
  * @tparam Function A valid function type.
  * @tparam Collector Type of collector to use, if any.
  * @tparam Collector Type of collector to use, if any.
  */
  */
-template<typename Function, typename Collector = DefaultCollectorType<Function>>
+template<typename Function, typename Collector = details::DefaultCollectorType<Function>>
 class SigH;
 class SigH;
 
 
 
 
@@ -111,18 +111,8 @@ class SigH;
  * @tparam Collector Type of collector to use, if any.
  * @tparam Collector Type of collector to use, if any.
  */
  */
 template<typename Ret, typename... Args, typename Collector>
 template<typename Ret, typename... Args, typename Collector>
-class SigH<Ret(Args...), Collector> final: private Invoker<Ret(Args...), Collector> {
-    using typename Invoker<Ret(Args...), Collector>::call_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...);
-    }
+class SigH<Ret(Args...), Collector> final: private details::Invoker<Ret(Args...), Collector> {
+    using call_type = typename details::Invoker<Ret(Args...), Collector>::call_type;
 
 
 public:
 public:
     /*! @brief Unsigned integer type. */
     /*! @brief Unsigned integer type. */
@@ -137,6 +127,112 @@ 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 {
+        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.
@@ -154,75 +250,16 @@ public:
     }
     }
 
 
     /**
     /**
-     * @brief Disconnects all the listeners from a signal.
-     */
-    void clear() noexcept {
-        calls.clear();
-    }
-
-    /**
-     * @brief Connects a free function to a signal.
+     * @brief Returns a sink object for the given signal.
      *
      *
-     * The signal handler performs checks to avoid multiple connections for free
-     * functions.
+     * 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.
      *
      *
-     * @tparam Function A valid free function pointer.
+     * @return A temporary sink object.
      */
      */
-    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...)>
-    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());
+    Sink sink() {
+        return { calls };
     }
     }
 
 
     /**
     /**
@@ -233,8 +270,7 @@ public:
      * @param args Arguments to use to invoke listeners.
      * @param args Arguments to use to invoke listeners.
      */
      */
     void publish(Args... args) {
     void publish(Args... args) {
-        for(auto pos = calls.size(); pos; --pos) {
-            auto &call = calls[pos-1];
+        for(auto &&call: calls) {
             call.second(call.first, args...);
             call.second(call.first, args...);
         }
         }
     }
     }

+ 123 - 89
src/entt/signal/signal.hpp

@@ -40,25 +40,6 @@ class Signal<void(Args...)> final {
     using proto_type = bool(*)(std::weak_ptr<void> &, Args...);
     using proto_type = bool(*)(std::weak_ptr<void> &, Args...);
     using call_type = std::pair<std::weak_ptr<void>, proto_type>;
     using call_type = std::pair<std::weak_ptr<void>, proto_type>;
 
 
-    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;
-    }
-
 public:
 public:
     /*! @brief Unsigned integer type. */
     /*! @brief Unsigned integer type. */
     using size_type = std::size_t;
     using size_type = std::size_t;
@@ -70,6 +51,121 @@ public:
     template<typename Class>
     template<typename Class>
     using instance_type = std::shared_ptr<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 {
+        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.
      * @brief Number of listeners connected to the signal.
      * @return Number of listeners currently connected.
      * @return Number of listeners currently connected.
@@ -87,76 +183,16 @@ public:
     }
     }
 
 
     /**
     /**
-     * @brief Disconnects all the listeners from a signal.
-     */
-    void clear() noexcept {
-        calls.clear();
-    }
-
-    /**
-     * @brief Connects a free function to a signal.
+     * @brief Returns a sink object for the given signal.
      *
      *
-     * The signal handler performs checks to avoid multiple connections for free
-     * functions.
+     * 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.
      *
      *
-     * @tparam Function A valid free function pointer.
+     * @return A temporary sink object.
      */
      */
-    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...)>
-    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());
+    Sink sink() {
+        return { calls };
     }
     }
 
 
     /**
     /**
@@ -169,9 +205,7 @@ public:
     void publish(Args... args) {
     void publish(Args... args) {
         std::vector<call_type> next;
         std::vector<call_type> next;
 
 
-        for(auto pos = calls.size(); pos; --pos) {
-            auto &call = calls[pos-1];
-
+        for(auto &&call: calls) {
             if((call.second)(call.first, args...)) {
             if((call.second)(call.first, args...)) {
                 next.push_back(call);
                 next.push_back(call);
             }
             }

+ 0 - 1
test/CMakeLists.txt

@@ -117,7 +117,6 @@ add_test(NAME resource COMMAND resource)
 add_executable(
 add_executable(
     signal
     signal
     $<TARGET_OBJECTS:odr>
     $<TARGET_OBJECTS:odr>
-    entt/signal/bus.cpp
     entt/signal/delegate.cpp
     entt/signal/delegate.cpp
     entt/signal/dispatcher.cpp
     entt/signal/dispatcher.cpp
     entt/signal/emitter.cpp
     entt/signal/emitter.cpp

+ 50 - 0
test/entt/entity/registry.cpp

@@ -6,6 +6,21 @@
 #include <entt/entity/entt_traits.hpp>
 #include <entt/entity/entt_traits.hpp>
 #include <entt/entity/registry.hpp>
 #include <entt/entity/registry.hpp>
 
 
+struct Listener {
+    void incr(entt::DefaultRegistry &, entt::DefaultRegistry::entity_type entity) {
+        last = entity;
+        ++counter;
+    }
+
+    void decr(entt::DefaultRegistry &, entt::DefaultRegistry::entity_type entity) {
+        last = entity;
+        --counter;
+    }
+
+    entt::DefaultRegistry::entity_type last;
+    int counter{0};
+};
+
 TEST(DefaultRegistry, Functionalities) {
 TEST(DefaultRegistry, Functionalities) {
     entt::DefaultRegistry registry;
     entt::DefaultRegistry registry;
 
 
@@ -508,3 +523,38 @@ TEST(DefaultRegistry, MergeTwoRegistries) {
     ne(dst.view<int, float, double>().begin(), dst.view<int, float, double>().end());
     ne(dst.view<int, float, double>().begin(), dst.view<int, float, double>().end());
     ne(dst.view<char, float, int>().begin(), dst.view<char, float, int>().end());
     ne(dst.view<char, float, int>().begin(), dst.view<char, float, int>().end());
 }
 }
+
+TEST(DefaultRegistry, Signals) {
+    entt::DefaultRegistry registry;
+    Listener listener;
+
+    registry.construction<int>().connect<Listener, &Listener::incr>(&listener);
+    registry.destruction<int>().connect<Listener, &Listener::decr>(&listener);
+
+    auto e0 = registry.create();
+    auto e1 = registry.create();
+
+    registry.assign<int>(e0);
+    registry.assign<int>(e1);
+
+    ASSERT_EQ(listener.counter, 2);
+    ASSERT_EQ(listener.last, e1);
+
+    registry.remove<int>(e0);
+
+    ASSERT_EQ(listener.counter, 1);
+    ASSERT_EQ(listener.last, e0);
+
+    registry.destruction<int>().disconnect<Listener, &Listener::decr>(&listener);
+    registry.remove<int>(e1);
+
+    ASSERT_EQ(listener.counter, 1);
+    ASSERT_EQ(listener.last, e0);
+
+    registry.construction<int>().disconnect<Listener, &Listener::incr>(&listener);
+    registry.assign<int>(e0);
+    registry.assign<int>(e1);
+
+    ASSERT_EQ(listener.counter, 1);
+    ASSERT_EQ(listener.last, e0);
+}

+ 0 - 141
test/entt/signal/bus.cpp

@@ -1,141 +0,0 @@
-#include <memory>
-#include <gtest/gtest.h>
-#include <entt/signal/bus.hpp>
-
-struct EventA
-{
-    EventA(int x, int y): value{x+y} {}
-    int value;
-};
-
-struct EventB {};
-struct EventC {};
-
-struct MyListener
-{
-    void receive(const EventA &) { A++; }
-    static void listen(const EventB &) { B++; }
-    void receive(const EventC &) { C++; }
-    void reset() { A = 0; B = 0; C = 0; }
-    int A{0};
-    static int B;
-    int C{0};
-};
-
-int MyListener::B = 0;
-
-template<typename Bus, typename Listener>
-void testRegUnregEmit(Listener listener) {
-    Bus bus;
-
-    listener->reset();
-    bus.template publish<EventA>(40, 2);
-    bus.template publish<EventB>();
-    bus.template publish<EventC>();
-
-    ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
-    ASSERT_TRUE(bus.empty());
-    ASSERT_EQ(listener->A, 0);
-    ASSERT_EQ(listener->B, 0);
-    ASSERT_EQ(listener->C, 0);
-
-    bus.reg(listener);
-    bus.template connect<EventB, &MyListener::listen>();
-
-    listener->reset();
-    bus.template publish<EventA>(40, 2);
-    bus.template publish<EventB>();
-    bus.template publish<EventC>();
-
-    ASSERT_EQ(bus.size(), (decltype(bus.size()))3);
-    ASSERT_FALSE(bus.empty());
-    ASSERT_EQ(listener->A, 1);
-    ASSERT_EQ(listener->B, 1);
-    ASSERT_EQ(listener->C, 1);
-
-    bus.unreg(listener);
-
-    listener->reset();
-    bus.template publish<EventA>(40, 2);
-    bus.template publish<EventB>();
-    bus.template publish<EventC>();
-
-    ASSERT_EQ(bus.size(), (decltype(bus.size()))1);
-    ASSERT_FALSE(bus.empty());
-    ASSERT_EQ(listener->A, 0);
-    ASSERT_EQ(listener->B, 1);
-    ASSERT_EQ(listener->C, 0);
-
-    bus.template disconnect<EventB, MyListener::listen>();
-
-    listener->reset();
-    bus.template publish<EventA>(40, 2);
-    bus.template publish<EventB>();
-    bus.template publish<EventC>();
-
-    ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
-    ASSERT_TRUE(bus.empty());
-    ASSERT_EQ(listener->A, 0);
-    ASSERT_EQ(listener->B, 0);
-    ASSERT_EQ(listener->C, 0);
-}
-
-TEST(ManagedBus, RegUnregEmit) {
-    using MyManagedBus = entt::ManagedBus<EventA, EventB, EventC>;
-    testRegUnregEmit<MyManagedBus>(std::make_shared<MyListener>());
-}
-
-TEST(ManagedBus, ExpiredListeners) {
-    entt::ManagedBus<EventA, EventB, EventC> bus;
-    auto listener = std::make_shared<MyListener>();
-
-    listener->reset();
-    bus.reg(listener);
-    bus.template publish<EventA>(40, 2);
-    bus.template publish<EventB>();
-
-    ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
-    ASSERT_FALSE(bus.empty());
-    ASSERT_EQ(listener->A, 1);
-    ASSERT_EQ(listener->B, 0);
-
-    listener->reset();
-    listener = nullptr;
-
-    ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
-    ASSERT_FALSE(bus.empty());
-
-    EXPECT_NO_THROW(bus.template publish<EventA>(40, 2));
-    EXPECT_NO_THROW(bus.template publish<EventC>());
-
-    ASSERT_EQ(bus.size(), (decltype(bus.size()))0);
-    ASSERT_TRUE(bus.empty());
-}
-
-TEST(UnmanagedBus, RegUnregEmit) {
-    using MyUnmanagedBus = entt::UnmanagedBus<EventA, EventB, EventC>;
-    auto ptr = std::make_unique<MyListener>();
-    testRegUnregEmit<MyUnmanagedBus>(ptr.get());
-}
-
-TEST(UnmanagedBus, ExpiredListeners) {
-    entt::UnmanagedBus<EventA, EventB, EventC> bus;
-    auto listener = std::make_unique<MyListener>();
-
-    listener->reset();
-    bus.reg(listener.get());
-    bus.template publish<EventA>(40, 2);
-    bus.template publish<EventB>();
-
-    ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
-    ASSERT_FALSE(bus.empty());
-    ASSERT_EQ(listener->A, 1);
-    ASSERT_EQ(listener->B, 0);
-
-    listener->reset();
-    listener = nullptr;
-
-    // dangling pointer inside ... well, unmanaged means unmanaged!! :-)
-    ASSERT_EQ(bus.size(), (decltype(bus.size()))2);
-    ASSERT_FALSE(bus.empty());
-}

+ 2 - 2
test/entt/signal/dispatcher.cpp

@@ -14,7 +14,7 @@ template<typename Dispatcher, typename Rec>
 void testDispatcher(Rec receiver) {
 void testDispatcher(Rec receiver) {
     Dispatcher dispatcher;
     Dispatcher dispatcher;
 
 
-    dispatcher.template connect<Event>(receiver);
+    dispatcher.template sink<Event>().connect(receiver);
     dispatcher.template trigger<Event>();
     dispatcher.template trigger<Event>();
     dispatcher.template enqueue<Event>();
     dispatcher.template enqueue<Event>();
 
 
@@ -28,7 +28,7 @@ void testDispatcher(Rec receiver) {
 
 
     receiver->reset();
     receiver->reset();
 
 
-    dispatcher.template disconnect<Event>(receiver);
+    dispatcher.template sink<Event>().disconnect(receiver);
     dispatcher.template trigger<Event>();
     dispatcher.template trigger<Event>();
     dispatcher.template enqueue<Event>();
     dispatcher.template enqueue<Event>();
     dispatcher.update();
     dispatcher.update();

+ 81 - 84
test/entt/signal/sigh.cpp

@@ -3,6 +3,48 @@
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 #include <entt/signal/sigh.hpp>
 #include <entt/signal/sigh.hpp>
 
 
+struct S {
+    static void f(int &v) { v = 42; }
+
+    bool g(int) { k = !k; return true; }
+    bool h(int) { return k; }
+
+    void i() {}
+    void l() {}
+
+    bool k{false};
+};
+
+template<typename Ret>
+struct TestCollectAll {
+    std::vector<Ret> vec{};
+    static int f() { return 42; }
+    static int g() { return 42; }
+    bool operator()(Ret r) noexcept {
+        vec.push_back(r);
+        return true;
+    }
+};
+
+template<>
+struct TestCollectAll<void> {
+    std::vector<int> vec{};
+    static void h() {}
+    bool operator()() noexcept {
+        return true;
+    }
+};
+
+template<typename Ret>
+struct TestCollectFirst {
+    std::vector<Ret> vec{};
+    static int f() { return 42; }
+    bool operator()(Ret r) noexcept {
+        vec.push_back(r);
+        return false;
+    }
+};
+
 TEST(SigH, Lifetime) {
 TEST(SigH, Lifetime) {
     using signal = entt::SigH<void(void)>;
     using signal = entt::SigH<void(void)>;
 
 
@@ -19,69 +61,60 @@ TEST(SigH, Lifetime) {
 }
 }
 
 
 TEST(SigH, Comparison) {
 TEST(SigH, Comparison) {
-    struct S {
-        void f() {}
-        void g() {}
-    };
-
     entt::SigH<void()> sig1;
     entt::SigH<void()> sig1;
     entt::SigH<void()> sig2;
     entt::SigH<void()> sig2;
 
 
     S s1;
     S s1;
     S s2;
     S s2;
 
 
-    sig1.connect<S, &S::f>(&s1);
-    sig2.connect<S, &S::f>(&s2);
+    sig1.sink().connect<S, &S::i>(&s1);
+    sig2.sink().connect<S, &S::i>(&s2);
 
 
     ASSERT_FALSE(sig1 == sig2);
     ASSERT_FALSE(sig1 == sig2);
     ASSERT_TRUE(sig1 != sig2);
     ASSERT_TRUE(sig1 != sig2);
 
 
-    sig1.disconnect<S, &S::f>(&s1);
-    sig2.disconnect<S, &S::f>(&s2);
+    sig1.sink().disconnect<S, &S::i>(&s1);
+    sig2.sink().disconnect<S, &S::i>(&s2);
 
 
-    sig1.connect<S, &S::f>(&s1);
-    sig2.connect<S, &S::g>(&s1);
+    sig1.sink().connect<S, &S::i>(&s1);
+    sig2.sink().connect<S, &S::l>(&s1);
 
 
     ASSERT_FALSE(sig1 == sig2);
     ASSERT_FALSE(sig1 == sig2);
     ASSERT_TRUE(sig1 != sig2);
     ASSERT_TRUE(sig1 != sig2);
 
 
-    sig1.disconnect<S, &S::f>(&s1);
-    sig2.disconnect<S, &S::g>(&s1);
+    sig1.sink().disconnect<S, &S::i>(&s1);
+    sig2.sink().disconnect<S, &S::l>(&s1);
 
 
     ASSERT_TRUE(sig1 == sig2);
     ASSERT_TRUE(sig1 == sig2);
     ASSERT_FALSE(sig1 != sig2);
     ASSERT_FALSE(sig1 != sig2);
 
 
-    sig1.connect<S, &S::f>(&s1);
-    sig1.connect<S, &S::g>(&s1);
-    sig2.connect<S, &S::f>(&s1);
-    sig2.connect<S, &S::g>(&s1);
+    sig1.sink().connect<S, &S::i>(&s1);
+    sig1.sink().connect<S, &S::l>(&s1);
+    sig2.sink().connect<S, &S::i>(&s1);
+    sig2.sink().connect<S, &S::l>(&s1);
 
 
     ASSERT_TRUE(sig1 == sig2);
     ASSERT_TRUE(sig1 == sig2);
 
 
-    sig1.disconnect<S, &S::f>(&s1);
-    sig1.disconnect<S, &S::g>(&s1);
-    sig2.disconnect<S, &S::f>(&s1);
-    sig2.disconnect<S, &S::g>(&s1);
+    sig1.sink().disconnect<S, &S::i>(&s1);
+    sig1.sink().disconnect<S, &S::l>(&s1);
+    sig2.sink().disconnect<S, &S::i>(&s1);
+    sig2.sink().disconnect<S, &S::l>(&s1);
 
 
-    sig1.connect<S, &S::f>(&s1);
-    sig1.connect<S, &S::g>(&s1);
-    sig2.connect<S, &S::g>(&s1);
-    sig2.connect<S, &S::f>(&s1);
+    sig1.sink().connect<S, &S::i>(&s1);
+    sig1.sink().connect<S, &S::l>(&s1);
+    sig2.sink().connect<S, &S::l>(&s1);
+    sig2.sink().connect<S, &S::i>(&s1);
 
 
     ASSERT_FALSE(sig1 == sig2);
     ASSERT_FALSE(sig1 == sig2);
 }
 }
 
 
-struct S {
-    static void f(int &v) { v = 42; }
-};
-
 TEST(SigH, Clear) {
 TEST(SigH, Clear) {
     entt::SigH<void(int &)> sigh;
     entt::SigH<void(int &)> sigh;
-    sigh.connect<&S::f>();
+    sigh.sink().connect<&S::f>();
 
 
     ASSERT_FALSE(sigh.empty());
     ASSERT_FALSE(sigh.empty());
 
 
-    sigh.clear();
+    sigh.sink().disconnect();
 
 
     ASSERT_TRUE(sigh.empty());
     ASSERT_TRUE(sigh.empty());
 }
 }
@@ -90,7 +123,7 @@ TEST(SigH, Swap) {
     entt::SigH<void(int &)> sigh1;
     entt::SigH<void(int &)> sigh1;
     entt::SigH<void(int &)> sigh2;
     entt::SigH<void(int &)> sigh2;
 
 
-    sigh1.connect<&S::f>();
+    sigh1.sink().connect<&S::f>();
 
 
     ASSERT_FALSE(sigh1.empty());
     ASSERT_FALSE(sigh1.empty());
     ASSERT_TRUE(sigh2.empty());
     ASSERT_TRUE(sigh2.empty());
@@ -105,7 +138,7 @@ TEST(SigH, Functions) {
     entt::SigH<void(int &)> sigh;
     entt::SigH<void(int &)> sigh;
     int v = 0;
     int v = 0;
 
 
-    sigh.connect<&S::f>();
+    sigh.sink().connect<&S::f>();
     sigh.publish(v);
     sigh.publish(v);
 
 
     ASSERT_FALSE(sigh.empty());
     ASSERT_FALSE(sigh.empty());
@@ -113,87 +146,51 @@ TEST(SigH, Functions) {
     ASSERT_EQ(42, v);
     ASSERT_EQ(42, v);
 
 
     v = 0;
     v = 0;
-    sigh.disconnect<&S::f>();
+    sigh.sink().disconnect<&S::f>();
     sigh.publish(v);
     sigh.publish(v);
 
 
     ASSERT_TRUE(sigh.empty());
     ASSERT_TRUE(sigh.empty());
     ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
     ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
     ASSERT_EQ(0, v);
     ASSERT_EQ(0, v);
 
 
-    sigh.connect<&S::f>();
+    sigh.sink().connect<&S::f>();
 }
 }
 
 
 TEST(SigH, Members) {
 TEST(SigH, Members) {
-    struct S {
-        bool f(int) { b = !b; return true; }
-        bool g(int) { return b; }
-        bool b{false};
-    };
-
     S s;
     S s;
     S *ptr = &s;
     S *ptr = &s;
     entt::SigH<bool(int)> sigh;
     entt::SigH<bool(int)> sigh;
 
 
-    sigh.connect<S, &S::f>(ptr);
+    sigh.sink().connect<S, &S::g>(ptr);
     sigh.publish(42);
     sigh.publish(42);
 
 
-    ASSERT_TRUE(s.b);
+    ASSERT_TRUE(s.k);
     ASSERT_FALSE(sigh.empty());
     ASSERT_FALSE(sigh.empty());
     ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
     ASSERT_EQ((entt::SigH<bool(int)>::size_type)1, sigh.size());
 
 
-    sigh.disconnect<S, &S::f>(ptr);
+    sigh.sink().disconnect<S, &S::g>(ptr);
     sigh.publish(42);
     sigh.publish(42);
 
 
-    ASSERT_TRUE(s.b);
+    ASSERT_TRUE(s.k);
     ASSERT_TRUE(sigh.empty());
     ASSERT_TRUE(sigh.empty());
     ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
     ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
 
 
-    sigh.connect<S, &S::f>(ptr);
-    sigh.connect<S, &S::g>(ptr);
+    sigh.sink().connect<S, &S::g>(ptr);
+    sigh.sink().connect<S, &S::h>(ptr);
 
 
     ASSERT_FALSE(sigh.empty());
     ASSERT_FALSE(sigh.empty());
     ASSERT_EQ((entt::SigH<bool(int)>::size_type)2, sigh.size());
     ASSERT_EQ((entt::SigH<bool(int)>::size_type)2, sigh.size());
 
 
-    sigh.disconnect(ptr);
+    sigh.sink().disconnect(ptr);
 
 
     ASSERT_TRUE(sigh.empty());
     ASSERT_TRUE(sigh.empty());
     ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
     ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
 }
 }
 
 
-template<typename Ret>
-struct TestCollectAll {
-    std::vector<Ret> vec{};
-    static int f() { return 42; }
-    static int g() { return 42; }
-    bool operator()(Ret r) noexcept {
-        vec.push_back(r);
-        return true;
-    }
-};
-
-template<>
-struct TestCollectAll<void> {
-    std::vector<int> vec{};
-    static void h() {}
-    bool operator()() noexcept {
-        return true;
-    }
-};
-
-template<typename Ret>
-struct TestCollectFirst {
-    std::vector<Ret> vec{};
-    static int f() { return 42; }
-    bool operator()(Ret r) noexcept {
-        vec.push_back(r);
-        return false;
-    }
-};
-
 TEST(SigH, Collector) {
 TEST(SigH, Collector) {
     entt::SigH<void(), TestCollectAll<void>> sigh_void;
     entt::SigH<void(), TestCollectAll<void>> sigh_void;
 
 
-    sigh_void.connect<&TestCollectAll<void>::h>();
+    sigh_void.sink().connect<&TestCollectAll<void>::h>();
     auto collector_void = sigh_void.collect();
     auto collector_void = sigh_void.collect();
 
 
     ASSERT_FALSE(sigh_void.empty());
     ASSERT_FALSE(sigh_void.empty());
@@ -201,9 +198,9 @@ TEST(SigH, Collector) {
 
 
     entt::SigH<int(), TestCollectAll<int>> sigh_all;
     entt::SigH<int(), TestCollectAll<int>> sigh_all;
 
 
-    sigh_all.connect<&TestCollectAll<int>::f>();
-    sigh_all.connect<&TestCollectAll<int>::f>();
-    sigh_all.connect<&TestCollectAll<int>::g>();
+    sigh_all.sink().connect<&TestCollectAll<int>::f>();
+    sigh_all.sink().connect<&TestCollectAll<int>::f>();
+    sigh_all.sink().connect<&TestCollectAll<int>::g>();
     auto collector_all = sigh_all.collect();
     auto collector_all = sigh_all.collect();
 
 
     ASSERT_FALSE(sigh_all.empty());
     ASSERT_FALSE(sigh_all.empty());
@@ -214,8 +211,8 @@ TEST(SigH, Collector) {
 
 
     entt::SigH<int(), TestCollectFirst<int>> sigh_first;
     entt::SigH<int(), TestCollectFirst<int>> sigh_first;
 
 
-    sigh_first.connect<&TestCollectFirst<int>::f>();
-    sigh_first.connect<&TestCollectFirst<int>::f>();
+    sigh_first.sink().connect<&TestCollectFirst<int>::f>();
+    sigh_first.sink().connect<&TestCollectFirst<int>::f>();
     auto collector_first = sigh_first.collect();
     auto collector_first = sigh_first.collect();
 
 
     ASSERT_FALSE(sigh_first.empty());
     ASSERT_FALSE(sigh_first.empty());

+ 52 - 52
test/entt/signal/signal.cpp

@@ -4,13 +4,18 @@
 #include <entt/signal/signal.hpp>
 #include <entt/signal/signal.hpp>
 
 
 struct S {
 struct S {
-    static void f(const int &j) { i = j; }
-    void g(const int &j) { i = j; }
-    void h(const int &) {}
-    static int i;
+    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::i = 0;
+int S::k = 0;
 
 
 TEST(Signal, Lifetime) {
 TEST(Signal, Lifetime) {
     using signal = entt::Signal<void(void)>;
     using signal = entt::Signal<void(void)>;
@@ -28,65 +33,60 @@ TEST(Signal, Lifetime) {
 }
 }
 
 
 TEST(Signal, Comparison) {
 TEST(Signal, Comparison) {
-    struct S {
-        void f() {}
-        void g() {}
-    };
-
     entt::Signal<void()> sig1;
     entt::Signal<void()> sig1;
     entt::Signal<void()> sig2;
     entt::Signal<void()> sig2;
 
 
     auto s1 = std::make_shared<S>();
     auto s1 = std::make_shared<S>();
     auto s2 = std::make_shared<S>();
     auto s2 = std::make_shared<S>();
 
 
-    sig1.connect<S, &S::f>(s1);
-    sig2.connect<S, &S::f>(s2);
+    sig1.sink().connect<S, &S::g>(s1);
+    sig2.sink().connect<S, &S::g>(s2);
 
 
     ASSERT_FALSE(sig1 == sig2);
     ASSERT_FALSE(sig1 == sig2);
     ASSERT_TRUE(sig1 != sig2);
     ASSERT_TRUE(sig1 != sig2);
 
 
-    sig1.disconnect<S, &S::f>(s1);
-    sig2.disconnect<S, &S::f>(s2);
+    sig1.sink().disconnect<S, &S::g>(s1);
+    sig2.sink().disconnect<S, &S::g>(s2);
 
 
-    sig1.connect<S, &S::f>(s1);
-    sig2.connect<S, &S::g>(s1);
+    sig1.sink().connect<S, &S::g>(s1);
+    sig2.sink().connect<S, &S::h>(s1);
 
 
     ASSERT_FALSE(sig1 == sig2);
     ASSERT_FALSE(sig1 == sig2);
     ASSERT_TRUE(sig1 != sig2);
     ASSERT_TRUE(sig1 != sig2);
 
 
-    sig1.disconnect<S, &S::f>(s1);
-    sig2.disconnect<S, &S::g>(s1);
+    sig1.sink().disconnect<S, &S::g>(s1);
+    sig2.sink().disconnect<S, &S::h>(s1);
 
 
     ASSERT_TRUE(sig1 == sig2);
     ASSERT_TRUE(sig1 == sig2);
     ASSERT_FALSE(sig1 != sig2);
     ASSERT_FALSE(sig1 != sig2);
 
 
-    sig1.connect<S, &S::f>(s1);
-    sig1.connect<S, &S::g>(s1);
-    sig2.connect<S, &S::f>(s1);
-    sig2.connect<S, &S::g>(s1);
+    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);
     ASSERT_TRUE(sig1 == sig2);
 
 
-    sig1.disconnect<S, &S::f>(s1);
-    sig1.disconnect<S, &S::g>(s1);
-    sig2.disconnect<S, &S::f>(s1);
-    sig2.disconnect<S, &S::g>(s1);
+    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.connect<S, &S::f>(s1);
-    sig1.connect<S, &S::g>(s1);
-    sig2.connect<S, &S::g>(s1);
-    sig2.connect<S, &S::f>(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);
     ASSERT_FALSE(sig1 == sig2);
 }
 }
 
 
 TEST(Signal, Clear) {
 TEST(Signal, Clear) {
     entt::Signal<void(const int &)> signal;
     entt::Signal<void(const int &)> signal;
-    signal.connect<&S::f>();
+    signal.sink().connect<&S::f>();
 
 
     ASSERT_FALSE(signal.empty());
     ASSERT_FALSE(signal.empty());
 
 
-    signal.clear();
+    signal.sink().disconnect();
 
 
     ASSERT_TRUE(signal.empty());
     ASSERT_TRUE(signal.empty());
 }
 }
@@ -95,7 +95,7 @@ TEST(Signal, Swap) {
     entt::Signal<void(const int &)> sig1;
     entt::Signal<void(const int &)> sig1;
     entt::Signal<void(const int &)> sig2;
     entt::Signal<void(const int &)> sig2;
 
 
-    sig1.connect<&S::f>();
+    sig1.sink().connect<&S::f>();
 
 
     ASSERT_FALSE(sig1.empty());
     ASSERT_FALSE(sig1.empty());
     ASSERT_TRUE(sig2.empty());
     ASSERT_TRUE(sig2.empty());
@@ -108,72 +108,72 @@ TEST(Signal, Swap) {
 
 
 TEST(Signal, Functions) {
 TEST(Signal, Functions) {
     entt::Signal<void(const int &)> signal;
     entt::Signal<void(const int &)> signal;
-    auto val = S::i + 1;
+    auto val = (S::k = 0) + 1;
 
 
-    signal.connect<&S::f>();
+    signal.sink().connect<&S::f>();
     signal.publish(val);
     signal.publish(val);
 
 
     ASSERT_FALSE(signal.empty());
     ASSERT_FALSE(signal.empty());
     ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
     ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
-    ASSERT_EQ(S::i, val);
+    ASSERT_EQ(S::k, val);
 
 
-    signal.disconnect<&S::f>();
+    signal.sink().disconnect<&S::f>();
     signal.publish(val+1);
     signal.publish(val+1);
 
 
     ASSERT_TRUE(signal.empty());
     ASSERT_TRUE(signal.empty());
     ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
     ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
-    ASSERT_EQ(S::i, val);
+    ASSERT_EQ(S::k, val);
 }
 }
 
 
 TEST(Signal, Members) {
 TEST(Signal, Members) {
     entt::Signal<void(const int &)> signal;
     entt::Signal<void(const int &)> signal;
     auto ptr = std::make_shared<S>();
     auto ptr = std::make_shared<S>();
-    auto val = S::i + 1;
+    auto val = (S::k = 0) + 1;
 
 
-    signal.connect<S, &S::g>(ptr);
+    signal.sink().connect<S, &S::i>(ptr);
     signal.publish(val);
     signal.publish(val);
 
 
     ASSERT_FALSE(signal.empty());
     ASSERT_FALSE(signal.empty());
     ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
     ASSERT_EQ(entt::Signal<void(const int &)>::size_type{1}, signal.size());
-    ASSERT_EQ(S::i, val);
+    ASSERT_EQ(S::k, val);
 
 
-    signal.disconnect<S, &S::g>(ptr);
+    signal.sink().disconnect<S, &S::i>(ptr);
     signal.publish(val+1);
     signal.publish(val+1);
 
 
     ASSERT_TRUE(signal.empty());
     ASSERT_TRUE(signal.empty());
     ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
     ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
-    ASSERT_EQ(S::i, val);
+    ASSERT_EQ(S::k, val);
 
 
     ++val;
     ++val;
 
 
-    signal.connect<S, &S::g>(ptr);
-    signal.connect<S, &S::h>(ptr);
+    signal.sink().connect<S, &S::i>(ptr);
+    signal.sink().connect<S, &S::l>(ptr);
     signal.publish(val);
     signal.publish(val);
 
 
     ASSERT_FALSE(signal.empty());
     ASSERT_FALSE(signal.empty());
     ASSERT_EQ(entt::Signal<void(const int &)>::size_type{2}, signal.size());
     ASSERT_EQ(entt::Signal<void(const int &)>::size_type{2}, signal.size());
-    ASSERT_EQ(S::i, val);
+    ASSERT_EQ(S::k, val);
 
 
-    signal.disconnect(ptr);
+    signal.sink().disconnect(ptr);
     signal.publish(val+1);
     signal.publish(val+1);
 
 
     ASSERT_TRUE(signal.empty());
     ASSERT_TRUE(signal.empty());
     ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
     ASSERT_EQ(entt::Signal<void(const int &)>::size_type{0}, signal.size());
-    ASSERT_EQ(S::i, val);
+    ASSERT_EQ(S::k, val);
 }
 }
 
 
 TEST(Signal, Cleanup) {
 TEST(Signal, Cleanup) {
     entt::Signal<void(const int &)> signal;
     entt::Signal<void(const int &)> signal;
     auto ptr = std::make_shared<S>();
     auto ptr = std::make_shared<S>();
-    signal.connect<S, &S::g>(ptr);
-    auto val = S::i;
+    signal.sink().connect<S, &S::i>(ptr);
+    auto val = (S::k = 0);
     ptr = nullptr;
     ptr = nullptr;
 
 
     ASSERT_FALSE(signal.empty());
     ASSERT_FALSE(signal.empty());
-    ASSERT_EQ(S::i, val);
+    ASSERT_EQ(S::k, val);
 
 
     signal.publish(val);
     signal.publish(val);
 
 
     ASSERT_TRUE(signal.empty());
     ASSERT_TRUE(signal.empty());
-    ASSERT_EQ(S::i, val);
+    ASSERT_EQ(S::k, val);
 }
 }