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

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

@@ -0,0 +1,335 @@
+#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 Default constructor, explicit on purpose. */
+    explicit Bus() noexcept = default;
+    /*! @brief Default destructor. */
+    ~Bus() noexcept = default;
+
+    /*! @brief Default copy constructor. */
+    Bus(const Bus &) = default;
+    /*! @brief Default move constructor. */
+    Bus(Bus &&) = default;
+
+    /*! @brief Default copy assignment operator. @return This bus. */
+    Bus & operator=(const Bus &) = default;
+    /*! @brief Default move assignment operator. @return This bus. */
+    Bus & operator=(Bus &&) = default;
+
+    /**
+     * @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 is 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 Default constructor, explicit on purpose. */
+    explicit Bus() noexcept = default;
+    /*! @brief Default destructor. */
+    virtual ~Bus() noexcept = default;
+
+    /*! @brief Default copy constructor. */
+    Bus(const Bus &) = default;
+    /*! @brief Default move constructor. */
+    Bus(Bus &&) = default;
+
+    /*! @brief Default copy assignment operator. @return This bus. */
+    Bus & operator=(const Bus &) = default;
+    /*! @brief Default move assignment operator. @return This bus. */
+    Bus & operator=(Bus &&) = default;
+
+    /**
+     * @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 is 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

+ 137 - 0
src/entt/signal/delegate.hpp

@@ -0,0 +1,137 @@
+#ifndef ENTT_SIGNAL_DELEGATE_HPP
+#define ENTT_SIGNAL_DELEGATE_HPP
+
+
+#include <utility>
+
+
+namespace entt {
+
+
+/**
+ * @brief Basic delegate implementation.
+ *
+ * Primary template isn't defined on purpose. All the specializations give a
+ * compile-time error unless the template parameter is a function type.
+ */
+template<typename>
+class Delegate;
+
+
+/**
+ * @brief A delegate class to send around functions and member functions.
+ *
+ * Unmanaged delegate for function pointers and member functions. Users of this
+ * class are in charge of disconnecting instances before deleting them.
+ *
+ * A delegate can be used as general purpose invoker with no memory overhead for
+ * free functions and member functions provided along with an instance on which
+ * to invoke them.
+ *
+ * @tparam Ret Return type of a function type.
+ * @tparam Args Types of arguments of a function type.
+ */
+template<typename Ret, typename... Args>
+class Delegate<Ret(Args...)> final {
+    using proto_type = Ret(*)(void *, Args...);
+    using stub_type = std::pair<void *, proto_type>;
+
+    static Ret fallback(void *, Args...) noexcept { return {}; }
+
+    template<Ret(*Function)(Args...)>
+    static Ret proto(void *, Args... args) {
+        return (Function)(args...);
+    }
+
+    template<typename Class, Ret(Class::*Member)(Args...)>
+    static Ret proto(void *instance, Args... args) {
+        return (static_cast<Class *>(instance)->*Member)(args...);
+    }
+
+public:
+    /*! @brief Default constructor, explicit on purpose. */
+    explicit Delegate() noexcept
+        : stub{std::make_pair(nullptr, &fallback)}
+    {}
+
+    /**
+     * @brief Binds a free function to a delegate.
+     * @tparam Function A valid free function pointer.
+     */
+    template<Ret(*Function)(Args...)>
+    void connect() noexcept {
+        stub = std::make_pair(nullptr, &proto<Function>);
+    }
+
+    /**
+     * @brief Connects a member function for a given instance to a delegate.
+     *
+     * The delegate isn't responsible for the connected object. Users must
+     * guarantee that the lifetime of the instance overcomes the one of the
+     * delegate.
+     *
+     * @tparam Class Type of class to which the member function belongs.
+     * @tparam Member Member function to connect to the delegate.
+     * @param instance A valid instance of type pointer to `Class`.
+     */
+    template<typename Class, Ret(Class::*Member)(Args...)>
+    void connect(Class *instance) noexcept {
+        stub = std::make_pair(instance, &proto<Class, Member>);
+    }
+
+    /**
+     * @brief Resets a delegate.
+     *
+     * After a reset, a delegate can be safely invoked with no effect.
+     */
+    void reset() noexcept {
+        stub = std::make_pair(nullptr, &fallback);
+    }
+
+    /**
+     * @brief Triggers a delegate.
+     * @param args Arguments to use to invoke the underlying function.
+     * @return The value returned by the underlying function.
+     */
+    Ret operator()(Args... args) {
+        return stub.second(stub.first, args...);
+    }
+
+    /**
+     * @brief Checks if the contents of the two delegates are different.
+     *
+     * Two delegates are identical if they contain the same listener.
+     *
+     * @param other Delegate with which to compare.
+     * @return True if the two delegates are identical, false otherwise.
+     */
+    bool operator==(const Delegate<Ret(Args...)> &other) const noexcept {
+        return stub.first == other.stub.first && stub.second == other.stub.second;
+    }
+
+private:
+    stub_type stub;
+};
+
+
+/**
+ * @brief Checks if the contents of the two delegates are different.
+ *
+ * Two delegates are identical if they contain the same listener.
+ *
+ * @tparam Ret Return type of a function type.
+ * @tparam Args Types of arguments of a function type.
+ * @param lhs A valid delegate object.
+ * @param rhs A valid delegate object.
+ * @return True if the two delegates are different, false otherwise.
+ */
+template<typename Ret, typename... Args>
+bool operator!=(const Delegate<Ret(Args...)> &lhs, const Delegate<Ret(Args...)> &rhs) noexcept {
+    return !(lhs == rhs);
+}
+
+
+}
+
+
+#endif // ENTT_SIGNAL_DELEGATE_HPP

