|
1 | 1 | # Tutorial 26
|
2 |
| -Stateful message sequence. |
| 2 | +Stateful messages sequence. |
| 3 | + |
| 4 | +There are protocols that don't differentiate messages by their IDs. Instead, the sequence of the messages is pre-determined. |
| 5 | +In this particular tutorial (see [schema](dsl/schema.xml)) all the messages are the same, i.e. have the same ID and without any fields. |
| 6 | +``` |
| 7 | +<message name="Msg1" id="0" order="0" displayName="^Msg1Name" /> |
| 8 | +<message name="Msg2" id="0" order="1" displayName="^Msg2Name" /> |
| 9 | +<message name="Msg3" id="0" order="2" displayName="^Msg3Name" /> |
| 10 | +``` |
| 11 | +Please note usage of the `nonUniqueMsgIdAllowed="true"` in the schema definition as well as **order** property for each `<message>` |
| 12 | + |
| 13 | +The frame is defined like this: |
| 14 | +``` |
| 15 | +<frame name="Frame"> |
| 16 | + <size name="Size"> |
| 17 | + <int name="SizeField" type="uint16" /> |
| 18 | + </size> |
| 19 | + <id name="Id"> |
| 20 | + <int name="IdField" type="uint8" pseudo="true" /> |
| 21 | + </id> |
| 22 | + <payload name="Data" /> |
| 23 | +</frame> |
| 24 | +``` |
| 25 | +Note that the field for the message ID is marked with `pseudo="true"`, i.e it is not serialized. Basically the real frame is |
| 26 | +``` |
| 27 | +| SIZE (2 bytes) | PAYLOAD | |
| 28 | +``` |
| 29 | + |
| 30 | +In most of the previous tutorials the messages were decoded and dispatched using the [comms::processAllWithDispatch()](https://commschamp.github.io/comms_doc/process_8h.html) |
| 31 | +function, which internally invoked the `read()` member function of the outermost |
| 32 | +[framing layer](https://commschamp.github.io/comms_doc/classcomms_1_1protocol_1_1MsgSizeLayer.html). |
| 33 | +The first parameter to the function can be a reference to a smart pointer (a variant of the `std::unique_ptr`) to the message object or a |
| 34 | +reference to the message object itself. When a smart pointer invocation is used the message object is dynamically allocated by the |
| 35 | +[comms::frame::MsgIdLayer](https://commschamp.github.io/comms_doc/classcomms_1_1protocol_1_1MsgIdLayer.html) framing layer. However, |
| 36 | +when a reference to already allocated message object is used it cannot be changed and the processed message ID information must match |
| 37 | +the ID reported by the message object. The [comms::processAllWithDispatch()](https://commschamp.github.io/comms_doc/process_8h.html) function |
| 38 | +used in previous tutorial internally uses the reference to a smart pointer invocation variant. To be able to support stateful sequence of message |
| 39 | +objects there is a need to use a different function ([comms::processSingleWithDispatch](https://commschamp.github.io/comms_doc/process_8h.html)) |
| 40 | +and process messages one by one while passing the reference to the pre-allocated message object. |
| 41 | + |
| 42 | +Let's take a look at the [server](src/ServerSession.cpp) implementation. |
| 43 | +```cpp |
| 44 | +std::size_t ServerSession::processInputImpl(const std::uint8_t* buf, std::size_t bufLen) |
| 45 | +{ |
| 46 | + std::cout << "Processing input: " << std::hex; |
| 47 | + std::copy_n(buf, bufLen, std::ostream_iterator<unsigned>(std::cout, " ")); |
| 48 | + std::cout << std::dec << std::endl; |
| 49 | + |
| 50 | + assert(m_msg); |
| 51 | + auto* prev = buf; |
| 52 | + auto consumed = 0U; |
| 53 | + while (consumed < bufLen) { |
| 54 | + auto es = comms::processSingleWithDispatch(buf, bufLen, m_frame, *m_msg, *this); |
| 55 | + if (es != comms::ErrorStatus::Success) { |
| 56 | + std::cerr << "ERROR: unexpected protocol sequence" << std::endl; |
| 57 | + return consumed; |
| 58 | + } |
| 59 | + |
| 60 | + consumed += static_cast<std::size_t>(std::distance(prev, buf)); |
| 61 | + } |
| 62 | + return consumed; |
| 63 | +} |
| 64 | +``` |
| 65 | +The message object is pre-allocated before at the start: |
| 66 | +```cpp |
| 67 | +bool ServerSession::startImpl() |
| 68 | +{ |
| 69 | + m_msg.reset(new Msg1); |
| 70 | + return true; |
| 71 | +} |
| 72 | +``` |
| 73 | +It means that when the first data buffer is reported the `Msg1` object is pre-allocated and is properly decoded. |
| 74 | + |
| 75 | +When it is properly handled, the next message object (`Msg2`) is pre-allocated for the next input and so on. |
| 76 | +```cpp |
| 77 | +void ServerSession::handle(Msg1& msg) |
| 78 | +{ |
| 79 | + std::cout << "Received message \"" << msg.doName() << "\"" << std::endl; |
| 80 | + sendMessage(msg); |
| 81 | + m_msg.reset(new Msg2); |
| 82 | +} |
| 83 | +``` |
| 84 | +
|
| 85 | +The [client](src/ClientSession.cpp) is very similar. The input message is pre-allocated every send. |
| 86 | +```cpp |
| 87 | +void ClientSession::sendMsg1() |
| 88 | +{ |
| 89 | + m_msg.reset(new Msg1); |
| 90 | + sendMessage(*m_msg); |
| 91 | +} |
| 92 | +
|
| 93 | +void ClientSession::sendMsg2() |
| 94 | +{ |
| 95 | + m_msg.reset(new Msg2); |
| 96 | + sendMessage(*m_msg); |
| 97 | +} |
| 98 | +
|
| 99 | +... |
| 100 | +``` |
| 101 | + |
| 102 | +When exchanging the messages both client and server allocate and dispatch proper message object (sequentially) for the same |
| 103 | +input. Below is the expected client side output. |
| 104 | +``` |
| 105 | +Sending message "Message 1" with ID=0 |
| 106 | +Sending raw data: 0 0 |
| 107 | +Processing input: 0 0 |
| 108 | +Received message "Message 1" |
| 109 | +Sending message "Message 2" with ID=0 |
| 110 | +Sending raw data: 0 0 |
| 111 | +Processing input: 0 0 |
| 112 | +Received message "Message 2" |
| 113 | +Sending message "Message 3" with ID=0 |
| 114 | +Sending raw data: 0 0 |
| 115 | +Processing input: 0 0 |
| 116 | +Received message "Message 3" |
| 117 | +``` |
| 118 | + |
| 119 | +Note that the `read()` member function of a [frame](https://commschamp.github.io/comms_doc/classcomms_1_1protocol_1_1MsgIdLayer.html) can |
| 120 | +receive a reference to the interface class (extends [comms::Message](https://commschamp.github.io/comms_doc/classcomms_1_1Message.html)) or |
| 121 | +the actual message object (extends [comms::MessageBase](https://commschamp.github.io/comms_doc/classcomms_1_1MessageBase.html)). In case of |
| 122 | +the latter the code will attempt to invoke non-virtual member functions of the message object, such as `doRead()` instead of polymorphic |
| 123 | +`read()`. |
| 124 | + |
| 125 | +## Summary |
| 126 | + |
| 127 | +- The transport framing allows deserialization of the pre-allocated message object, which allows implementation of the |
| 128 | +stateful sequence of messages. |
| 129 | +- The processing functions from the [comms/process.h](https://commschamp.github.io/comms_doc/process_8h.html) wrap the |
| 130 | +frame function(s) invocations and allow transparent passing of the same message parameter to the frame function, |
| 131 | +whether it is a reference to the message object itself or a reference to a smart pointer to the message object. |
3 | 132 |
|
4 | 133 | [Read Previous Tutorial](../tutorial25) <-----------------------
|
0 commit comments