Mercurial > code
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); }