+ 235 - 0
src/entt/signal/dispatcher.hpp

@@ -0,0 +1,235 @@
+#ifndef ENTT_SIGNAL_DISPATCHER_HPP
+#define ENTT_SIGNAL_DISPATCHER_HPP
+
+
+#include <vector>
+#include <memory>
+#include <utility>
+#include <cstdint>
+#include "../core/family.hpp"
+#include "signal.hpp"
+#include "sigh.hpp"
+
+
+namespace entt {
+
+
+/**
+ * @brief Basic dispatcher implementation.
+ *
+ * A dispatcher can be used either to trigger an immediate event or to enqueue
+ * events to be published all together once per tick.<br/>
+ * Listeners are provided in the form of member functions. For each event of
+ * type `Event`, listeners must have the following signature:
+ * `void(const Event &)`. Member functions named `receive` are automatically
+ * detected and registered or unregistered by the dispatcher.
+ *
+ * @tparam Sig Type of the signal handler to use.
+ */
+template<template<typename...> class Sig>
+class Dispatcher final {
+    using event_family = Family<struct InternalDispatcherEventFamily>;
+
+    template<typename Class, typename Event>
+    using instance_type = typename Sig<void(const Event &)>::template instance_type<Class>;
+
+    struct BaseSignalWrapper {
+        virtual ~BaseSignalWrapper() = default;
+        virtual void publish(std::size_t) = 0;
+    };
+
+    template<typename Event>
+    struct SignalWrapper final: BaseSignalWrapper {
+        void publish(std::size_t current) final override {
+            for(auto &&event: events[current]) {
+                signal.publish(event);
+            }
+
+            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));
+        }
+
+        template<typename... Args>
+        inline void trigger(Args&&... args) {
+            signal.publish({ std::forward<Args>(args)... });
+        }
+
+        template<typename... Args>
+        inline void enqueue(std::size_t current, Args&&... args) {
+            events[current].push_back({ std::forward<Args>(args)... });
+        }
+
+    private:
+        Sig<void(const Event &)> signal{};
+        std::vector<Event> events[2];
+    };
+
+    inline static std::size_t buffer(bool mode) {
+        return mode ? 0 : 1;
+    }
+
+    template<typename Event>
+    SignalWrapper<Event> & wrapper() {
+        auto type = event_family::type<Event>();
+
+        if(!(type < wrappers.size())) {
+            wrappers.resize(type + 1);
+        }
+
+        if(!wrappers[type]) {
+            wrappers[type] = std::make_unique<SignalWrapper<Event>>();
+        }
+
+        return static_cast<SignalWrapper<Event> &>(*wrappers[type]);
+    }
+
+public:
+    /*! @brief Default constructor, explicit on purpose. */
+    explicit Dispatcher() noexcept
+        : wrappers{}, mode{false}
+    {}
+
+    /*! @brief Default destructor. */
+    ~Dispatcher() = default;
+
+    /*! @brief Default copy constructor. */
+    Dispatcher(const Dispatcher &) = default;
+    /*! @brief Default move constructor. */
+    Dispatcher(Dispatcher &&) = default;
+
+    /*! @brief Default copy assignment operator. @return This dispatcher. */
+    Dispatcher & operator=(const Dispatcher &) = default;
+    /*! @brief Default move assignment operator. @return This dispatcher. */
+    Dispatcher & operator=(Dispatcher &&) = default;
+
+    /**
+     * @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.
+     *
+     * @warning
+     * Connecting a listener during an update may lead to unexpected behavior.
+     * Register listeners before or after invoking the update if possible.
+     *
+     * @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.
+     *
+     * A matching member function has the following signature:
+     * `void receive(const Event &)`. Member functions named `receive` are
+     * automatically detected and unregistered if available.
+     *
+     * @warning
+     * Disonnecting a listener during an update may lead to unexpected behavior.
+     * Unregister listeners before or after invoking the update if possible.
+     *
+     * @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.
+     */
+    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));
+    }
+
+    /**
+     * @brief Triggers an immediate event of the given type.
+     *
+     * All the listeners registered for the given type are immediately notified.
+     * The event is discarded after the execution.
+     *
+     * @tparam Event Type of event to trigger.
+     * @tparam Args Types of arguments to use to construct the event.
+     * @param args Arguments to use to construct the event.
+     */
+    template<typename Event, typename... Args>
+    void trigger(Args&&... args) {
+        wrapper<Event>().trigger(std::forward<Args>(args)...);
+    }
+
+    /**
+     * @brief Enqueues an event of the given type.
+     *
+     * An event of the given type is queued. No listener is invoked. Use the
+     * `update` member function to notify listeners when ready.
+     *
+     * @tparam Event Type of event to trigger.
+     * @tparam Args Types of arguments to use to construct the event.
+     * @param args Arguments to use to construct the event.
+     */
+    template<typename Event, typename... Args>
+    void enqueue(Args&&... args) {
+        wrapper<Event>().enqueue(buffer(mode), std::forward<Args>(args)...);
+    }
+
+    /**
+     * @brief Delivers all the pending events.
+     *
+     * This method is blocking and it doesn't return until all the events are
+     * delivered to the registered listeners. It's responsability of the users
+     * to reduce at a minimum the time spent in the bodies of the listeners.
+     */
+    void update() {
+        auto buf = buffer(mode);
+        mode = !mode;
+
+        for(auto &&wrapper: wrappers) {
+            if(wrapper) {
+                wrapper->publish(buf);
+            }
+        }
+    }
+
+private:
+    std::vector<std::unique_ptr<BaseSignalWrapper>> wrappers;
+    bool mode;
+};
+
+
+/**
+ * @brief Managed dispatcher.
+ *
+ * A managed dispatcher uses the Signal class template as an underlying type.
+ * The type of the instances is the one required by the signal handler:
+ * `std::shared_ptr<Class>` (a shared pointer).
+ */
+using ManagedDispatcher = Dispatcher<Signal>;
+
+
+/**
+ * @brief Unmanaged dispatcher.
+ *
+ * An unmanaged dispatcher uses the SigH class template as an underlying type.
+ * The type of the instances is the one required by the signal handler:
+ * `Class *` (a naked pointer).<br/>
+ * When it comes to work with this kind of dispatcher, users must guarantee that
+ * the lifetimes of the instances overcome the one of the dispatcher itself.
+ */
+using UnmanagedDispatcher = Dispatcher<SigH>;
+
+
+}
+
+
+#endif // ENTT_SIGNAL_DISPATCHER_HPP

