Просмотр исходного кода

signals on component creation/destruction (#62)

Michele Caini 8 лет назад
Родитель
Сommit
e99d7e2c3c

+ 92 - 112
README.md

@@ -15,6 +15,7 @@
       * [Pay per use](#pay-per-use)
    * [Vademecum](#vademecum)
    * [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
+      * [Observe changes](#observe-changes)
       * [Single instance components](#single-instance-components)
       * [Runtime components](#runtime-components)
          * [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)
 * [Crash Course: events, signals and everything in between](#crash-course-events-signals-and-everything-in-between)
    * [Signals](#signals)
-   * [Compile-time event bus](#compile-time-event-bus)
    * [Delegate](#delegate)
    * [Event dispatcher](#event-dispatcher)
    * [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.
 * A cooperative scheduler for processes of any type.
 * All what is needed for resource management (cache, loaders, handles).
-* Signal handlers of any type, delegates and 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.
 * 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 |
 |-----------|-------------|-------------|
 | 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, 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** |
@@ -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
 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
 
 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:
 
   Pros:
+
   * 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
     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.
 
   Cons:
+
   * Their performance tend to degenerate when the number of components to
     iterate grows up and the most of the entities have all of them.
 
 * Persistent views:
 
   Pros:
+
   * Once prepared, creating and destroying them isn't expensive at all because
     they don't have any type of initialization.
   * They are the best tool for iterating entities for mmultiple components and
     most entities have them all.
 
   Cons:
+
   * They have dedicated data structures and thus affect the memory usage to a
     minimal extent.
   * 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:
 
   Pros:
+
   * 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
     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.
 
   Cons:
+
   * 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.
 
 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 standard view to iterate entities for a single component.
 * 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/>
 They differ in the way they work around the tradeoff between performance, memory
 usage and safety. Managed listeners must be wrapped in an `std::shared_ptr` and
-the 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
-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
 
@@ -1962,7 +2016,8 @@ entt::Signal<void(int, char)> signal;
 ```
 
 From now on, free functions and member functions that respect the given
-signature can be easily connected to and disconnected from the signal:
+signature can be easily connected to and disconnected from the signal by means
+of a sink:
 
 ```cpp
 void foo(int, char) { /* ... */ }
@@ -1975,18 +2030,22 @@ struct 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
-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
@@ -2000,8 +2059,8 @@ signal.publish(42, 'c');
 This is more or less all what a managed signal handler has to offer.<br/>
 A bunch of other member functions are exposed actually. As an example, there is
 a method to use to know how many listeners a managed signal handler contains
-(`size`) or if it contains at least a listener (`empty`), 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
 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
 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
-listeners in all their forms:
+listeners in all their forms by means of a sink::
 
 ```cpp
 void foo(int, char) { /* ... */ }
@@ -2053,18 +2111,22 @@ struct S {
 
 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 ...
-signal.disconnect<S, &S::bar>(&instance);
+signal.sink().disconnect<S, &S::bar>(&instance);
 
 // ... 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
@@ -2095,8 +2157,8 @@ int g() { return 1; }
 
 entt::SigH<int(), MyCollector<int>> signal;
 
-signal.connect<&f>();
-signal.connect<&g>();
+signal.sink().connect<&f>();
+signal.sink().connect<&g>();
 
 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
 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
 
 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
 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
-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
 struct AnEvent { int value; };
@@ -2293,16 +2273,16 @@ struct 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
 selectively remove listeners:
 
 ```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

+ 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
-* 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)
-* 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
 * 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 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

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

@@ -10,7 +10,7 @@
 namespace entt {
 
 
-namespace {
+namespace details {
 
 
 template<typename... Types>
@@ -87,7 +87,7 @@ private:
  * @tparam Types List of types for which to generate identifiers.
  */
 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 <type_traits>
 #include "../core/family.hpp"
+#include "../signal/sigh.hpp"
 #include "entt_traits.hpp"
 #include "snapshot.hpp"
 #include "sparse_set.hpp"
@@ -35,9 +36,27 @@ template<typename Entity>
 class Registry {
     using tag_family = Family<struct InternalRegistryTagFamily>;
     using component_family = Family<struct InternalRegistryComponentFamily>;
-    using view_family = Family<struct InternalRegistryViewFamily>;
+    using handler_family = Family<struct InternalRegistryHandlerFamily>;
     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 {
         Entity entity;
     };
@@ -55,45 +74,38 @@ class Registry {
 
     template<typename 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>
-        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)...);
-
-            for(auto &&listener: listeners) {
-                if((registry.*listener.second)(entity)) {
-                    listener.first->construct(entity);
-                }
-            }
-
+            listeners.first.publish(registry, entity);
             return component;
         }
 
         void destroy(Entity entity) override {
+            listeners.second.publish(registry, 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:
-        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>
@@ -122,40 +134,12 @@ class Registry {
         }
 
         if(!pools[ctype]) {
-            pools[ctype] = std::make_unique<Pool<Component>>();
+            pools[ctype] = std::make_unique<Pool<Component>>(*this);
         }
 
         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:
     /*! @brief Underlying entity identifier. */
     using entity_type = typename traits_type::entity_type;
@@ -168,6 +152,10 @@ public:
     /*! @brief Unsigned integer 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. */
     Registry() = default;
 
@@ -316,7 +304,6 @@ public:
     bool fast(entity_type entity) const noexcept {
         const auto pos = size_type(entity & traits_type::entity_mask);
         assert(pos < entities.size());
-        // the in-use control bit permits to avoid accessing the direct vector
         return (entities[pos] == entity);
     }
 
@@ -375,7 +362,7 @@ public:
     entity_type create(Component &&... components) noexcept {
         using accumulator_type = int[];
         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;
         return entity;
     }
@@ -402,7 +389,7 @@ public:
     entity_type create() noexcept {
         using accumulator_type = int[];
         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;
         return entity;
     }
@@ -469,11 +456,11 @@ public:
         next = entt;
         ++available;
 
-        for(auto &&cpool: pools) {
+        std::for_each(pools.begin(), pools.end(), [entity](auto &cpool) {
             if(cpool && cpool->has(entity)) {
                 cpool->destroy(entity);
             }
-        }
+        });
     }
 
     /**
@@ -663,7 +650,7 @@ public:
     template<typename Component, typename... Args>
     Component & assign(entity_type entity, Args &&... args) {
         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)
                 ? (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>
     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() {
         if(contains<Component...>()) {
             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
-            accumulator_type accumulator = { (pool<Component>().remove(set), 0)... };
-            handlers[vtype].reset();
+            accumulator_type accumulator = { (disconnect(pool<Component>()), 0)... };
+            handlers[htype].reset();
             (void)accumulator;
         }
     }
@@ -1158,8 +1225,8 @@ public:
     template<typename... Component>
     bool contains() const noexcept {
         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>
     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/handle.hpp"
 #include "resource/loader.hpp"
-#include "signal/bus.hpp"
 #include "signal/delegate.hpp"
 #include "signal/dispatcher.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>
     struct SignalWrapper final: BaseSignalWrapper {
+        using sink_type = typename Sig<void(const Event &)>::Sink;
+
         void publish(std::size_t current) override {
             for(const auto &event: events[current]) {
                 signal.publish(event);
@@ -48,14 +50,8 @@ class Dispatcher final {
             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>
@@ -93,52 +89,36 @@ class Dispatcher final {
     }
 
 public:
+    /*! @brief Type of sink for the given event. */
+    template<typename Event>
+    using sink_type = typename SignalWrapper<Event>::sink_type;
+
     /*! @brief Default constructor. */
     Dispatcher() noexcept
         : 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);
         mode = !mode;
 
-        for(auto pos = wrappers.size(); pos; --pos) {
-            auto &wrapper = wrappers[pos-1];
-
+        for(auto &&wrapper: wrappers) {
             if(wrapper) {
                 wrapper->publish(buf);
             }

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

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

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

@@ -10,7 +10,7 @@
 namespace entt {
 
 
-namespace {
+namespace details {
 
 
 template<typename, typename>
@@ -83,7 +83,7 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
  * @tparam Function A valid function type.
  * @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;
 
 
@@ -111,18 +111,8 @@ class SigH;
  * @tparam Collector Type of collector to use, if any.
  */
 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:
     /*! @brief Unsigned integer type. */
@@ -137,6 +127,112 @@ public:
     template<typename Class>
     using instance_type = Class *;
 
+   /**
+    * @brief Sink implementation.
+    *
+    * A sink is an opaque object used to connect listeners to signals.<br/>
+    * The function type for a listener is the one of the signal to which it
+    * belongs.
+    *
+    * The clear separation between a signal and a sink permits to store the
+    * former as private data member without exposing the publish functionality
+    * to the users of a class.
+    */
+    class Sink final {
+        friend class SigH;
+
+        template<Ret(*Function)(Args...)>
+        static Ret proto(void *, Args... args) {
+            return (Function)(args...);
+        }
+
+        template<typename Class, Ret(Class::*Member)(Args... args)>
+        static Ret proto(void *instance, Args... args) {
+            return (static_cast<Class *>(instance)->*Member)(args...);
+        }
+
+        Sink(std::vector<call_type> &calls)
+            : calls{calls}
+        {}
+
+    public:
+        /**
+         * @brief Connects a free function to a signal.
+         *
+         * The signal handler performs checks to avoid multiple connections for
+         * free functions.
+         *
+         * @tparam Function A valid free function pointer.
+         */
+        template<Ret(*Function)(Args...)>
+        void connect() {
+            disconnect<Function>();
+            calls.emplace_back(nullptr, &proto<Function>);
+        }
+
+        /**
+         * @brief Connects a member function for a given instance to a signal.
+         *
+         * The signal isn't responsible for the connected object. Users must
+         * guarantee that the lifetime of the instance overcomes the one of the
+         * signal. On the other side, the signal handler performs checks to
+         * avoid multiple connections for the same member function of a given
+         * instance.
+         *
+         * @tparam Class Type of class to which the member function belongs.
+         * @tparam Member Member function to connect to the signal.
+         * @param instance A valid instance of type pointer to `Class`.
+         */
+        template <typename Class, Ret(Class::*Member)(Args...) = &Class::receive>
+        void connect(instance_type<Class> instance) {
+            disconnect<Class, Member>(instance);
+            calls.emplace_back(instance, &proto<Class, Member>);
+        }
+
+        /**
+         * @brief Disconnects a free function from a signal.
+         * @tparam Function A valid free function pointer.
+         */
+        template<Ret(*Function)(Args...)>
+        void disconnect() {
+            call_type target{nullptr, &proto<Function>};
+            calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
+        }
+
+        /**
+         * @brief Disconnects the given member function from a signal.
+         * @tparam Class Type of class to which the member function belongs.
+         * @tparam Member Member function to connect to the signal.
+         * @param instance A valid instance of type pointer to `Class`.
+         */
+        template<typename Class, Ret(Class::*Member)(Args...)>
+        void disconnect(instance_type<Class> instance) {
+            call_type target{instance, &proto<Class, Member>};
+            calls.erase(std::remove(calls.begin(), calls.end(), std::move(target)), calls.end());
+        }
+
+        /**
+         * @brief Removes all existing connections for the given instance.
+         * @tparam Class Type of class to which the member function belongs.
+         * @param instance A valid instance of type pointer to `Class`.
+         */
+        template<typename Class>
+        void disconnect(instance_type<Class> instance) {
+            auto func = [instance](const call_type &call) { return call.first == instance; };
+            calls.erase(std::remove_if(calls.begin(), calls.end(), std::move(func)), calls.end());
+        }
+
+        /**
+         * @brief Disconnects all the listeners from a signal.
+         */
+        void disconnect() {
+            calls.clear();
+        }
+
+    private:
+        std::vector<call_type> &calls;
+    };
+
     /**
      * @brief Number of listeners connected to the signal.
      * @return Number of listeners currently connected.
@@ -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.
      */
     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...);
         }
     }

+ 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 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:
     /*! @brief Unsigned integer type. */
     using size_type = std::size_t;
@@ -70,6 +51,121 @@ public:
     template<typename Class>
     using instance_type = std::shared_ptr<Class>;
 
+    /**
+     * @brief Sink implementation.
+     *
+     * A sink is an opaque object used to connect listeners to signals.<br/>
+     * The function type for a listener is the one of the signal to which it
+     * belongs.
+     *
+     * The clear separation between a signal and a sink permits to store the
+     * former as private data member without exposing the publish functionality
+     * to the users of a class.
+     */
+    class Sink final {
+        friend class Signal;
+
+        template<void(*Function)(Args...)>
+        static bool proto(std::weak_ptr<void> &, Args... args) {
+            Function(args...);
+            return true;
+        }
+
+        template<typename Class, void(Class::*Member)(Args...)>
+        static bool proto(std::weak_ptr<void> &wptr, Args... args) {
+            bool ret = false;
+
+            if(!wptr.expired()) {
+                auto ptr = std::static_pointer_cast<Class>(wptr.lock());
+                (ptr.get()->*Member)(args...);
+                ret = true;
+            }
+
+            return ret;
+        }
+
+        Sink(std::vector<call_type> &calls)
+            : calls{calls}
+        {}
+
+    public:
+        /**
+         * @brief Connects a free function to a signal.
+         *
+         * The signal handler performs checks to avoid multiple connections for
+         * free functions.
+         *
+         * @tparam Function A valid free function pointer.
+         */
+        template<void(*Function)(Args...)>
+        void connect() {
+            disconnect<Function>();
+            calls.emplace_back(std::weak_ptr<void>{}, &proto<Function>);
+        }
+
+        /**
+         * @brief Connects a member function for a given instance to a signal.
+         *
+         * The signal handler performs checks to avoid multiple connections for
+         * the same member function of a given instance.
+         *
+         * @tparam Class Type of class to which the member function belongs.
+         * @tparam Member Member function to connect to the signal.
+         * @param instance A valid instance of type pointer to `Class`.
+         */
+        template<typename Class, void(Class::*Member)(Args...) = &Class::receive>
+        void connect(instance_type<Class> instance) {
+            disconnect<Class, Member>(instance);
+            calls.emplace_back(std::move(instance), &proto<Class, Member>);
+        }
+
+        /**
+         * @brief Disconnects a free function from a signal.
+         * @tparam Function A valid free function pointer.
+         */
+        template<void(*Function)(Args...)>
+        void disconnect() {
+            calls.erase(std::remove_if(calls.begin(), calls.end(),
+                [](const call_type &call) { return call.second == &proto<Function> && !call.first.lock(); }
+            ), calls.end());
+        }
+
+        /**
+         * @brief Disconnects the given member function from a signal.
+         * @tparam Class Type of class to which the member function belongs.
+         * @tparam Member Member function to connect to the signal.
+         * @param instance A valid instance of type pointer to `Class`.
+         */
+        template<typename Class, void(Class::*Member)(Args...)>
+        void disconnect(instance_type<Class> instance) {
+            calls.erase(std::remove_if(calls.begin(), calls.end(),
+                [instance{std::move(instance)}](const call_type &call) { return call.second == &proto<Class, Member> && call.first.lock() == instance; }
+            ), calls.end());
+        }
+
+        /**
+         * @brief Removes all existing connections for the given instance.
+         * @tparam Class Type of class to which the member function belongs.
+         * @param instance A valid instance of type pointer to `Class`.
+         */
+        template<typename Class>
+        void disconnect(instance_type<Class> instance) {
+            calls.erase(std::remove_if(calls.begin(), calls.end(),
+                [instance{std::move(instance)}](const call_type &call) { return call.first.lock() == instance; }
+            ), calls.end());
+        }
+
+        /**
+         * @brief Disconnects all the listeners from a signal.
+         */
+        void disconnect() {
+            calls.clear();
+        }
+
+    private:
+        std::vector<call_type> &calls;
+    };
+
     /**
      * @brief Number of listeners connected to the signal.
      * @return Number of listeners currently connected.
@@ -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) {
         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...)) {
                 next.push_back(call);
             }

+ 0 - 1
test/CMakeLists.txt

@@ -117,7 +117,6 @@ add_test(NAME resource COMMAND resource)
 add_executable(
     signal
     $<TARGET_OBJECTS:odr>
-    entt/signal/bus.cpp
     entt/signal/delegate.cpp
     entt/signal/dispatcher.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/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) {
     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<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) {
     Dispatcher dispatcher;
 
-    dispatcher.template connect<Event>(receiver);
+    dispatcher.template sink<Event>().connect(receiver);
     dispatcher.template trigger<Event>();
     dispatcher.template enqueue<Event>();
 
@@ -28,7 +28,7 @@ void testDispatcher(Rec receiver) {
 
     receiver->reset();
 
-    dispatcher.template disconnect<Event>(receiver);
+    dispatcher.template sink<Event>().disconnect(receiver);
     dispatcher.template trigger<Event>();
     dispatcher.template enqueue<Event>();
     dispatcher.update();

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

@@ -3,6 +3,48 @@
 #include <gtest/gtest.h>
 #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) {
     using signal = entt::SigH<void(void)>;
 
@@ -19,69 +61,60 @@ TEST(SigH, Lifetime) {
 }
 
 TEST(SigH, Comparison) {
-    struct S {
-        void f() {}
-        void g() {}
-    };
-
     entt::SigH<void()> sig1;
     entt::SigH<void()> sig2;
 
     S s1;
     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_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_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_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);
 
-    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);
 }
 
-struct S {
-    static void f(int &v) { v = 42; }
-};
-
 TEST(SigH, Clear) {
     entt::SigH<void(int &)> sigh;
-    sigh.connect<&S::f>();
+    sigh.sink().connect<&S::f>();
 
     ASSERT_FALSE(sigh.empty());
 
-    sigh.clear();
+    sigh.sink().disconnect();
 
     ASSERT_TRUE(sigh.empty());
 }
@@ -90,7 +123,7 @@ TEST(SigH, Swap) {
     entt::SigH<void(int &)> sigh1;
     entt::SigH<void(int &)> sigh2;
 
-    sigh1.connect<&S::f>();
+    sigh1.sink().connect<&S::f>();
 
     ASSERT_FALSE(sigh1.empty());
     ASSERT_TRUE(sigh2.empty());
@@ -105,7 +138,7 @@ TEST(SigH, Functions) {
     entt::SigH<void(int &)> sigh;
     int v = 0;
 
-    sigh.connect<&S::f>();
+    sigh.sink().connect<&S::f>();
     sigh.publish(v);
 
     ASSERT_FALSE(sigh.empty());
@@ -113,87 +146,51 @@ TEST(SigH, Functions) {
     ASSERT_EQ(42, v);
 
     v = 0;
-    sigh.disconnect<&S::f>();
+    sigh.sink().disconnect<&S::f>();
     sigh.publish(v);
 
     ASSERT_TRUE(sigh.empty());
     ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
     ASSERT_EQ(0, v);
 
-    sigh.connect<&S::f>();
+    sigh.sink().connect<&S::f>();
 }
 
 TEST(SigH, Members) {
-    struct S {
-        bool f(int) { b = !b; return true; }
-        bool g(int) { return b; }
-        bool b{false};
-    };
-
     S s;
     S *ptr = &s;
     entt::SigH<bool(int)> sigh;
 
-    sigh.connect<S, &S::f>(ptr);
+    sigh.sink().connect<S, &S::g>(ptr);
     sigh.publish(42);
 
-    ASSERT_TRUE(s.b);
+    ASSERT_TRUE(s.k);
     ASSERT_FALSE(sigh.empty());
     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);
 
-    ASSERT_TRUE(s.b);
+    ASSERT_TRUE(s.k);
     ASSERT_TRUE(sigh.empty());
     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_EQ((entt::SigH<bool(int)>::size_type)2, sigh.size());
 
-    sigh.disconnect(ptr);
+    sigh.sink().disconnect(ptr);
 
     ASSERT_TRUE(sigh.empty());
     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) {
     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();
 
     ASSERT_FALSE(sigh_void.empty());
@@ -201,9 +198,9 @@ TEST(SigH, Collector) {
 
     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();
 
     ASSERT_FALSE(sigh_all.empty());
@@ -214,8 +211,8 @@ TEST(SigH, Collector) {
 
     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();
 
     ASSERT_FALSE(sigh_first.empty());

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

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