#ifndef ENTT_SIGNAL_DISPATCHER_HPP #define ENTT_SIGNAL_DISPATCHER_HPP #include #include #include #include #include #include "../config/config.h" #include "../core/fwd.hpp" #include "../core/type_info.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.
* Listeners are provided in the form of member functions. For each event of * type `Event`, listeners are such that they can be invoked with an argument of * type `Event &`, no matter what the return type is. * * The dispatcher creates instances of the `sigh` class internally. Refer to the * documentation of the latter for more details. */ class dispatcher { struct basic_pool { virtual ~basic_pool() = default; virtual void publish() = 0; virtual void disconnect(void *) = 0; virtual void clear() ENTT_NOEXCEPT = 0; }; template struct pool_handler final: basic_pool { static_assert(std::is_same_v>, "Invalid event type"); using signal_type = sigh; using sink_type = typename signal_type::sink_type; void publish() override { const auto length = events.size(); for(std::size_t pos{}; pos < length; ++pos) { signal.publish(events[pos]); } events.erase(events.cbegin(), events.cbegin()+length); } void disconnect(void *instance) override { sink().disconnect(instance); } void clear() ENTT_NOEXCEPT override { events.clear(); } [[nodiscard]] sink_type sink() ENTT_NOEXCEPT { return entt::sink{signal}; } template void trigger(Args &&... args) { Event instance{std::forward(args)...}; signal.publish(instance); } template void enqueue(Args &&... args) { if constexpr(std::is_aggregate_v) { events.push_back(Event{std::forward(args)...}); } else { events.emplace_back(std::forward(args)...); } } private: signal_type signal{}; std::vector events; }; template [[nodiscard]] pool_handler & assure() { const auto index = type_seq::value(); if(!(index < pools.size())) { pools.resize(std::size_t(index)+1u); } if(!pools[index]) { pools[index].reset(new pool_handler{}); } return static_cast &>(*pools[index]); } public: /*! @brief Default constructor. */ dispatcher() = default; /*! @brief Default move constructor. */ dispatcher(dispatcher &&) = default; /*! @brief Default move assignment operator. @return This dispatcher. */ dispatcher & operator=(dispatcher &&) = default; /** * @brief Returns a sink object for the given event. * * A sink is an opaque object used to connect listeners to events. * * The function type for a listener is _compatible_ with: * @code{.cpp} * void(Event &); * @endcode * * The order of invocation of the listeners isn't guaranteed. * * @sa sink * * @tparam Event Type of event of which to get the sink. * @return A temporary sink object. */ template [[nodiscard]] auto sink() { return assure().sink(); } /** * @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 void trigger(Args &&... args) { assure().trigger(std::forward(args)...); } /** * @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. * @param event An instance of the given type of event. */ template void trigger(Event &&event) { assure>().trigger(std::forward(event)); } /** * @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 enqueue. * @tparam Args Types of arguments to use to construct the event. * @param args Arguments to use to construct the event. */ template void enqueue(Args &&... args) { assure().enqueue(std::forward(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 enqueue. * @param event An instance of the given type of event. */ template void enqueue(Event &&event) { assure>().enqueue(std::forward(event)); } /** * @brief Utility function to disconnect everything related to a given value * or instance from a dispatcher. * @tparam Type Type of class or type of payload. * @param value_or_instance A valid object that fits the purpose. */ template void disconnect(Type &value_or_instance) { disconnect(&value_or_instance); } /** * @brief Utility function to disconnect everything related to a given value * or instance from a dispatcher. * @tparam Type Type of class or type of payload. * @param value_or_instance A valid object that fits the purpose. */ template void disconnect(Type *value_or_instance) { for(auto &&cpool: pools) { if(cpool) { cpool->disconnect(value_or_instance); } } } /** * @brief Discards all the events queued so far. * * If no types are provided, the dispatcher will clear all the existing * pools. * * @tparam Event Type of events to discard. */ template void clear() { if constexpr(sizeof...(Event) == 0) { for(auto &&cpool: pools) { if(cpool) { cpool->clear(); } } } else { (assure().clear(), ...); } } /** * @brief Delivers all the pending events of the given type. * * This method is blocking and it doesn't return until all the events are * delivered to the registered listeners. It's responsibility of the users * to reduce at a minimum the time spent in the bodies of the listeners. * * @tparam Event Type of events to send. */ template void update() { assure().publish(); } /** * @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 responsibility of the users * to reduce at a minimum the time spent in the bodies of the listeners. */ void update() const { for(auto pos = pools.size(); pos; --pos) { if(auto &&cpool = pools[pos-1]; cpool) { cpool->publish(); } } } private: std::vector> pools; }; } #endif