+ 344 - 0
src/entt/signal/emitter.hpp

@@ -0,0 +1,344 @@
+#ifndef ENTT_SIGNAL_EMITTER_HPP
+#define ENTT_SIGNAL_EMITTER_HPP
+
+
+#include <type_traits>
+#include <functional>
+#include <algorithm>
+#include <utility>
+#include <cstdint>
+#include <memory>
+#include <vector>
+#include <list>
+
+
+namespace entt {
+
+
+/**
+ * @brief General purpose event emitter.
+ *
+ * The emitter class template follows the CRTP idiom. To create a custom emitter
+ * type, derived classes must inherit directly from the base class as:
+ *
+ * ```cpp
+ * struct MyEmitter: Emitter<MyEmitter> {
+ *     // ...
+ * }
+ * ```
+ *
+ * Handlers for the type of events are created internally on the fly. It's not
+ * required to specify in advance the full list of accepted types.<br/>
+ * Moreover, whenever an event is published, an emitter provides the listeners
+ * with a reference to itself along with a const reference to the event.
+ * Therefore listeners have an handy way to work with it without incurring in
+ * the need of capturing a reference to the emitter.
+ *
+ * @tparam Derived Actual type of emitter that extends the class template.
+ */
+template<typename Derived>
+class Emitter {
+    struct BaseHandler {
+        virtual ~BaseHandler() = default;
+        virtual bool empty() const noexcept = 0;
+        virtual void clear() noexcept = 0;
+    };
+
+    template<typename Event>
+    struct Handler final: BaseHandler {
+        using listener_type = std::function<void(const Event &, Derived &)>;
+        using element_type = std::pair<bool, listener_type>;
+        using container_type = std::list<element_type>;
+        using connection_type = typename container_type::iterator;
+
+        bool empty() const noexcept override {
+            auto pred = [](auto &&element){ return element.first; };
+
+            return std::all_of(onceL.cbegin(), onceL.cend(), pred) &&
+                    std::all_of(onL.cbegin(), onL.cend(), pred);
+        }
+
+        void clear() noexcept override {
+            if(publishing) {
+                auto func = [](auto &&element){ element.first = true; };
+                std::for_each(onceL.begin(), onceL.end(), func);
+                std::for_each(onL.begin(), onL.end(), func);
+            } else {
+                onceL.clear();
+                onL.clear();
+            }
+        }
+
+        inline connection_type once(listener_type listener) {
+            return onceL.emplace(onceL.cend(), false, std::move(listener));
+        }
+
+        inline connection_type on(listener_type listener) {
+            return onL.emplace(onL.cend(), false, std::move(listener));
+        }
+
+        void erase(connection_type conn) noexcept {
+            conn->first = true;
+
+            if(!publishing) {
+                auto pred = [](auto &&element){ return element.first; };
+                onceL.remove_if(pred);
+                onL.remove_if(pred);
+            }
+        }
+
+        void publish(const Event &event, Derived &ref) {
+            container_type currentL;
+            onceL.swap(currentL);
+
+            auto func = [&event, &ref](auto &&element) {
+                return element.first ? void() : element.second(event, ref);
+            };
+
+            publishing = true;
+
+            std::for_each(onL.rbegin(), onL.rend(), func);
+            std::for_each(currentL.rbegin(), currentL.rend(), func);
+
+            publishing = false;
+
+            onL.remove_if([](auto &&element){ return element.first; });
+        }
+
+    private:
+        bool publishing{false};
+        container_type onceL{};
+        container_type onL{};
+    };
+
+    static std::size_t next() noexcept {
+        static std::size_t counter = 0;
+        return counter++;
+    }
+
+    template<typename>
+    static std::size_t type() noexcept {
+        static std::size_t value = next();
+        return value;
+    }
+
+    template<typename Event>
+    Handler<Event> & handler() noexcept {
+        std::size_t family = type<Event>();
+
+        if(!(family < handlers.size())) {
+            handlers.resize(family+1);
+        }
+
+        if(!handlers[family]) {
+            handlers[family] = std::make_unique<Handler<Event>>();
+        }
+
+        return static_cast<Handler<Event> &>(*handlers[family]);
+    }
+
+public:
+    /** @brief Type of listeners accepted for the given type of event. */
+    template<typename Event>
+    using Listener = typename Handler<Event>::listener_type;
+
+    /**
+     * @brief Generic connection type for events.
+     *
+     * Type of the connection object returned by the event emitter whenever a
+     * listener for the given type is registered.<br/>
+     * It can be used to break connections still in use.
+     *
+     * @tparam Event Type of event for which the connection is created.
+     */
+    template<typename Event>
+    struct Connection final: private Handler<Event>::connection_type {
+        /** @brief Event emitters are friend classes of connections. */
+        friend class Emitter;
+
+        /*! @brief Default constructor, explicit on purpose. */
+        explicit Connection() = default;
+
+        /*! @brief Default copy constructor. */
+        Connection(const Connection &) = default;
+        /*! @brief Default move constructor. */
+        Connection(Connection &&) = default;
+
+        /*! @brief Default destructor. */
+        ~Connection() = default;
+
+        /**
+         * @brief Creates a connection that wraps its underlying instance.
+         * @param conn A connection object to wrap.
+         */
+        Connection(typename Handler<Event>::connection_type conn)
+            : Handler<Event>::connection_type{std::move(conn)}
+        {}
+
+        /**
+         * @brief Default copy assignament operator.
+         * @return This connection.
+         */
+        Connection & operator=(const Connection &) = default;
+
+        /**
+         * @brief Default move assignment operator.
+         * @return This connection.
+         */
+        Connection & operator=(Connection &&) = default;
+    };
+
+    /*! @brief Default constructor, explicit on purpose. */
+    explicit Emitter() noexcept = default;
+
+    /*! @brief Copying an emitter isn't allowed. */
+    Emitter(const Emitter &) = delete;
+    /*! @brief Default move constructor. */
+    Emitter(Emitter &&) = default;
+
+    /*! @brief Default destructor. */
+    virtual ~Emitter() noexcept {
+        static_assert(std::is_base_of<Emitter<Derived>, Derived>::value, "!");
+    }
+
+    /*! @brief Copying an emitter isn't allowed. @return This emitter. */
+    Emitter & operator=(const Emitter &) = delete;
+    /*! @brief Default move assignament operator. @return This emitter. */
+    Emitter & operator=(Emitter &&) = default;
+
+    /**
+     * @brief Emits the given event.
+     *
+     * All the listeners registered for the specific event type are invoked with
+     * the given event. The event type must either have a proper constructor for
+     * the arguments provided or be an aggregate type.
+     *
+     * @tparam Event Type of event to publish.
+     * @tparam Args Types of arguments to use to construct the event.
+     * @param args Parameters to use to initialize the event.
+     */
+    template<typename Event, typename... Args>
+    void publish(Args&&... args) {
+        handler<Event>().publish({ std::forward<Args>(args)... }, *static_cast<Derived *>(this));
+    }
+
+    /**
+     * @brief Registers a long-lived listener with the event emitter.
+     *
+     * This method can be used to register a listener designed to be invoked
+     * more than once for the given event type.<br/>
+     * The connection returned by the method can be freely discarded. It's meant
+     * to be used later to disconnect the listener if required.
+     *
+     * The listener is as a callable object that can be moved and the type of
+     * which is `void(const Event &, Derived &)`.
+     *
+     * @note
+     * Whenever an event is emitted, the emitter provides the listener with a
+     * reference to the derived class. Listeners don't have to capture those
+     * instances for later uses.
+     *
+     * @tparam Event Type of event to which to connect the listener.
+     * @param listener The listener to register.
+     * @return Connection object that can be used to disconnect the listener.
+     */
+    template<typename Event>
+    Connection<Event> on(Listener<Event> listener) {
+        return handler<Event>().on(std::move(listener));
+    }
+
+    /**
+     * @brief Registers a short-lived listener with the event emitter.
+     *
+     * This method can be used to register a listener designed to be invoked
+     * only once for the given event type.<br/>
+     * The connection returned by the method can be freely discarded. It's meant
+     * to be used later to disconnect the listener if required.
+     *
+     * The listener is as a callable object that can be moved and the type of
+     * which is `void(const Event &, Derived &)`.
+     *
+     * @note
+     * Whenever an event is emitted, the emitter provides the listener with a
+     * reference to the derived class. Listeners don't have to capture those
+     * instances for later uses.
+     *
+     * @tparam Event Type of event to which to connect the listener.
+     * @param listener The listener to register.
+     * @return Connection object that can be used to disconnect the listener.
+     */
+    template<typename Event>
+    Connection<Event> once(Listener<Event> listener) {
+        return handler<Event>().once(std::move(listener));
+    }
+
+    /**
+     * @brief Disconnects a listener from the event emitter.
+     *
+     * Do not use twice the same connection to disconnect a listener, it results
+     * in undefined behavior. Once used, discard the connection object.
+     *
+     * @tparam Event Type of event of the connection.
+     * @param conn A valid connection.
+     */
+    template<typename Event>
+    void erase(Connection<Event> conn) noexcept {
+        handler<Event>().erase(std::move(conn));
+    }
+
+    /**
+     * @brief Disconnects all the listeners for the given event type.
+     *
+     * All the connections previously returned for the given event are
+     * invalidated. Using them results in undefined behaviour.
+     *
+     * @tparam Event Type of event to reset.
+     */
+    template<typename Event>
+    void clear() noexcept {
+        handler<Event>().clear();
+    }
+
+    /**
+     * @brief Disconnects all the listeners.
+     *
+     * All the connections previously returned are invalidated. Using them
+     * results in undefined behaviour.
+     */
+    void clear() noexcept {
+        std::for_each(handlers.begin(), handlers.end(),
+                      [](auto &&handler){ if(handler) { handler->clear(); } });
+    }
+
+    /**
+     * @brief Checks if there are listeners registered for the specific event.
+     * @tparam Event Type of event to test.
+     * @return True if there are no listeners registered, false otherwise.
+     */
+    template<typename Event>
+    bool empty() const noexcept {
+        std::size_t family = type<Event>();
+
+        return (!(family < handlers.size()) ||
+                !handlers[family] ||
+                static_cast<Handler<Event> &>(*handlers[family]).empty());
+    }
+
+    /**
+     * @brief Checks if there are listeners registered with the event emitter.
+     * @return True if there are no listeners registered, false otherwise.
+     */
+    bool empty() const noexcept {
+        return std::all_of(handlers.cbegin(), handlers.cend(),
+                           [](auto &&handler){ return !handler || handler->empty(); });
+    }
+
+private:
+    std::vector<std::unique_ptr<BaseHandler>> handlers{};
+};
+
+
+}
+
+
+#endif // ENTT_SIGNAL_EMITTER_HPP

