@@ -162,7 +162,7 @@ using msg1_defn = extend<header_defn, "msg", type_f::with_required<1>, payload_f
162
162
using msg2_defn = extend<header_defn, "msg", type_f::with_required<2>, payload_f>;
163
163
----
164
164
165
- ==== Combining and packing messages
165
+ ==== Overlaying and packing messages
166
166
167
167
It is sometimes useful to combine multiple message definitions, to avoid
168
168
repetition. For example, adding a payload message to a header:
@@ -176,13 +176,22 @@ using data_f = field<"data", std::uint32_t>::located<at{0_dw, 15_msb, 7_lsb}>;
176
176
using payload_defn = message<"payload", data_f>;
177
177
178
178
using msg_defn = extend<
179
- combine <"msg", header_defn, payload_defn>,
179
+ overlay <"msg", header_defn, payload_defn>,
180
180
type_f::with_required<1>>;
181
+
182
+ // resulting message layout:
183
+ // byte |0 |1 |
184
+ // bit |01234567|01234567|
185
+ // field |header |payload |
181
186
----
182
187
183
- The combined definition incorporates all the fields of the messages. And as
184
- shown, the combination might typically be `extend`ed with a constraint on the
185
- header field.
188
+ The combined (overlaid) definition incorporates all the fields of the messages.
189
+ And as shown, the combination might typically be `extend`ed with a constraint on
190
+ the header field.
191
+
192
+ NOTE: It is possible to have overlapping message fields! Fields just determine
193
+ which parts of the data are read/written, and overlapping field definitions are
194
+ sometimes useful.
186
195
187
196
Other times it is useful to automatically concatenate or `pack` messages
188
197
together, where the field locations in each message start at 0.
@@ -201,17 +210,16 @@ using msg_defn = extend<
201
210
type_f::with_required<1>>;
202
211
203
212
// resulting message layout:
204
- // byte 0 1
205
- // bit 01234567 01234567
206
- // field |type|xx |data |
213
+ // byte | 0 |1 |
214
+ // bit |012345|67| 01234567|
215
+ // field |type |xx|data |
207
216
----
208
217
209
218
The second parameter to `pack` (`std::uint8_t` in the example above) defines how
210
219
the messages are packed together - in this case, each subsequent message is
211
220
byte-aligned.
212
221
213
- CAUTION: After combining or packing messages, the fields inside them may have
214
- moved!
222
+ CAUTION: After packing messages, the fields inside them may have moved!
215
223
216
224
Any matchers defined on the original fields may cause problems when matching
217
225
against raw data, because they will be looking in the wrong place. (Matching
@@ -238,6 +246,34 @@ using msg_defn = extend<
238
246
using new_data_f = msg_defn::field_t<"data">;
239
247
----
240
248
249
+ ==== Relaxed messages
250
+
251
+ During prototyping, it can be useful to specify message types, but not worry
252
+ about where they are located yet. The compiler can automatically place them in
253
+ storage for us, and this is what `relaxed_message` is for.
254
+
255
+ [source,cpp]
256
+ ----
257
+ // just prototyping: we want field types, but we don't care about layout yet
258
+ using type_f = field<"type", std::uint8_t>;
259
+ using data_f = field<"data">, std::uint32_t>;
260
+ using msg_defn = relaxed_message<"msg", type_f, data_f>;
261
+
262
+ // msg_defn has both fields, at unspecified locations
263
+ ----
264
+
265
+ [source,cpp]
266
+ ----
267
+ // we want to fix the type, but we don't care about the rest
268
+ using type_f = field<"type", std::uint8_t>::located<at{0_dw, 3_msb, 0_lsb}>;
269
+ using field0_f = field<"f0">, std::uint8_t>;
270
+ using field1_f = field<"f1">, std::uint16_t>;
271
+ using field2_f = field<"f2">, std::uint32_t>;
272
+ using msg_defn = relaxed_message<"msg", type_f, field0_f, field1_f, field2_f>;
273
+
274
+ // msg_defn has type as the first 4 bits; the other fields are at unspecified locations
275
+ ----
276
+
241
277
==== Owning vs view types
242
278
243
279
An owning message uses underlying storage: by default, this is a `std::array` of
0 commit comments