| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- #ifndef ENTT_SIGNAL_SIGH_HPP
- #define ENTT_SIGNAL_SIGH_HPP
- #include <cstddef>
- #include <memory>
- #include <type_traits>
- #include <utility>
- #include <vector>
- #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<typename Type>
- 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<typename Type, typename Allocator>
- 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<typename Ret, typename... Args, typename Allocator>
- class sigh<Ret(Args...), Allocator> {
- friend class sink<sigh<Ret(Args...), Allocator>>;
- using alloc_traits = std::allocator_traits<Allocator>;
- using delegate_type = delegate<Ret(Args...)>;
- using container_type = std::vector<delegate_type, typename alloc_traits::template rebind_alloc<delegate_type>>;
- 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<sigh<Ret(Args...), Allocator>>;
- /*! @brief Default constructor. */
- sigh() noexcept(std::is_nothrow_default_constructible_v<allocator_type> &&std::is_nothrow_constructible_v<container_type, const allocator_type &>)
- : 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<container_type, const allocator_type &>)
- : calls{allocator} {}
- /**
- * @brief Copy constructor.
- * @param other The instance to copy from.
- */
- sigh(const sigh &other) noexcept(std::is_nothrow_copy_constructible_v<container_type>)
- : 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<container_type, const container_type &, const allocator_type &>)
- : calls{other.calls, allocator} {}
- /**
- * @brief Move constructor.
- * @param other The instance to move from.
- */
- sigh(sigh &&other) noexcept(std::is_nothrow_move_constructible_v<container_type>)
- : 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<container_type, container_type &&, const allocator_type &>)
- : calls{std::move(other.calls), allocator} {}
- /**
- * @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<container_type>) {
- 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<container_type>) {
- 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<container_type>) {
- 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<typename Func>
- void collect(Func func, Args... args) const {
- for(auto pos = calls.size(); pos; --pos) {
- if constexpr(std::is_void_v<Ret> || !std::is_invocable_v<Func, Ret>) {
- calls[pos - 1u](args...);
- if constexpr(std::is_invocable_r_v<bool, Func>) {
- if(func()) {
- break;
- }
- } else {
- func();
- }
- } else {
- if constexpr(std::is_invocable_r_v<bool, Func, Ret>) {
- 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<typename>
- friend class sink;
- connection(delegate<void(void *)> 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<bool>(disconnect);
- }
- /*! @brief Breaks the connection. */
- void release() {
- if(disconnect) {
- disconnect(signal);
- disconnect.reset();
- }
- }
- private:
- delegate<void(void *)> 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.<br/>
- * 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() {
- 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 = std::move(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<bool>(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.<br/>
- * The function type for a listener is the one of the signal to which it
- * belongs.
- *
- * The clear separation between a signal and a sink permits to store the former
- * as private data member without exposing the publish functionality to the
- * users of 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<typename Ret, typename... Args, typename Allocator>
- class sink<sigh<Ret(Args...), Allocator>> {
- using signal_type = sigh<Ret(Args...), Allocator>;
- using delegate_type = typename signal_type::delegate_type;
- using difference_type = typename signal_type::container_type::difference_type;
- template<auto Candidate, typename Type>
- static void release(Type value_or_instance, void *signal) {
- sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>(value_or_instance);
- }
- template<auto Candidate>
- static void release(void *signal) {
- sink{*static_cast<signal_type *>(signal)}.disconnect<Candidate>();
- }
- template<typename Func>
- 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<Ret(Args...), Allocator> &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<auto Candidate, typename... Type>
- connection connect(Type &&...value_or_instance) {
- disconnect<Candidate>(value_or_instance...);
- delegate_type call{};
- call.template connect<Candidate>(value_or_instance...);
- signal->calls.push_back(std::move(call));
- delegate<void(void *)> conn{};
- conn.template connect<&release<Candidate, Type...>>(value_or_instance...);
- return {std::move(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<auto Candidate, typename... Type>
- void disconnect(Type &&...value_or_instance) {
- delegate_type call{};
- call.template connect<Candidate>(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) {
- if(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<typename Ret, typename... Args, typename Allocator>
- sink(sigh<Ret(Args...), Allocator> &) -> sink<sigh<Ret(Args...), Allocator>>;
- } // namespace entt
- #endif
|