+ 46 - 48
src/entt/signal/sigh.hpp

@@ -71,24 +71,27 @@ using DefaultCollectorType = typename DefaultCollector<Function>::collector_type
 
 
 /**
- * @brief Signal handler.
+ * @brief Unmanaged signal handler declaration.
  *
  * Primary template isn't defined on purpose. All the specializations give a
  * compile-time error unless the template parameter is a function type.
+ *
+ * @tparam Function A valid function type.
+ * @tparam Collector Type of collector to use, if any.
  */
-template<typename Function, typename = DefaultCollectorType<Function>>
+template<typename Function, typename Collector = DefaultCollectorType<Function>>
 class SigH;
 
 
 /**
- * @brief Signal handler.
+ * @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 be used later to notify a bunch of listeners.
+ * * Creating signals used later to notify a bunch of listeners.
  * * Collecting results from a set of functions like in a voting system.
  *
  * The default collector does nothing. To properly collect data, define and use
@@ -98,8 +101,8 @@ class SigH;
  *   otherwise.
  *
  * @tparam Ret Return type of a function type.
- * @tparam Args Types of the arguments of a function type.
- * @tparam Collector The type of the collector to use if any.
+ * @tparam Args Types of arguments of a function type.
+ * @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> {
@@ -121,6 +124,13 @@ public:
     /*! @brief Collector type. */
     using collector_type = Collector;
 
