changeset 596:b747700dd63d

Signals: use free functions for more generic
author David Demelier <markand@malikania.fr>
date Sat, 05 Nov 2016 15:31:29 +0100
parents b46305a783ef
children 2a0b3a7363f2
files modules/signals/signals.hpp modules/signals/test/main.cpp
diffstat 2 files changed, 118 insertions(+), 194 deletions(-) [+]
line wrap: on
line diff
--- a/modules/signals/signals.hpp	Tue Sep 27 22:39:07 2016 +0200
+++ b/modules/signals/signals.hpp	Sat Nov 05 15:31:29 2016 +0100
@@ -27,160 +27,96 @@
 #include <algorithm>
 #include <cstdint>
 #include <functional>
-#include <stack>
-#include <unordered_map>
+#include <iterator>
+#include <utility>
 #include <vector>
 
 /**
- * \brief Stores and call registered functions.
- *
- * This class is intended to be use as a public field in the desired object.
- *
- * The user just have to call one of connect(), disconnect() or the call
- * operator to use this class.
- *
- * It stores the callable as std::function so type-erasure is complete.
- *
- * The user is responsible of taking care that the object is still alive
- * in case that the function takes a reference to the object.
- *
- * It is forbidden to connect, disconnect, clearing the signal from a callback,
- * instead you should postpone this operation for example, in an event loop
- * event.
- *
- * It is also forbidden to throw in user callbacks, to make such guarantee, the
- * call operator is noexcept.
+ * \brief utilities for basic signal management.
  */
