Messages#
The InOutput
and Step
instances inside the Pipeline
can communicate with each other through messages stored in the InOutputMessageBus or the TaskMessageBus.
Note that the Messages do not derive from any base-class such as Message
(and are therefore written in italics and not verbatim
)!
Any class or struct can be used as a message on a MessageBus, as long as it provides two public fields and a serialization function (which may be empty):
1static constexpr uint32_t message_type_id;
2static std::string GetMessageName();
3
4template <class Archive>
5void serialize(Archive& archive)
6{
7}
The message_type_id
should be a number (unique for each type) and is used to retrieve a Message of a specific type from the MessageBus
quickly.
The GetMessageName()
function should return a std::string
with the type name of the Message and is used in error messages.
The DEFINE_MPMCA_PIPELINE_MESSAGE_ID
macro is provided to help you define these two fields.
For example, placing the following line inside a struct definition:
1DEFINE_MPMCA_PIPELINE_MESSAGE_ID("StructName");
will be expanded into:
1static constexpr uint32_t message_type_id = 0x281F0CAC; // actual crc32 might be different
2static std::string GetMessageName() { return "StructName"; };
The macro accepts one or more input arguments, from which it will calculate a unique hash (CRC32) to be used as message_type_id
and a concatenated string to be used as message name.
The macro will convert numbers into characters when generating the name, which can be used to create a unique message_type_id
and message name for templated structs that take one or more numbers as template arguments. For example:
1template <size_t N>
2struct SomeMessageType {
3
4 DEFINE_MPMCA_PIPELINE_MESSAGE_ID("ASillyExample<", N, ">");
5
6 std::array<int, N> some_data_member;
7 std::array<float, N> some_other_data_member;
8
9 template <class Archive>
10 void serialize(Archive& archive)
11 {
12 archive(some_data_member, some_other_data_member);
13 }
14};
will expand into something similar to the following for the type SomeMessageType<6>
:
1static constexpr uint32_t message_type_id = 0x281F0CAC; // actual crc32 might be different
2static std::string GetMessageName() { return "ASillyExample<6>";};
For more information on serialization for datalogging using the Cereal serialization library, see this page.
MessageBus#
The Pipeline
contains two separate MessageBus
objects: the TaskMessageBus and the InOutputMessageBus.
The TaskMessageBus is used for communication between InOutput
and Task
instances during the MainTick()
and TaskCompleted()
calls.
The InOutputMessageBus is used for communication between InOutput
instances during the Tick()
calls.
Instances of messages on the MessageBus
are typically constructed and placed on the MessageBus
before the Pipeline
starts performing updates.
That is, for performance reasons, it is undesired to construct, push and pop the same types of messages onto the MessageBus
on every update cycle, as this typically involves expensive memory allocation calls.
Instead, the messages are constructed and pushed onto the MessageBus
during the Pipeline::Prepare()
call by InOutput
and Step
instances, and merely updated during the periodic update calls.
During the Pipeline::Prepare()
call, every InOutput
and Step
instance will be given the opportunity to
- create messages and push them onto the
MessageBus
, inside thePrepareTaskMessageBus()
andPrepareInOutputMessageBus()
callbacks, and then - check whether the messages it requires for proper functionality were indeed added to the
MessageBus
, inside theCheckTaskMessageBus()
andCheckInOutputMessageBus()
callbacks.
Typically, the InOutput
and Step
instances that will update information on a Message should create and push Messages, and instances that merely read information should check whether the Messages were indeed added.
As such, the MessageBus
is a somewhat specific implementation of the classic publish-subscribe design pattern.