+    /**
+     * @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 Default constructor, explicit on purpose. */
     explicit SigH() noexcept = default;
 
@@ -129,7 +139,7 @@ public:
 
     /**
      * @brief Copy constructor, listeners are also connected to this signal.
-     * @param other A signal to be used as source to initialize this instance.
+     * @param other A signal to use as source to initialize this instance.
      */
     SigH(const SigH &other)
         : calls{other.calls}
@@ -137,15 +147,18 @@ public:
 
     /**
      * @brief Default move constructor.
-     * @param other A signal to be used as source to initialize this instance.
+     * @param other A signal to use as source to initialize this instance.
      */
     SigH(SigH &&other): SigH{} {
         swap(*this, other);
     }
 
     /**
-     * @brief Assignment operator, listeners are also connected to this signal.
-     * @param other A signal to be used as source to initialize this instance.
+     * @brief Copy assignment operator.
+     *
+     * Listeners are also connected to this signal.
+     *
+     * @param other A signal to use as source to initialize this instance.
      * @return This signal.
      */
     SigH & operator=(const SigH &other) {
@@ -154,8 +167,8 @@ public:
     }
 
     /**
-     * @brief Default move operator.
-     * @param other A signal to be used as source to initialize this instance.
+     * @brief Move assignment operator.
+     * @param other A signal to use as source to initialize this instance.
      * @return This signal.
      */
     SigH & operator=(SigH &&other) {
@@ -164,15 +177,15 @@ public:
     }
 
     /**
-     * @brief The number of listeners connected to the signal.
-     * @return The number of listeners currently connected.
+     * @brief Number of listeners connected to the signal.
+     * @return Number of listeners currently connected.
      */
     size_type size() const noexcept {
         return calls.size();
     }
 
     /**
-     * @brief Returns true is at least a listener is connected to the signal.
+     * @brief Returns false is at least a listener is connected to the signal.
      * @return True if the signal has no listeners connected, false otherwise.
      */
     bool empty() const noexcept {
@@ -180,16 +193,15 @@ public:
     }
 
     /**
-     * @brief Disconnects all the listeners from the signal.
+     * @brief Disconnects all the listeners from a signal.
      */
     void clear() noexcept {
         calls.clear();
     }
 
     /**
-     * @brief Connects a free function to the signal.
+     * @brief Connects a free function to a signal.
      *
-     * @note
      * The signal handler performs checks to avoid multiple connections for free
      * functions.
      *
@@ -202,28 +214,25 @@ public:
     }
 
     /**
-     * @brief Connects the member function for the given instance to the signal.
+     * @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.
+     * signal. On the other side, the signal handler performs checks to avoid
+     * multiple connections for the same member function of a given instance.
      *
-     * @warning
-     * The signal handler performs checks to avoid multiple connections for the
-     * same member function of a given instance.
-     *
-     * @tparam Class The type of the class to which the member function belongs.
-     * @tparam Member The member function to connect to the 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 connect(Class *instance) {
+    void connect(instance_type<Class> instance) {
         disconnect<Class, Member>(instance);
         calls.emplace_back(instance, &proto<Class, Member>);
     }
 
     /**
-     * @brief Disconnects a free function from the signal.
+     * @brief Disconnects a free function from a signal.
      * @tparam Function A valid free function pointer.
      */
     template<Ret(*Function)(Args...)>
