Michele Caini пре 6 година
родитељ
комит
dd4c73b41f
5 измењених фајлова са 221 додато и 193 уклоњено
  1. 37 24
      src/entt/entity/registry.hpp
  2. 1 1
      src/entt/signal/dispatcher.hpp
  3. 2 2
      src/entt/signal/fwd.hpp
  4. 142 136
      src/entt/signal/sigh.hpp
  5. 39 30
      test/entt/signal/sigh.cpp

+ 37 - 24
src/entt/entity/registry.hpp

@@ -47,11 +47,6 @@ class basic_registry {
 
 
     template<typename Component>
     template<typename Component>
     struct pool_handler: storage<Entity, Component> {
     struct pool_handler: storage<Entity, Component> {
-        using reference_type = std::conditional_t<std::is_empty_v<Component>, const Component &, Component &>;
-
-        sigh<void(basic_registry &, const Entity, reference_type)> on_construct;
-        sigh<void(basic_registry &, const Entity, reference_type)> on_replace;
-        sigh<void(basic_registry &, const Entity)> on_destroy;
         void *group{};
         void *group{};
 
 
         pool_handler() ENTT_NOEXCEPT = default;
         pool_handler() ENTT_NOEXCEPT = default;
@@ -60,15 +55,27 @@ class basic_registry {
             : storage<Entity, Component>{other}
             : storage<Entity, Component>{other}
         {}
         {}
 
 
+        auto on_construct() ENTT_NOEXCEPT {
+            return sink{construction};
+        }
+
+        auto on_replace() ENTT_NOEXCEPT {
+            return sink{update};
+        }
+
+        auto on_destroy() ENTT_NOEXCEPT {
+            return sink{destruction};
+        }
+
         template<typename... Args>
         template<typename... Args>
         decltype(auto) assign(basic_registry &registry, const Entity entt, Args &&... args) {
         decltype(auto) assign(basic_registry &registry, const Entity entt, Args &&... args) {
             if constexpr(std::is_empty_v<Component>) {
             if constexpr(std::is_empty_v<Component>) {
                 storage<Entity, Component>::construct(entt);
                 storage<Entity, Component>::construct(entt);
-                on_construct.publish(registry, entt, Component{});
+                construction.publish(registry, entt, Component{});
                 return Component{std::forward<Args>(args)...};
                 return Component{std::forward<Args>(args)...};
             } else {
             } else {
                 auto &component = storage<Entity, Component>::construct(entt, std::forward<Args>(args)...);
                 auto &component = storage<Entity, Component>::construct(entt, std::forward<Args>(args)...);
-                on_construct.publish(registry, entt, component);
+                construction.publish(registry, entt, component);
                 return component;
                 return component;
             }
             }
         }
         }
@@ -80,17 +87,17 @@ class basic_registry {
             if constexpr(std::is_empty_v<Component>) {
             if constexpr(std::is_empty_v<Component>) {
                 storage<Entity, Component>::batch(first, last);
                 storage<Entity, Component>::batch(first, last);
 
 
-                if(!on_construct.empty()) {
+                if(!construction.empty()) {
                     std::for_each(first, last, [&registry, this](const auto entt) {
                     std::for_each(first, last, [&registry, this](const auto entt) {
-                        on_construct.publish(registry, entt, Component{});
+                        construction.publish(registry, entt, Component{});
                     });
                     });
                 }
                 }
             } else {
             } else {
                 component = storage<Entity, Component>::batch(first, last);
                 component = storage<Entity, Component>::batch(first, last);
 
 
-                if(!on_construct.empty()) {
+                if(!construction.empty()) {
                     std::for_each(first, last, [&registry, component, this](const auto entt) mutable {
                     std::for_each(first, last, [&registry, component, this](const auto entt) mutable {
-                        on_construct.publish(registry, entt, *(component++));
+                        construction.publish(registry, entt, *(component++));
                     });
                     });
                 }
                 }
             }
             }
@@ -99,7 +106,7 @@ class basic_registry {
         }
         }
 
 
         void remove(basic_registry &registry, const Entity entt) {
         void remove(basic_registry &registry, const Entity entt) {
-            on_destroy.publish(registry, entt);
+            destruction.publish(registry, entt);
             storage<Entity, Component>::destroy(entt);
             storage<Entity, Component>::destroy(entt);
         }
         }
 
 
@@ -107,14 +114,20 @@ class basic_registry {
         decltype(auto) replace(basic_registry &registry, const Entity entt, Args &&... args) {
         decltype(auto) replace(basic_registry &registry, const Entity entt, Args &&... args) {
             if constexpr(std::is_empty_v<Component>) {
             if constexpr(std::is_empty_v<Component>) {
                 ENTT_ASSERT((storage<Entity, Component>::has(entt)));
                 ENTT_ASSERT((storage<Entity, Component>::has(entt)));
-                on_replace.publish(registry, entt, Component{});
+                update.publish(registry, entt, Component{});
                 return Component{std::forward<Args>(args)...};
                 return Component{std::forward<Args>(args)...};
             } else {
             } else {
                 Component component{std::forward<Args>(args)...};
                 Component component{std::forward<Args>(args)...};
-                on_replace.publish(registry, entt, component);
+                update.publish(registry, entt, component);
                 return (storage<Entity, Component>::get(entt) = std::move(component));
                 return (storage<Entity, Component>::get(entt) = std::move(component));
             }
             }
         }
         }
+
+    private:
+        using reference_type = std::conditional_t<std::is_empty_v<Component>, const Component &, Component &>;
+        sigh<void(basic_registry &, const Entity, reference_type)> construction{};
+        sigh<void(basic_registry &, const Entity, reference_type)> update{};
+        sigh<void(basic_registry &, const Entity)> destruction{};
     };
     };
 
 
     template<typename Component>
     template<typename Component>
@@ -902,7 +915,7 @@ public:
      */
      */
     template<typename Component>
     template<typename Component>
     auto on_construct() ENTT_NOEXCEPT {
     auto on_construct() ENTT_NOEXCEPT {
-        return assure<Component>()->on_construct.sink();
+        return assure<Component>()->on_construct();
     }
     }
 
 
     /**
     /**
@@ -933,7 +946,7 @@ public:
      */
      */
     template<typename Component>
     template<typename Component>
     auto on_replace() ENTT_NOEXCEPT {
     auto on_replace() ENTT_NOEXCEPT {
-        return assure<Component>()->on_replace.sink();
+        return assure<Component>()->on_replace();
     }
     }
 
 
     /**
     /**
@@ -965,7 +978,7 @@ public:
      */
      */
     template<typename Component>
     template<typename Component>
     auto on_destroy() ENTT_NOEXCEPT {
     auto on_destroy() ENTT_NOEXCEPT {
-        return assure<Component>()->on_destroy.sink();
+        return assure<Component>()->on_destroy();
     }
     }
 
 
     /**
     /**
@@ -1095,7 +1108,7 @@ public:
      */
      */
     template<typename Component>
     template<typename Component>
     void reset() {
     void reset() {
-        if(auto *cpool = assure<Component>(); cpool->on_destroy.empty()) {
+        if(auto *cpool = assure<Component>(); cpool->on_destroy().empty()) {
             // no group set, otherwise the signal wouldn't be empty
             // no group set, otherwise the signal wouldn't be empty
             cpool->reset();
             cpool->reset();
         } else {
         } else {
@@ -1320,14 +1333,14 @@ public:
             ENTT_ASSERT((!std::get<pool_type<Owned> *>(curr->cpools)->group && ...));
             ENTT_ASSERT((!std::get<pool_type<Owned> *>(curr->cpools)->group && ...));
 
 
             ((std::get<pool_type<Owned> *>(curr->cpools)->group = curr), ...);
             ((std::get<pool_type<Owned> *>(curr->cpools)->group = curr), ...);
-            (std::get<pool_type<Owned> *>(curr->cpools)->on_construct.sink().template connect<&handler_type::template maybe_valid_if<Owned>>(curr), ...);
-            (std::get<pool_type<Owned> *>(curr->cpools)->on_destroy.sink().template connect<&handler_type::discard_if>(curr), ...);
+            (std::get<pool_type<Owned> *>(curr->cpools)->on_construct().template connect<&handler_type::template maybe_valid_if<Owned>>(curr), ...);
+            (std::get<pool_type<Owned> *>(curr->cpools)->on_destroy().template connect<&handler_type::discard_if>(curr), ...);
 
 
-            (std::get<pool_type<Get> *>(curr->cpools)->on_construct.sink().template connect<&handler_type::template maybe_valid_if<Get>>(curr), ...);
-            (std::get<pool_type<Get> *>(curr->cpools)->on_destroy.sink().template connect<&handler_type::discard_if>(curr), ...);
+            (std::get<pool_type<Get> *>(curr->cpools)->on_construct().template connect<&handler_type::template maybe_valid_if<Get>>(curr), ...);
+            (std::get<pool_type<Get> *>(curr->cpools)->on_destroy().template connect<&handler_type::discard_if>(curr), ...);
 
 
-            (std::get<pool_type<Exclude> *>(curr->cpools)->on_destroy.sink().template connect<&handler_type::template maybe_valid_if<Exclude>>(curr), ...);
-            (std::get<pool_type<Exclude> *>(curr->cpools)->on_construct.sink().template connect<&handler_type::discard_if>(curr), ...);
+            (std::get<pool_type<Exclude> *>(curr->cpools)->on_destroy().template connect<&handler_type::template maybe_valid_if<Exclude>>(curr), ...);
+            (std::get<pool_type<Exclude> *>(curr->cpools)->on_construct().template connect<&handler_type::discard_if>(curr), ...);
 
 
             const auto *cpool = std::min({
             const auto *cpool = std::min({
                 static_cast<sparse_set<Entity> *>(std::get<pool_type<Owned> *>(curr->cpools))...,
                 static_cast<sparse_set<Entity> *>(std::get<pool_type<Owned> *>(curr->cpools))...,

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

@@ -54,7 +54,7 @@ class dispatcher {
         }
         }
 
 
         sink_type sink() ENTT_NOEXCEPT {
         sink_type sink() ENTT_NOEXCEPT {
-            return signal.sink();
+            return entt::sink{signal};
         }
         }
 
 
         template<typename... Args>
         template<typename... Args>

+ 2 - 2
src/entt/signal/fwd.hpp

@@ -14,11 +14,11 @@ class delegate;
 
 
 /*! @class sink */
 /*! @class sink */
 template<typename>
 template<typename>
-class sink;
+struct sink;
 
 
 /*! @class sigh */
 /*! @class sigh */
 template<typename>
 template<typename>
-struct sigh;
+class sigh;
 
 
 
 
 }
 }

+ 142 - 136
src/entt/signal/sigh.hpp

@@ -16,7 +16,7 @@ namespace entt {
 
 
 
 
 /**
 /**
- * @brief Unmanaged signal handler declaration.
+ * @brief Sink implementation.
  *
  *
  * Primary template isn't defined on purpose. All the specializations give a
  * Primary template isn't defined on purpose. All the specializations give a
  * compile-time error unless the template parameter is a function type.
  * compile-time error unless the template parameter is a function type.
@@ -24,11 +24,11 @@ namespace entt {
  * @tparam Function A valid function type.
  * @tparam Function A valid function type.
  */
  */
 template<typename Function>
 template<typename Function>
-struct sigh;
+struct sink;
 
 
 
 
 /**
 /**
- * @brief Sink implementation.
+ * @brief Unmanaged signal handler declaration.
  *
  *
  * Primary template isn't defined on purpose. All the specializations give a
  * Primary template isn't defined on purpose. All the specializations give a
  * compile-time error unless the template parameter is a function type.
  * compile-time error unless the template parameter is a function type.
@@ -36,7 +36,121 @@ struct sigh;
  * @tparam Function A valid function type.
  * @tparam Function A valid function type.
  */
  */
 template<typename Function>
 template<typename Function>
-class sink;
+class sigh;
+
+
+/**
+ * @brief Unmanaged signal handler definition.
+ *
+ * Unmanaged signal handler. It works directly with naked pointers to classes
+ * and pointers to member functions as well as pointers to free functions. Users
+ * of this class are in charge of disconnecting instances before deleting them.
+ *
+ * This class serves mainly two purposes:
+ *
+ * * Creating signals to use later to notify a bunch of listeners.
+ * * Collecting results from a set of functions like in a voting system.
+ *
+ * @tparam Ret Return type of a function type.
+ * @tparam Args Types of arguments of a function type.
+ */
+template<typename Ret, typename... Args>
+class sigh<Ret(Args...)> {
+    /*! @brief A sink is allowed to modify a signal. */
+    friend struct sink<Ret(Args...)>;
+
+public:
+    /*! @brief Unsigned integer type. */
+    using size_type = typename std::vector<delegate<Ret(Args...)>>::size_type;
+    /*! @brief Sink type. */
+    using sink_type = entt::sink<Ret(Args...)>;
+
+    /**
+     * @brief Instance type when it comes to connecting member functions.
+     * @tparam Class Type of class to which the member function belongs.
+     */
+    template<typename Class>
+    using instance_type = Class *;
+
+    /**
+     * @brief Number of listeners connected to the signal.
+     * @return Number of listeners currently connected.
+     */
+    size_type size() const ENTT_NOEXCEPT {
+        return calls.size();
+    }
+
+    /**
+     * @brief Returns false if at least a listener is connected to the signal.
+     * @return True if the signal has no listeners connected, false otherwise.
+     */
+    bool empty() const ENTT_NOEXCEPT {
+        return calls.empty();
+    }
+
+    /**
+     * @brief Triggers a signal.
+     *
+     * All the listeners are notified. Order isn't guaranteed.
+     *
+     * @param args Arguments to use to invoke listeners.
+     */
+    void publish(Args... args) const {
+        for(auto pos = calls.size(); pos; --pos) {
+            calls[pos-1](args...);
+        }
+    }
+
+    /**
+     * @brief Collects return values from the listeners.
+     *
+     * The collector must expose a call operator with the following properties:
+     *
+     * * The return type is either `void` or such that it's convertible to
+     *   `bool`. In the second case, a true value will stop the iteration.
+     * * The list of parameters is empty if `Ret` is `void`, otherwise it
+     *   contains a single element such that `Ret` is convertible to it.
+     *
+     * @tparam Func Type of collector to use, if any.
+     * @param func A valid function object.
+     * @param args Arguments to use to invoke listeners.
+     */
+    template<typename Func>
+    void collect(Func func, Args... args) const {
+        bool stop = false;
+
+        for(auto pos = calls.size(); pos && !stop; --pos) {
+            if constexpr(std::is_void_v<Ret>) {
+                if constexpr(std::is_invocable_r_v<bool, Func>) {
+                    calls[pos-1](args...);
+                    stop = func();
+                } else {
+                    calls[pos-1](args...);
+                    func();
+                }
+            } else {
+                if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
+                    stop = func(calls[pos-1](args...));
+                } else {
+                    func(calls[pos-1](args...));
+                }
+            }
+        }
+    }
+
+    /**
+     * @brief Swaps listeners between the two signals.
+     * @param lhs A valid signal object.
+     * @param rhs A valid signal object.
+     */
+    friend void swap(sigh &lhs, sigh &rhs) {
+        using std::swap;
+        swap(lhs.calls, rhs.calls);
+    }
+
+private:
+    std::vector<delegate<Ret(Args...)>> calls;
+};
 
 
 
 
 /**
 /**
@@ -54,22 +168,21 @@ class sink;
  * @tparam Args Types of arguments of a function type.
  * @tparam Args Types of arguments of a function type.
  */
  */
 template<typename Ret, typename... Args>
 template<typename Ret, typename... Args>
-class sink<Ret(Args...)> {
-    /*! @brief A signal is allowed to create sinks. */
-    template<typename>
-    friend struct sigh;
-
-    sink(std::vector<delegate<Ret(Args...)>> *ref) ENTT_NOEXCEPT
-        : calls{ref}
+struct sink<Ret(Args...)> {
+    /**
+     * @brief Constructs a sink that is allowed to modify a given signal.
+     * @param ref A valid reference to a signal object.
+     */
+    sink(sigh<Ret(Args...)> &ref) ENTT_NOEXCEPT
+        : parent{&ref}
     {}
     {}
 
 
-public:
     /**
     /**
      * @brief Returns false if at least a listener is connected to the sink.
      * @brief Returns false if at least a listener is connected to the sink.
      * @return True if the sink has no listeners connected, false otherwise.
      * @return True if the sink has no listeners connected, false otherwise.
      */
      */
     bool empty() const ENTT_NOEXCEPT {
     bool empty() const ENTT_NOEXCEPT {
-        return calls->empty();
+        return parent->calls.empty();
     }
     }
 
 
     /**
     /**
@@ -85,7 +198,7 @@ public:
         disconnect<Function>();
         disconnect<Function>();
         delegate<Ret(Args...)> delegate{};
         delegate<Ret(Args...)> delegate{};
         delegate.template connect<Function>();
         delegate.template connect<Function>();
-        calls->emplace_back(std::move(delegate));
+        parent->calls.emplace_back(std::move(delegate));
     }
     }
 
 
     /**
     /**
@@ -109,7 +222,7 @@ public:
         disconnect<Candidate>(value_or_instance);
         disconnect<Candidate>(value_or_instance);
         delegate<Ret(Args...)> delegate{};
         delegate<Ret(Args...)> delegate{};
         delegate.template connect<Candidate>(value_or_instance);
         delegate.template connect<Candidate>(value_or_instance);
-        calls->emplace_back(std::move(delegate));
+        parent->calls.emplace_back(std::move(delegate));
     }
     }
 
 
     /**
     /**
@@ -118,9 +231,10 @@ public:
      */
      */
     template<auto Function>
     template<auto Function>
     void disconnect() {
     void disconnect() {
+        auto &calls = parent->calls;
         delegate<Ret(Args...)> delegate{};
         delegate<Ret(Args...)> delegate{};
         delegate.template connect<Function>();
         delegate.template connect<Function>();
-        calls->erase(std::remove(calls->begin(), calls->end(), std::move(delegate)), calls->end());
+        calls.erase(std::remove(calls.begin(), calls.end(), std::move(delegate)), calls.end());
     }
     }
 
 
     /**
     /**
@@ -132,11 +246,12 @@ public:
      */
      */
     template<auto Candidate, typename Type>
     template<auto Candidate, typename Type>
     void disconnect(Type *value_or_instance) {
     void disconnect(Type *value_or_instance) {
+        auto &calls = parent->calls;
         delegate<Ret(Args...)> delegate{};
         delegate<Ret(Args...)> delegate{};
         delegate.template connect<Candidate>(value_or_instance);
         delegate.template connect<Candidate>(value_or_instance);
-        calls->erase(std::remove_if(calls->begin(), calls->end(), [delegate](const auto &other) {
+        calls.erase(std::remove_if(calls.begin(), calls.end(), [delegate](const auto &other) {
             return other == delegate && other.instance() == delegate.instance();
             return other == delegate && other.instance() == delegate.instance();
-        }), calls->end());
+        }), calls.end());
     }
     }
 
 
     /**
     /**
@@ -145,142 +260,33 @@ public:
      * @param value_or_instance A valid pointer that fits the purpose.
      * @param value_or_instance A valid pointer that fits the purpose.
      */
      */
     void disconnect(const void *value_or_instance) {
     void disconnect(const void *value_or_instance) {
-        calls->erase(std::remove_if(calls->begin(), calls->end(), [value_or_instance](const auto &delegate) {
+        auto &calls = parent->calls;
+        calls.erase(std::remove_if(calls.begin(), calls.end(), [value_or_instance](const auto &delegate) {
             return value_or_instance == delegate.instance();
             return value_or_instance == delegate.instance();
-        }), calls->end());
+        }), calls.end());
     }
     }
 
 
     /*! @brief Disconnects all the listeners from a signal. */
     /*! @brief Disconnects all the listeners from a signal. */
     void disconnect() {
     void disconnect() {
-        calls->clear();
+        parent->calls.clear();
     }
     }
 
 
 private:
 private:
-    std::vector<delegate<Ret(Args...)>> *calls;
+    sigh<Ret(Args...)> *parent;
 };
 };
 
 
 
 
 /**
 /**
- * @brief Unmanaged signal handler definition.
+ * @brief Deduction guide.
  *
  *
- * Unmanaged signal handler. It works directly with naked pointers to classes
- * and pointers to member functions as well as pointers to free functions. Users
- * of this class are in charge of disconnecting instances before deleting them.
- *
- * This class serves mainly two purposes:
- *
- * * Creating signals to use later to notify a bunch of listeners.
- * * Collecting results from a set of functions like in a voting system.
+ * It allows to deduce the function type of a sink directly from the signal it
+ * refers to.
  *
  *
  * @tparam Ret Return type of a function type.
  * @tparam Ret Return type of a function type.
  * @tparam Args Types of arguments of a function type.
  * @tparam Args Types of arguments of a function type.
  */
  */
 template<typename Ret, typename... Args>
 template<typename Ret, typename... Args>
-struct sigh<Ret(Args...)> {
-    /*! @brief Unsigned integer type. */
-    using size_type = typename std::vector<delegate<Ret(Args...)>>::size_type;
-    /*! @brief Sink type. */
-    using sink_type = entt::sink<Ret(Args...)>;
-
-    /**
-     * @brief Instance type when it comes to connecting member functions.
-     * @tparam Class Type of class to which the member function belongs.
-     */
-    template<typename Class>
-    using instance_type = Class *;
-
-    /**
-     * @brief Number of listeners connected to the signal.
-     * @return Number of listeners currently connected.
-     */
-    size_type size() const ENTT_NOEXCEPT {
-        return calls.size();
-    }
-
-    /**
-     * @brief Returns false if at least a listener is connected to the signal.
-     * @return True if the signal has no listeners connected, false otherwise.
-     */
-    bool empty() const ENTT_NOEXCEPT {
-        return calls.empty();
-    }
-
-    /**
-     * @brief Returns a sink object for the given signal.
-     *
-     * A sink is an opaque object used to connect listeners to signals.<br/>
-     * The function type for a listener is the one of the signal to which it
-     * belongs. The order of invocation of the listeners isn't guaranteed.
-     *
-     * @return A temporary sink object.
-     */
-    sink_type sink() ENTT_NOEXCEPT {
-        return { &calls };
-    }
-
-    /**
-     * @brief Triggers a signal.
-     *
-     * All the listeners are notified. Order isn't guaranteed.
-     *
-     * @param args Arguments to use to invoke listeners.
-     */
-    void publish(Args... args) const {
-        for(auto pos = calls.size(); pos; --pos) {
-            calls[pos-1](args...);
-        }
-    }
-
-    /**
-     * @brief Collects return values from the listeners.
-     *
-     * The collector must expose a call operator with the following properties:
-     *
-     * * The return type is either `void` or such that it's convertible to
-     *   `bool`. In the second case, a true value will stop the iteration.
-     * * The list of parameters is empty if `Ret` is `void`, otherwise it
-     *   contains a single element such that `Ret` is convertible to it.
-     *
-     * @tparam Func Type of collector to use, if any.
-     * @param func A valid function object.
-     * @param args Arguments to use to invoke listeners.
-     */
-    template<typename Func>
-    void collect(Func func, Args... args) const {
-        bool stop = false;
-
-        for(auto pos = calls.size(); pos && !stop; --pos) {
-            if constexpr(std::is_void_v<Ret>) {
-                if constexpr(std::is_invocable_r_v<bool, Func>) {
-                    calls[pos-1](args...);
-                    stop = func();
-                } else {
-                    calls[pos-1](args...);
-                    func();
-                }
-            } else {
-                if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
-                    stop = func(calls[pos-1](args...));
-                } else {
-                    func(calls[pos-1](args...));
-                }
-            }
-        }
-    }
-
-    /**
-     * @brief Swaps listeners between the two signals.
-     * @param lhs A valid signal object.
-     * @param rhs A valid signal object.
-     */
-    friend void swap(sigh &lhs, sigh &rhs) {
-        using std::swap;
-        swap(lhs.calls, rhs.calls);
-    }
-
-private:
-    std::vector<delegate<Ret(Args...)>> calls;
-};
+sink(sigh<Ret(Args...)> &) ENTT_NOEXCEPT -> sink<Ret(Args...)>;
 
 
 
 
 }
 }

+ 39 - 30
test/entt/signal/sigh.cpp

@@ -41,33 +41,37 @@ TEST(SigH, Lifetime) {
 
 
 TEST(SigH, Clear) {
 TEST(SigH, Clear) {
     entt::sigh<void(int &)> sigh;
     entt::sigh<void(int &)> sigh;
-    sigh.sink().connect<&sigh_listener::f>();
+    entt::sink sink{sigh};
 
 
-    ASSERT_FALSE(sigh.sink().empty());
+    sink.connect<&sigh_listener::f>();
+
+    ASSERT_FALSE(sink.empty());
     ASSERT_FALSE(sigh.empty());
     ASSERT_FALSE(sigh.empty());
 
 
-    sigh.sink().disconnect();
+    sink.disconnect();
 
 
-    ASSERT_TRUE(sigh.sink().empty());
+    ASSERT_TRUE(sink.empty());
     ASSERT_TRUE(sigh.empty());
     ASSERT_TRUE(sigh.empty());
 }
 }
 
 
 TEST(SigH, Swap) {
 TEST(SigH, Swap) {
     entt::sigh<void(int &)> sigh1;
     entt::sigh<void(int &)> sigh1;
     entt::sigh<void(int &)> sigh2;
     entt::sigh<void(int &)> sigh2;
+    entt::sink sink1{sigh1};
+    entt::sink sink2{sigh2};
 
 
-    sigh1.sink().connect<&sigh_listener::f>();
+    sink1.connect<&sigh_listener::f>();
 
 
-    ASSERT_FALSE(sigh1.sink().empty());
-    ASSERT_TRUE(sigh2.sink().empty());
+    ASSERT_FALSE(sink1.empty());
+    ASSERT_TRUE(sink2.empty());
 
 
     ASSERT_FALSE(sigh1.empty());
     ASSERT_FALSE(sigh1.empty());
     ASSERT_TRUE(sigh2.empty());
     ASSERT_TRUE(sigh2.empty());
 
 
     std::swap(sigh1, sigh2);
     std::swap(sigh1, sigh2);
 
 
-    ASSERT_TRUE(sigh1.sink().empty());
-    ASSERT_FALSE(sigh2.sink().empty());
+    ASSERT_TRUE(sink1.empty());
+    ASSERT_FALSE(sink2.empty());
 
 
     ASSERT_TRUE(sigh1.empty());
     ASSERT_TRUE(sigh1.empty());
     ASSERT_FALSE(sigh2.empty());
     ASSERT_FALSE(sigh2.empty());
@@ -75,9 +79,10 @@ TEST(SigH, Swap) {
 
 
 TEST(SigH, Functions) {
 TEST(SigH, Functions) {
     entt::sigh<void(int &)> sigh;
     entt::sigh<void(int &)> sigh;
+    entt::sink sink{sigh};
     int v = 0;
     int v = 0;
 
 
-    sigh.sink().connect<&sigh_listener::f>();
+    sink.connect<&sigh_listener::f>();
     sigh.publish(v);
     sigh.publish(v);
 
 
     ASSERT_FALSE(sigh.empty());
     ASSERT_FALSE(sigh.empty());
@@ -85,19 +90,19 @@ TEST(SigH, Functions) {
     ASSERT_EQ(42, v);
     ASSERT_EQ(42, v);
 
 
     v = 0;
     v = 0;
-    sigh.sink().disconnect<&sigh_listener::f>();
+    sink.disconnect<&sigh_listener::f>();
     sigh.publish(v);
     sigh.publish(v);
 
 
     ASSERT_TRUE(sigh.empty());
     ASSERT_TRUE(sigh.empty());
     ASSERT_EQ(static_cast<entt::sigh<void(int &)>::size_type>(0), sigh.size());
     ASSERT_EQ(static_cast<entt::sigh<void(int &)>::size_type>(0), sigh.size());
     ASSERT_EQ(v, 0);
     ASSERT_EQ(v, 0);
 
 
-    sigh.sink().connect<&sigh_listener::f>();
+    sink.connect<&sigh_listener::f>();
 
 
     ASSERT_FALSE(sigh.empty());
     ASSERT_FALSE(sigh.empty());
     ASSERT_EQ(static_cast<entt::sigh<void(int &)>::size_type>(1), sigh.size());
     ASSERT_EQ(static_cast<entt::sigh<void(int &)>::size_type>(1), sigh.size());
 
 
-    sigh.sink().disconnect(nullptr);
+    sink.disconnect(nullptr);
 
 
     ASSERT_TRUE(sigh.empty());
     ASSERT_TRUE(sigh.empty());
     ASSERT_EQ(static_cast<entt::sigh<void(int &)>::size_type>(0), sigh.size());}
     ASSERT_EQ(static_cast<entt::sigh<void(int &)>::size_type>(0), sigh.size());}
@@ -105,28 +110,29 @@ TEST(SigH, Functions) {
 TEST(SigH, Members) {
 TEST(SigH, Members) {
     sigh_listener l1, l2;
     sigh_listener l1, l2;
     entt::sigh<bool(int)> sigh;
     entt::sigh<bool(int)> sigh;
+    entt::sink sink{sigh};
 
 
-    sigh.sink().connect<&sigh_listener::g>(&l1);
+    sink.connect<&sigh_listener::g>(&l1);
     sigh.publish(42);
     sigh.publish(42);
 
 
     ASSERT_TRUE(l1.k);
     ASSERT_TRUE(l1.k);
     ASSERT_FALSE(sigh.empty());
     ASSERT_FALSE(sigh.empty());
     ASSERT_EQ(static_cast<entt::sigh<bool(int)>::size_type>(1), sigh.size());
     ASSERT_EQ(static_cast<entt::sigh<bool(int)>::size_type>(1), sigh.size());
 
 
-    sigh.sink().disconnect<&sigh_listener::g>(&l1);
+    sink.disconnect<&sigh_listener::g>(&l1);
     sigh.publish(42);
     sigh.publish(42);
 
 
     ASSERT_TRUE(l1.k);
     ASSERT_TRUE(l1.k);
     ASSERT_TRUE(sigh.empty());
     ASSERT_TRUE(sigh.empty());
     ASSERT_EQ(static_cast<entt::sigh<bool(int)>::size_type>(0), sigh.size());
     ASSERT_EQ(static_cast<entt::sigh<bool(int)>::size_type>(0), sigh.size());
 
 
-    sigh.sink().connect<&sigh_listener::g>(&l1);
-    sigh.sink().connect<&sigh_listener::h>(&l2);
+    sink.connect<&sigh_listener::g>(&l1);
+    sink.connect<&sigh_listener::h>(&l2);
 
 
     ASSERT_FALSE(sigh.empty());
     ASSERT_FALSE(sigh.empty());
     ASSERT_EQ(static_cast<entt::sigh<bool(int)>::size_type>(2), sigh.size());
     ASSERT_EQ(static_cast<entt::sigh<bool(int)>::size_type>(2), sigh.size());
 
 
-    sigh.sink().disconnect(&l1);
+    sink.disconnect(&l1);
 
 
     ASSERT_FALSE(sigh.empty());
     ASSERT_FALSE(sigh.empty());
     ASSERT_EQ(static_cast<entt::sigh<bool(int)>::size_type>(1), sigh.size());
     ASSERT_EQ(static_cast<entt::sigh<bool(int)>::size_type>(1), sigh.size());
@@ -135,10 +141,11 @@ TEST(SigH, Members) {
 TEST(SigH, Collector) {
 TEST(SigH, Collector) {
     sigh_listener listener;
     sigh_listener listener;
     entt::sigh<bool(int)> sigh;
     entt::sigh<bool(int)> sigh;
+    entt::sink sink{sigh};
     int cnt = 0;
     int cnt = 0;
 
 
-    sigh.sink().connect<&sigh_listener::g>(&listener);
-    sigh.sink().connect<&sigh_listener::h>(&listener);
+    sink.connect<&sigh_listener::g>(&listener);
+    sink.connect<&sigh_listener::h>(&listener);
 
 
     listener.k = true;
     listener.k = true;
     sigh.collect([&listener, &cnt](bool value) {
     sigh.collect([&listener, &cnt](bool value) {
@@ -164,10 +171,11 @@ TEST(SigH, Collector) {
 TEST(SigH, CollectorVoid) {
 TEST(SigH, CollectorVoid) {
     sigh_listener listener;
     sigh_listener listener;
     entt::sigh<void(int)> sigh;
     entt::sigh<void(int)> sigh;
+    entt::sink sink{sigh};
     int cnt = 0;
     int cnt = 0;
 
 
-    sigh.sink().connect<&sigh_listener::g>(&listener);
-    sigh.sink().connect<&sigh_listener::h>(&listener);
+    sink.connect<&sigh_listener::g>(&listener);
+    sink.connect<&sigh_listener::h>(&listener);
     sigh.collect([&cnt]() { ++cnt; }, 42);
     sigh.collect([&cnt]() { ++cnt; }, 42);
 
 
     ASSERT_FALSE(sigh.empty());
     ASSERT_FALSE(sigh.empty());
@@ -184,22 +192,23 @@ TEST(SigH, CollectorVoid) {
 
 
 TEST(SigH, ConstNonConstNoExcept) {
 TEST(SigH, ConstNonConstNoExcept) {
     entt::sigh<void()> sigh;
     entt::sigh<void()> sigh;
+    entt::sink sink{sigh};
     const_nonconst_noexcept functor;
     const_nonconst_noexcept functor;
     const const_nonconst_noexcept cfunctor;
     const const_nonconst_noexcept cfunctor;
 
 
-    sigh.sink().connect<&const_nonconst_noexcept::f>(&functor);
-    sigh.sink().connect<&const_nonconst_noexcept::g>(&functor);
-    sigh.sink().connect<&const_nonconst_noexcept::h>(&cfunctor);
-    sigh.sink().connect<&const_nonconst_noexcept::i>(&cfunctor);
+    sink.connect<&const_nonconst_noexcept::f>(&functor);
+    sink.connect<&const_nonconst_noexcept::g>(&functor);
+    sink.connect<&const_nonconst_noexcept::h>(&cfunctor);
+    sink.connect<&const_nonconst_noexcept::i>(&cfunctor);
     sigh.publish();
     sigh.publish();
 
 
     ASSERT_EQ(functor.cnt, 2);
     ASSERT_EQ(functor.cnt, 2);
     ASSERT_EQ(cfunctor.cnt, 2);
     ASSERT_EQ(cfunctor.cnt, 2);
 
 
-    sigh.sink().disconnect<&const_nonconst_noexcept::f>(&functor);
-    sigh.sink().disconnect<&const_nonconst_noexcept::g>(&functor);
-    sigh.sink().disconnect<&const_nonconst_noexcept::h>(&cfunctor);
-    sigh.sink().disconnect<&const_nonconst_noexcept::i>(&cfunctor);
+    sink.disconnect<&const_nonconst_noexcept::f>(&functor);
+    sink.disconnect<&const_nonconst_noexcept::g>(&functor);
+    sink.disconnect<&const_nonconst_noexcept::h>(&cfunctor);
+    sink.disconnect<&const_nonconst_noexcept::i>(&cfunctor);
     sigh.publish();
     sigh.publish();
 
 
     ASSERT_EQ(functor.cnt, 2);
     ASSERT_EQ(functor.cnt, 2);