diff --git a/wpiutil/src/main/native/include/wpi/Signal.h b/wpiutil/src/main/native/include/wpi/Signal.h index 1832123999..e518b71e9a 100644 --- a/wpiutil/src/main/native/include/wpi/Signal.h +++ b/wpiutil/src/main/native/include/wpi/Signal.h @@ -35,6 +35,7 @@ SOFTWARE. */ #pragma once #include +#include #include #include #include @@ -495,6 +496,38 @@ 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()(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; + } + } + } + }; + public: using arg_list = trait::typelist; using ext_arg_list = trait::typelist; @@ -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 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; - } - } + if (!m_block && m_func) m_func(std::forward(a)...); } /** @@ -573,8 +586,29 @@ public: * @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(Callable && c) { + connect_connection(Callable && c) { using slot_t = detail::Slot; auto s = std::make_shared(std::forward(c)); add_slot(s); @@ -697,7 +731,7 @@ public: */ template ScopedConnection connect_scoped(CallArgs && ...args) { - return connect(std::forward(args)...); + return connect_connection(std::forward(args)...); } /** @@ -736,16 +770,35 @@ private: template 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(); + 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_slots.reset(); + m_func = nullptr; } private: - SlotPtr m_slots; + std::function m_func; Lockable m_mutex; std::atomic m_block; }; diff --git a/wpiutil/src/test/native/cpp/sigslot/signal.cpp b/wpiutil/src/test/native/cpp/sigslot/signal.cpp index 34d0a1e492..a4f92080ed 100644 --- a/wpiutil/src/test/native/cpp/sigslot/signal.cpp +++ b/wpiutil/src/test/native/cpp/sigslot/signal.cpp @@ -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 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 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 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 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 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 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 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};