Thread safe signals
What are signals ?
A Signal is essentially a list of functions to call on an
event. Signals currently may have 0,1,2 or 3 parameters which will
be passed to the functions in the list. The advantage of using
signals over simple function pointers is the exact form of the or
number of functions need not be known by the provider of the
signal. They are simpler to use that virtual functions as no new
class need be defined.
A useful feature of the signal implementation is that two
signal structures can be connected together without
creating a reference counted dependence between them. If either
of the signal structures is destroyed the connection between
them is automaticly broken.
If you are in a situation where you only want to call 1
function, but still want flexibilty in the exact form of that
function its worth looking at the Calls and Triggers
in the RAVL core.
Notes on RAVL's signal implementation
The RAVL signal system has been inspired by libsigc++, but written
to work with RAVL style reference counting.
Features:
- Signal objects are reference counted.
- Adding and removing connections is thread safe.
- The default implementation makes copies of the objects the
signal will be delived to, enabling it to call methods of
reference counted objects safely.
How to use them.
The following code fragments are from the example code for
Signal1C. The complete
example is here.
The signal code is part of the threads library so you have to
add RavlThreads to your USESLIBS line in
defs.mk. We're going to use signals with 1 paramiter so we
include the Signal1 header file.
#include "Ravl/Threads/Signal1.hh"
Creating a signal is simple, just declare it as a variable with
an appropriate default value. The following code creates an
integer signal with a default value of 0.
Signal1C sig(0);
Calling global functions.
Suppose we have a function 'PrintNumberA' we wish to be called
when the signal is triggered. Note, the arguments of methods must
always be non-const references to a type, and the functions should
always return a bool. This significanly simplifies the
implementation as it avoids implementing code for all possible
combinations argument types and return types.
bool PrintNumberA(int &i) {
cout << "PrintNumberA called with value " << i << "\n";
return true;
}
This an be connected to the signal with the Connect(..) command.
Connect(sig, &PrintNumberA);
Similarly if you wish to connect a second signal to this you again
call Connect(...).
Signal1C sig2(1);
Connect(sig, sig2);
Finally to send a signal you use the () operator on the signal
itself. If you omit a paramiter the default value you initalise it
with will be used instead. Calling the following:
sig2(3); // Send the signal with a value of 3.
Prints the message
PrintNumberA called with value 3
The arguments of the function do not have to match the
signal you are calling. If the function takes more paramiters than
you are using you can supply the values when you call connect. To connect
the following function 'PrintNumber2':
bool PrintNumber2(int &i,RealT &v2) {
cout << "PrintNumberA called with value " << i << " v2=" << v2 << "\n";
return true;
}
you supply the extra paramiter at the end of the connect command, like this:
Connect(sig, &PrintNumber2,0,0.1);
Note: you have to supply values for all arguments of the function
even where they are provided by the signal. When the signal is triggered
it will call 'PrintNumber2' with the the first paramiter from the signal
and the second set to a real value of 0.1.
Calling class methods.
Calling class methods is a little more complicated. You have to
tell the system which instance of the class to call the method on.
Suppose you had the following class:
class MyClassC {
public:
bool Print(int &i) {
cout << "Value=" << i << "\n";
}
};
There are two ways to connect to a method you can either connect
to a copy of the object (essentail for reference counted objects.)
or to a reference to it. If you use a refrence it is the programmers
responsibility to ensure the object is valid when a signal is called.
To connect with a copy you would call Connect as follows:
MyClassC xyz;
Connect(sig,xyz,&MyClassC::Print);
To connect with a reference you would use:
MyClassC xyz;
ConnectRef(sig,xyz,&MyClassC::Print);
Note: There are some potential race conditions in multi-threaded
applications if you use the reference method of connecting to
classes. It is possible that the class processing a signal is
destroyied before the call is complete with unpredictable results.
This is guaranteed not to happen with the reference counted calls,
the signal mechanism ensures a reference is kept to the object
until all calls are complete.
Normal classes:
Normal functions:
Connect(Signal0C &,Signal0C &) | Connect signal to another signal. |
Connect(Signal0C &,Signal0FuncBodyC::FuncT) | Connect signal to a function with 0 args. |
Connect(Signal0C &,const DataT &,bool (*func)() ) | Connect signal to a method with 0 args. |
ConnectRef(Signal0C &,DataT &,bool (*func)() ) | Connect signal to a method with 0 args. |
operator <<(ostream &,const Signal2C &) | IO Operator. |
Connect(Signal0C &,Signal2C &) | Connect a signal to another signal. |
operator <<(ostream &,const Signal3C &) | IO Operator. |
Connect(Signal0C &,Signal3C &) | Connect to a signal |
Connect(Signal0C &,const ObjT &,bool (*func)(Data1T &,Data2T &,Data3T &) ,const Data1T &,const Data2T &,const Data3T &) | Connect a signal to a method. |
ConnectRef(Signal0C &,ObjT &,bool (*func)(Data1T &,Data2T &,Data3T &) ,const Data1T &,const Data2T &,const Data3T &) | Connect a signal to a method. |
operator <<(ostream &,const Signal1C &) | IO Operator. |
Connect(Signal0C &,Signal1C &) | Connect two signals together. |
Connect(Signal0C &,bool (*func)(DataT &) ,const DataT &) | Connect a signal to a function. |
Connect(Signal0C &,const ObjT &,bool (*func)(DataT & arg) ,const DataT &) | Connect a signal to a method. |
ConnectRef(Signal0C &,ObjT &,bool (*func)(DataT & arg) ,const DataT &) | Connect a signal to a method. |
Advanced classes:
Develop classes:
Develop functions: