/*----------------------------------------------------------------------------*/ /* Copyright (c) 2018-2019 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 #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; struct CallSlots { SlotPtr m_slots; SignalBase& m_base; CallSlots(SignalBase& base) : m_base(base) {} template void operator()(A && ... a) { SlotPtr *prev = nullptr; SlotPtr *curr = m_slots ? &m_slots : nullptr; while (curr) { // call non blocked, non connected slots if ((*curr)->connected()) { if (!m_base.m_block && !(*curr)->blocked()) (*curr)->operator()(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; } } } }; 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_func, o.m_func); } SignalBase & operator=(SignalBase && o) { std::scoped_lock lock(m_mutex, o.m_mutex); std::swap(m_func, o.m_func); 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) const { lock_type lock(m_mutex); if (!m_block && m_func) m_func(std::forward(a)...); } /** * 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 void connect(Callable && c) { if (!m_func) { m_func = std::forward(c); } else { using slot_t = detail::Slot; auto s = std::make_shared(std::forward(c)); add_slot(s); } } /** * Connect a callable of compatible arguments, returning a Connection * * 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_connection(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_connection(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); if (!m_func) { // nothing stored m_func = CallSlots(*this); auto slots = m_func.template target(); s->next = slots->m_slots; slots->m_slots = s; } else if (auto call_slots = m_func.template target()) { // already CallSlots s->next = call_slots->m_slots; call_slots->m_slots = s; } else { // was normal std::function, need to move it into a call slot using slot_t = detail::Slot, arg_list>; auto s2 = std::make_shared( std::forward>(m_func)); m_func = CallSlots(*this); auto slots = m_func.template target(); s2->next = slots->m_slots; s->next = s2; slots->m_slots = s; } } void clear() { m_func = nullptr; } private: std::function m_func; mutable 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. * This is significantly smaller than the thread-safe variant. */ template using Signal = 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_mt = 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