Browse Source

sigh: allocator support

Michele Caini 4 years ago
parent
commit
37250843e1

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

@@ -1,6 +1,8 @@
 #ifndef ENTT_SIGNAL_FWD_HPP
 #define ENTT_SIGNAL_FWD_HPP
 
+#include <memory>
+
 namespace entt {
 
 template<typename>
@@ -18,7 +20,7 @@ struct scoped_connection;
 template<typename>
 class sink;
 
-template<typename>
+template<typename Type, typename = std::allocator<delegate<Type>>>
 class sigh;
 
 } // namespace entt

+ 107 - 17
src/entt/signal/sigh.hpp

@@ -19,9 +19,9 @@ namespace entt {
  * Primary template isn't defined on purpose. All the specializations give a
  * compile-time error unless the template parameter is a function type.
  *
- * @tparam Function A valid function type.
+ * @tparam Type A valid signal handler type.
  */
-template<typename Function>
+template<typename Type>
 class sink;
 
 /**
@@ -30,9 +30,10 @@ class sink;
  * Primary template isn't defined on purpose. All the specializations give a
  * compile-time error unless the template parameter is a function type.
  *
- * @tparam Function A valid function type.
+ * @tparam Type A valid function type.
+ * @tparam Allocator Type of allocator used to manage memory and elements.
  */
-template<typename Function>
+template<typename Type, typename Allocator>
 class sigh;
 
 /**
@@ -49,17 +50,104 @@ class sigh;
  *
  * @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>
-class sigh<Ret(Args...)> {
+template<typename Ret, typename... Args, typename Allocator>
+class sigh<Ret(Args...), Allocator> {
     /*! @brief A sink is allowed to modify a signal. */
-    friend class sink<Ret(Args...)>;
+    friend class sink<sigh<Ret(Args...), Allocator>>;
+
+    using allocator_traits = std::allocator_traits<Allocator>;
+    using alloc = typename allocator_traits::template rebind_alloc<delegate<Ret(Args...)>>;
+    using alloc_traits = typename std::allocator_traits<alloc>;
+
+    using container_type = std::vector<delegate<Ret(Args...)>, alloc>;
 
 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<Ret(Args...)>;
+    using sink_type = sink<sigh<Ret(Args...), Allocator>>;
+
+    /*! @brief Default constructor. */
+    sigh()
+        : sigh{allocator_type{}} {}
+
+    /**
+     * @brief Constructs a signal handler with a given allocator.
+     * @param allocator The allocator to use.
+     */
+    explicit sigh(const allocator_type &allocator)
+        : calls{allocator} {}
+
+    /**
+     * @brief Default copy constructor.
+     * @param other The instance to copy from.
+     */
+    sigh(const sigh &other)
+        : 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)
+        : calls{other.calls, allocator} {}
+
+    /**
+     * @brief Default move constructor.
+     * @param other The instance to move from.
+     */
+    sigh(sigh &&other) ENTT_NOEXCEPT
+        : 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) ENTT_NOEXCEPT
+        : calls{std::move(other.calls), allocator} {}
+
+    /**
+     * @brief Default copy assignment operator.
+     * @param other The instance to copy from.
+     * @return This signal handler.
+     */
+    sigh &operator=(const sigh &other) {
+        calls = other.calls;
+        return *this;
+    }
+
+    /**
+     * @brief Default move assignment operator.
+     * @param other The instance to move from.
+     * @return This signal handler.
+     */
+    sigh &operator=(sigh &&other) ENTT_NOEXCEPT {
+        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) {
+        using std::swap;
+        swap(calls, other.calls);
+    }
+
+    /**
+     * @brief Returns the associated allocator.
+     * @return The associated allocator.
+     */
+    [[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
+        return calls.get_allocator();
+    }
 
     /**
      * @brief Instance type when it comes to connecting member functions.
@@ -133,7 +221,7 @@ public:
     }
 
 private:
-    std::vector<delegate<Ret(Args...)>> calls;
+    container_type calls;
 };
 
 /**
@@ -271,10 +359,11 @@ private:
  *
  * @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>
-class sink<Ret(Args...)> {
-    using signal_type = sigh<Ret(Args...)>;
+template<typename Ret, typename... Args, typename Allocator>
+class sink<sigh<Ret(Args...), Allocator>> {
+    using signal_type = sigh<Ret(Args...), Allocator>;
     using difference_type = typename std::iterator_traits<typename decltype(signal_type::calls)::iterator>::difference_type;
 
     template<auto Candidate, typename Type>
@@ -292,7 +381,7 @@ 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...)> &ref) ENTT_NOEXCEPT
+    sink(sigh<Ret(Args...), Allocator> &ref) ENTT_NOEXCEPT
         : offset{},
           signal{&ref} {}
 
@@ -507,14 +596,15 @@ private:
 /**
  * @brief Deduction guide.
  *
- * It allows to deduce the function type of a sink directly from the signal it
- * refers to.
+ * 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>
-sink(sigh<Ret(Args...)> &) -> sink<Ret(Args...)>;
+template<typename Ret, typename... Args, typename Allocator>
+sink(sigh<Ret(Args...), Allocator> &) -> sink<sigh<Ret(Args...), Allocator>>;
 
 } // namespace entt
 

+ 2 - 2
test/entt/entity/throwing_allocator.hpp → test/entt/common/throwing_allocator.hpp

@@ -1,5 +1,5 @@
-#ifndef ENTT_ENTITY_THROWING_ALLOCATOR_HPP
-#define ENTT_ENTITY_THROWING_ALLOCATOR_HPP
+#ifndef ENTT_COMMON_THROWING_ALLOCATOR_HPP
+#define ENTT_COMMON_THROWING_ALLOCATOR_HPP
 
 #include <cstddef>
 #include <memory>

+ 6 - 6
test/entt/entity/throwing_component.hpp → test/entt/common/throwing_type.hpp

@@ -1,19 +1,19 @@
-#ifndef ENTT_ENTITY_THROWING_COMPONENT_HPP
-#define ENTT_ENTITY_THROWING_COMPONENT_HPP
+#ifndef ENTT_COMMON_THROWING_TYPE_HPP
+#define ENTT_COMMON_THROWING_TYPE_HPP
 
 namespace test {
 
-class throwing_component {
+class throwing_type {
     struct test_exception {};
 
 public:
     using exception_type = test_exception;
     static constexpr auto moved_from_value = -1;
 
-    throwing_component(int value)
+    throwing_type(int value)
         : data{value} {}
 
-    throwing_component(const throwing_component &other)
+    throwing_type(const throwing_type &other)
         : data{other.data} {
         if(data == trigger_on_value) {
             data = moved_from_value;
@@ -21,7 +21,7 @@ public:
         }
     }
 
-    throwing_component &operator=(const throwing_component &other) {
+    throwing_type &operator=(const throwing_type &other) {
         if(other.data == trigger_on_value) {
             data = moved_from_value;
             throw exception_type{};

+ 1 - 1
test/entt/entity/sparse_set.cpp

@@ -7,7 +7,7 @@
 #include <gtest/gtest.h>
 #include <entt/entity/entity.hpp>
 #include <entt/entity/sparse_set.hpp>
-#include "throwing_allocator.hpp"
+#include "../common/throwing_allocator.hpp"
 
 struct empty_type {};
 

+ 12 - 12
test/entt/entity/storage.cpp

@@ -7,8 +7,8 @@
 #include <gtest/gtest.h>
 #include <entt/entity/component.hpp>
 #include <entt/entity/storage.hpp>
-#include "throwing_allocator.hpp"
-#include "throwing_component.hpp"
+#include "../common/throwing_allocator.hpp"
+#include "../common/throwing_type.hpp"
 
 struct empty_stable_type {};
 
@@ -1480,28 +1480,28 @@ TEST(Storage, ThrowingAllocator) {
 }
 
 TEST(Storage, ThrowingComponent) {
-    entt::storage<test::throwing_component> pool;
-    test::throwing_component::trigger_on_value = 42;
+    entt::storage<test::throwing_type> pool;
+    test::throwing_type::trigger_on_value = 42;
 
     // strong exception safety
-    ASSERT_THROW(pool.emplace(entt::entity{0}, test::throwing_component{42}), typename test::throwing_component::exception_type);
+    ASSERT_THROW(pool.emplace(entt::entity{0}, test::throwing_type{42}), typename test::throwing_type::exception_type);
     ASSERT_TRUE(pool.empty());
 
     const entt::entity entities[2u]{entt::entity{42}, entt::entity{1}};
-    const test::throwing_component components[2u]{42, 1};
+    const test::throwing_type components[2u]{42, 1};
 
     // basic exception safety
-    ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), test::throwing_component{42}), typename test::throwing_component::exception_type);
+    ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), test::throwing_type{42}), typename test::throwing_type::exception_type);
     ASSERT_EQ(pool.size(), 0u);
     ASSERT_FALSE(pool.contains(entt::entity{1}));
 
     // basic exception safety
-    ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), typename test::throwing_component::exception_type);
+    ASSERT_THROW(pool.insert(std::begin(entities), std::end(entities), std::begin(components)), typename test::throwing_type::exception_type);
     ASSERT_EQ(pool.size(), 0u);
     ASSERT_FALSE(pool.contains(entt::entity{1}));
 
     // basic exception safety
-    ASSERT_THROW(pool.insert(std::rbegin(entities), std::rend(entities), std::rbegin(components)), typename test::throwing_component::exception_type);
+    ASSERT_THROW(pool.insert(std::rbegin(entities), std::rend(entities), std::rbegin(components)), typename test::throwing_type::exception_type);
     ASSERT_EQ(pool.size(), 1u);
     ASSERT_TRUE(pool.contains(entt::entity{1}));
     ASSERT_EQ(pool.get(entt::entity{1}), 1);
@@ -1511,7 +1511,7 @@ TEST(Storage, ThrowingComponent) {
     pool.emplace(entt::entity{42}, 42);
 
     // basic exception safety
-    ASSERT_THROW(pool.erase(entt::entity{1}), typename test::throwing_component::exception_type);
+    ASSERT_THROW(pool.erase(entt::entity{1}), typename test::throwing_type::exception_type);
     ASSERT_EQ(pool.size(), 2u);
     ASSERT_TRUE(pool.contains(entt::entity{42}));
     ASSERT_TRUE(pool.contains(entt::entity{1}));
@@ -1519,9 +1519,9 @@ TEST(Storage, ThrowingComponent) {
     ASSERT_EQ(pool.at(1u), entt::entity{42});
     ASSERT_EQ(pool.get(entt::entity{42}), 42);
     // the element may have been moved but it's still there
-    ASSERT_EQ(pool.get(entt::entity{1}), test::throwing_component::moved_from_value);
+    ASSERT_EQ(pool.get(entt::entity{1}), test::throwing_type::moved_from_value);
 
-    test::throwing_component::trigger_on_value = 99;
+    test::throwing_type::trigger_on_value = 99;
     pool.erase(entt::entity{1});
 
     ASSERT_EQ(pool.size(), 1u);

+ 55 - 1
test/entt/signal/sigh.cpp

@@ -2,6 +2,8 @@
 #include <vector>
 #include <gtest/gtest.h>
 #include <entt/signal/sigh.hpp>
+#include "../common/throwing_allocator.hpp"
+#include "../common/throwing_type.hpp"
 
 struct sigh_listener {
     static void f(int &v) {
@@ -122,7 +124,7 @@ TEST_F(SigH, Swap) {
     ASSERT_FALSE(sigh1.empty());
     ASSERT_TRUE(sigh2.empty());
 
-    std::swap(sigh1, sigh2);
+    sigh1.swap(sigh2);
 
     ASSERT_TRUE(sink1.empty());
     ASSERT_FALSE(sink2.empty());
@@ -530,3 +532,55 @@ TEST_F(SigH, UnboundMemberFunction) {
 
     ASSERT_TRUE(listener.k);
 }
+
+TEST_F(SigH, CustomAllocator) {
+    test::throwing_allocator<entt::delegate<void(int)>> allocator;
+
+    auto test = [&](auto curr) {
+        ASSERT_EQ(curr.get_allocator(), allocator);
+        ASSERT_TRUE(curr.empty());
+
+        entt::sink sink{curr};
+        sigh_listener listener;
+        sink.template connect<&sigh_listener::i>(listener);
+
+        decltype(curr) copy{curr, allocator};
+        sink.disconnect(listener);
+
+        ASSERT_TRUE(curr.empty());
+        ASSERT_FALSE(copy.empty());
+
+        curr = copy;
+
+        ASSERT_FALSE(curr.empty());
+        ASSERT_FALSE(copy.empty());
+
+        decltype(curr) move{std::move(copy), allocator};
+
+        ASSERT_TRUE(copy.empty());
+        ASSERT_FALSE(move.empty());
+
+        sink = entt::sink{move};
+        sink.disconnect(&listener);
+
+        ASSERT_TRUE(copy.empty());
+        ASSERT_TRUE(move.empty());
+
+        sink.template connect<&sigh_listener::i>(listener);
+        copy.swap(move);
+
+        ASSERT_FALSE(copy.empty());
+        ASSERT_TRUE(move.empty());
+
+        sink = entt::sink{copy};
+        sink.disconnect();
+
+        ASSERT_TRUE(copy.empty());
+        ASSERT_TRUE(move.empty());
+    };
+
+    entt::sigh<void(int), decltype(allocator)> sigh{allocator};
+
+    test(sigh);
+    test(std::move(sigh));
+}