mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-25 01:41:43 +00:00
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.
This commit is contained in:
783
wpiutil/src/main/native/include/wpi/Signal.h
Normal file
783
wpiutil/src/main/native/include/wpi/Signal.h
Normal file
@@ -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 <atomic>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "wpi/mutex.h"
|
||||
|
||||
namespace wpi {
|
||||
|
||||
namespace sig {
|
||||
|
||||
namespace trait {
|
||||
|
||||
/// represent a list of types
|
||||
template <typename...> 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<typename T>
|
||||
std::weak_ptr<T> to_weak(std::weak_ptr<T> w) {
|
||||
return w;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::weak_ptr<T> to_weak(std::shared_ptr<T> s) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// tools
|
||||
namespace detail {
|
||||
|
||||
template <class...>
|
||||
struct voider { using type = void; };
|
||||
|
||||
// void_t from c++17
|
||||
template <class...T>
|
||||
using void_t = typename detail::voider<T...>::type;
|
||||
|
||||
|
||||
template <typename, typename, typename = void, typename = void>
|
||||
struct is_callable : std::false_type {};
|
||||
|
||||
template <typename F, typename P, typename... T>
|
||||
struct is_callable<F, P, typelist<T...>,
|
||||
void_t<decltype(((*std::declval<P>()).*std::declval<F>())(std::declval<T>()...))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename F, typename... T>
|
||||
struct is_callable<F, typelist<T...>,
|
||||
void_t<decltype(std::declval<F>()(std::declval<T>()...))>>
|
||||
: std::true_type {};
|
||||
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct is_weak_ptr : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_weak_ptr<T, void_t<decltype(std::declval<T>().expired()),
|
||||
decltype(std::declval<T>().lock()),
|
||||
decltype(std::declval<T>().reset())>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct is_weak_ptr_compatible : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_weak_ptr_compatible<T, void_t<decltype(to_weak(std::declval<T>()))>>
|
||||
: is_weak_ptr<decltype(to_weak(std::declval<T>()))> {};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// determine if a pointer is convertible into a "weak" pointer
|
||||
template <typename P>
|
||||
constexpr bool is_weak_ptr_compatible_v = detail::is_weak_ptr_compatible<std::decay_t<P>>::value;
|
||||
|
||||
/// determine if a type T (Callable or Pmf) is callable with supplied arguments in L
|
||||
template <typename L, typename... T>
|
||||
constexpr bool is_callable_v = detail::is_callable<T..., L>::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<bool> m_connected;
|
||||
std::atomic<bool> 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<detail::SlotState> 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<detail::SlotState> 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 <typename, typename...> friend class SignalBase;
|
||||
Connection(std::weak_ptr<detail::SlotState> s) noexcept
|
||||
: m_state{std::move(s)}
|
||||
{}
|
||||
|
||||
protected:
|
||||
std::weak_ptr<detail::SlotState> 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 <typename, typename...> friend class SignalBase;
|
||||
ScopedConnection(std::weak_ptr<detail::SlotState> s) noexcept
|
||||
: Connection{std::move(s)}
|
||||
{}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename...>
|
||||
class SlotBase;
|
||||
|
||||
template <typename... T>
|
||||
using SlotPtr = std::shared_ptr<SlotBase<T...>>;
|
||||
|
||||
/* 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 <typename... Args>
|
||||
class SlotBase : public SlotState {
|
||||
public:
|
||||
using base_types = trait::typelist<Args...>;
|
||||
|
||||
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 <typename... U>
|
||||
void operator()(U && ...u) {
|
||||
if (SlotState::connected() && !SlotState::blocked())
|
||||
call_slot(std::forward<U>(u)...);
|
||||
}
|
||||
|
||||
SlotPtr<Args...> next;
|
||||
};
|
||||
|
||||
template <typename, typename...> 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 <typename Func, typename... Args>
|
||||
class Slot<Func, trait::typelist<Args...>> : public SlotBase<Args...> {
|
||||
public:
|
||||
template <typename F>
|
||||
constexpr Slot(F && f) : func{std::forward<F>(f)} {}
|
||||
|
||||
virtual void call_slot(Args ...args) override {
|
||||
func(args...);
|
||||
}
|
||||
|
||||
private:
|
||||
std::decay_t<Func> func;
|
||||
};
|
||||
|
||||
/*
|
||||
* Variation of slot that prepends a Connection object to the callable
|
||||
*/
|
||||
template <typename Func, typename... Args>
|
||||
class Slot<Func, trait::typelist<Connection&, Args...>> : public SlotBase<Args...> {
|
||||
public:
|
||||
template <typename F>
|
||||
constexpr Slot(F && f) : func{std::forward<F>(f)} {}
|
||||
|
||||
virtual void call_slot(Args ...args) override {
|
||||
func(conn, args...);
|
||||
}
|
||||
|
||||
Connection conn;
|
||||
|
||||
private:
|
||||
std::decay_t<Func> 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 <typename Pmf, typename Ptr, typename... Args>
|
||||
class Slot<Pmf, Ptr, trait::typelist<Args...>> : public SlotBase<Args...> {
|
||||
public:
|
||||
template <typename F, typename P>
|
||||
constexpr Slot(F && f, P && p)
|
||||
: pmf{std::forward<F>(f)},
|
||||
ptr{std::forward<P>(p)} {}
|
||||
|
||||
virtual void call_slot(Args ...args) override {
|
||||
((*ptr).*pmf)(args...);
|
||||
}
|
||||
|
||||
private:
|
||||
std::decay_t<Pmf> pmf;
|
||||
std::decay_t<Ptr> ptr;
|
||||
};
|
||||
|
||||
/*
|
||||
* Variation of slot that prepends a Connection object to the callable
|
||||
*/
|
||||
template <typename Pmf, typename Ptr, typename... Args>
|
||||
class Slot<Pmf, Ptr, trait::typelist<Connection&, Args...>> : public SlotBase<Args...> {
|
||||
public:
|
||||
template <typename F, typename P>
|
||||
constexpr Slot(F && f, P && p)
|
||||
: pmf{std::forward<F>(f)},
|
||||
ptr{std::forward<P>(p)} {}
|
||||
|
||||
virtual void call_slot(Args ...args) override {
|
||||
((*ptr).*pmf)(conn, args...);
|
||||
}
|
||||
|
||||
Connection conn;
|
||||
|
||||
private:
|
||||
std::decay_t<Pmf> pmf;
|
||||
std::decay_t<Ptr> ptr;
|
||||
};
|
||||
|
||||
template <typename, typename, typename...> 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 <typename Func, typename WeakPtr, typename... Args>
|
||||
class SlotTracked<Func, WeakPtr, trait::typelist<Args...>> : public SlotBase<Args...> {
|
||||
public:
|
||||
template <typename F, typename P>
|
||||
constexpr SlotTracked(F && f, P && p)
|
||||
: func{std::forward<F>(f)},
|
||||
ptr{std::forward<P>(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> func;
|
||||
std::decay_t<WeakPtr> ptr;
|
||||
};
|
||||
|
||||
template <typename, typename, typename...> 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 <typename Pmf, typename WeakPtr, typename... Args>
|
||||
class SlotPmfTracked<Pmf, WeakPtr, trait::typelist<Args...>> : public SlotBase<Args...> {
|
||||
public:
|
||||
template <typename F, typename P>
|
||||
constexpr SlotPmfTracked(F && f, P && p)
|
||||
: pmf{std::forward<F>(f)},
|
||||
ptr{std::forward<P>(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> pmf;
|
||||
std::decay_t<WeakPtr> 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 <typename Lockable, typename... T>
|
||||
class SignalBase {
|
||||
using lock_type = std::unique_lock<Lockable>;
|
||||
using SlotPtr = detail::SlotPtr<T...>;
|
||||
|
||||
public:
|
||||
using arg_list = trait::typelist<T...>;
|
||||
using ext_arg_list = trait::typelist<Connection&, T...>;
|
||||
|
||||
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 <typename... A>
|
||||
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>(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 <typename Callable>
|
||||
std::enable_if_t<trait::is_callable_v<arg_list, Callable>, Connection>
|
||||
connect(Callable && c) {
|
||||
using slot_t = detail::Slot<Callable, arg_list>;
|
||||
auto s = std::make_shared<slot_t>(std::forward<Callable>(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 <typename Callable>
|
||||
std::enable_if_t<trait::is_callable_v<ext_arg_list, Callable>, Connection>
|
||||
connect_extended(Callable && c) {
|
||||
using slot_t = detail::Slot<Callable, ext_arg_list>;
|
||||
auto s = std::make_shared<slot_t>(std::forward<Callable>(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 <typename Pmf, typename Ptr>
|
||||
std::enable_if_t<trait::is_callable_v<arg_list, Pmf, Ptr> &&
|
||||
!trait::is_weak_ptr_compatible_v<Ptr>, Connection>
|
||||
connect(Pmf && pmf, Ptr && ptr) {
|
||||
using slot_t = detail::Slot<Pmf, Ptr, arg_list>;
|
||||
auto s = std::make_shared<slot_t>(std::forward<Pmf>(pmf), std::forward<Ptr>(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 <typename Pmf, typename Ptr>
|
||||
std::enable_if_t<trait::is_callable_v<ext_arg_list, Pmf, Ptr> &&
|
||||
!trait::is_weak_ptr_compatible_v<Ptr>, Connection>
|
||||
connect_extended(Pmf && pmf, Ptr && ptr) {
|
||||
using slot_t = detail::Slot<Pmf, Ptr, ext_arg_list>;
|
||||
auto s = std::make_shared<slot_t>(std::forward<Pmf>(pmf), std::forward<Ptr>(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 <typename Pmf, typename Ptr>
|
||||
std::enable_if_t<!trait::is_callable_v<arg_list, Pmf> &&
|
||||
trait::is_weak_ptr_compatible_v<Ptr>, Connection>
|
||||
connect(Pmf && pmf, Ptr && ptr) {
|
||||
using trait::to_weak;
|
||||
auto w = to_weak(std::forward<Ptr>(ptr));
|
||||
using slot_t = detail::SlotPmfTracked<Pmf, decltype(w), arg_list>;
|
||||
auto s = std::make_shared<slot_t>(std::forward<Pmf>(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 <typename Callable, typename Trackable>
|
||||
std::enable_if_t<trait::is_callable_v<arg_list, Callable> &&
|
||||
trait::is_weak_ptr_compatible_v<Trackable>, Connection>
|
||||
connect(Callable && c, Trackable && ptr) {
|
||||
using trait::to_weak;
|
||||
auto w = to_weak(std::forward<Trackable>(ptr));
|
||||
using slot_t = detail::SlotTracked<Callable, decltype(w), arg_list>;
|
||||
auto s = std::make_shared<slot_t>(std::forward<Callable>(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 <typename... CallArgs>
|
||||
ScopedConnection connect_scoped(CallArgs && ...args) {
|
||||
return connect(std::forward<CallArgs>(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 <typename S>
|
||||
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<bool> 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 <typename... T>
|
||||
using Signal_st = SignalBase<detail::NullMutex, T...>;
|
||||
|
||||
/**
|
||||
* 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 <typename... T>
|
||||
using Signal = SignalBase<mutex, T...>;
|
||||
|
||||
/**
|
||||
* 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 <typename... T>
|
||||
using Signal_r = SignalBase<recursive_mutex, T...>;
|
||||
|
||||
} // namespace sig
|
||||
} // namespace wpi
|
||||
Reference in New Issue
Block a user