@@ -233,30 +242,30 @@ public:
     }
 
     /**
-     * @brief Disconnects the given member function from the signal.
-     * @tparam Class The type of the class to which the member function belongs.
-     * @tparam Member The member function to connect to the signal.
+     * @brief Disconnects the given member function from a signal.
+     * @tparam Class Type of class to which the member function belongs.
+     * @tparam Member Member function to connect to the signal.
      * @param instance A valid instance of type pointer to `Class`.
      */
     template<typename Class, Ret(Class::*Member)(Args...)>
-    void disconnect(Class *instance) {
+    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 The type of the class to which the member function belongs.
+     * @tparam Class Type of class to which the member function belongs.
      * @param instance A valid instance of type pointer to `Class`.
      */
     template<typename Class>
-    void disconnect(Class *instance) {
+    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 Triggers the signal.
+     * @brief Triggers a signal.
      *
      * All the listeners are notified. Order isn't guaranteed.
      *
@@ -305,7 +314,7 @@ public:
      * @return True if the two signals are identical, false otherwise.
      */
     bool operator==(const SigH &other) const noexcept {
-        return (calls.size() == other.calls.size()) && std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin());
+        return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend());
     }
 
 private:
@@ -320,7 +329,7 @@ private:
  * listeners registered exactly in the same order.
  *
  * @tparam Ret Return type of a function type.
- * @tparam Args Types of the arguments of a function type.
+ * @tparam Args Types of arguments of a function type.
  * @param lhs A valid signal object.
  * @param rhs A valid signal object.
  * @return True if the two signals are different, false otherwise.
@@ -331,17 +340,6 @@ bool operator!=(const SigH<Ret(Args...)> &lhs, const SigH<Ret(Args...)> &rhs) no
 }
 
 
-/**
- * @brief Event handler.
- *
- * Unmanaged event handler. Collecting data for this kind of signals doesn't
- * make sense at all. Its sole purpose is to provide the listeners with the
- * given event.
- */
-template<typename Event>
-using EventH = SigH<void(const Event &)>;
-
-
 }
 
 

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

@@ -0,0 +1,272 @@
+#ifndef ENTT_SIGNAL_SIGNAL_HPP
+#define ENTT_SIGNAL_SIGNAL_HPP
+
+
+#include <memory>
+#include <vector>
+#include <utility>
+#include <cstdint>
+#include <iterator>
+#include <algorithm>
+
+
+namespace entt {
+
+
+/**
+ * @brief Managed signal handler declaration.
+ *
+ * Primary template isn't defined on purpose. All the specializations give a
+ * compile-time error unless the template parameter is a function type.
+ */
+template<typename>
+class Signal;
+
+
+/**
+ * @brief Managed signal handler definition.
+ *
+ * Managed signal handler. It works with weak pointers to classes and pointers
+ * to member functions as well as pointers to free functions. References are
+ * automatically removed when the instances to which they point are freed.
+ *
+ * This class can be used to create signals used later to notify a bunch of
+ * listeners.
+ *
+ * @tparam Args Types of arguments of a function type.
+ */
+template<typename... Args>
+class Signal<void(Args...)> final {
+    using proto_type = bool(*)(std::weak_ptr<void> &, Args...);
+    using call_type = std::pair<std::weak_ptr<void>, proto_type>;
+
+    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;
+
+    /**
+     * @brief Instance type when it comes to connecting member functions.
+     * @tparam Class Type of class to which the member function belongs.
+     */
+    template<typename Class>
+    using instance_type = std::shared_ptr<Class>;
+
+    /*! @brief Default constructor, explicit on purpose. */
+    explicit Signal() noexcept = default;
+
+    /*! @brief Default destructor. */
+    ~Signal() noexcept = default;
+
+    /**
+     * @brief Copy constructor, listeners are also connected to this signal.
+     * @param other A signal to use as source to initialize this instance.
+     */
+    Signal(const Signal &other)
+        : calls{other.calls}
+    {}
+
+    /**
+     * @brief Default move constructor.
+     * @param other A signal to use as source to initialize this instance.
+     */
+    Signal(Signal &&other): Signal{} {
+        swap(*this, other);
+    }
+
+    /**
+     * @brief Copy assignment operator.
+     *
+     * Listeners are also connected to this signal.
+     *
+     * @param other A signal to use as source to initialize this instance.
+     * @return This signal.
+     */
+    Signal & operator=(const Signal &other) {
+        calls = other.calls;
+        return *this;
+    }
+
+    /**
+     * @brief Move assignment operator.
+     * @param other A signal to use as source to initialize this instance.
+     * @return This signal.
+     */
+    Signal & operator=(Signal &&other) {
+        swap(*this, other);
+        return *this;
+    }
+
+    /**
+     * @brief Number of listeners connected to the signal.
+     * @return Number of listeners currently connected.
+     */
+    size_type size() const noexcept {
+        return calls.size();
+    }
+
+    /**
+     * @brief Returns false is at least a listener is connected to the signal.
+     * @return True if the signal has no listeners connected, false otherwise.
+     */
+    bool empty() const noexcept {
+        return calls.empty();
+    }
+
+    /**
+     * @brief Disconnects all the listeners from a signal.
+     */
+    void clear() noexcept {
+        calls.clear();
+    }
+
+    /**
+     * @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...)>
+    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 Triggers a signal.
+     *
+     * All the listeners are notified. Order isn't guaranteed.
+     *
+     * @param args Arguments to use to invoke listeners.
+     */
+    void publish(Args... args) {
+        for(auto it = calls.rbegin(), end = calls.rend(); it != end; it++) {
+            if(!(it->second)(it->first, args...)) {
+                calls.erase(std::next(it).base());
+            }
+        }
+    }
+
+    /**
+     * @brief Swaps listeners between the two signals.
+     * @param lhs A valid signal object.
+     * @param rhs A valid signal object.
+     */
+    friend void swap(Signal &lhs, Signal &rhs) {
+        using std::swap;
+        swap(lhs.calls, rhs.calls);
+    }
+
+    /**
+     * @brief Checks if the contents of the two signals are identical.
+     *
+     * Two signals are identical if they have the same size and the same
+     * listeners registered exactly in the same order.
+     *
+     * @param other Signal with which to compare.
+     * @return True if the two signals are identical, false otherwise.
+     */
+    bool operator==(const Signal &other) const noexcept {
+        return std::equal(calls.cbegin(), calls.cend(), other.calls.cbegin(), other.calls.cend(), [](const auto &lhs, const auto &rhs) {
+            return (lhs.second == rhs.second) && (lhs.first.lock() == rhs.first.lock());
+        });
+    }
+
+private:
+    std::vector<call_type> calls;
+};
+
+
+/**
+ * @brief Checks if the contents of the two signals are different.
+ *
+ * Two signals are identical if they have the same size and the same
+ * listeners registered exactly in the same order.
+ *
+ * @tparam Args Types of arguments of a function type.
+ * @param lhs A valid signal object.
+ * @param rhs A valid signal object.
+ * @return True if the two signals are different, false otherwise.
+ */
+template<typename... Args>
+bool operator!=(const Signal<void(Args...)> &lhs, const Signal<void(Args...)> &rhs) noexcept {
+    return !(lhs == rhs);
+}
+
+
+}
+
+
+#endif // ENTT_SIGNAL_SIGNAL_HPP

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

