Ver código fonte

dispatcher: allocator support

Michele Caini 4 anos atrás
pai
commit
8a350acf39
4 arquivos alterados com 158 adições e 27 exclusões
  1. 1 1
      TODO
  2. 101 25
      src/entt/signal/dispatcher.hpp
  3. 5 1
      src/entt/signal/fwd.hpp
  4. 51 0
      test/entt/signal/dispatcher.cpp

+ 1 - 1
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)

+ 101 - 25
src/entt/signal/dispatcher.hpp

@@ -1,6 +1,7 @@
 #ifndef ENTT_SIGNAL_DISPATCHER_HPP
 #define ENTT_SIGNAL_DISPATCHER_HPP
 
+#include <algorithm>
 #include <cstddef>
 #include <memory>
 #include <type_traits>
@@ -8,6 +9,7 @@
 #include <vector>
 #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<typename Event>
-struct dispatcher_handler final: basic_dispatcher_handler {
+template<typename Event, typename Allocator>
+class dispatcher_handler final: public basic_dispatcher_handler {
     static_assert(std::is_same_v<Event, std::decay_t<Event>>, "Invalid event type");
 
+    using alloc_traits = std::allocator_traits<Allocator>;
+    using signal_type = sigh<void(Event &), typename alloc_traits::template rebind_alloc<void (*)(Event &)>>;
+    using container_type = std::vector<Event, typename alloc_traits::template rebind_alloc<Event>>;
+
+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<void(Event &)> signal{};
-    std::vector<Event> 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<typename Allocator>
+class basic_dispatcher {
     template<typename Event>
-    [[nodiscard]] internal::dispatcher_handler<Event> &assure(const id_type id) {
-        if(auto &&ptr = pools[id]; !ptr) {
-            auto *cpool = new internal::dispatcher_handler<Event>{};
-            ptr.reset(cpool);
-            return *cpool;
-        } else {
-            return static_cast<internal::dispatcher_handler<Event> &>(*ptr);
+    using handler_type = internal::dispatcher_handler<Event, Allocator>;
+
+    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<internal::basic_dispatcher_handler>;
+
+    using alloc_traits = std::allocator_traits<Allocator>;
+    using container_allocator = typename alloc_traits::template rebind_alloc<std::pair<const key_type, mapped_type>>;
+    using container_type = dense_map<id_type, mapped_type, identity, std::equal_to<id_type>, container_allocator>;
+
+    template<typename Event>
+    [[nodiscard]] handler_type<Event> &assure(const id_type id) {
+        auto &&ptr = pools.first()[id];
+
+        if(!ptr) {
+            const auto &allocator = pools.second();
+            ptr = std::allocate_shared<handler_type<Event>>(allocator, allocator);
         }
+
+        return static_cast<handler_type<Event> &>(*ptr);
     }
 
     template<typename Event>
-    [[nodiscard]] const internal::dispatcher_handler<Event> *assure(const id_type id) const {
-        if(const auto it = pools.find(id); it != pools.end()) {
-            return static_cast<const internal::dispatcher_handler<Event> *>(it->second.get());
+    [[nodiscard]] const handler_type<Event> *assure(const id_type id) const {
+        auto &container = pools.first();
+
+        if(const auto it = container.find(id); it != container.end()) {
+            return static_cast<const handler_type<Event> *>(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 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 constructor. */
-    dispatcher(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 Default move assignment operator. @return This dispatcher. */
-    dispatcher &operator=(dispatcher &&) = default;
+    /**
+     * @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<typename Type>
     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<id_type, std::unique_ptr<internal::basic_dispatcher_handler>, identity> pools;
+    compressed_pair<container_type, allocator_type> pools;
 };
 
 } // namespace entt

+ 5 - 1
src/entt/signal/fwd.hpp

@@ -8,7 +8,8 @@ namespace entt {
 template<typename>
 class delegate;
 
-class dispatcher;
+template<typename = std::allocator<char>>
+class basic_dispatcher;
 
 template<typename>
 class emitter;
@@ -23,6 +24,9 @@ class sink;
 template<typename Type, typename = std::allocator<Type *>>
 class sigh;
 
+/*! @brief Alias declaration for the most common use case. */
+using dispatcher = basic_dispatcher<>;
+
 } // namespace entt
 
 #endif

+ 51 - 0
test/entt/signal/dispatcher.cpp

@@ -1,3 +1,4 @@
+#include <memory>
 #include <utility>
 #include <gtest/gtest.h>
 #include <entt/core/hashed_string.hpp>
@@ -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<an_event>(), 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<an_event>().connect<&receiver::receive>(receiver);
+    dispatcher.enqueue<an_event>();
+
+    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<char> allocator;
+    entt::dispatcher dispatcher{allocator};
+    receiver receiver;
+
+    ASSERT_EQ(dispatcher.get_allocator(), allocator);
+    ASSERT_FALSE(dispatcher.get_allocator() != allocator);
+
+    dispatcher.enqueue<an_event>();
+    decltype(dispatcher) other{std::move(dispatcher), allocator};
+
+    ASSERT_EQ(dispatcher.size(), 0u);
+    ASSERT_EQ(other.size<an_event>(), 1u);
+
+    dispatcher = std::move(other);
+
+    ASSERT_EQ(dispatcher.size<an_event>(), 1u);
+    ASSERT_EQ(other.size(), 0u);
+}