Signals and slots provide a means for communication of events. The classes here let you have a many-to-many relationship (unlike most event callback techniques), and also templatise the data transfer to allow the coder to customise the event data that is communicated from signal to slot. It also implements a few conditional (event) related classes.
The ideas for this signal/slots implementation come from a few sources.
Qt was the original signal/slots implementation, but it needs a pre-processor to compile the code. Sigslot and Boost on the other hand are pure ISO C++, but both have some disadvantages. The boost implementation is fairly heavy and a little cumbersome, although its now morphing into version 2 with even more functionality (I haven't yet road tested a comparison between the two). Sigslot was the precursor, is more lightweight, but has some oddities - it isn't fully typesafed, can't handle global/static function loading.
None of these are thread-safe and it can be somewhat inconvenient manually connecting signal-slot pairs when they are buried far from each other (e.g. deep in parallel heirarchies of c++ objects).
This motivated this library - what was needed was an abstract way of connecting pairs with an engine in the background to do the hard work - ultimately leaving the programmer free from any tedious details. To do this, the sigslots library uniquely names connections (i.e. topic name in ros-speak), via a string. Signals and slots can connect to the topic simply by referring to a string. This was originally motivated by the way many posix ipc structures connect (e.g. semaphores and shared memory), but later also merged very neatly with the way ros topics connect (in particular nodelets). Adding mutex's for thread safety so that sigslot destruction would occur safely was the next step.
For a light version of sigslots suitable for firmware projects, see ecl_sigslots_lite.
Anywhere that triggers an event requiring a callback to be executed can be implemented with a signal. These can be placed anywhere in your code and can be connected to one or many slots.
Anywhere that a callback function is required can be implemented with a slot. These can be placed anywhere in your code and are initialised with either a static (global) function, or a member function. Once initialised, they can be hooked up to a signal.
The signal-slot pairs developed here only ever accept one template argument representing the data to be transmitted from signal to slot. It would be easy to extend this to more (aka SigSlot/Boost) but I have yet to find a need for it - if you wish multiple arguments, simply wrap up your data in a single struct/class. Conceptually, this makes the code more readable anyway.
The data class itself could be as simple as an error id or as complicated as the current state in a fsm. Note that both signal and slot must use the same type.
Include the following at the top of any translation unit which requires this library:
You will also need to link to -lecl_sigslots.
Loading of slots can be done directly to free or member functions through the constructors. Below is a simple example for various types of loading.
Signal<const int&> signal; Slot<const int&> slot(f); signal.connect("Dudes"); slot.connect("Dudes");
Signals and slots have no limit to the number of connections they may make.
Every time a signal emits, the connected slots are consecutively run with the data that is emitted.
signal.emit(3); // Pass the '3' to all the slot functions.
Every emit, the slots are consecutively run - this means that your slots should by nature be short and concise! Otherwise you'll get serious bottlenecks. This is always a good habit to get into for slots otherwise you'll frequently run into problems. If you have a heavy callback, consider spinning that callback off into a thread. That way the thread response will still be quick (cost of a thread creation) and you can still manage heavy workloads.
A signal can relay another signal, effectively posing temporarily as a slot.
@code Signal<> signal; Signal<> signal_relay; Slot<> slot(f); signal.connect("First_Topic"); signal_relay.connectAsSlot("First_Topic"); signal_relay.connect("Second_Topic"); slot.connect("Second_Topic"); signal.emit();
Often you may wish to see what is actually connected. This can be done by calling the SigSlotsManager<T>::printStatistics() function. Note that there is a manager for each templatised family of sigslots (i.e. SigSlotsManager<>, SigSlotsManager<int>). Some example code:
A a; Signal<> sig_void; Slot<> slot_void0(g); Slot<> slot_void1(&A::g,a); sig_void.connect("void_test"); slot_void0.connect("void_test"); slot_void1.connect("void_test"); sig_void.emit(); ecl::SigSlotsManager<>::printStatistics();
would print output:
Topics Name: void_test # Subscribers: 2 # Publishers : 1
If you do need a sigslot implementation that can handle massive numbers of sigslots, fast connection and disconnection, then you probably need to look at the old ecl signals or boost/qt. At the moment, we can't foresee a need for that in control systems, but if needed, this library can be extended.
This will be addressed on an 'as needed' basis.