NAP
signalslot.h
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4 
5 #pragma once
6 
7 // Std includes
8 #include <functional>
9 #include <set>
10 #include <vector>
11 #include <memory>
12 #include <iostream>
13 
14 // Pybind includes
15 #include "python.h"
16 
17 namespace nap
18 {
19 
20  // Forward declarations
21  template<typename... Args> class Slot;
22 
27  template <typename... Args>
28  class Signal final
29  {
30  public:
31  using Function = std::function<void(Args... args)>;
32 
33  // Destruction
34  ~Signal();
35 
36  // Connection
37 
41  void connect(Signal<Args...>& signal);
42 
46  void disconnect(Signal<Args...>& signal);
47 
54  void connect(Slot<Args...>& slot);
55 
59  void disconnect(Slot<Args...>& slot);
60 
67  void connect(const Function& inFunction);
68 
69 #ifdef NAP_ENABLE_PYTHON
70 
73  void connect(const pybind11::function pythonFunction);
74 #endif // NAP_ENABLE_PYTHON
75 
79  template <typename U, typename F>
80  void connect(U* object, F memberFunction)
81  {
82  connect(std::bind(memberFunction, object, std::placeholders::_1));
83  }
84 
88  inline void operator()(Args... args)
89  {
90  trigger(std::forward<Args>(args)...);
91  }
92 
96  void trigger(Args... args);
97 
98  private:
99  void addCause(Signal<Args...>& event);
100  void removeCause(Signal<Args...>& event);
101 
102  private:
103 
104  // Data acts like a variant type with a few known types.
105  // The reason for this approach is that we can use a single list
106  // of elements in Signal instead of having a separate list for each.
107  // This saves out a number of dynamic memory allocations and a large
108  // overhead in member data, without introducing any virtual function
109  // calls.
110  struct Data
111  {
112  union
113  {
114  struct
115  {
116  Signal<Args...>* mSignal;
117  } Signal;
118 
119  struct
120  {
121  Slot<Args...>* mSlot;
122  } Slot;
123  };
124 
125  enum class EType : uint8_t
126  {
127  SignalCause,
128  SignalEffect,
129  SlotEffect
130  };
131  EType mType;
132  };
133 
134  // Members
135  std::vector<Data> mData; // Signal/slot effects, Signal causes
136  std::unique_ptr<std::vector<Function>> mFunctionEffects; // function objects
137  };
138 
139 
146  template <typename... Args>
147  class Slot final
148  {
149  public:
150  using Function = std::function<void(Args... args)>;
151 
152  public:
153  //@name Construction
154  Slot() = default;
155 
156  Slot(Function inFunction) :
157  mFunction(inFunction)
158  {
159  }
160 
163  template <typename U, typename F>
164  Slot(U* parent, F memberFunction) :
165  mFunction(std::bind(memberFunction, parent, std::placeholders::_1))
166  {
167  }
168 
171  template <typename U, typename F>
172  Slot(U* parent, F memberFunction, Signal<Args...>& signal) :
173  mFunction(std::bind(memberFunction, parent, std::placeholders::_1))
174  {
175  signal.connect(*this);
176  }
177 
179  {
180  disconnect();
181  }
182 
186  void disconnect();
187 
188  void setFunction(Function func)
189  {
190  mFunction = func;
191  }
192 
193  void trigger(Args... args)
194  {
195  if (mFunction)
196  mFunction(std::forward<Args>(args)...);
197  }
198 
199  void copyCauses(const Slot& rhs)
200  {
201  for (auto cause : rhs.mCauses)
202  cause->connect(*this);
203  }
204 
205  private:
206  template<typename... Args_> friend class Signal;
207 
208  void addCause(Signal<Args...>& event);
209  void removeCause(Signal<Args...>& event);
210 
211  private:
212  typedef std::vector<Signal<Args...>*> SignalList;
213  Function mFunction;
214  SignalList mCauses;
215  };
216 
217 
219  // Template Implementations
221 
222  template <typename... Args>
224  {
225  for (auto& data : mData)
226  {
227  if (data.mType == Data::EType::SignalCause)
228  data.Signal.mSignal->disconnect(*this);
229  else if (data.mType == Data::EType::SignalEffect)
230  data.Signal.mSignal->removeCause(*this);
231  else
232  data.Slot.mSlot->removeCause(*this);
233  }
234  }
235 
236  template <typename... Args>
238  {
239  Data data;
240  data.mType = Data::EType::SignalCause;
241  data.Signal.mSignal = &signal;
242  mData.push_back(data);
243  }
244 
245  template <typename... Args>
246  void Signal<Args...>::removeCause(Signal<Args...>& event)
247  {
248  for (int index = 0; index < mData.size(); ++index)
249  {
250  Data& value = mData[index];
251  if (value.mType == Data::EType::SignalCause && value.Signal.mSignal == &event)
252  {
253  mData.erase(mData.begin() + index);
254  break;
255  }
256  }
257  }
258 
259  template <typename... Args>
261  {
262  Data data;
263  data.mType = Data::EType::SignalEffect;
264  data.Signal.mSignal = &signal;
265  mData.push_back(data);
266 
267  signal.addCause(*this);
268  }
269 
270  template <typename... Args>
272  {
273  for (int index = 0; index < mData.size(); ++index)
274  {
275  Data& value = mData[index];
276  if (value.mType == Data::EType::SignalEffect && value.Signal.mSignal == &signal)
277  {
278  value.Signal.mSignal->removeCause(*this);
279  mData.erase(mData.begin() + index);
280  break;
281  }
282  }
283  }
284 
285  template <typename... Args>
287  {
288  Data data;
289  data.mType = Data::EType::SlotEffect;
290  data.Slot.mSlot = &slot;
291  mData.push_back(data);
292 
293  slot.addCause(*this);
294  }
295 
296  template <typename... Args>
298  {
299  for (int index = 0; index < mData.size(); ++index)
300  {
301  Data& data = mData[index];
302  if (data.mType == Data::EType::SlotEffect && data.Slot.mSlot == &slot)
303  {
304  data.Slot.mSlot->removeCause(*this);
305  mData.erase(mData.begin() + index);
306  break;
307  }
308  }
309  }
310 
311 
312  template <typename... Args>
313  void Signal<Args...>::connect(const Function& inFunction)
314  {
315  if (!mFunctionEffects)
316  mFunctionEffects = std::make_unique<std::vector<Function>>();
317 
318  mFunctionEffects->emplace_back(inFunction);
319  }
320 
321 #ifdef NAP_ENABLE_PYTHON
322  template <typename... Args>
323  void Signal<Args...>::connect(const pybind11::function pythonFunction)
324  {
325  Function func = [pythonFunction](Args... args)
326  {
327  try
328  {
329  pythonFunction(pybind11::cast(std::forward<Args>(args)..., std::is_lvalue_reference<Args>::value
330  ? pybind11::return_value_policy::reference : pybind11::return_value_policy::automatic_reference)...);
331  }
332  catch (const pybind11::error_already_set& err)
333  {
334  auto message = std::string("Runtime python error while executing signal: ") + std::string(err.what());
335 
336  // TODO It would be preferable to log python error message using the nap logger.
337  // Unfortunately the logger is not accessible in signalslot.h though because it uses Signals itself.
338  std::cout << message << std::endl;
339  }
340  };
341  connect(func);
342  }
343 #endif // NAP_ENABLE_PYTHON
344 
345 
346  template <typename... Args>
347  void Signal<Args...>::trigger(Args... args)
348  {
349  for (auto& data : mData)
350  {
351  if (data.mType == Data::EType::SignalEffect)
352  data.Signal.mSignal->trigger(std::forward<Args>(args)...);
353  else if (data.mType == Data::EType::SlotEffect)
354  data.Slot.mSlot->trigger(std::forward<Args>(args)...);
355  }
356 
357  if (mFunctionEffects)
358  for (auto& effect : *mFunctionEffects)
359  effect(std::forward<Args>(args)...);
360  }
361 
362  template <typename... Args>
364  {
365  for (int index = mCauses.size() - 1; index >= 0; --index)
366  mCauses[index]->disconnect(*this);
367  }
368 
369  template <typename... Args>
371  {
372  mCauses.push_back(&event);
373  }
374 
375  template <typename... Args>
376  void Slot<Args...>::removeCause(Signal<Args...>& event)
377  {
378  for (typename SignalList::iterator pos = mCauses.begin(); pos != mCauses.end(); ++pos)
379  {
380  if ((*pos) == &event)
381  {
382  mCauses.erase(pos);
383  break;
384  }
385  }
386  }
387 
388 } // End Namespace nap
389 
390 
392 // Macros
394 
395 
396 // Creates a slot with the given @NAME and @TYPE and binds it to @FUNCTION using a lambda
397 #define NSLOT(NAME, TYPE, FUNCTION) nap::Slot<TYPE> NAME = {[&](TYPE inValue) -> void { FUNCTION(inValue); }};
398 
399 // Creates a slot with @NAME and @TYPE without binding it to a function
400 #define CREATE_SLOT(NAME, TYPE) nap::Slot<TYPE> NAME;
401 
402 // Binds the already defined slot with @NAME and @TYPE to @FUNCTION
403 #define BIND_SLOT(NAME, TYPE, FUNCTION) NAME([&](TYPE inValue) -> void { FUNCTION(inValue); })
404 
405 // SIGNAL MACRO
406 #define NSIGNAL(NAME, TYPE) nap::Signal<TYPE> NAME;
nap::Slot< ControllerValue >::Function
std::function< void(Args... args)> Function
Definition: signalslot.h:150
nap::Slot::~Slot
~Slot()
Definition: signalslot.h:178
nap::Slot
Slot.
Definition: signalslot.h:21
nap::Signal::connect
void connect(Signal< Args... > &signal)
Definition: signalslot.h:260
nap::Signal::~Signal
~Signal()
Definition: signalslot.h:223
nap::Slot::Slot
Slot(U *parent, F memberFunction, Signal< Args... > &signal)
Definition: signalslot.h:172
nap::Slot::Slot
Slot()=default
nap::Signal::operator()
void operator()(Args... args)
Definition: signalslot.h:88
nap::Slot::Slot
Slot(U *parent, F memberFunction)
Definition: signalslot.h:164
nap::Signal< const nap::WebSocketConnectionFailedEvent & >::Function
std::function< void(Args... args)> Function
Definition: signalslot.h:31
nap::Slot::trigger
void trigger(Args... args)
Definition: signalslot.h:193
nap::Signal
Definition: signalslot.h:28
nap::Signal::trigger
void trigger(Args... args)
Definition: signalslot.h:347
nap::Slot::copyCauses
void copyCauses(const Slot &rhs)
Definition: signalslot.h:199
nap
Definition: templateapp.h:17
nap::Slot::disconnect
void disconnect()
Definition: signalslot.h:363
nap::Signal::connect
void connect(U *object, F memberFunction)
Definition: signalslot.h:80
nap::Signal::disconnect
void disconnect(Signal< Args... > &signal)
Definition: signalslot.h:271
nap::Slot::setFunction
void setFunction(Function func)
Definition: signalslot.h:188
nap::Slot::Slot
Slot(Function inFunction)
Definition: signalslot.h:156