From 876c6504714859eb7aa453e4c378b5bb78efc2a9 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Wed, 27 Jun 2018 23:01:17 -0700 Subject: [PATCH] wpiutil: Add a signal-slot implementation. (#1163) Imported from https://github.com/palacaze/sigslot Classes were renamed from lowercase_me to UppercaseMe style, primarily to avoid conflicting with the C standard library "signal" function. They were also moved to the "wpi::sig" namespace. --- wpiutil/.styleguide | 1 + wpiutil/src/main/native/include/wpi/Signal.h | 783 ++++++++++++++++++ .../native/cpp/sigslot/function-traits.cpp | 135 +++ .../src/test/native/cpp/sigslot/recursive.cpp | 97 +++ .../native/cpp/sigslot/signal-extended.cpp | 140 ++++ .../native/cpp/sigslot/signal-threaded.cpp | 93 +++ .../native/cpp/sigslot/signal-tracking.cpp | 181 ++++ .../src/test/native/cpp/sigslot/signal.cpp | 540 ++++++++++++ 8 files changed, 1970 insertions(+) create mode 100644 wpiutil/src/main/native/include/wpi/Signal.h create mode 100644 wpiutil/src/test/native/cpp/sigslot/function-traits.cpp create mode 100644 wpiutil/src/test/native/cpp/sigslot/recursive.cpp create mode 100644 wpiutil/src/test/native/cpp/sigslot/signal-extended.cpp create mode 100644 wpiutil/src/test/native/cpp/sigslot/signal-threaded.cpp create mode 100644 wpiutil/src/test/native/cpp/sigslot/signal-tracking.cpp create mode 100644 wpiutil/src/test/native/cpp/sigslot/signal.cpp diff --git a/wpiutil/.styleguide b/wpiutil/.styleguide index 9828f4a994..75d4483b1c 100644 --- a/wpiutil/.styleguide +++ b/wpiutil/.styleguide @@ -30,6 +30,7 @@ generatedFileExclude { src/main/native/include/wpi/Path\.h$ src/main/native/include/wpi/PointerLikeTypeTraits\.h$ src/main/native/include/wpi/STLExtras\.h$ + src/main/native/include/wpi/Signal\.h$ src/main/native/include/wpi/SmallPtrSet\.h$ src/main/native/include/wpi/SmallSet\.h$ src/main/native/include/wpi/SmallString\.h$ diff --git a/wpiutil/src/main/native/include/wpi/Signal.h b/wpiutil/src/main/native/include/wpi/Signal.h new file mode 100644 index 0000000000..1832123999 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/Signal.h @@ -0,0 +1,783 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +/* + +Sigslot, a signal-slot library + +https://github.com/palacaze/sigslot + +MIT License + +Copyright (c) 2017 Pierre-Antoine Lacaze + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ +#pragma once +#include +#include +#include +#include + +#include "wpi/mutex.h" + +namespace wpi { + +namespace sig { + +namespace trait { + +/// represent a list of types +template struct typelist {}; + +/** + * Pointers that can be converted to a weak pointer concept for tracking + * purpose must implement the to_weak() function in order to make use of + * ADL to convert that type and make it usable + */ + +template +std::weak_ptr to_weak(std::weak_ptr w) { + return w; +} + +template +std::weak_ptr to_weak(std::shared_ptr s) { + return s; +} + +// tools +namespace detail { + +template +struct voider { using type = void; }; + +// void_t from c++17 +template +using void_t = typename detail::voider::type; + + +template +struct is_callable : std::false_type {}; + +template +struct is_callable, + void_t()).*std::declval())(std::declval()...))>> + : std::true_type {}; + +template +struct is_callable, + void_t()(std::declval()...))>> + : std::true_type {}; + + +template +struct is_weak_ptr : std::false_type {}; + +template +struct is_weak_ptr().expired()), + decltype(std::declval().lock()), + decltype(std::declval().reset())>> + : std::true_type {}; + +template +struct is_weak_ptr_compatible : std::false_type {}; + +template +struct is_weak_ptr_compatible()))>> + : is_weak_ptr()))> {}; + +} // namespace detail + +/// determine if a pointer is convertible into a "weak" pointer +template +constexpr bool is_weak_ptr_compatible_v = detail::is_weak_ptr_compatible>::value; + +/// determine if a type T (Callable or Pmf) is callable with supplied arguments in L +template +constexpr bool is_callable_v = detail::is_callable::value; + +} // namespace trait + + +namespace detail { + +/* SlotState holds slot type independent state, to be used to interact with + * slots indirectly through connection and ScopedConnection objects. + */ +class SlotState { +public: + constexpr SlotState() noexcept + : m_connected(true), + m_blocked(false) {} + + virtual ~SlotState() = default; + + bool connected() const noexcept { return m_connected; } + bool disconnect() noexcept { return m_connected.exchange(false); } + + bool blocked() const noexcept { return m_blocked.load(); } + void block() noexcept { m_blocked.store(true); } + void unblock() noexcept { m_blocked.store(false); } + +private: + std::atomic m_connected; + std::atomic m_blocked; +}; + +} // namespace detail + +/** + * ConnectionBlocker is a RAII object that blocks a connection until destruction + */ +class ConnectionBlocker { +public: + ConnectionBlocker() = default; + ~ConnectionBlocker() noexcept { release(); } + + ConnectionBlocker(const ConnectionBlocker &) = delete; + ConnectionBlocker & operator=(const ConnectionBlocker &) = delete; + + ConnectionBlocker(ConnectionBlocker && o) noexcept + : m_state{std::move(o.m_state)} + {} + + ConnectionBlocker & operator=(ConnectionBlocker && o) noexcept { + release(); + m_state.swap(o.m_state); + return *this; + } + +private: + friend class Connection; + ConnectionBlocker(std::weak_ptr s) noexcept + : m_state{std::move(s)} + { + auto d = m_state.lock(); + if (d) d->block(); + } + + void release() noexcept { + auto d = m_state.lock(); + if (d) d->unblock(); + } + +private: + std::weak_ptr m_state; +}; + + +/** + * A Connection object allows interaction with an ongoing slot connection + * + * It allows common actions such as connection blocking and disconnection. + * Note that Connection is not a RAII object, one does not need to hold one + * such object to keep the signal-slot connection alive. + */ +class Connection { +public: + Connection() = default; + virtual ~Connection() = default; + + Connection(const Connection &) noexcept = default; + Connection & operator=(const Connection &) noexcept = default; + Connection(Connection &&) noexcept = default; + Connection & operator=(Connection &&) noexcept = default; + + bool valid() const noexcept { + return !m_state.expired(); + } + + bool connected() const noexcept { + const auto d = m_state.lock(); + return d && d->connected(); + } + + bool disconnect() noexcept { + auto d = m_state.lock(); + return d && d->disconnect(); + } + + bool blocked() const noexcept { + const auto d = m_state.lock(); + return d && d->blocked(); + } + + void block() noexcept { + auto d = m_state.lock(); + if(d) + d->block(); + } + + void unblock() noexcept { + auto d = m_state.lock(); + if(d) + d->unblock(); + } + + ConnectionBlocker blocker() const noexcept { + return ConnectionBlocker{m_state}; + } + +protected: + template friend class SignalBase; + Connection(std::weak_ptr s) noexcept + : m_state{std::move(s)} + {} + +protected: + std::weak_ptr m_state; +}; + +/** + * ScopedConnection is a RAII version of Connection + * It disconnects the slot from the signal upon destruction. + */ +class ScopedConnection : public Connection { +public: + ScopedConnection() = default; + ~ScopedConnection() { + disconnect(); + } + + ScopedConnection(const Connection &c) noexcept : Connection(c) {} + ScopedConnection(Connection &&c) noexcept : Connection(std::move(c)) {} + + ScopedConnection(const ScopedConnection &) noexcept = delete; + ScopedConnection & operator=(const ScopedConnection &) noexcept = delete; + + ScopedConnection(ScopedConnection && o) noexcept + : Connection{std::move(o.m_state)} + {} + + ScopedConnection & operator=(ScopedConnection && o) noexcept { + disconnect(); + m_state.swap(o.m_state); + return *this; + } + +private: + template friend class SignalBase; + ScopedConnection(std::weak_ptr s) noexcept + : Connection{std::move(s)} + {} +}; + +namespace detail { + +template +class SlotBase; + +template +using SlotPtr = std::shared_ptr>; + +/* A base class for slot objects. This base type only depends on slot argument + * types, it will be used as an element in an intrusive singly-linked list of + * slots, hence the public next member. + */ +template +class SlotBase : public SlotState { +public: + using base_types = trait::typelist; + + virtual ~SlotBase() noexcept = default; + + // method effectively responsible for calling the "slot" function with + // supplied arguments whenever emission happens. + virtual void call_slot(Args...) = 0; + + template + void operator()(U && ...u) { + if (SlotState::connected() && !SlotState::blocked()) + call_slot(std::forward(u)...); + } + + SlotPtr next; +}; + +template class Slot {}; + +/* + * A slot object holds state information, and a callable to to be called + * whenever the function call operator of its SlotBase base class is called. + */ +template +class Slot> : public SlotBase { +public: + template + constexpr Slot(F && f) : func{std::forward(f)} {} + + virtual void call_slot(Args ...args) override { + func(args...); + } + +private: + std::decay_t func; +}; + +/* + * Variation of slot that prepends a Connection object to the callable + */ +template +class Slot> : public SlotBase { +public: + template + constexpr Slot(F && f) : func{std::forward(f)} {} + + virtual void call_slot(Args ...args) override { + func(conn, args...); + } + + Connection conn; + +private: + std::decay_t func; +}; + +/* + * A slot object holds state information, an object and a pointer over member + * function to be called whenever the function call operator of its SlotBase + * base class is called. + */ +template +class Slot> : public SlotBase { +public: + template + constexpr Slot(F && f, P && p) + : pmf{std::forward(f)}, + ptr{std::forward

(p)} {} + + virtual void call_slot(Args ...args) override { + ((*ptr).*pmf)(args...); + } + +private: + std::decay_t pmf; + std::decay_t ptr; +}; + +/* + * Variation of slot that prepends a Connection object to the callable + */ +template +class Slot> : public SlotBase { +public: + template + constexpr Slot(F && f, P && p) + : pmf{std::forward(f)}, + ptr{std::forward

(p)} {} + + virtual void call_slot(Args ...args) override { + ((*ptr).*pmf)(conn, args...); + } + + Connection conn; + +private: + std::decay_t pmf; + std::decay_t ptr; +}; + +template class SlotTracked {}; + +/* + * An implementation of a slot that tracks the life of a supplied object + * through a weak pointer in order to automatically disconnect the slot + * on said object destruction. + */ +template +class SlotTracked> : public SlotBase { +public: + template + constexpr SlotTracked(F && f, P && p) + : func{std::forward(f)}, + ptr{std::forward

(p)} + {} + + virtual void call_slot(Args ...args) override { + if (! SlotState::connected()) + return; + if (ptr.expired()) + SlotState::disconnect(); + else + func(args...); + } + +private: + std::decay_t func; + std::decay_t ptr; +}; + +template class SlotPmfTracked {}; + +/* + * An implementation of a slot as a pointer over member function, that tracks + * the life of a supplied object through a weak pointer in order to automatically + * disconnect the slot on said object destruction. + */ +template +class SlotPmfTracked> : public SlotBase { +public: + template + constexpr SlotPmfTracked(F && f, P && p) + : pmf{std::forward(f)}, + ptr{std::forward

(p)} + {} + + virtual void call_slot(Args ...args) override { + if (! SlotState::connected()) + return; + auto sp = ptr.lock(); + if (!sp) + SlotState::disconnect(); + else + ((*sp).*pmf)(args...); + } + +private: + std::decay_t pmf; + std::decay_t ptr; +}; + + +// noop mutex for thread-unsafe use +struct NullMutex { + NullMutex() = default; + NullMutex(const NullMutex &) = delete; + NullMutex operator=(const NullMutex &) = delete; + NullMutex(NullMutex &&) = delete; + NullMutex operator=(NullMutex &&) = delete; + + bool try_lock() { return true; } + void lock() {} + void unlock() {} +}; + +} // namespace detail + + +/** + * SignalBase is an implementation of the observer pattern, through the use + * of an emitting object and slots that are connected to the signal and called + * with supplied arguments when a signal is emitted. + * + * wpi::SignalBase is the general implementation, whose locking policy must be + * set in order to decide thread safety guarantees. wpi::Signal and wpi::Signal_st + * are partial specializations for multi-threaded and single-threaded use. + * + * It does not allow slots to return a value. + * + * @tparam Lockable a lock type to decide the lock policy + * @tparam T... the argument types of the emitting and slots functions. + */ +template +class SignalBase { + using lock_type = std::unique_lock; + using SlotPtr = detail::SlotPtr; + +public: + using arg_list = trait::typelist; + using ext_arg_list = trait::typelist; + + SignalBase() noexcept : m_block(false) {} + ~SignalBase() { + disconnect_all(); + } + + SignalBase(const SignalBase&) = delete; + SignalBase & operator=(const SignalBase&) = delete; + + SignalBase(SignalBase && o) + : m_block{o.m_block.load()} + { + lock_type lock(o.m_mutex); + std::swap(m_slots, o.m_slots); + } + + SignalBase & operator=(SignalBase && o) { + lock_type lock1(m_mutex, std::defer_lock); + lock_type lock2(o.m_mutex, std::defer_lock); + std::lock(lock1, lock2); + + std::swap(m_slots, o.m_slots); + m_block.store(o.m_block.exchange(m_block.load())); + return *this; + } + + /** + * Emit a signal + * + * Effect: All non blocked and connected slot functions will be called + * with supplied arguments. + * Safety: With proper locking (see wpi::Signal), emission can happen from + * multiple threads simultaneously. The guarantees only apply to the + * signal object, it does not cover thread safety of potentially + * shared state used in slot functions. + * + * @param a... arguments to emit + */ + template + void operator()(A && ... a) { + lock_type lock(m_mutex); + SlotPtr *prev = nullptr; + SlotPtr *curr = m_slots ? &m_slots : nullptr; + + while (curr) { + // call non blocked, non connected slots + if ((*curr)->connected()) { + if (!m_block && !(*curr)->blocked()) + (*curr)->operator()(std::forward(a)...); + prev = curr; + curr = (*curr)->next ? &((*curr)->next) : nullptr; + } + // remove slots marked as disconnected + else { + if (prev) { + (*prev)->next = (*curr)->next; + curr = (*prev)->next ? &((*prev)->next) : nullptr; + } + else + curr = (*curr)->next ? &((*curr)->next) : nullptr; + } + } + } + + /** + * Connect a callable of compatible arguments + * + * Effect: Creates and stores a new slot responsible for executing the + * supplied callable for every subsequent signal emission. + * Safety: Thread-safety depends on locking policy. + * + * @param c a callable + * @return a Connection object that can be used to interact with the slot + */ + template + std::enable_if_t, Connection> + connect(Callable && c) { + using slot_t = detail::Slot; + auto s = std::make_shared(std::forward(c)); + add_slot(s); + return Connection(s); + } + + /** + * Connect a callable with an additional Connection argument + * + * The callable's first argument must be of type Connection. This overload + * the callable to manage it's own connection through this argument. + * + * @param c a callable + * @return a Connection object that can be used to interact with the slot + */ + template + std::enable_if_t, Connection> + connect_extended(Callable && c) { + using slot_t = detail::Slot; + auto s = std::make_shared(std::forward(c)); + s->conn = Connection(s); + add_slot(s); + return Connection(s); + } + + /** + * Overload of connect for pointers over member functions + * + * @param pmf a pointer over member function + * @param ptr an object pointer + * @return a Connection object that can be used to interact with the slot + */ + template + std::enable_if_t && + !trait::is_weak_ptr_compatible_v, Connection> + connect(Pmf && pmf, Ptr && ptr) { + using slot_t = detail::Slot; + auto s = std::make_shared(std::forward(pmf), std::forward(ptr)); + add_slot(s); + return Connection(s); + } + + /** + * Overload of connect for pointer over member functions and + * + * @param pmf a pointer over member function + * @param ptr an object pointer + * @return a Connection object that can be used to interact with the slot + */ + template + std::enable_if_t && + !trait::is_weak_ptr_compatible_v, Connection> + connect_extended(Pmf && pmf, Ptr && ptr) { + using slot_t = detail::Slot; + auto s = std::make_shared(std::forward(pmf), std::forward(ptr)); + s->conn = Connection(s); + add_slot(s); + return Connection(s); + } + + /** + * Overload of connect for lifetime object tracking and automatic disconnection + * + * Ptr must be convertible to an object following a loose form of weak pointer + * concept, by implementing the ADL-detected conversion function to_weak(). + * + * This overload covers the case of a pointer over member function and a + * trackable pointer of that class. + * + * Note: only weak references are stored, a slot does not extend the lifetime + * of a suppied object. + * + * @param pmf a pointer over member function + * @param ptr a trackable object pointer + * @return a Connection object that can be used to interact with the slot + */ + template + std::enable_if_t && + trait::is_weak_ptr_compatible_v, Connection> + connect(Pmf && pmf, Ptr && ptr) { + using trait::to_weak; + auto w = to_weak(std::forward(ptr)); + using slot_t = detail::SlotPmfTracked; + auto s = std::make_shared(std::forward(pmf), w); + add_slot(s); + return Connection(s); + } + + /** + * Overload of connect for lifetime object tracking and automatic disconnection + * + * Trackable must be convertible to an object following a loose form of weak + * pointer concept, by implementing the ADL-detected conversion function to_weak(). + * + * This overload covers the case of a standalone callable and unrelated trackable + * object. + * + * Note: only weak references are stored, a slot does not extend the lifetime + * of a suppied object. + * + * @param c a callable + * @param ptr a trackable object pointer + * @return a Connection object that can be used to interact with the slot + */ + template + std::enable_if_t && + trait::is_weak_ptr_compatible_v, Connection> + connect(Callable && c, Trackable && ptr) { + using trait::to_weak; + auto w = to_weak(std::forward(ptr)); + using slot_t = detail::SlotTracked; + auto s = std::make_shared(std::forward(c), w); + add_slot(s); + return Connection(s); + } + + /** + * Creates a connection whose duration is tied to the return object + * Use the same semantics as connect + */ + template + ScopedConnection connect_scoped(CallArgs && ...args) { + return connect(std::forward(args)...); + } + + /** + * Disconnects all the slots + * Safety: Thread safety depends on locking policy + */ + void disconnect_all() { + lock_type lock(m_mutex); + clear(); + } + + /** + * Blocks signal emission + * Safety: thread safe + */ + void block() noexcept { + m_block.store(true); + } + + /** + * Unblocks signal emission + * Safety: thread safe + */ + void unblock() noexcept { + m_block.store(false); + } + + /** + * Tests blocking state of signal emission + */ + bool blocked() const noexcept { + return m_block.load(); + } + +private: + template + void add_slot(S &s) { + lock_type lock(m_mutex); + s->next = m_slots; + m_slots = s; + } + + void clear() { + m_slots.reset(); + } + +private: + SlotPtr m_slots; + Lockable m_mutex; + std::atomic m_block; +}; + +/** + * Specialization of SignalBase to be used in single threaded contexts. + * Slot connection, disconnection and signal emission are not thread-safe. + * The performance improvement over the thread-safe variant is not impressive, + * so this is not very useful. + */ +template +using Signal_st = SignalBase; + +/** + * Specialization of SignalBase to be used in multi-threaded contexts. + * Slot connection, disconnection and signal emission are thread-safe. + * + * Beware of accidentally using recursive signal emission or cycles between + * two or more signals in your code. Locking std::mutex more than once is + * undefined behaviour, even if it "seems to work somehow". Use signal_r + * instead for that use case. + */ +template +using Signal = SignalBase; + +/** + * Specialization of SignalBase to be used in multi-threaded contexts, allowing + * for recursive signal emission and emission cycles. + * Slot connection, disconnection and signal emission are thread-safe. + */ +template +using Signal_r = SignalBase; + +} // namespace sig +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/sigslot/function-traits.cpp b/wpiutil/src/test/native/cpp/sigslot/function-traits.cpp new file mode 100644 index 0000000000..240e688b2f --- /dev/null +++ b/wpiutil/src/test/native/cpp/sigslot/function-traits.cpp @@ -0,0 +1,135 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +/* + +Sigslot, a signal-slot library + +https://github.com/palacaze/sigslot + +MIT License + +Copyright (c) 2017 Pierre-Antoine Lacaze + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +#include "wpi/Signal.h" // NOLINT(build/include_order) + +#include "gtest/gtest.h" // NOLINT(build/include_order) + +#include + +using namespace wpi::sig::trait; + +namespace { + +void f1(int, char, float) {} +void f2(int, char, float) noexcept {} + +struct oo { + void operator()(int) {} + void operator()(int, char, float) {} +}; + +struct s { + static void s1(int, char, float) {} + static void s2(int, char, float) noexcept {} + + void f1(int, char, float) {} + void f2(int, char, float) const {} + void f3(int, char, float) volatile {} + void f4(int, char, float) const volatile {} + void f5(int, char, float) noexcept {} + void f6(int, char, float) const noexcept {} + void f7(int, char, float) volatile noexcept {} + void f8(int, char, float) const volatile noexcept {} +}; + +struct o1 { + void operator()(int, char, float) {} +}; +struct o2 { + void operator()(int, char, float) const {} +}; +struct o3 { + void operator()(int, char, float) volatile {} +}; +struct o4 { + void operator()(int, char, float) const volatile {} +}; +struct o5 { + void operator()(int, char, float) noexcept {} +}; +struct o6 { + void operator()(int, char, float) const noexcept {} +}; +struct o7 { + void operator()(int, char, float) volatile noexcept {} +}; +struct o8 { + void operator()(int, char, float) const volatile noexcept {} +}; + +using t = typelist; + +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); +static_assert(is_callable_v, ""); + +} // namespace + +namespace wpi { + +TEST(Signal, FunctionTraits) { + auto l1 = [](int, char, float) {}; + auto l2 = [&](int, char, float) mutable {}; + auto l3 = [&](auto...) mutable {}; + + static_assert(is_callable_v, ""); + static_assert(is_callable_v, ""); + static_assert(is_callable_v, ""); + + f1(0, '0', 0.0); + f2(0, '0', 0.0); +} + +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/sigslot/recursive.cpp b/wpiutil/src/test/native/cpp/sigslot/recursive.cpp new file mode 100644 index 0000000000..85ae9a9533 --- /dev/null +++ b/wpiutil/src/test/native/cpp/sigslot/recursive.cpp @@ -0,0 +1,97 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +/* + +Sigslot, a signal-slot library + +https://github.com/palacaze/sigslot + +MIT License + +Copyright (c) 2017 Pierre-Antoine Lacaze + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +#include "wpi/Signal.h" // NOLINT(build/include_order) + +#include "gtest/gtest.h" + +namespace { + +template +struct object { + object(T i) : v{i} {} // NOLINT(runtime/explicit) + + void inc_val(const T& i) { + if (i != v) { + v++; + sig(v); + } + } + + void dec_val(const T& i) { + if (i != v) { + v--; + sig(v); + } + } + + T v; + wpi::sig::Signal_r sig; +}; + +} // namespace + +namespace wpi { + +TEST(Signal, Recursive) { + object i1(-1); + object i2(10); + + i1.sig.connect(&object::dec_val, &i2); + i2.sig.connect(&object::inc_val, &i1); + + i1.inc_val(0); + + ASSERT_EQ(i1.v, i2.v); +} + +TEST(Signal, SelfRecursive) { + int i = 0; + + wpi::sig::Signal_r s; + s.connect([&](int v) { + if (i < 10) { + i++; + s(v + 1); + } + }); + + s(0); + + ASSERT_EQ(i, 10); +} + +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/sigslot/signal-extended.cpp b/wpiutil/src/test/native/cpp/sigslot/signal-extended.cpp new file mode 100644 index 0000000000..1ebbc8e139 --- /dev/null +++ b/wpiutil/src/test/native/cpp/sigslot/signal-extended.cpp @@ -0,0 +1,140 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +/* + +Sigslot, a signal-slot library + +https://github.com/palacaze/sigslot + +MIT License + +Copyright (c) 2017 Pierre-Antoine Lacaze + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +#include "wpi/Signal.h" // NOLINT(build/include_order) + +#include "gtest/gtest.h" + +using namespace wpi::sig; + +namespace { + +int sum = 0; + +void f(Connection& c, int i) { + sum += i; + c.disconnect(); +} + +struct s { + static void sf(Connection& c, int i) { + sum += i; + c.disconnect(); + } + void f(Connection& c, int i) { + sum += i; + c.disconnect(); + } +}; + +struct o { + void operator()(Connection& c, int i) { + sum += i; + c.disconnect(); + } +}; + +} // namespace + +namespace wpi { + +TEST(SignalExtended, FreeConnection) { + sum = 0; + Signal sig; + sig.connect_extended(f); + + sig(1); + ASSERT_EQ(sum, 1); + sig(1); + ASSERT_EQ(sum, 1); +} + +TEST(SignalExtended, StaticConnection) { + sum = 0; + Signal sig; + sig.connect_extended(&s::sf); + + sig(1); + ASSERT_EQ(sum, 1); + sig(1); + ASSERT_EQ(sum, 1); +} + +TEST(SignalExtended, PmfConnection) { + sum = 0; + Signal sig; + s p; + sig.connect_extended(&s::f, &p); + + sig(1); + ASSERT_EQ(sum, 1); + sig(1); + ASSERT_EQ(sum, 1); +} + +TEST(SignalExtended, FunctionObjectConnection) { + sum = 0; + Signal sig; + sig.connect_extended(o{}); + + sig(1); + ASSERT_EQ(sum, 1); + sig(1); + ASSERT_EQ(sum, 1); +} + +TEST(SignalExtended, LambdaConnection) { + sum = 0; + Signal sig; + + sig.connect_extended([&](Connection& c, int i) { + sum += i; + c.disconnect(); + }); + sig(1); + ASSERT_EQ(sum, 1); + + sig.connect_extended([&](Connection& c, int i) mutable { + sum += 2 * i; + c.disconnect(); + }); + sig(1); + ASSERT_EQ(sum, 3); + sig(1); + ASSERT_EQ(sum, 3); +} + +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/sigslot/signal-threaded.cpp b/wpiutil/src/test/native/cpp/sigslot/signal-threaded.cpp new file mode 100644 index 0000000000..f3b05ef2f9 --- /dev/null +++ b/wpiutil/src/test/native/cpp/sigslot/signal-threaded.cpp @@ -0,0 +1,93 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +/* + +Sigslot, a signal-slot library + +https://github.com/palacaze/sigslot + +MIT License + +Copyright (c) 2017 Pierre-Antoine Lacaze + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +#include "wpi/Signal.h" // NOLINT(build/include_order) + +#include "gtest/gtest.h" // NOLINT(build/include_order) + +#include +#include +#include + +using namespace wpi::sig; + +namespace { + +std::atomic sum{0}; + +void f(int i) { sum += i; } + +void emit_many(Signal& sig) { + for (int i = 0; i < 10000; ++i) sig(1); +} + +void connect_emit(Signal& sig) { + for (int i = 0; i < 100; ++i) { + auto s = sig.connect_scoped(f); + for (int j = 0; j < 100; ++j) sig(1); + } +} + +} // namespace + +namespace wpi { + +TEST(Signal, ThreadedMix) { + sum = 0; + + Signal sig; + + std::array threads; + for (auto& t : threads) t = std::thread(connect_emit, std::ref(sig)); + + for (auto& t : threads) t.join(); +} + +TEST(Signal, ThreadedEmission) { + sum = 0; + + Signal sig; + sig.connect(f); + + std::array threads; + for (auto& t : threads) t = std::thread(emit_many, std::ref(sig)); + + for (auto& t : threads) t.join(); + + ASSERT_EQ(sum, 100000); +} + +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/sigslot/signal-tracking.cpp b/wpiutil/src/test/native/cpp/sigslot/signal-tracking.cpp new file mode 100644 index 0000000000..89ffd3648d --- /dev/null +++ b/wpiutil/src/test/native/cpp/sigslot/signal-tracking.cpp @@ -0,0 +1,181 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +/* + +Sigslot, a signal-slot library + +https://github.com/palacaze/sigslot + +MIT License + +Copyright (c) 2017 Pierre-Antoine Lacaze + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +#include "wpi/Signal.h" // NOLINT(build/include_order) + +#include "gtest/gtest.h" // NOLINT(build/include_order) + +#include +#include +#include + +using namespace wpi::sig; + +namespace { + +int sum = 0; + +void f1(int i) { sum += i; } +struct o1 { + void operator()(int i) { sum += 2 * i; } +}; + +struct s { + void f1(int i) { sum += i; } + void f2(int i) const { sum += 2 * i; } +}; + +struct oo { + void operator()(int i) { sum += i; } + void operator()(double i) { sum += std::round(4 * i); } +}; + +struct dummy {}; + +static_assert(trait::is_callable_v, decltype(&s::f1), + std::shared_ptr>, + ""); + +} // namespace + +namespace wpi { + +TEST(Signal, TrackShared) { + sum = 0; + Signal sig; + + auto s1 = std::make_shared(); + sig.connect(&s::f1, s1); + + auto s2 = std::make_shared(); + std::weak_ptr w2 = s2; + sig.connect(&s::f2, w2); + + sig(1); + ASSERT_EQ(sum, 3); + + s1.reset(); + sig(1); + ASSERT_EQ(sum, 5); + + s2.reset(); + sig(1); + ASSERT_EQ(sum, 5); +} + +TEST(Signal, TrackOther) { + sum = 0; + Signal sig; + + auto d1 = std::make_shared(); + sig.connect(f1, d1); + + auto d2 = std::make_shared(); + std::weak_ptr w2 = d2; + sig.connect(o1(), w2); + + sig(1); + ASSERT_EQ(sum, 3); + + d1.reset(); + sig(1); + ASSERT_EQ(sum, 5); + + d2.reset(); + sig(1); + ASSERT_EQ(sum, 5); +} + +TEST(Signal, TrackOverloadedFunctionObject) { + sum = 0; + Signal sig; + Signal sig1; + + auto d1 = std::make_shared(); + sig.connect(oo{}, d1); + sig(1); + ASSERT_EQ(sum, 1); + + d1.reset(); + sig(1); + ASSERT_EQ(sum, 1); + + auto d2 = std::make_shared(); + std::weak_ptr w2 = d2; + sig1.connect(oo{}, w2); + sig1(1); + ASSERT_EQ(sum, 5); + + d2.reset(); + sig1(1); + ASSERT_EQ(sum, 5); +} + +TEST(Signal, TrackGenericLambda) { + std::stringstream s; + + auto f = [&](auto a, auto... args) { + using result_t = int[]; + s << a; + result_t r{ + 1, + ((void)(s << args), 1)..., + }; + (void)r; + }; + + Signal sig1; + Signal sig2; + Signal sig3; + + auto d1 = std::make_shared(); + sig1.connect(f, d1); + sig2.connect(f, d1); + sig3.connect(f, d1); + + sig1(1); + sig2("foo"); + sig3(4.1); + ASSERT_EQ(s.str(), "1foo4.1"); + + d1.reset(); + sig1(2); + sig2("bar"); + sig3(3.0); + ASSERT_EQ(s.str(), "1foo4.1"); +} + +} // namespace wpi diff --git a/wpiutil/src/test/native/cpp/sigslot/signal.cpp b/wpiutil/src/test/native/cpp/sigslot/signal.cpp new file mode 100644 index 0000000000..34d0a1e492 --- /dev/null +++ b/wpiutil/src/test/native/cpp/sigslot/signal.cpp @@ -0,0 +1,540 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +/* + +Sigslot, a signal-slot library + +https://github.com/palacaze/sigslot + +MIT License + +Copyright (c) 2017 Pierre-Antoine Lacaze + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +#include "wpi/Signal.h" // NOLINT(build/include_order) + +#include "gtest/gtest.h" // NOLINT(build/include_order) + +#include +#include +#include + +using namespace wpi::sig; + +namespace { + +int sum = 0; + +void f1(int i) { sum += i; } +void f2(int i) noexcept { sum += 2 * i; } + +struct s { + static void s1(int i) { sum += i; } + static void s2(int i) noexcept { sum += 2 * i; } + + void f1(int i) { sum += i; } + void f2(int i) const { sum += i; } + void f3(int i) volatile { sum += i; } + void f4(int i) const volatile { sum += i; } + void f5(int i) noexcept { sum += i; } + void f6(int i) const noexcept { sum += i; } + void f7(int i) volatile noexcept { sum += i; } + void f8(int i) const volatile noexcept { sum += i; } +}; + +struct oo { + void operator()(int i) { sum += i; } + void operator()(double i) { sum += std::round(4 * i); } +}; + +struct o1 { + void operator()(int i) { sum += i; } +}; +struct o2 { + void operator()(int i) const { sum += i; } +}; +struct o3 { + void operator()(int i) volatile { sum += i; } +}; +struct o4 { + void operator()(int i) const volatile { sum += i; } +}; +struct o5 { + void operator()(int i) noexcept { sum += i; } +}; +struct o6 { + void operator()(int i) const noexcept { sum += i; } +}; +struct o7 { + void operator()(int i) volatile noexcept { sum += i; } +}; +struct o8 { + void operator()(int i) const volatile noexcept { sum += i; } +}; + +} // namespace + +namespace wpi { + +TEST(Signal, FreeConnection) { + sum = 0; + Signal sig; + + auto c1 = sig.connect(f1); + sig(1); + ASSERT_EQ(sum, 1); + + sig.connect(f2); + sig(1); + ASSERT_EQ(sum, 4); +} + +TEST(Signal, StaticConnection) { + sum = 0; + Signal sig; + + sig.connect(&s::s1); + sig(1); + ASSERT_EQ(sum, 1); + + sig.connect(&s::s2); + sig(1); + ASSERT_EQ(sum, 4); +} + +TEST(Signal, PmfConnection) { + sum = 0; + Signal sig; + s p; + + sig.connect(&s::f1, &p); + sig.connect(&s::f2, &p); + sig.connect(&s::f3, &p); + sig.connect(&s::f4, &p); + sig.connect(&s::f5, &p); + sig.connect(&s::f6, &p); + sig.connect(&s::f7, &p); + sig.connect(&s::f8, &p); + + sig(1); + ASSERT_EQ(sum, 8); +} + +TEST(Signal, ConstPmfConnection) { + sum = 0; + Signal sig; + const s p; + + sig.connect(&s::f2, &p); + sig.connect(&s::f4, &p); + sig.connect(&s::f6, &p); + sig.connect(&s::f8, &p); + + sig(1); + ASSERT_EQ(sum, 4); +} + +TEST(Signal, FunctionObjectConnection) { + sum = 0; + Signal sig; + + sig.connect(o1{}); + sig.connect(o2{}); + sig.connect(o3{}); + sig.connect(o4{}); + sig.connect(o5{}); + sig.connect(o6{}); + sig.connect(o7{}); + sig.connect(o8{}); + + sig(1); + ASSERT_EQ(sum, 8); +} + +TEST(Signal, OverloadedFunctionObjectConnection) { + sum = 0; + Signal sig; + Signal sig1; + + sig.connect(oo{}); + sig(1); + ASSERT_EQ(sum, 1); + + sig1.connect(oo{}); + sig1(1); + ASSERT_EQ(sum, 5); +} + +TEST(Signal, LambdaConnection) { + sum = 0; + Signal sig; + + sig.connect([&](int i) { sum += i; }); + sig(1); + ASSERT_EQ(sum, 1); + + sig.connect([&](int i) mutable { sum += 2 * i; }); + sig(1); + ASSERT_EQ(sum, 4); +} + +TEST(Signal, GenericLambdaConnection) { + std::stringstream s; + + auto f = [&](auto a, auto... args) { + using result_t = int[]; + s << a; + result_t r{ + 1, + ((void)(s << args), 1)..., + }; + (void)r; + }; + + Signal sig1; + Signal sig2; + Signal sig3; + + sig1.connect(f); + sig2.connect(f); + sig3.connect(f); + sig1(1); + sig2("foo"); + sig3(4.1); + + ASSERT_EQ(s.str(), "1foo4.1"); +} + +TEST(Signal, LvalueEmission) { + sum = 0; + Signal sig; + + auto c1 = sig.connect(f1); + int v = 1; + sig(v); + ASSERT_EQ(sum, 1); + + sig.connect(f2); + sig(v); + ASSERT_EQ(sum, 4); +} + +TEST(Signal, Mutation) { + int res = 0; + Signal sig; + + sig.connect([](int& r) { r += 1; }); + sig(res); + ASSERT_EQ(res, 1); + + sig.connect([](int& r) mutable { r += 2; }); + sig(res); + ASSERT_EQ(res, 4); +} + +TEST(Signal, CompatibleArgs) { + long ll = 0; // NOLINT(runtime/int) + std::string ss; + short ii = 0; // NOLINT(runtime/int) + + auto f = [&](long l, const std::string& s, short i) { // NOLINT(runtime/int) + ll = l; + ss = s; + ii = i; + }; + + Signal sig; + sig.connect(f); + sig('0', "foo", true); + + ASSERT_EQ(ll, 48); + ASSERT_EQ(ss, "foo"); + ASSERT_EQ(ii, 1); +} + +TEST(Signal, Disconnection) { + // test removing only connected + { + sum = 0; + Signal sig; + + auto sc = sig.connect(f1); + sig(1); + ASSERT_EQ(sum, 1); + + sc.disconnect(); + sig(1); + ASSERT_EQ(sum, 1); + } + + // test removing first connected + { + sum = 0; + Signal sig; + + auto sc = sig.connect(f1); + sig(1); + ASSERT_EQ(sum, 1); + + sig.connect(f2); + sig(1); + ASSERT_EQ(sum, 4); + + sc.disconnect(); + sig(1); + ASSERT_EQ(sum, 6); + } + + // test removing last connected + { + sum = 0; + Signal sig; + + sig.connect(f1); + sig(1); + ASSERT_EQ(sum, 1); + + auto sc = sig.connect(f2); + sig(1); + ASSERT_EQ(sum, 4); + + sc.disconnect(); + sig(1); + ASSERT_EQ(sum, 5); + } +} + +TEST(Signal, ScopedConnection) { + sum = 0; + Signal sig; + + { + auto sc1 = sig.connect_scoped(f1); + sig(1); + ASSERT_EQ(sum, 1); + + auto sc2 = sig.connect_scoped(f2); + sig(1); + ASSERT_EQ(sum, 4); + } + + sig(1); + ASSERT_EQ(sum, 4); + + sum = 0; + + { + ScopedConnection sc1 = sig.connect(f1); + sig(1); + ASSERT_EQ(sum, 1); + + ScopedConnection sc2 = sig.connect(f2); + sig(1); + ASSERT_EQ(sum, 4); + } + + sig(1); + ASSERT_EQ(sum, 4); +} + +TEST(Signal, ConnectionBlocking) { + sum = 0; + Signal sig; + + auto c1 = sig.connect(f1); + sig.connect(f2); + sig(1); + ASSERT_EQ(sum, 3); + + c1.block(); + sig(1); + ASSERT_EQ(sum, 5); + + c1.unblock(); + sig(1); + ASSERT_EQ(sum, 8); +} + +TEST(Signal, ConnectionBlocker) { + sum = 0; + Signal sig; + + auto c1 = sig.connect(f1); + sig.connect(f2); + sig(1); + ASSERT_EQ(sum, 3); + + { + auto cb = c1.blocker(); + sig(1); + ASSERT_EQ(sum, 5); + } + + sig(1); + ASSERT_EQ(sum, 8); +} + +TEST(Signal, SignalBlocking) { + sum = 0; + Signal sig; + + sig.connect(f1); + sig.connect(f2); + sig(1); + ASSERT_EQ(sum, 3); + + sig.block(); + sig(1); + ASSERT_EQ(sum, 3); + + sig.unblock(); + sig(1); + ASSERT_EQ(sum, 6); +} + +TEST(Signal, AllDisconnection) { + sum = 0; + Signal sig; + + sig.connect(f1); + sig.connect(f2); + sig(1); + ASSERT_EQ(sum, 3); + + sig.disconnect_all(); + sig(1); + ASSERT_EQ(sum, 3); +} + +TEST(Signal, ConnectionCopyingMoving) { + sum = 0; + Signal sig; + + auto sc1 = sig.connect(f1); + auto sc2 = sig.connect(f2); + + auto sc3 = sc1; + auto sc4{sc2}; + + auto sc5 = std::move(sc3); + auto sc6{std::move(sc4)}; + + sig(1); + ASSERT_EQ(sum, 3); + + sc5.block(); + sig(1); + ASSERT_EQ(sum, 5); + + sc1.unblock(); + sig(1); + ASSERT_EQ(sum, 8); + + sc6.disconnect(); + sig(1); + ASSERT_EQ(sum, 9); +} + +TEST(Signal, ScopedConnectionMoving) { + sum = 0; + Signal sig; + + { + auto sc1 = sig.connect_scoped(f1); + sig(1); + ASSERT_EQ(sum, 1); + + auto sc2 = sig.connect_scoped(f2); + sig(1); + ASSERT_EQ(sum, 4); + + auto sc3 = std::move(sc1); + sig(1); + ASSERT_EQ(sum, 7); + + auto sc4{std::move(sc2)}; + sig(1); + ASSERT_EQ(sum, 10); + } + + sig(1); + ASSERT_EQ(sum, 10); +} + +TEST(Signal, SignalMoving) { + sum = 0; + Signal sig; + + sig.connect(f1); + sig.connect(f2); + + sig(1); + ASSERT_EQ(sum, 3); + + auto sig2 = std::move(sig); + sig2(1); + ASSERT_EQ(sum, 6); + + auto sig3 = std::move(sig2); + sig3(1); + ASSERT_EQ(sum, 9); +} + +template +struct object { + object(); + object(T i) : v{i} {} // NOLINT(runtime/explicit) + + const T& val() const { return v; } + T& val() { return v; } + void set_val(const T& i) { + if (i != v) { + v = i; + s(i); + } + } + + Signal& sig() { return s; } + + private: + T v; + Signal s; +}; + +TEST(Signal, Loop) { + object i1(0); + object i2(3); + + i1.sig().connect(&object::set_val, &i2); + i2.sig().connect(&object::set_val, &i1); + + i1.set_val(1); + + ASSERT_EQ(i1.val(), 1); + ASSERT_EQ(i2.val(), 1); +} + +} // namespace wpi