@@ -0,0 +1,141 @@
+#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());
+}

+ 45 - 0
test/entt/signal/delegate.cpp

@@ -0,0 +1,45 @@
+#include <gtest/gtest.h>
+#include <entt/signal/delegate.hpp>
+
+int f(int i) {
+    return i*i;
+}
+
+struct S {
+    int f(int i) {
+        return i+i;
+    }
+};
+
+TEST(Delegate, Functionalities) {
+    entt::Delegate<int(int)> ffdel;
+    entt::Delegate<int(int)> mfdel;
+    S test;
+
+    ASSERT_EQ(ffdel(42), int{});
+    ASSERT_EQ(mfdel(42), int{});
+
+    ffdel.connect<&f>();
+    mfdel.connect<S, &S::f>(&test);
+
+    ASSERT_EQ(ffdel(3), 9);
+    ASSERT_EQ(mfdel(3), 6);
+
+    ffdel.reset();
+    mfdel.reset();
+
+    ASSERT_EQ(ffdel(42), int{});
+    ASSERT_EQ(mfdel(42), int{});
+}
+
+TEST(Delegate, Comparison) {
+    entt::Delegate<int(int)> delegate;
+    entt::Delegate<int(int)> def;
+    delegate.connect<&f>();
+
+    ASSERT_EQ(def, entt::Delegate<int(int)>{});
+    ASSERT_NE(def, delegate);
+
+    ASSERT_TRUE(def == entt::Delegate<int(int)>{});
+    ASSERT_TRUE (def != delegate);
+}

+ 47 - 0
test/entt/signal/dispatcher.cpp

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

+ 117 - 0
test/entt/signal/emitter.cpp

@@ -0,0 +1,117 @@
+#include <gtest/gtest.h>
+#include <entt/signal/emitter.hpp>
+
+struct TestEmitter: entt::Emitter<TestEmitter> {};
+
+struct FooEvent { int i; char c; };
+struct BarEvent {};
+
+TEST(Emitter, Clear) {
+    TestEmitter emitter;
+
+    ASSERT_TRUE(emitter.empty());
+
+    emitter.on<FooEvent>([](const auto &, const auto &){});
+
+    ASSERT_FALSE(emitter.empty());
+    ASSERT_FALSE(emitter.empty<FooEvent>());
+    ASSERT_TRUE(emitter.empty<BarEvent>());
+
+    emitter.clear<BarEvent>();
+
+    ASSERT_FALSE(emitter.empty());
+    ASSERT_FALSE(emitter.empty<FooEvent>());
+    ASSERT_TRUE(emitter.empty<BarEvent>());
+
+    emitter.clear<FooEvent>();
+
+    ASSERT_TRUE(emitter.empty());
+    ASSERT_TRUE(emitter.empty<FooEvent>());
+    ASSERT_TRUE(emitter.empty<BarEvent>());
+
+    emitter.on<FooEvent>([](const auto &, const auto &){});
+    emitter.on<BarEvent>([](const auto &, const auto &){});
+
+    ASSERT_FALSE(emitter.empty());
+    ASSERT_FALSE(emitter.empty<FooEvent>());
+    ASSERT_FALSE(emitter.empty<BarEvent>());
+
+    emitter.clear();
+
+    ASSERT_TRUE(emitter.empty());
+    ASSERT_TRUE(emitter.empty<FooEvent>());
+    ASSERT_TRUE(emitter.empty<BarEvent>());
+}
+
+TEST(Emitter, ClearPublishing) {
+    TestEmitter emitter;
+    bool invoked = false;
+
+    ASSERT_TRUE(emitter.empty());
+
+    emitter.on<BarEvent>([&invoked](const auto &, auto &em){
+        invoked = true;
+        em.clear();
+    });
+
+    emitter.publish<BarEvent>();
+
+    ASSERT_TRUE(emitter.empty());
+    ASSERT_TRUE(invoked);
+}
+
+TEST(Emitter, On) {
+    TestEmitter emitter;
+
+    emitter.on<FooEvent>([](const auto &, const auto &){});
+
+    ASSERT_FALSE(emitter.empty());
+    ASSERT_FALSE(emitter.empty<FooEvent>());
+
+    emitter.publish<FooEvent>(0, 'c');
+
+    ASSERT_FALSE(emitter.empty());
+    ASSERT_FALSE(emitter.empty<FooEvent>());
+}
+
+TEST(Emitter, Once) {
+    TestEmitter emitter;
+
+    emitter.once<BarEvent>([](const auto &, const auto &){});
+
+    ASSERT_FALSE(emitter.empty());
+    ASSERT_FALSE(emitter.empty<BarEvent>());
+
+    emitter.publish<BarEvent>();
+
+    ASSERT_TRUE(emitter.empty());
+    ASSERT_TRUE(emitter.empty<BarEvent>());
+}
+
+TEST(Emitter, OnceAndErase) {
+    TestEmitter emitter;
+
+    auto conn = emitter.once<FooEvent>([](const auto &, const auto &){});
+
+    ASSERT_FALSE(emitter.empty());
+    ASSERT_FALSE(emitter.empty<FooEvent>());
+
+    emitter.erase(conn);
+
+    ASSERT_TRUE(emitter.empty());
+    ASSERT_TRUE(emitter.empty<FooEvent>());
+}
+
+TEST(Emitter, OnAndErase) {
+    TestEmitter emitter;
+
+    auto conn = emitter.on<BarEvent>([](const auto &, const auto &){});
+
+    ASSERT_FALSE(emitter.empty());
+    ASSERT_FALSE(emitter.empty<BarEvent>());
+
+    emitter.erase(conn);
+
+    ASSERT_TRUE(emitter.empty());
+    ASSERT_TRUE(emitter.empty<BarEvent>());
+}

