Poulpe is a header-only lightweight compile-time observer design pattern (c++17 or above).
It allows to send and receive data and events (called signals) from one class to others without other coupling than the signal type.
- A class can be an emitter and a receiver at the same time.
- A class can emit an unlimited number of signals to an unlimited number of receivers.
- A class can receive an unlimited number of signals from an unlimited number of emitters.
- Signals are passed by reference which means they can be modified on the fly if received as non const.
- A signal emitted as const cannot be received as non const reference.
- Signals can be template classes
In order to make a class able to receive a signal of type MySignal, it has to implement the function void pReceive(MySignal);
Signal definition
/// my_signal.h ///
// Example of an empty signal definition
struct MySignal{};
Receiver definition
/// my_receiver.h ///
#include "my_signal.h"
struct MyReceiver{
void pReceive(MySignal s);
};
The classes reponsible for emitting signals must take a typename emitter in template parameter
/// my_emitter.h ///
#include "my_signal.h"
template<typename emitter_t>
struct MyEmitter{
void test(){
// signal send :
// the line below will call every receivers that implement
// the function "void pReceive(MySignal)"
emitter_t::pEmit(MySignal());
}
};
Then add the receiver to the Poulpe declaration :
/// main.cpp ///
#include "poulpe.h"
#include "my_receiver.h"
// list of receiver's types,
// there is no limitation on the number of receivers
DEFINE_RECEIVERS(MyReceiver)
// instantiation of the receiver
MyReceiver sRX;
// list of receiver's instances,
// it has to be listed in the same order as in DEFINE_RECEIVERS
CREATE_POULPE(sRX)
After the lines above, a type Emitter is declared, it is used to send the signals from emitters to receivers.
/// main.cpp ///
int main(){
// instantiation of the emitter
MyEmitter<poulpe::Emitter> e;
e.test();
return 0;
}
Poulpe automatically handles multiple signal types and calls only the receivers that implement the right pReceive function.
// Example of empty signal definitions
struct MySignalA{};
struct MySignalB{};
struct MyReceiverA{
void pReceive(MySignalA s);
};
struct MyReceiverB{
void pReceive(MySignalB s);
};
Then add the receiver to the Poulpe declaration :
// list of receiver's types,
// there is no limitation on the number of receivers
DEFINE_RECEIVERS(MyReceiverA, MyReceiverB)
// instantiation of the receivers
MyReceiverA sRXA;
MyReceiverB sRXB;
// list of receiver's instances,
// it has to be listed in the same order as in DEFINE_RECEIVERS
CREATE_POULPE(sRXA, sRXB)
template<typename emitter_t>
struct MyEmitter{
void testA(){
// the line below will call every receivers that implement
// the function "void pReceive(MySignalA)"
emitter_t::pEmit(MySignalA());
}
void testB(){
// the line below will call every receivers that implement
// the function "void pReceive(MySignalB)"
emitter_t::pEmit(MySignalB());
}
};
int main(){
// instantiation of the emitter
MyEmitter<poulpe::Emitter> e;
e.testA();
e.testB();
return 0;
}
It's possible to send templated signals.
template<int I>
struct MySignal{};
struct MyReceiverA{
// this function will be called for every MySignal<5> sent
void pReceive(MySignal<5> s);
};
struct MyReceiverB{
// this function will be called for every MySignal<I> sent
template<int I>
void pReceive(MySignal<I> s);
};
struct MyReceiverC{
// this function will be called for any type sent
template<typename signal_t>
void pReceive(signal_t s);
};
Signals are passed by reference, which means that they can be modified on the fly by receivers.
struct GetValue{ bool m; };
struct MyReceiver{
void pReceive(GetValue& s) {
s.m = true;
}
};
template<typename emitter_t>
struct MyEmitter{
void getValue(){
// instantiation of the signal
GetValue gv;
// init the member
gv.m = false;
// emitt signal
emitter_t::pEmit(gv);
// gv.m is now true
if(!gv.m) {
// error
}
}
};
int main(){
// instantiation of the emitter
MyEmitter<poulpe::Emitter> e;
e.getValue();
return 0;
}
A class can be an emitter and a receiver at the same time
struct MySignalA{};
struct MySignalB{};
template<typename emitter_t>
struct MyRxTx{
void test(){
emitter_t::pEmit(MySignalA());
}
void pReceive(MySignalB s){
// do stuff
}
};
DEFINE_RECEIVERS(MyRxTx<poulpe::Emitter>)
MyRxTx<poulpe::Emitter> sRX;
CREATE_POULPE(sRX)