Signal-slot communication¶
Overview¶
“Signals and slots” is a language construct introduced in Qt [1] for communication between objects.
[1] | http://wiki.qt.io/Qt_signal-slot_quick_start |
The concept is that objects and services(explained in 2.3) can send signals containing event information which can be received by other services using special functions known as slots.
FW4SPL implementation¶
In the FW4SPL architecture, the library fwCom
provides a set of tools
dedicated to communication. These communications are based on the signal and
slot concept.
fwCom
provides the following features :
- function and method wrapping
- direct slot calling
- asynchronous slot calling
- ability to work with multiple threads
- auto-disconnection of slot and signals
- arguments loss between slots and signals
Slots¶
Slots are wrappers for functions and class methods that can be attached
to a fwThread::Worker
. The purpose of this class is to provide
synchronous and asynchronous mechanisms for method and function calling.
Slots have a common base class : SlotBase. This allows the storage of them in the same container. Slots are designed such that they can be called, even where only the argument type is known.
Examples :
A slot wrapping the function sum, which is a function with the signature int (int, int) :
::fwCom::Slot< int (int, int) >::sptr slotSum = ::fwCom::newSlot( &sum );
A slot wrapping the function start with signature void() of
the object a
which class type is A
:
::fwCom::Slot< void() >::sptr slotStart = ::fwCom::newSlot(&A::start, &a);
Execution of the slots using the run method :
slotSum->run(40,2);
slotStart->run();
Execution of the slots using the method call, which returns the result of the execution :
int result = slotSum->call(40,2);
slotStart->call();
A slot declaration and execution, through a SlotBase :
::fwCom::Slot< size_t (std::string) > slotLen
= ::fwCom::Slot< size_t (std::string) >::New( &len );
::fwCom::SlotBase::sptr slotBaseLen = slotLen;
::fwCom::SlotBase::sptr slotBaseSum = slotSum;
slotBaseSum->run(40,2);
slotBaseSum->run<int, int>(40,2);
// This one needs the explicit argument type
slotBaseLen->run<std::string>("R2D2");
result = slotBaseSum->call<int>(40,2);
result = slotBaseSum->call<int, int, int>(40,2);
result = slotBaseLen->call<size_t, std::string>("R2D2");
Signals¶
Signals allow to perform grouped calls on slots. For this purpose, a signal class provides a mechanism to connect slots.
Examples:
The following instruction declares a signal with a void signature.
::fwCom::Signal< void() >::sptr sig = ::fwCom::Signal< void() >::New();
The connection between a signal and a slot of the same information type:
sig->connect(slotStart);
The following instruction will trigger the execution of all slots connected to this signal:
sig->emit();
It is possible to connect multiple slots with the same information type to the same signal and trigger their simultaneous execution.
Signals can take several arguments as a signature which will trigger their connected slots by passing the right arguments.
In the following example a signal is declared of type void(int, int). The signal is connected to two different types of slot, void (int) and int (int, int).
using namespace fwCom;
Signal< void(int, int) >::sptr sig2 = Signal< void(int, int) >::New();
Slot< int(int, int) >::sptr slot1 = Slot< int(int, int) >::New(...);
Slot< void(int) >::sptr slot2 = Slot< void(int) >::New(...);
sig2->connect(slot1);
sig2->connect(slot2);
sig2->emit(21, 42);
Here 2 points need to be highlighted :
- A signal cannot return a value. Consequently their return type is void. Thus, the return value of a slot, triggered by a signal, equally cannot be retrieved.
- To successfully trigger a slot using a signal, the minimum requirement as to the number of arguments or fitting argument types has to be given by the signal. In the last example the slot slot2 only requires one argument of type int, but the signal is emitting two arguments of type int. Because the signal signature fulfills the slot’s argument number and argument type, the signal can successfully trigger the slot slot2. The slot slot2 takes the first emitted argument which fits its parameter (here 21, the second argument is ignored).
Disconnection¶
The disconnect method is called between one signal and one slot, to stop their existing connection. A disconnection assumes a signal slot connection. Once a signal slot connection is disconnected, it cannot be triggered by this signal. Both connection and disconnection of a signal slot connection can be done at any time.
sig2->disconnect(slot1);
sig2->emit(21, 42); // do not trigger slot1 anymore
The instructions above will cause the execution of slot2. Due to the disconnection between sig2 and slot1, the slot slot1 is not triggered by sig2.
Connection handling¶
The connection between a slot and a signal returns a connection handler:
::fwCom::Connection connection = signal->connect(slot);
Each connection handler provides a mechanism which allows a signal slot connection to be disabled temporarily. The slot stays connected to the signal, but it will not be triggered while the connection is blocked :
::fwCom::Connection::Blocker lock(connection);
signal->emit();
// 'slot' will not be executed while 'lock' is alive or until lock is
// reset
Connection handlers can also be used to disconnect a slot and a signal :
connection.disconnect();
// slot is not connected anymore
Auto-disconnection¶
Slots and signals can handle an automatic disconnection :
- on slot destruction : every signal slot connection to this slot will be destroyed
- on signal destruction : every slot connection to the signal will be destroyed
All related connection handlers will be invalidated when an automatic disconnection occurs.
Manage slots or signals in a class¶
The library fwCom
provides two helper classes to manage signals or slots in
a structure.
HasSlots¶
The class HasSlots
offers mapping between a key (string defining the slot name)
and a slot. HasSlots
allows the management of many slots using a map. To use
this helper in a class, the class must inherit from HasSlots
and must register the slots
in the constructor:
struct ThisClassHasSlots : public HasSlots
{
typedef Slot< int()> GetValueSlotType;
ThisClassHasSlots()
{
newSlot("sum", &ThisClassHasSlots::sum, this);
newSlot("getValue", &ThisClassHasSlots::getValue, this);
}
int sum(int a, int b)
{
return a+b;
}
int getValue()
{
return 4;
}
};
Then, slots can be used as below :
ThisClassHasSlots obj;
obj.slot("sum")->call<int>(5,9);
obj.slot< ThisClassHasSlots::GetValueSlotType >("getValue")->call();
HasSignals¶
The class HasSignals
provides mapping between a key (string defining the signal name) and a signal.
HasSignals
allows the management of many signals using a map, similar to HasSlots
. To use this helper in a class, the class must inherit from
HasSignals
as seen below and must register signals in the constructor:
struct ThisClassHasSignals : public HasSignals
{
typedef ::fwCom::Signal< void()> SignalType;
ThisClassHasSignals()
{
newSignal< SignalType >("sig");
}
};
Then, signals can be used as below:
ThisClassHasSignals obj;
Slot< void()>::sptr slot = ::fwCom::newSlot(&anyFunction)
obj.signal("sig")->connect( slot );
obj.signal< SignalsTestHasSignals::SignalType >("sig")->emit();
obj.signal("sig")->disconnect( slot );
Signals and slots used in objects and services¶
Signals are used in both objects and services, whereas slots are only used in services. The abstract
class fwData::Object
inherits from the HasSignals
class as a basis to use signals :
class Object : public ::fwCom::HasSignals
{
/// Key in m_signals map of signal m_sigObjectModified
static const ::fwCom::Signals::SignalKeyType s_MODIFIED_SIG;
//...
/// Type of signal m_sigObjectModified
typedef ::fwCom::Signal< void ( CSPTR( ::fwServices::ObjectMsg ) ) >
ObjectModifiedSignalType;
/// Signal that emits an ObjectMsg when an object is modified
ObjectModifiedSignalType::sptr m_sigObjectModified;
Object()
{
m_sigObjectModified = newSignal< ObjectModifiedSignalType >(s_MODIFIED_SIG);
//...
}
}
Moreover the abstract class fwService::IService
inherits from the HasSlots
class and the HasSignals
class, as a basis to communicate through signals and slots. Actually, the methods start()
, stop()
, swap()
and update()
are all slots. Here is an extract with update()
:
class IService : public ::fwCom::HasSlots, public ::fwCom::HasSignals
{
typedef ::boost::shared_future< void > SharedFutureType;
/// Key in m_slots map of slot m_slotUpdate
static const ::fwCom::Slots::SlotKeyType s_UPDATE_SLOT;
/// Type of signal m_slotUpdate
typedef ::fwCom::Slot<SharedFutureType()> UpdateSlotType;
/// Slot to call update method
UpdateSlotType::sptr m_slotUpdate;
IService()
{
//...
m_slotUpdate = newSlot( s_UPDATE_SLOT, &IService::update, this ) ;
//...
}
//...
}
To automatically connect object signals and service slots, it is possible to override the method
IService::getAutoConnections()
. Please note that to be effective the attribute “autoConnect”
of the service must be set to “yes” in the xml configuration (see App-config).
The default implementation of this method connect the s_MODIFIED_SIG
object signal to the
s_UPDATE_SLOT
slot.
IService::KeyConnectionsMap IService::getAutoConnections() const
{
KeyConnectionsMap connections;
connections.push( "data1", ::fwData::Object::s_MODIFIED_SIG, s_UPDATE_SLOT ) );
connections.push( "data2", ::fwData::Object::s_MODIFIED_SIG, s_UPDATE_SLOT ) );
return connections;
}
Object signals¶
Objects have signals that can be used to inform of modifications.
The base class ::fwData::Object
has the following signals available.
Objects | Available messages |
---|---|
Object | {modified , addedFields , changedFields , removedFields } |
Thus all objects in FW4SPL can use the previous signals. Some object classes define extra signals.
Objects | Available messages |
---|---|
Composite | {addedObjects , changedObjects , removedObjects } |
Graph | {updated } |
Image | {bufferModified , landmarkAdded , landmarkRemoved , landmarkDisplayed , distanceAdded , distanceRemoved , distanceDisplayed , sliceIndexModified , sliceTypeModified , visibilityModified , transparencyModified } |
Mesh | {vertexModified , pointColorsModified , cellColorsModified , pointNormalsModified , cellNormalsModified , pointTexCoordsModified , cellTexCoordsModified } |
ModelSeries | {reconstructionsAdded , reconstructionsRemoved } |
PlaneList | {planeAdded , planeRemoved , visibilityModified } |
Plane | {selected } |
PointList | {pointAdded , pointRemoved } |
Reconstruction | {meshModified , visibilityModified } |
ResectionDB | {resectionAdded , safePartAdded } |
Resection | {reconstructionAdded , pointTexCoordsModified } |
Vector | {addedObjects , removedObjects } |
… | … |
Proxy¶
The class ::fwServices::registry::Proxy
is a communication element and singleton in the architecture.
It defines a proxy for
signal/slot connections. The proxy concept is used to declare
communication channels: all signals registered in a proxy’s channel are
connected to all slots registered in the same channel. This concept is
useful to create multiple connections or when the slots/signals have not yet been created (possible in dynamic programs).
The following shows an example where one signal is connected to several slots:
const std::string CHANNEL = "myChannel";
::fwServices::registry::Proxy::sptr proxy
= ::fwServices::registry::Proxy::getDefault();
::fwCom::Signal< void() >::sptr sig = ::fwCom::Signal< void() >::New();
::fwCom::Slot< void() >::sptr slot1 = ::fwCom::newSlot( &myFunc1 );
::fwCom::Slot< void() >::sptr slot2 = ::fwCom::newSlot( &myFunc2 );
::fwCom::Slot< void() >::sptr slot3 = ::fwCom::newSlot( &myFunc3 );
proxy->connect(CHANNEL, sig);
proxy->connect(CHANNEL, slot1);
proxy->connect(CHANNEL, slot2);
proxy->connect(CHANNEL, slot3);
sig->emit(); // All slots are called