Signal: Optimize to use plain std::function.

This optimizes the common case of a single simple callback (e.g. std::function
or lambda) so no additional allocation is required.  As a Connection return
value is not possible in this case, provide a separate connect_connection()
function to provide that.
This commit is contained in:
Peter Johnson
2018-06-29 22:00:58 -07:00
parent 1ecaaafa6c
commit c8afe9bc2f
2 changed files with 94 additions and 41 deletions

View File

@@ -35,6 +35,7 @@ SOFTWARE.
*/
#pragma once
#include <atomic>
#include <functional>
#include <memory>
#include <type_traits>
#include <utility>
@@ -495,6 +496,38 @@ class SignalBase {
using lock_type = std::unique_lock<Lockable>;
using SlotPtr = detail::SlotPtr<T...>;
struct CallSlots {
SlotPtr m_slots;
SignalBase& m_base;
CallSlots(SignalBase& base) : m_base(base) {}
template <typename... A>
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()(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;
}
}
}
};
public:
using arg_list = trait::typelist<T...>;
using ext_arg_list = trait::typelist<Connection&, T...>;
@@ -511,7 +544,7 @@ public:
: m_block{o.m_block.load()}
{
lock_type lock(o.m_mutex);
std::swap(m_slots, o.m_slots);
std::swap(m_func, o.m_func);
}
SignalBase & operator=(SignalBase && o) {
@@ -519,7 +552,7 @@ public:
lock_type lock2(o.m_mutex, std::defer_lock);
std::lock(lock1, lock2);
std::swap(m_slots, o.m_slots);
std::swap(m_func, o.m_func);
m_block.store(o.m_block.exchange(m_block.load()));
return *this;
}
@@ -539,27 +572,7 @@ public:
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;
}
}
if (!m_block && m_func) m_func(std::forward<A>(a)...);
}
/**
@@ -573,8 +586,29 @@ public:
* @return a Connection object that can be used to interact with the slot
*/
template <typename Callable>
void connect(Callable && c) {
if (!m_func) {
m_func = std::forward<Callable>(c);
} else {
using slot_t = detail::Slot<Callable, arg_list>;
auto s = std::make_shared<slot_t>(std::forward<Callable>(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 <typename Callable>
std::enable_if_t<trait::is_callable_v<arg_list, Callable>, Connection>
connect(Callable && c) {
connect_connection(Callable && c) {
using slot_t = detail::Slot<Callable, arg_list>;
auto s = std::make_shared<slot_t>(std::forward<Callable>(c));
add_slot(s);
@@ -697,7 +731,7 @@ public:
*/
template <typename... CallArgs>
ScopedConnection connect_scoped(CallArgs && ...args) {
return connect(std::forward<CallArgs>(args)...);
return connect_connection(std::forward<CallArgs>(args)...);
}
/**
@@ -736,16 +770,35 @@ private:
template <typename S>
void add_slot(S &s) {
lock_type lock(m_mutex);
s->next = m_slots;
m_slots = s;
if (!m_func) {
// nothing stored
m_func = CallSlots(*this);
auto slots = m_func.template target<CallSlots>();
s->next = slots->m_slots;
slots->m_slots = s;
} else if (auto call_slots = m_func.template target<CallSlots>()) {
// 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<std::function<void(T...)>, arg_list>;
auto s2 = std::make_shared<slot_t>(
std::forward<std::function<void(T...)>>(m_func));
m_func = CallSlots(*this);
auto slots = m_func.template target<CallSlots>();
s2->next = slots->m_slots;
s->next = s2;
slots->m_slots = s;
}
}
void clear() {
m_slots.reset();
m_func = nullptr;
}
private:
SlotPtr m_slots;
std::function<void(T...)> m_func;
Lockable m_mutex;
std::atomic<bool> m_block;
};

View File

@@ -49,7 +49,7 @@ namespace {
int sum = 0;
void f1(int i) { sum += i; }
void f2(int i) noexcept { sum += 2 * i; }
void f2(int i) /*noexcept*/ { sum += 2 * i; }
struct s {
static void s1(int i) { sum += i; }
@@ -103,7 +103,7 @@ TEST(Signal, FreeConnection) {
sum = 0;
Signal<int> sig;
auto c1 = sig.connect(f1);
auto c1 = sig.connect_connection(f1);
sig(1);
ASSERT_EQ(sum, 1);
@@ -232,7 +232,7 @@ TEST(Signal, LvalueEmission) {
sum = 0;
Signal<int> sig;
auto c1 = sig.connect(f1);
auto c1 = sig.connect_connection(f1);
int v = 1;
sig(v);
ASSERT_EQ(sum, 1);
@@ -281,7 +281,7 @@ TEST(Signal, Disconnection) {
sum = 0;
Signal<int> sig;
auto sc = sig.connect(f1);
auto sc = sig.connect_connection(f1);
sig(1);
ASSERT_EQ(sum, 1);
@@ -295,7 +295,7 @@ TEST(Signal, Disconnection) {
sum = 0;
Signal<int> sig;
auto sc = sig.connect(f1);
auto sc = sig.connect_connection(f1);
sig(1);
ASSERT_EQ(sum, 1);
@@ -317,7 +317,7 @@ TEST(Signal, Disconnection) {
sig(1);
ASSERT_EQ(sum, 1);
auto sc = sig.connect(f2);
auto sc = sig.connect_connection(f2);
sig(1);
ASSERT_EQ(sum, 4);
@@ -347,11 +347,11 @@ TEST(Signal, ScopedConnection) {
sum = 0;
{
ScopedConnection sc1 = sig.connect(f1);
ScopedConnection sc1 = sig.connect_connection(f1);
sig(1);
ASSERT_EQ(sum, 1);
ScopedConnection sc2 = sig.connect(f2);
ScopedConnection sc2 = sig.connect_connection(f2);
sig(1);
ASSERT_EQ(sum, 4);
}
@@ -364,7 +364,7 @@ TEST(Signal, ConnectionBlocking) {
sum = 0;
Signal<int> sig;
auto c1 = sig.connect(f1);
auto c1 = sig.connect_connection(f1);
sig.connect(f2);
sig(1);
ASSERT_EQ(sum, 3);
@@ -382,7 +382,7 @@ TEST(Signal, ConnectionBlocker) {
sum = 0;
Signal<int> sig;
auto c1 = sig.connect(f1);
auto c1 = sig.connect_connection(f1);
sig.connect(f2);
sig(1);
ASSERT_EQ(sum, 3);
@@ -433,8 +433,8 @@ TEST(Signal, ConnectionCopyingMoving) {
sum = 0;
Signal<int> sig;
auto sc1 = sig.connect(f1);
auto sc2 = sig.connect(f2);
auto sc1 = sig.connect_connection(f1);
auto sc2 = sig.connect_connection(f2);
auto sc3 = sc1;
auto sc4{sc2};