CsSignal  1.3.1
cs_signal.h
1 /***********************************************************************
2 *
3 * Copyright (c) 2016-2024 Barbara Geller
4 * Copyright (c) 2016-2024 Ansel Sermersheim
5 *
6 * This file is part of CsSignal.
7 *
8 * CsSignal is free software, released under the BSD 2-Clause license.
9 * For license details refer to LICENSE provided with this project.
10 *
11 * CsSignal is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 *
15 * https://opensource.org/licenses/BSD-2-Clause
16 *
17 ***********************************************************************/
18 
19 #ifndef LIB_CS_SIGNAL_H
20 #define LIB_CS_SIGNAL_H
21 
22 #include <algorithm>
23 #include <exception>
24 #include <mutex>
25 #include <stdexcept>
26 #include <thread>
27 #include <type_traits>
28 #include <tuple>
29 #include <unordered_set>
30 
31 #include "cs_internal.h"
32 #include "cs_macro.h"
33 #include "cs_slot.h"
34 #include "cs_rcu_guarded.h"
35 #include "cs_rcu_list.h"
36 
37 namespace CsSignal {
38 
39 enum class ConnectionKind {
40  AutoConnection,
41  DirectConnection,
42  QueuedConnection,
43  BlockingQueuedConnection
44 };
45 
46 enum class DisconnectKind {
47  DisconnectAll,
48  DisconnectOne
49 };
50 
51 template <class Iter1, class Iter2, class T>
52 Iter1 find(Iter1 iter1, const Iter2 &iter2, const T &value)
53 {
54  while (iter1 != iter2) {
55  if (value == *iter1) {
56  break;
57  }
58 
59  ++iter1;
60  }
61 
62  return iter1;
63 }
64 
65 template<class Sender, class SignalClass, class ...SignalArgs, class Receiver,
66  class SlotClass, class ...SlotArgs, class SlotReturn>
67 bool connect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...),
68  const Receiver &receiver, SlotReturn (SlotClass::*slotMethod)(SlotArgs...),
69  ConnectionKind type = ConnectionKind::AutoConnection, bool uniqueConnection = false);
70 
71 template<class Sender, class SignalClass, class ...SignalArgs, class Receiver, class T>
72 bool connect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...),
73  const Receiver &receiver, T slotLambda,
74  ConnectionKind type = ConnectionKind::AutoConnection, bool uniqueConnection = false);
75 
76 template<class Sender, class Receiver>
77 bool connect(const Sender &sender, std::unique_ptr<Internal::BentoAbstract> signalMethod_Bento,
78  const Receiver &receiver, std::unique_ptr<Internal::BentoAbstract> slotMethod_Bento,
79  ConnectionKind type = ConnectionKind::AutoConnection, bool uniqueConnection = false);
80 
81 // base class
82 class LIB_SIG_EXPORT SignalBase
83 {
84  public:
85  virtual ~SignalBase();
86 
87  protected:
88  static Internal::BentoAbstract *&get_threadLocal_currentSignal();
89 
90  int internal_cntConnections(const SlotBase *receiver,
91  const Internal::BentoAbstract &signalMethod_Bento) const;
92 
93  std::set<SlotBase *> internal_receiverList(
94  const Internal::BentoAbstract &signalMethod_Bento) const;
95 
96  private:
97  // part of destructor
98  static std::mutex &get_mutex_beingDestroyed();
99  static std::unordered_set<const SignalBase *> &get_beingDestroyed();
100 
101  // part of disconnect
102  mutable int m_activateBusy = 0;
103 
104  struct ConnectStruct {
105  std::unique_ptr<const Internal::BentoAbstract> signalMethod;
106  const SlotBase *receiver;
107  std::unique_ptr<const Internal::BentoAbstract> slotMethod;
108  ConnectionKind type;
109  };
110 
111  // list of connections from my Signal to some Receiver
112  mutable libguarded::SharedList<ConnectStruct> m_connectList;
113 
114  void addConnection(std::unique_ptr<const Internal::BentoAbstract> signalMethod, const SlotBase *,
115  std::unique_ptr<const Internal::BentoAbstract> slotMethod, ConnectionKind type,
116  libguarded::SharedList<ConnectStruct>::write_handle &senderListHandle) const;
117 
118  virtual void handleException(std::exception_ptr data);
119 
120  template<class Sender, class SignalClass, class ...SignalArgTypes, class ...Ts>
121  friend void activate(Sender &sender, void (SignalClass::*signal)(SignalArgTypes...), Ts &&... Vs);
122 
123  template<class Sender, class SignalClass, class ...SignalArgs, class Receiver,
124  class SlotClass, class ...SlotArgs, class SlotReturn>
125  friend bool connect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...),
126  const Receiver &receiver, SlotReturn (SlotClass::*slotMethod)(SlotArgs...),
127  ConnectionKind type, bool uniqueConnection);
128 
129  template<class Sender, class SignalClass, class ...SignalArgs, class Receiver, class T>
130  friend bool connect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...),
131  const Receiver &receiver, T slotLambda,
132  ConnectionKind type, bool uniqueConnection);
133 
134  template<class Sender, class Receiver>
135  friend bool connect(const Sender &sender, std::unique_ptr<Internal::BentoAbstract> signalMethod_Bento,
136  const Receiver &receiver, std::unique_ptr<Internal::BentoAbstract> slotMethod_Bento,
137  ConnectionKind type, bool uniqueConnection);
138 
139  template<class Sender, class Receiver>
140  friend bool internal_disconnect(const Sender &sender, const Internal::BentoAbstract *signalBento,
141  const Receiver *receiver, const Internal::BentoAbstract *slotBento);
142 
143  friend class SlotBase;
144 };
145 
146 template<class Sender, class SignalClass, class ...SignalArgTypes, class ...Ts>
147 void activate(Sender &sender, void (SignalClass::*signal)(SignalArgTypes...), Ts &&... Vs)
148 {
149  // ensure signal args are passed
150  static_assert( std::is_convertible<std::tuple<Ts...>, std::tuple<SignalArgTypes...>>::value,
151  "activate(): Signal parameter mismatch.");
152 
153  Internal::Bento<void (SignalClass::*)(SignalArgTypes...)> signal_Bento(signal);
154 
155  // save the address of sender
156  const SignalBase *senderPtr = &sender;
157 
158  // store the signal data, false indicates the data will not be copied
159  CsSignal::Internal::TeaCup_Data<SignalArgTypes...> dataPack(false, std::forward<Ts>(Vs)...);
160 
161  SignalBase *priorSender = SlotBase::get_threadLocal_currentSender();
162  SlotBase::get_threadLocal_currentSender() = &sender;
163 
164  Internal::BentoAbstract *priorSignal = SignalBase::get_threadLocal_currentSignal();
165  SignalBase::get_threadLocal_currentSignal() = &signal_Bento;
166 
167  // threading and queuedConnections
168  auto senderListHandle = sender.m_connectList.lock_read();
169 
170  for (const auto &connection : *senderListHandle) {
171 
172  if (*(connection.signalMethod) != signal_Bento) {
173  // no match in connectionList for this signal
174  continue;
175  }
176 
177  SlotBase *receiver = const_cast<SlotBase *>(connection.receiver);
178 
179  // const reference to a unique ptr
180  const std::unique_ptr<const CsSignal::Internal::BentoAbstract> &slot_Bento = connection.slotMethod;
181 
182  bool receiverInSameThread = receiver->compareThreads();
183 
184  int old_activateBusy = sender.m_activateBusy;
185  sender.m_activateBusy++;
186 
187  try {
188 
189  if ( (connection.type == ConnectionKind::AutoConnection && ! receiverInSameThread) ||
190  (connection.type == ConnectionKind::QueuedConnection)) {
191 
192  // passing true indicates the data will be copied (stored on the heap)
193  PendingSlot tempObj(&sender, signal_Bento.clone(), receiver, slot_Bento->clone(),
194  std::make_unique<CsSignal::Internal::TeaCup_Data<SignalArgTypes...>>(true, std::forward<Ts>(Vs)... ));
195 
196  receiver->queueSlot(std::move(tempObj), ConnectionKind::QueuedConnection);
197 
198  } else if (connection.type == ConnectionKind::BlockingQueuedConnection) {
199 
200  // passing false indicates the data will not be copied
201  PendingSlot tempObj(&sender, signal_Bento.clone(), receiver, slot_Bento->clone(),
202  std::make_unique<CsSignal::Internal::TeaCup_Data<SignalArgTypes...>>(false, std::forward<Ts>(Vs)... ));
203 
204  receiver->queueSlot(std::move(tempObj), ConnectionKind::BlockingQueuedConnection);
205 
206  } else if (connection.type == ConnectionKind::DirectConnection || connection.type == ConnectionKind::AutoConnection) {
207  // direct connection
208 
209  // invoke calls the actual method
210  slot_Bento->invoke(receiver, &dataPack);
211  }
212 
213  std::lock_guard<std::mutex> lock(SignalBase::get_mutex_beingDestroyed()); // should be a read lock
214 
215  if (SignalBase::get_beingDestroyed().count(senderPtr)) {
216  // sender has been destroyed
217  SlotBase::get_threadLocal_currentSender() = priorSender;
218  SignalBase::get_threadLocal_currentSignal() = priorSignal;
219 
220  if (old_activateBusy == 0) {
221  SignalBase::get_beingDestroyed().erase(senderPtr);
222  }
223 
224  return;
225  }
226 
227  } catch (...) {
228  SlotBase::get_threadLocal_currentSender() = priorSender;
229  SignalBase::get_threadLocal_currentSignal() = priorSignal;
230 
231  std::lock_guard<std::mutex> lock(SignalBase::get_mutex_beingDestroyed()); // should be a read lock
232 
233  if (SignalBase::get_beingDestroyed().count(senderPtr)) {
234  // sender has been destroyed, all done
235 
236  if (old_activateBusy == 0) {
237  SignalBase::get_beingDestroyed().erase(senderPtr);
238  }
239 
240  return;
241 
242  } else {
243  sender.handleException(std::current_exception());
244  SlotBase::get_threadLocal_currentSender() = &sender;
245 
246  }
247  }
248 
249  try {
250  sender.m_activateBusy--;
251 
252  } catch (std::exception &) {
253  SlotBase::get_threadLocal_currentSender() = priorSender;
254  SignalBase::get_threadLocal_currentSignal() = priorSignal;
255 
256  std::throw_with_nested(std::invalid_argument("activate(): Failed to obtain sender lock"));
257  }
258  }
259 
260  SlotBase::get_threadLocal_currentSender() = priorSender;
261  SignalBase::get_threadLocal_currentSignal() = priorSignal;
262 }
263 
264 // signal & slot method ptr
265 template<class Sender, class SignalClass, class ...SignalArgs, class Receiver, class SlotClass,
266  class ...SlotArgs, class SlotReturn>
267 bool connect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...),
268  const Receiver &receiver, SlotReturn (SlotClass::*slotMethod)(SlotArgs...),
269  ConnectionKind type, bool uniqueConnection)
270 {
271 
272 /*
273  // is the sender an rvalue reference
274  static_assert(! std::is_rvalue_reference<Sender &&>::value,
275  "connect(): Sender can not be an rvalue");
276 
277  // is the receiver an rvalue reference
278  static_assert(! std::is_rvalue_reference<Receiver &&>::value,
279  "connect(): Receiver can not be an rvalue");
280 */
281 
282  // (1) Sender must be the same class as SignalClass OR (2) Sender is a child of SignalClass
283  static_assert( std::is_base_of<SignalClass, Sender>::value,
284  "connect(): Signal was not a child class of Sender");
285 
286  // (1) Receiver must be the same class as SlotClass OR (2) Receiver is a child of SlotClass
287  static_assert( std::is_base_of<SlotClass, Receiver>::value,
288  "connect(): Slot was not a child class of Receiver");
289 
290  // compare signal and slot parameter list
291  static_assert( Internal::cs_check_connect_args<void (*)(SignalArgs...), void (*)(SlotArgs...) >::value,
292  "connect(): Incompatible signal/slot arguments");
293 
294  if (signalMethod == nullptr) {
295  throw std::invalid_argument("connect() Can not connect, signal is null");
296  }
297 
298  if (slotMethod == nullptr) {
299  throw std::invalid_argument("connect(): Can not connect, slot is null");
300  }
301 
302  std::unique_ptr<Internal::Bento<void (SignalClass::*)(SignalArgs...)>>
303  signalMethod_Bento(new Internal::Bento<void (SignalClass::*)(SignalArgs...)>(signalMethod));
304 
305  std::unique_ptr<Internal::Bento<SlotReturn (SlotClass::*)(SlotArgs...)>>
306  slotMethod_Bento(new Internal::Bento<SlotReturn (SlotClass::*)(SlotArgs...)>(slotMethod));
307 
308  auto senderListHandle = sender.m_connectList.lock_write();
309 
310  if (uniqueConnection) {
311  // ensure the connection is not added twice
312 
313  for (auto &item : *senderListHandle) {
314 
315  if (item.receiver != &receiver) {
316  continue;
317  }
318 
319  if (*(item.signalMethod) != *(signalMethod_Bento)) {
320  continue;
321  }
322 
323  if (*(item.slotMethod) != *(slotMethod_Bento)) {
324  continue;
325  }
326 
327  // connection already exists
328  return false;
329  }
330  }
331 
332  sender.addConnection(std::move(signalMethod_Bento), &receiver, std::move(slotMethod_Bento), type, senderListHandle);
333 
334  return true;
335 }
336 
337 // signal method ptr, slot lambda
338 template<class Sender, class SignalClass, class ...SignalArgs, class Receiver, class T>
339 bool connect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...), const Receiver &receiver,
340  T slotLambda, ConnectionKind type, bool uniqueConnection)
341 {
342  // Sender must be the same class as SignalClass and Sender is a child of SignalClass
343  Internal::cs_testConnect_SenderSignal<Sender, SignalClass>();
344 
345  // compare signal and slot parameter list
346  Internal::cs_testConnect_SignalSlotArgs_1<T, SignalArgs...>();
347 
348  if (signalMethod == nullptr) {
349  throw std::invalid_argument("connect(): Can not connect, signal is null");
350  }
351 
352  std::unique_ptr<Internal::Bento<void (SignalClass::*)(SignalArgs...)>>
353  signalMethod_Bento(new Internal::Bento<void (SignalClass::*)(SignalArgs...)>(signalMethod));
354 
355  std::unique_ptr<Internal::Bento<T>> slotLambda_Bento(new Internal::Bento<T>(slotLambda));
356 
357  auto senderListHandle = sender.m_connectList.lock_write();
358 
359  if (uniqueConnection) {
360  // ensure the connection is not added twice
361 
362  for (auto &item : *senderListHandle) {
363 
364  if (item.receiver != &receiver) {
365  continue;
366  }
367 
368  if (*(item.signalMethod) != *(signalMethod_Bento)) {
369  continue;
370  }
371 
372  // unable to test if the passed slotLambda = slotLambda_Bento
373 
374  // connection already exists
375  return false;
376  }
377  }
378 
379  sender.addConnection(std::move(signalMethod_Bento), &receiver, std::move(slotLambda_Bento), type, senderListHandle);
380 
381  return true;
382 }
383 
384 
385 // signal & slot bento
386 template<class Sender, class Receiver>
387 bool connect(const Sender &sender, std::unique_ptr<Internal::BentoAbstract> signalMethod_Bento, const Receiver &receiver,
388  std::unique_ptr<Internal::BentoAbstract> slotMethod_Bento, ConnectionKind type, bool uniqueConnection)
389 {
390  auto senderListHandle = sender.m_connectList.lock_write();
391 
392  if (uniqueConnection) {
393  // ensure the connection is not added twice
394 
395  for (const auto &item : *senderListHandle) {
396 
397  if (item.receiver != &receiver) {
398  continue;
399  }
400 
401  if (*(item.signalMethod) != *(signalMethod_Bento)) {
402  continue;
403  }
404 
405  if (*(item.slotMethod) != *(slotMethod_Bento)) {
406  continue;
407  }
408 
409  // connection already exists
410  return false;
411  }
412  }
413 
414  sender.addConnection(std::move(signalMethod_Bento), &receiver, std::move(slotMethod_Bento), type, senderListHandle);
415 
416  return true;
417 }
418 
419 // signal & slot method ptr
420 template<class Sender, class SignalClass, class ...SignalArgs, class Receiver, class SlotClass, class ...SlotArgs, class SlotReturn>
421 bool disconnect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...), const Receiver &receiver,
422  SlotReturn (SlotClass::*slotMethod)(SlotArgs...))
423 {
424  // Sender must be the same class as SignalClass and Sender is a child of SignalClass
425  Internal::cs_testConnect_SenderSignal<Sender, SignalClass>();
426 
427  // Receiver must be the same class as SlotClass and Receiver is a child of SlotClass
428  Internal::cs_testConnect_ReceiverSlot<SlotClass, Receiver>();
429 
430  // signal & slot arguments do not agree
431  Internal::cs_testConnect_SignalSlotArgs_2< void (*)(SignalArgs...), void (*)(SlotArgs...) >();
432 
433  Internal::Bento<void (SignalClass::*)(SignalArgs...)> signalMethod_Bento(signalMethod);
434  Internal::Bento<SlotReturn (SlotClass::*)(SlotArgs...)> slotMethod_Bento(slotMethod);
435 
436  if (! internal_disconnect(sender, &signalMethod_Bento, &receiver, &slotMethod_Bento)) {
437  return false;
438  }
439 
440  return true;
441 }
442 
443 // signal method ptr, slot lambda or function ptr
444 template<class Sender, class SignalClass, class ...SignalArgs, class Receiver, class T>
445 bool disconnect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...), const Receiver &receiver, T slotMethod)
446 {
447  // lambda, compile error
448  static_assert(std::is_convertible<decltype(slotMethod == slotMethod), bool>::value,
449  "disconnect(): Slot argument invalid or calling disconnect using a lambda" );
450 
451  // function ptr
452  Internal::Bento<void (SignalClass::*)(SignalArgs...)> signalMethod_Bento(signalMethod);
453  Internal::Bento<T> slotMethod_Bento(slotMethod);
454 
455  if (! internal_disconnect(sender, &signalMethod_Bento, &receiver, &slotMethod_Bento)) {
456  return false;
457  }
458 
459  return true;
460 }
461 
462 // signal & slot bento objects
463 template<class Sender, class Receiver>
464 bool internal_disconnect(const Sender &sender, const Internal::BentoAbstract *signalBento,
465  const Receiver *receiver, const Internal::BentoAbstract *slotBento)
466 {
467  bool retval = false;
468  auto senderListHandle = sender.m_connectList.lock_write();
469 
470  for (auto iter = senderListHandle->begin(); iter != senderListHandle->end(); ++iter) {
471  const SignalBase::ConnectStruct &temp = *iter;
472 
473  bool isMatch = false;
474 
475  if (signalBento == nullptr && receiver == nullptr) {
476  // delete all connections in Sender
477  isMatch = true;
478 
479  } else if (receiver != nullptr) {
480 
481  if (receiver == temp.receiver) {
482 
483  if (signalBento == nullptr && (slotBento == nullptr || *slotBento == *temp.slotMethod)) {
484  isMatch = true;
485 
486  } else if (signalBento != nullptr && *signalBento == *temp.signalMethod && (slotBento == nullptr ||
487  *slotBento == *temp.slotMethod)) {
488  isMatch = true;
489  }
490  }
491 
492  } else if (signalBento != nullptr) {
493  // receiver must be null therefore slot is null
494 
495  if (*signalBento == *temp.signalMethod) {
496  isMatch = true;
497  }
498  }
499 
500  if (isMatch) {
501  // delete possible sender in the receiver
502  retval = true;
503 
504  // lock temp.receiver and erase
505  auto receiverListHandle = temp.receiver->m_possibleSenders.lock_write();
506  receiverListHandle->erase(find(receiverListHandle->begin(), receiverListHandle->end(), &sender));
507 
508  // delete connection in sender
509  senderListHandle->erase(iter);
510  }
511  }
512 
513  return retval;
514 }
515 
516 } // namespace
517 
518 // method pointer cast used to resolve ambiguous method overloading for signals and slots
519 
520 // 1
521 template<class... Args>
522 class cs_mp_cast_internal
523 {
524  public:
525  template<class className>
526  constexpr auto operator()(void (className::*methodPtr)(Args ...)) const {
527  return methodPtr;
528  }
529 };
530 
531 template<class... Args>
532 constexpr cs_mp_cast_internal<Args...> cs_mp_cast;
533 
534 
535 // 2
536 template<class... Args>
537 class cs_cmp_cast_internal
538 {
539  public:
540  template<class className>
541  constexpr auto operator()(void (className::*methodPtr)(Args ...) const) const {
542  return methodPtr;
543  }
544 };
545 
546 template<class... Args>
547 constexpr cs_cmp_cast_internal<Args...> cs_cmp_cast;
548 
549 #endif
#define LIB_SIG_EXPORT
Definition: cs_macro.h:28