-template <typename... Args>
-class Signal {
-public:
-    /**
-     * \brief Opaque connection identifier.
-     * \see disconnect
-     */
-    using Index = std::intmax_t;
-
-private:
-    using Function = std::function<void (Args...)>;
-    using List = std::vector<Function>;
-    using Stack = std::stack<Index>;
-
-    List m_functions;
-    Stack m_reuseable;
+namespace sig {
 
-#if !defined(NDEBUG)
-    mutable bool m_locked{false};
-#endif
-
-    template <typename Func>
-    inline Index add(Func &&function)
-    {
-        assert(!m_locked);
-
-        std::size_t id;
+/**
+ * Add a new handler for the given signal.
+ *
+ * \param sig the signal container
+ * \param callable the callable object
+ * \return the signal position
+ * \throw exceptions if the container cannot allocate more memory
+ */
+template <typename Signal, typename Callable>
+std::uintmax_t connect(Signal& sig, Callable&& callable)
+{
+    auto it = std::find_if(sig.begin(), sig.end(), [&] (auto &f) {
+        return f == nullptr;
+    });
 
-        if (!m_reuseable.empty()) {
-            id = m_reuseable.top();
-            m_functions[id] = std::forward<Func>(function);
-            m_reuseable.pop();
-        } else {
-            m_functions.push_back(std::forward<Func>(function));
-            id = m_functions.size() - 1;
-        }
-
-        return id;
-    }
-
-public:
-    /**
-     * Register a new function to the signal.
-     *
-     * \pre must not be called from an handler
-     * \param function the function to copy
-     * \return the index for removing
-     * \throw exceptions on bad allocation
-     */
-    inline Index connect(const Function &function)
-    {
-        return add(function);
+    if (it != sig.end())
+        *it = std::forward<Callable>(callable);
+    else {
+        sig.push_back(std::move(callable));
+        it = sig.end() - 1;
     }
 
-    /**
-     * Overloaded function for move semantics.
-     *
-     * \pre must not be called from an handler
-     * \param function the function to move
-     * \return the connection index
-     * \throw exceptions on bad allocation
-     */
-    inline Index connect(Function &&function)
-    {
-        return add(function);
-    }
+    return std::distance(sig.begin(), it);
+}
 
-    /**
-     * Disconnect an handler.
-     *
-     * \pre must not be called from an handler
-     * \param id the id to disconnect (will be set to 0)
-     */
-    inline void disconnect(Index &id)
-    {
-        assert(!m_locked);
+/**
+ * Remove an handler from the given signal.
+ *
+ * If the id is out of bounds, does nothing.
+ *
+ * \param sig the signal container
+ * \param id the id
+ * \return true if a signal has been removed
+ */
+template <typename Signal>
+bool disconnect(Signal& sig, std::uintmax_t id) noexcept
+{
+    if (id < 0 || id > sig.size() || sig[id] == nullptr)
+        return false;
 
-        if (id < 0 || static_cast<std::size_t>(id) >= m_functions.size())
-            return;
+    sig[id] = nullptr;
 
-        m_functions[id] = nullptr;
-        m_reuseable.push(id);
-        id = -1;
-    }
+    return true;
+}
 
-    /**
-     * Remove all registered functions.
-     *
-     * \pre must not be called from an handler
-     */
-    inline void clear() noexcept
-    {
-        assert(!m_locked);
-
-        m_functions.clear();
-
-        while (!m_reuseable.empty())
-            m_reuseable.pop();
-    }
+/**
+ * Disable all handlers in the given signal.
+ *
+ * \param sig the signal
+ */
+template <typename Signal>
+void clear(Signal& sig) noexcept
+{
+    for (auto &f : sig)
+        f = nullptr;
+}
 
-    /**
-     * Get the number of signals connected.
-     *
-     * \return the number
-     */
-    inline std::size_t size() const noexcept
-    {
-        return std::count_if(m_functions.begin(), m_functions.end(), [&] (auto fn) {
-            return fn != nullptr;
-        });
-    }
+/**
+ * Call all handlers from the given signal.
+ *
+ * It's not allowed to throw in handlers.
+ *
+ * Undefined behaviour if the signal is modified while iterating the handlers.
+ *
+ * \param sig the signal
+ * \param args the arguments to pass
+ */
+template <typename Signal, typename... Args>
+void emit(Signal& sig, Args&&... args) noexcept
+{
+    for (auto &f : sig)
+        if (f)
+            f(std::forward<Args>(args)...);
+}
 
-    /**
-     * Call every functions.
-     *
-     * \pre must not be called from an handler
-     * \param args the arguments to pass to the signal
-     */
-    void operator()(Args... args) const noexcept
-    {
-        assert(!m_locked);
+/**
+ * \brief Convenient type for most purposes.
+ */
+template <typename... Args>
+using signal = std::vector<std::function<void (Args...)>>;
 
-        m_locked = true;
-
-        for (auto &f : m_functions)
-            if (f)
-                f(args...);
-
-        m_locked = false;
-    }
-};
+} // !sig
 
 #endif // !SIGNALS_HPP
--- a/modules/signals/test/main.cpp	Tue Sep 27 22:39:07 2016 +0200
+++ b/modules/signals/test/main.cpp	Sat Nov 05 15:31:29 2016 +0100
@@ -24,97 +24,88 @@
 
 TEST(Connect, none)
 {
-    Signal<> sig;
-
-    sig();
-
-    ASSERT_EQ(0U, sig.size());
+    sig::signal<> sig;
+    sig::emit(sig);
 }
 
 TEST(Connect, simple)
 {
-    Signal<> sig;
+    sig::signal<> sig;
 
     bool called = false;
 
-    sig.connect([&] () {
+    sig::connect(sig, [&] () {
         called = true;
     });
-    sig();
+    sig::emit(sig);
 
-    ASSERT_EQ(1U, sig.size());
     ASSERT_TRUE(called);
 }
 
 TEST(Connect, more)
 {
-    Signal<> sig;
+    sig::signal<> sig;
 
     bool f1 = false, f2 = false;
 
-    sig.connect([&] () {
+    sig::connect(sig, [&] () {
         f1 = true;
     });
-    sig.connect([&] () {
+    sig::connect(sig, [&] () {
         f2 = true;
     });
-    sig();
+    sig::emit(sig);
 
-    ASSERT_EQ(2U, sig.size());
     ASSERT_TRUE(f1);
     ASSERT_TRUE(f2);
 }
 
 TEST(Disconnect, simple)
 {
-    Signal<> sig;
+    sig::signal<> sig;
 
     auto called = false;
-    auto index = sig.connect([&] () {
+    auto index = sig::connect(sig, [&] () {
         called = true;
     });
-    sig.disconnect(index);
-    sig();
+    sig::disconnect(sig, index);
+    sig::emit(sig);
 
-    ASSERT_EQ(0U, sig.size());
     ASSERT_FALSE(called);
 }
 
 TEST(Disconnect, more)
 {
-    Signal<> sig;
+    sig::signal<> sig;
 
     auto called = false;
-    auto index = sig.connect([&] () {
+    auto index = sig::connect(sig, [&] () {
         called = true;
     });
-    sig.disconnect(index);
-    sig();
+    sig::disconnect(sig, index);
+    sig::emit(sig);
 
-    ASSERT_EQ(0U, sig.size());
     ASSERT_FALSE(called);
-    ASSERT_EQ(-1, index);
 }
 
 TEST(Disconnect, middle)
 {
-    Signal<> sig;
+    sig::signal<> sig;
 
     bool f1 = false, f2 = false, f3 = false;
 
-    sig.connect([&] () {
+    sig::connect(sig, [&] () {
         f1 = true;
     });
-    auto idx = sig.connect([&] () {
+    auto idx = sig::connect(sig, [&] () {
         f2 = true;
     });
-    sig.connect([&] () {
+    sig::connect(sig, [&] () {
         f3 = true;
     });
-    sig.disconnect(idx);
-    sig();
+    sig::disconnect(sig, idx);
+    sig::emit(sig);
 
-    ASSERT_EQ(2U, sig.size());
     ASSERT_TRUE(f1);
     ASSERT_FALSE(f2);
     ASSERT_TRUE(f3);
@@ -122,24 +113,23 @@
 
 TEST(Disconnect, two)
 {
-    Signal<> sig;
+    sig::signal<> sig;
 
     bool f1 = false, f2 = false, f3 = false;
 
-    sig.connect([&] () {
+    sig::connect(sig, [&] () {
         f1 = true;
     });
-    auto idx1 = sig.connect([&] () {
+    auto idx1 = sig::connect(sig, [&] () {
         f2 = true;
     });
-    auto idx2 = sig.connect([&] () {
+    auto idx2 = sig::connect(sig, [&] () {
         f3 = true;
     });
-    sig.disconnect(idx1);
-    sig.disconnect(idx2);
-    sig();
+    sig::disconnect(sig, idx1);
+    sig::disconnect(sig, idx2);
+    sig::emit(sig);
 
-    ASSERT_EQ(1U, sig.size());
     ASSERT_TRUE(f1);
     ASSERT_FALSE(f2);
     ASSERT_FALSE(f3);
@@ -147,26 +137,25 @@
 
 TEST(Disconnect, reuse)
 {
-    Signal<> sig;
+    sig::signal<> sig;
 
     bool f1 = false, f2 = false, f3 = false;
 
-    sig.connect([&] () {
+    sig::connect(sig, [&] () {
         f1 = true;
     });
-    auto idx = sig.connect([&] () {
+    auto idx = sig::connect(sig, [&] () {
         f2 = true;
     });
-    sig.connect([&] () {
+    sig::connect(sig, [&] () {
         f3 = true;
     });
-    sig.disconnect(idx);
-    sig.connect([&] () {
+    sig::disconnect(sig, idx);
+    sig::connect(sig, [&] () {
         f2 = true;
     });
-    sig();
+    sig::emit(sig);
 
-    ASSERT_EQ(3U, sig.size());
     ASSERT_TRUE(f1);
     ASSERT_TRUE(f2);
     ASSERT_TRUE(f3);
@@ -174,20 +163,19 @@
 
 TEST(Clear, basics)
 {
-    Signal<> sig;
+    sig::signal<> sig;
 
     bool f1 = false, f2 = false;
 
-    sig.connect([&] () {
+    sig::connect(sig, [&] () {
         f1 = true;
     });
-    sig.connect([&] () {
+    sig::connect(sig, [&] () {
         f2 = true;
     });
-    sig.clear();
-    sig();
+    sig::clear(sig);
+    sig::emit(sig);
 
-    ASSERT_EQ(0U, sig.size());
     ASSERT_FALSE(f1);
     ASSERT_FALSE(f2);
 }