#ifndef ENTT_SIGNAL_SIGH_HPP #define ENTT_SIGNAL_SIGH_HPP #include #include #include #include #include #include "delegate.hpp" #include "fwd.hpp" namespace entt { /** * @brief Sink class. * * Primary template isn't defined on purpose. All the specializations give a * compile-time error unless the template parameter is a function type. * * @tparam Type A valid signal handler type. */ template class sink; /** * @brief Unmanaged signal handler. * * Primary template isn't defined on purpose. All the specializations give a * compile-time error unless the template parameter is a function type. * * @tparam Type A valid function type. * @tparam Allocator Type of allocator used to manage memory and elements. */ template class sigh; /** * @brief Unmanaged signal handler. * * It works directly with references 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. * @tparam Allocator Type of allocator used to manage memory and elements. */ template class sigh { friend class sink>; using alloc_traits = std::allocator_traits; using delegate_type = delegate; using container_type = std::vector>; public: /*! @brief Allocator type. */ using allocator_type = Allocator; /*! @brief Unsigned integer type. */ using size_type = std::size_t; /*! @brief Sink type. */ using sink_type = sink>; /*! @brief Default constructor. */ sigh() noexcept(std::is_nothrow_default_constructible_v && std::is_nothrow_constructible_v) : sigh{allocator_type{}} {} /** * @brief Constructs a signal handler with a given allocator. * @param allocator The allocator to use. */ explicit sigh(const allocator_type &allocator) noexcept(std::is_nothrow_constructible_v) : calls{allocator} {} /** * @brief Copy constructor. * @param other The instance to copy from. */ sigh(const sigh &other) noexcept(std::is_nothrow_copy_constructible_v) : calls{other.calls} {} /** * @brief Allocator-extended copy constructor. * @param other The instance to copy from. * @param allocator The allocator to use. */ sigh(const sigh &other, const allocator_type &allocator) noexcept(std::is_nothrow_constructible_v) : calls{other.calls, allocator} {} /** * @brief Move constructor. * @param other The instance to move from. */ sigh(sigh &&other) noexcept(std::is_nothrow_move_constructible_v) : calls{std::move(other.calls)} {} /** * @brief Allocator-extended move constructor. * @param other The instance to move from. * @param allocator The allocator to use. */ sigh(sigh &&other, const allocator_type &allocator) noexcept(std::is_nothrow_constructible_v) : calls{std::move(other.calls), allocator} {} /*! @brief Default destructor. */ ~sigh() noexcept = default; /** * @brief Copy assignment operator. * @param other The instance to copy from. * @return This signal handler. */ sigh &operator=(const sigh &other) noexcept(std::is_nothrow_copy_assignable_v) { calls = other.calls; return *this; } /** * @brief Move assignment operator. * @param other The instance to move from. * @return This signal handler. */ sigh &operator=(sigh &&other) noexcept(std::is_nothrow_move_assignable_v) { calls = std::move(other.calls); return *this; } /** * @brief Exchanges the contents with those of a given signal handler. * @param other Signal handler to exchange the content with. */ void swap(sigh &other) noexcept(std::is_nothrow_swappable_v) { using std::swap; swap(calls, other.calls); } /** * @brief Returns the associated allocator. * @return The associated allocator. */ [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { return calls.get_allocator(); } /** * @brief Number of listeners connected to the signal. * @return Number of listeners currently connected. */ [[nodiscard]] size_type size() const noexcept { return calls.size(); } /** * @brief Returns false if at least a listener is connected to the signal. * @return True if the signal has no listeners connected, false otherwise. */ [[nodiscard]] bool empty() const 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 - 1u](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 void collect(Func func, Args... args) const { for(auto pos = calls.size(); pos; --pos) { if constexpr(std::is_void_v || !std::is_invocable_v) { calls[pos - 1u](args...); if constexpr(std::is_invocable_r_v) { if(func()) { break; } } else { func(); } } else { if constexpr(std::is_invocable_r_v) { if(func(calls[pos - 1u](args...))) { break; } } else { func(calls[pos - 1u](args...)); } } } } private: container_type calls; }; /** * @brief Connection class. * * Opaque object the aim of which is to allow users to release an already * estabilished connection without having to keep a reference to the signal or * the sink that generated it. */ class connection { template friend class sink; connection(delegate fn, void *ref) : disconnect{fn}, signal{ref} {} public: /*! @brief Default constructor. */ connection() : disconnect{}, signal{} {} /** * @brief Checks whether a connection is properly initialized. * @return True if the connection is properly initialized, false otherwise. */ [[nodiscard]] explicit operator bool() const noexcept { return static_cast(disconnect); } /*! @brief Breaks the connection. */ void release() { if(disconnect) { disconnect(signal); disconnect.reset(); } } private: delegate disconnect; void *signal; }; /** * @brief Scoped connection class. * * Opaque object the aim of which is to allow users to release an already * estabilished connection without having to keep a reference to the signal or * the sink that generated it.
* A scoped connection automatically breaks the link between the two objects * when it goes out of scope. */ struct scoped_connection { /*! @brief Default constructor. */ scoped_connection() = default; /** * @brief Constructs a scoped connection from a basic connection. * @param other A valid connection object. */ scoped_connection(const connection &other) : conn{other} {} /*! @brief Default copy constructor, deleted on purpose. */ scoped_connection(const scoped_connection &) = delete; /** * @brief Move constructor. * @param other The scoped connection to move from. */ scoped_connection(scoped_connection &&other) noexcept : conn{std::exchange(other.conn, {})} {} /*! @brief Automatically breaks the link on destruction. */ ~scoped_connection() noexcept { conn.release(); } /** * @brief Default copy assignment operator, deleted on purpose. * @return This scoped connection. */ scoped_connection &operator=(const scoped_connection &) = delete; /** * @brief Move assignment operator. * @param other The scoped connection to move from. * @return This scoped connection. */ scoped_connection &operator=(scoped_connection &&other) noexcept { conn = std::exchange(other.conn, {}); return *this; } /** * @brief Acquires a connection. * @param other The connection object to acquire. * @return This scoped connection. */ scoped_connection &operator=(connection other) { conn = other; return *this; } /** * @brief Checks whether a scoped connection is properly initialized. * @return True if the connection is properly initialized, false otherwise. */ [[nodiscard]] explicit operator bool() const noexcept { return static_cast(conn); } /*! @brief Breaks the connection. */ void release() { conn.release(); } private: connection conn; }; /** * @brief Sink class. * * A sink is used to connect listeners to signals and to disconnect them.
* The function type for a listener is the one of the signal to which it * belongs. * * The clear separation between a signal and a sink permits to store the former * as private data member without exposing the publish functionality to the * users of the class. * * @warning * Lifetime of a sink must not overcome that of the signal to which it refers. * In any other case, attempting to use a sink results in undefined behavior. * * @tparam Ret Return type of a function type. * @tparam Args Types of arguments of a function type. * @tparam Allocator Type of allocator used to manage memory and elements. */ template class sink> { using signal_type = sigh; using delegate_type = typename signal_type::delegate_type; using difference_type = typename signal_type::container_type::difference_type; template static void release(Type value_or_instance, void *signal) { sink{*static_cast(signal)}.disconnect(value_or_instance); } template static void release(void *signal) { sink{*static_cast(signal)}.disconnect(); } template void disconnect_if(Func callback) { for(auto pos = signal->calls.size(); pos; --pos) { if(auto &elem = signal->calls[pos - 1u]; callback(elem)) { elem = std::move(signal->calls.back()); signal->calls.pop_back(); } } } public: /** * @brief Constructs a sink that is allowed to modify a given signal. * @param ref A valid reference to a signal object. */ sink(sigh &ref) noexcept : signal{&ref} {} /** * @brief Returns false if at least a listener is connected to the sink. * @return True if the sink has no listeners connected, false otherwise. */ [[nodiscard]] bool empty() const noexcept { return signal->calls.empty(); } /** * @brief Connects a free function (with or without payload), a bound or an * unbound member to a signal. * @tparam Candidate Function or member to connect to the signal. * @tparam Type Type of class or type of payload, if any. * @param value_or_instance A valid object that fits the purpose, if any. * @return A properly initialized connection object. */ template connection connect(Type &&...value_or_instance) { disconnect(value_or_instance...); delegate_type call{}; call.template connect(value_or_instance...); signal->calls.push_back(std::move(call)); delegate conn{}; conn.template connect<&release>(value_or_instance...); return {conn, signal}; } /** * @brief Disconnects a free function (with or without payload), a bound or * an unbound member from a signal. * @tparam Candidate Function or member to disconnect from the signal. * @tparam Type Type of class or type of payload, if any. * @param value_or_instance A valid object that fits the purpose, if any. */ template void disconnect(Type &&...value_or_instance) { delegate_type call{}; call.template connect(value_or_instance...); disconnect_if([&call](const auto &elem) { return elem == call; }); } /** * @brief Disconnects free functions with payload or bound members from a * signal. * @param value_or_instance A valid object that fits the purpose. */ void disconnect(const void *value_or_instance) { ENTT_ASSERT(value_or_instance != nullptr, "Invalid value or instance"); disconnect_if([value_or_instance](const auto &elem) { return elem.data() == value_or_instance; }); } /*! @brief Disconnects all the listeners from a signal. */ void disconnect() { signal->calls.clear(); } private: signal_type *signal; }; /** * @brief Deduction guide. * * It allows to deduce the signal handler type of a sink directly from the * signal it refers to. * * @tparam Ret Return type of a function type. * @tparam Args Types of arguments of a function type. * @tparam Allocator Type of allocator used to manage memory and elements. */ template sink(sigh &) -> sink>; } // namespace entt #endif