+ 13 - 0
test/entt/signal/sigh.cpp

@@ -75,6 +75,17 @@ struct S {
     static void f(int &v) { v = 42; }
 };
 
+TEST(SigH, Clear) {
+    entt::SigH<void(int &)> sigh;
+    sigh.connect<&S::f>();
+
+    ASSERT_FALSE(sigh.empty());
+
+    sigh.clear();
+
+    ASSERT_TRUE(sigh.empty());
+}
+
 TEST(SigH, Functions) {
     entt::SigH<void(int &)> sigh;
     int v = 0;
@@ -93,6 +104,8 @@ TEST(SigH, Functions) {
     ASSERT_TRUE(sigh.empty());
     ASSERT_EQ((entt::SigH<bool(int)>::size_type)0, sigh.size());
     ASSERT_EQ(0, v);
+
+    sigh.connect<&S::f>();
 }
 
 TEST(SigH, Members) {

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

@@ -0,0 +1,164 @@
+#include <memory>
+#include <utility>
+#include <gtest/gtest.h>
+#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;
+};
+
+int S::i = 0;
+
+TEST(Signal, Lifetime) {
+    using signal = entt::Signal<void(void)>;
+
+    ASSERT_NO_THROW(signal{});
+
+    signal src{}, other{};
+
+    ASSERT_NO_THROW(signal{src});
+    ASSERT_NO_THROW(signal{std::move(other)});
+    ASSERT_NO_THROW(src = other);
+    ASSERT_NO_THROW(src = std::move(other));
+
+    ASSERT_NO_THROW(delete new signal{});
+}
+
+TEST(Signal, Comparison) {
+    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);
+
+    ASSERT_FALSE(sig1 == sig2);
+    ASSERT_TRUE(sig1 != sig2);
+
+    sig1.disconnect<S, &S::f>(s1);
+    sig2.disconnect<S, &S::f>(s2);
+
+    sig1.connect<S, &S::f>(s1);
+    sig2.connect<S, &S::g>(s1);
+
+    ASSERT_FALSE(sig1 == sig2);
+    ASSERT_TRUE(sig1 != sig2);
+
+    sig1.disconnect<S, &S::f>(s1);
+    sig2.disconnect<S, &S::g>(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);
+
+    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.connect<S, &S::f>(s1);
+    sig1.connect<S, &S::g>(s1);
+    sig2.connect<S, &S::g>(s1);
+    sig2.connect<S, &S::f>(s1);
+
+    ASSERT_FALSE(sig1 == sig2);
+}
+
+TEST(Signal, Clear) {
+    entt::Signal<void(const int &)> signal;
+    signal.connect<&S::f>();
+
+    ASSERT_FALSE(signal.empty());
+
+    signal.clear();
+
+    ASSERT_TRUE(signal.empty());
+}
+
+TEST(Signal, Functions) {
+    entt::Signal<void(const int &)> signal;
+    auto val = S::i + 1;
+
+    signal.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);
+
+    signal.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);
+}
+
+TEST(Signal, Members) {
+    entt::Signal<void(const int &)> signal;
+    auto ptr = std::make_shared<S>();
+    auto val = S::i + 1;
+
+    signal.connect<S, &S::g>(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);
+
+    signal.disconnect<S, &S::g>(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);
+
+    ++val;
+
+    signal.connect<S, &S::g>(ptr);
+    signal.connect<S, &S::h>(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);
+
+    signal.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);
+}
+
+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;
+    ptr = nullptr;
+
+    ASSERT_FALSE(signal.empty());
+    ASSERT_EQ(S::i, val);
+
+    signal.publish(val);
+
+    ASSERT_TRUE(signal.empty());
+    ASSERT_EQ(S::i, val);
+}