diff --git a/TODO b/TODO index a9c689403..92154ae23 100644 --- a/TODO +++ b/TODO @@ -4,7 +4,7 @@ WIP: * get rid of storage_traits class template -* uses-allocator construction: any (with allocator support), cache, dispatcher, poly, ... +* uses-allocator construction: any (with allocator support), cache, poly, ... * add an ENTT_NOEXCEPT with args and use it to make ie compressed_pair conditionally noexcept * process scheduler: reviews, use free lists internally * runtime events (emitter) diff --git a/src/entt/signal/dispatcher.hpp b/src/entt/signal/dispatcher.hpp index 20c7d141a..448aea759 100644 --- a/src/entt/signal/dispatcher.hpp +++ b/src/entt/signal/dispatcher.hpp @@ -1,6 +1,7 @@ #ifndef ENTT_SIGNAL_DISPATCHER_HPP #define ENTT_SIGNAL_DISPATCHER_HPP +#include #include #include #include @@ -8,6 +9,7 @@ #include #include "../config/config.h" #include "../container/dense_map.hpp" +#include "../core/compressed_pair.hpp" #include "../core/fwd.hpp" #include "../core/type_info.hpp" #include "../core/utility.hpp" @@ -31,10 +33,21 @@ struct basic_dispatcher_handler { virtual std::size_t size() const ENTT_NOEXCEPT = 0; }; -template -struct dispatcher_handler final: basic_dispatcher_handler { +template +class dispatcher_handler final: public basic_dispatcher_handler { static_assert(std::is_same_v>, "Invalid event 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(); @@ -76,8 +89,8 @@ struct dispatcher_handler final: basic_dispatcher_handler { } private: - sigh signal{}; - std::vector events; + signal_type signal; + container_type events; }; } // namespace internal @@ -98,40 +111,103 @@ private: * * 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. */ -class dispatcher { +template +class basic_dispatcher { template - [[nodiscard]] internal::dispatcher_handler &assure(const id_type id) { - if(auto &&ptr = pools[id]; !ptr) { - auto *cpool = new internal::dispatcher_handler{}; - ptr.reset(cpool); - return *cpool; - } else { - return static_cast &>(*ptr); + using handler_type = internal::dispatcher_handler; + + using key_type = id_type; + // std::shared_ptr because of its type erased allocator which is pretty 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) { + auto &&ptr = pools.first()[id]; + + if(!ptr) { + const auto &allocator = pools.second(); + ptr = std::allocate_shared>(allocator, allocator); } + + return static_cast &>(*ptr); } template - [[nodiscard]] const internal::dispatcher_handler *assure(const id_type id) const { - if(const auto it = pools.find(id); it != pools.end()) { - return static_cast *>(it->second.get()); + [[nodiscard]] const handler_type *assure(const id_type id) const { + auto &container = pools.first(); + + if(const auto it = container.find(id); it != container.end()) { + 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. */ - dispatcher() = default; + basic_dispatcher() + : basic_dispatcher{allocator_type{}} {} - /*! @brief Default move constructor. */ - dispatcher(dispatcher &&) = default; + /** + * @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 Default move assignment operator. @return This dispatcher. */ - dispatcher &operator=(dispatcher &&) = default; + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_dispatcher(basic_dispatcher &&other) ENTT_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) ENTT_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) ENTT_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 ENTT_NOEXCEPT { + return pools.second(); + } /** * @brief Returns the number of pending events for a given type. @@ -152,7 +228,7 @@ public: size_type size() const ENTT_NOEXCEPT { size_type count{}; - for(auto &&cpool: pools) { + for(auto &&cpool: pools.first()) { count += cpool.second->size(); } @@ -266,7 +342,7 @@ public: */ template void disconnect(Type *value_or_instance) { - for(auto &&cpool: pools) { + for(auto &&cpool: pools.first()) { cpool.second->disconnect(value_or_instance); } } @@ -283,7 +359,7 @@ public: /*! @brief Discards all the events queued so far. */ void clear() ENTT_NOEXCEPT { - for(auto &&cpool: pools) { + for(auto &&cpool: pools.first()) { cpool.second->clear(); } } @@ -300,13 +376,13 @@ public: /*! @brief Delivers all the pending events. */ void update() const { - for(auto &&cpool: pools) { + for(auto &&cpool: pools.first()) { cpool.second->publish(); } } private: - dense_map, identity> pools; + compressed_pair pools; }; } // namespace entt diff --git a/src/entt/signal/fwd.hpp b/src/entt/signal/fwd.hpp index 045c5f386..59df1ea52 100644 --- a/src/entt/signal/fwd.hpp +++ b/src/entt/signal/fwd.hpp @@ -8,7 +8,8 @@ namespace entt { template class delegate; -class dispatcher; +template> +class basic_dispatcher; template class emitter; @@ -23,6 +24,9 @@ class sink; template> class sigh; +/*! @brief Alias declaration for the most common use case. */ +using dispatcher = basic_dispatcher<>; + } // namespace entt #endif diff --git a/test/entt/signal/dispatcher.cpp b/test/entt/signal/dispatcher.cpp index 22856b673..f23a3a47c 100644 --- a/test/entt/signal/dispatcher.cpp +++ b/test/entt/signal/dispatcher.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -29,8 +30,12 @@ struct receiver { TEST(Dispatcher, Functionalities) { entt::dispatcher dispatcher; + entt::dispatcher other; receiver receiver; + ASSERT_NO_FATAL_FAILURE(entt::dispatcher{std::move(dispatcher)}); + ASSERT_NO_FATAL_FAILURE(dispatcher = std::move(other)); + ASSERT_EQ(dispatcher.size(), 0u); ASSERT_EQ(dispatcher.size(), 0u); @@ -87,6 +92,32 @@ TEST(Dispatcher, Functionalities) { ASSERT_EQ(receiver.cnt, 0); } +TEST(Dispatcher, Swap) { + entt::dispatcher dispatcher; + entt::dispatcher other; + receiver receiver; + + dispatcher.sink().connect<&receiver::receive>(receiver); + dispatcher.enqueue(); + + ASSERT_EQ(dispatcher.size(), 1u); + ASSERT_EQ(other.size(), 0u); + ASSERT_EQ(receiver.cnt, 0); + + dispatcher.swap(other); + dispatcher.update(); + + ASSERT_EQ(dispatcher.size(), 0u); + ASSERT_EQ(other.size(), 1u); + ASSERT_EQ(receiver.cnt, 0); + + other.update(); + + ASSERT_EQ(dispatcher.size(), 0u); + ASSERT_EQ(other.size(), 0u); + ASSERT_EQ(receiver.cnt, 1); +} + TEST(Dispatcher, StopAndGo) { entt::dispatcher dispatcher; receiver receiver; @@ -154,3 +185,23 @@ TEST(Dispatcher, NamedQueue) { ASSERT_EQ(receiver.cnt, 3); } + +TEST(Dispatcher, CustomAllocator) { + std::allocator allocator; + entt::dispatcher dispatcher{allocator}; + receiver receiver; + + ASSERT_EQ(dispatcher.get_allocator(), allocator); + ASSERT_FALSE(dispatcher.get_allocator() != allocator); + + dispatcher.enqueue(); + decltype(dispatcher) other{std::move(dispatcher), allocator}; + + ASSERT_EQ(dispatcher.size(), 0u); + ASSERT_EQ(other.size(), 1u); + + dispatcher = std::move(other); + + ASSERT_EQ(dispatcher.size(), 1u); + ASSERT_EQ(other.size(), 0u); +}