#ifndef ENTT_SIGNAL_DISPATCHER_HPP #define ENTT_SIGNAL_DISPATCHER_HPP #include #include #include #include #include #include #include "../container/dense_map.hpp" #include "../core/compressed_pair.hpp" #include "../core/fwd.hpp" #include "../core/type_info.hpp" #include "../core/utility.hpp" #include "fwd.hpp" #include "sigh.hpp" namespace entt { /** * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. */ namespace internal { struct basic_dispatcher_handler { virtual ~basic_dispatcher_handler() = default; virtual void publish() = 0; virtual void disconnect(void *) = 0; virtual void clear() noexcept = 0; virtual std::size_t size() const noexcept = 0; }; template class dispatcher_handler final: public basic_dispatcher_handler { static_assert(std::is_same_v>, "Invalid type"); using alloc_traits = std::allocator_traits; using signal_type = sigh; using container_type = std::vector>; public: using allocator_type = Allocator; dispatcher_handler(const allocator_type &allocator) : signal{allocator}, events{allocator} {} 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 { bucket().disconnect(instance); } void clear() noexcept override { events.clear(); } [[nodiscard]] auto bucket() noexcept { return typename signal_type::sink_type{signal}; } void trigger(Type event) { signal.publish(event); } template void enqueue(Args &&...args) { if constexpr(std::is_aggregate_v) { events.push_back(Type{std::forward(args)...}); } else { events.emplace_back(std::forward(args)...); } } std::size_t size() const noexcept override { return events.size(); } private: signal_type signal; container_type events; }; } // namespace internal /** * Internal details not to be documented. * @endcond */ /** * @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 `Type`, listeners are such that they can be invoked with an argument of * type `Type &`, 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. * * @tparam Allocator Type of allocator used to manage memory and elements. */ template class basic_dispatcher { template using handler_type = internal::dispatcher_handler; using key_type = id_type; // std::shared_ptr because of its type erased allocator which is useful here using mapped_type = std::shared_ptr; using alloc_traits = std::allocator_traits; using container_allocator = typename alloc_traits::template rebind_alloc>; using container_type = dense_map, container_allocator>; template [[nodiscard]] handler_type &assure(const id_type id) { static_assert(std::is_same_v>, "Non-decayed types not allowed"); auto &&ptr = pools.first()[id]; if(!ptr) { const auto &allocator = get_allocator(); ptr = std::allocate_shared>(allocator, allocator); } return static_cast &>(*ptr); } template [[nodiscard]] const handler_type *assure(const id_type id) const { static_assert(std::is_same_v>, "Non-decayed types not allowed"); if(auto it = pools.first().find(id); it != pools.first().cend()) { return static_cast *>(it->second.get()); } return nullptr; } public: /*! @brief Allocator type. */ using allocator_type = Allocator; /*! @brief Unsigned integer type. */ using size_type = std::size_t; /*! @brief Default constructor. */ basic_dispatcher() : basic_dispatcher{allocator_type{}} {} /** * @brief Constructs a dispatcher with a given allocator. * @param allocator The allocator to use. */ explicit basic_dispatcher(const allocator_type &allocator) : pools{allocator, allocator} {} /** * @brief Move constructor. * @param other The instance to move from. */ basic_dispatcher(basic_dispatcher &&other) noexcept : pools{std::move(other.pools)} {} /** * @brief Allocator-extended move constructor. * @param other The instance to move from. * @param allocator The allocator to use. */ basic_dispatcher(basic_dispatcher &&other, const allocator_type &allocator) noexcept : pools{container_type{std::move(other.pools.first()), allocator}, allocator} {} /** * @brief Move assignment operator. * @param other The instance to move from. * @return This dispatcher. */ basic_dispatcher &operator=(basic_dispatcher &&other) noexcept { pools = std::move(other.pools); return *this; } /** * @brief Exchanges the contents with those of a given dispatcher. * @param other Dispatcher to exchange the content with. */ void swap(basic_dispatcher &other) { using std::swap; swap(pools, other.pools); } /** * @brief Returns the associated allocator. * @return The associated allocator. */ [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { return pools.second(); } /** * @brief Returns the number of pending events for a given type. * @tparam Type Type of event for which to return the count. * @param id Name used to map the event queue within the dispatcher. * @return The number of pending events for the given type. */ template size_type size(const id_type id = type_hash::value()) const noexcept { const auto *cpool = assure>(id); return cpool ? cpool->size() : 0u; } /** * @brief Returns the total number of pending events. * @return The total number of pending events. */ size_type size() const noexcept { size_type count{}; for(auto &&cpool: pools.first()) { count += cpool.second->size(); } return count; } /** * @brief Returns a sink object for the given event and queue. * * A sink is an opaque object used to connect listeners to events. * * The function type for a listener is _compatible_ with: * * @code{.cpp} * void(Type &); * @endcode * * The order of invocation of the listeners isn't guaranteed. * * @sa sink * * @tparam Type Type of event of which to get the sink. * @param id Name used to map the event queue within the dispatcher. * @return A temporary sink object. */ template [[nodiscard]] auto sink(const id_type id = type_hash::value()) { return assure(id).bucket(); } /** * @brief Triggers an immediate event of a given type. * @tparam Type Type of event to trigger. * @param value An instance of the given type of event. */ template void trigger(Type &&value = {}) { trigger(type_hash>::value(), std::forward(value)); } /** * @brief Triggers an immediate event on a queue of a given type. * @tparam Type Type of event to trigger. * @param value An instance of the given type of event. * @param id Name used to map the event queue within the dispatcher. */ template void trigger(const id_type id, Type &&value = {}) { assure>(id).trigger(std::forward(value)); } /** * @brief Enqueues an event of the given type. * @tparam Type 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) { enqueue_hint(type_hash::value(), std::forward(args)...); } /** * @brief Enqueues an event of the given type. * @tparam Type Type of event to enqueue. * @param value An instance of the given type of event. */ template void enqueue(Type &&value) { enqueue_hint(type_hash>::value(), std::forward(value)); } /** * @brief Enqueues an event of the given type. * @tparam Type Type of event to enqueue. * @tparam Args Types of arguments to use to construct the event. * @param id Name used to map the event queue within the dispatcher. * @param args Arguments to use to construct the event. */ template void enqueue_hint(const id_type id, Args &&...args) { assure(id).enqueue(std::forward(args)...); } /** * @brief Enqueues an event of the given type. * @tparam Type Type of event to enqueue. * @param id Name used to map the event queue within the dispatcher. * @param value An instance of the given type of event. */ template void enqueue_hint(const id_type id, Type &&value) { assure>(id).enqueue(std::forward(value)); } /** * @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.first()) { cpool.second->disconnect(value_or_instance); } } /** * @brief Discards all the events stored so far in a given queue. * @tparam Type Type of event to discard. * @param id Name used to map the event queue within the dispatcher. */ template void clear(const id_type id = type_hash::value()) { assure(id).clear(); } /*! @brief Discards all the events queued so far. */ void clear() noexcept { for(auto &&cpool: pools.first()) { cpool.second->clear(); } } /** * @brief Delivers all the pending events of a given queue. * @tparam Type Type of event to send. * @param id Name used to map the event queue within the dispatcher. */ template void update(const id_type id = type_hash::value()) { assure(id).publish(); } /*! @brief Delivers all the pending events. */ void update() const { for(auto &&cpool: pools.first()) { cpool.second->publish(); } } private: compressed_pair pools; }; } // namespace entt #endif