Communication¶
In CLAID, any communication between Modules (either locally in the same application or remotely in different applications or on different hardware) is implemented via Channels. Using CLAID's Module API, not a single line of extra Code has to be written to move data between different, no matter where they are running.
Code example
Simple code showing how easy it is in CLAID to send data between Modules (no matter if they're running in the same process).
class ReceiverModule
{
Channel<string> channel;
void initialize()
{
// Subscribe to the channel and register a callback.
channel = subscribe<string>("MyStringChannel", &PrinterModule::onNewData, this);
}
// Callback function that automatically get's called
// when new data is available.
void onNewData(ChannelData<string> data)
{
// Of course, any other data type can be sent just as easily.
// String was just used for the sake of providing an intuitive example.
std::cout << "Received message: " << data.value() << "\n";
}
}
How does CLAID implement channels?¶
In CLAID, a Channel is a class that basically consists of two important components: First, a fixed-size Ringbuffer with individual mutexes per element. Second, a list of subscribers, i.e. Modules that subscribed to the corresponding channel. When inserting data (i.e., posting), the Ringbuffer is filled continuously, each time overriding the element at the current position. When the Ringbuffer is full, previous data get's overriden. However, please note: Elements of the Ringbuffer might survive even if the corresponding index in the Buffer is overriden OR even if the Ringbuffer get's destroyed. If any Module still holds a reference to a (previous) element in the buffer, the element will not get deleted (internal reference counter / shared pointer), even if new data was already inserted at the corresponding index.
Channels in CLAID are generally thread safe.
How does publishing and subscribing by ChannelID work?¶
To manage all the channels available in one instance of CLAID, we employ a Component called "ChannelManager". The ChannelManager holds a list of Channels mapped to the corresponding ChannelID. When a Module wants to publish/subscribe a Channel, it uses the global (per process) ChannelManager instance to get access to the Channel. If a Module wants to access a Channel that has not yet been published/subscribed to, the ChannelManager will automatically create a new Channel with the corresponding datatype and insert it into the list. Furthermore, when trying to publish/subscribe, the ChannelManager checks if the datatype of the call matches the datatype of the Channel.
Local communication¶
The process of sharing data between Modules running in in the same application/process we term local communication. This case is easy: Each channel holds a Ringbuffer and a list of subscribers. Whenever new data is inserted into the current position of the Ringbuffer, each registered Subscriber (i.e., Module) get s notified that new data is available. The Modules can then query the Ringbuffer for data inserted at that index. Since the beginning of the insertion, until the time when all Subscribers have queried the Buffer for the new data, the corresponding element is locked. In other words: The element at the current index of the Ringbuffer cannot get overriden until all subscribed Modules had the chance to query the Buffer for the newly inserted data.
When data is inserted (i.e., posted to the channel), the appropriate element in the Ringbuffer will be locked. CLAID's channel Ringbuffer uses an individual Mutex per element in the buffer. Therefore, each element can be accessed individually. The next element can already be written, while a previous one is still being read.