diff --git a/docs/message.adoc b/docs/message.adoc index 4cd32162..92cddbdf 100644 --- a/docs/message.adoc +++ b/docs/message.adoc @@ -162,7 +162,7 @@ using msg1_defn = extend, payload_f using msg2_defn = extend, payload_f>; ---- -==== Combining and packing messages +==== Overlaying and packing messages It is sometimes useful to combine multiple message definitions, to avoid repetition. For example, adding a payload message to a header: @@ -176,13 +176,22 @@ using data_f = field<"data", std::uint32_t>::located; using payload_defn = message<"payload", data_f>; using msg_defn = extend< - combine<"msg", header_defn, payload_defn>, + overlay<"msg", header_defn, payload_defn>, type_f::with_required<1>>; + +// resulting message layout: +// byte |0 |1 | +// bit |01234567|01234567| +// field |header |payload | ---- -The combined definition incorporates all the fields of the messages. And as -shown, the combination might typically be `extend`​ed with a constraint on the -header field. +The resulting definition incorporates all the fields of the messages. +And as shown, the combination might typically be `extend`​ed with a constraint on +the header field. + +NOTE: It is possible to have overlapping message fields! Fields just determine +which parts of the data are read/written, and overlapping field definitions are +sometimes useful. Other times it is useful to automatically concatenate or `pack` messages together, where the field locations in each message start at 0. @@ -193,7 +202,7 @@ using type_f = field<"type", std::uint32_t>::located; using header_defn = message<"header", type_f>; // note: data_f collides with type_f under a naive combination -using data_f = field<"data">, std::uint32_t>::located; +using data_f = field<"data", std::uint32_t>::located; using payload_defn = message<"payload", data_f>; using msg_defn = extend< @@ -201,17 +210,16 @@ using msg_defn = extend< type_f::with_required<1>>; // resulting message layout: -// byte 0 1 -// bit 01234567 01234567 -// field |type|xx |data | +// byte |0 |1 | +// bit |012345|67|01234567| +// field |type |xx|data | ---- The second parameter to `pack` (`std::uint8_t` in the example above) defines how the messages are packed together - in this case, each subsequent message is byte-aligned. -CAUTION: After combining or packing messages, the fields inside them may have -moved! +CAUTION: After packing messages, the fields inside them may have moved! Any matchers defined on the original fields may cause problems when matching against raw data, because they will be looking in the wrong place. (Matching @@ -238,6 +246,34 @@ using msg_defn = extend< using new_data_f = msg_defn::field_t<"data">; ---- +==== Relaxed messages + +During prototyping, it can be useful to specify message types, but not worry +about where they are located yet. The compiler can automatically place them in +storage for us, and this is what `relaxed_message` is for. + +[source,cpp] +---- +// just prototyping: we want field types, but we don't care about layout yet +using type_f = field<"type", std::uint8_t>; +using data_f = field<"data", std::uint32_t>; +using msg_defn = relaxed_message<"msg", type_f, data_f>; + +// msg_defn has both fields, at unspecified locations +---- + +[source,cpp] +---- +// we want to fix the type, but we don't care about the rest +using type_f = field<"type", std::uint8_t>::located; +using field0_f = field<"f0", std::uint8_t>; +using field1_f = field<"f1", std::uint16_t>; +using field2_f = field<"f2", std::uint32_t>; +using msg_defn = relaxed_message<"msg", type_f, field0_f, field1_f, field2_f>; + +// msg_defn has type as the first 4 bits; the other fields are at unspecified locations +---- + ==== Owning vs view types An owning message uses underlying storage: by default, this is a `std::array` of diff --git a/include/msg/message.hpp b/include/msg/message.hpp index d887371f..1bdfa22c 100644 --- a/include/msg/message.hpp +++ b/include/msg/message.hpp @@ -699,26 +699,26 @@ using shifted_msgs = msg_offsets, stdx::type_list>>; -template struct combiner { +template struct overlayer { template using fn = msg::message; }; -template struct combine_q { +template struct overlay_q { template requires(sizeof...(Msgs) > 0) using fn = boost::mp11::mp_apply_q< - combiner>, + overlayer>, boost::mp11::mp_append>; }; } // namespace detail template requires(sizeof...(Msgs) > 0) -using combine = typename detail::combine_q::template fn; +using overlay = typename detail::overlay_q::template fn; template requires(sizeof...(Msgs) > 0) -using pack = boost::mp11::mp_apply_q, +using pack = boost::mp11::mp_apply_q, detail::shifted_msgs>; namespace detail { @@ -739,7 +739,7 @@ struct field_locator { using unlocated_fields = boost::mp11::mp_second; using located_msg = - boost::mp11::mp_apply_q>, located_fields>; + boost::mp11::mp_apply_q>, located_fields>; using auto_fields = boost::mp11::mp_sort; diff --git a/test/msg/message.cpp b/test/msg/message.cpp index 7837d6f1..1f4a1e90 100644 --- a/test/msg/message.cpp +++ b/test/msg/message.cpp @@ -614,7 +614,7 @@ TEST_CASE("shift all fields in a message", "[message]") { STATIC_REQUIRE(std::is_same_v); } -TEST_CASE("combine messages", "[message]") { +TEST_CASE("overlay messages", "[message]") { using f1 = field<"f1", std::uint32_t>::located; using f2 = field<"f2", std::uint32_t>::located; using m1 = message<"m1", f1, f2>; @@ -623,19 +623,19 @@ TEST_CASE("combine messages", "[message]") { using f4 = field<"f4", std::uint32_t>::located; using m2 = message<"m2", f3, f4>; - using defn = combine<"defn", m1, m2::shifted_by<1, std::uint32_t>>; + using defn = overlay<"defn", m1, m2::shifted_by<1, std::uint32_t>>; using expected_defn = message<"defn", f1, f2, f3::shifted_by<1, std::uint32_t>, f4::shifted_by<1, std::uint32_t>>; STATIC_REQUIRE(std::is_same_v); } -TEST_CASE("combine 1 message", "[message]") { +TEST_CASE("overlay 1 message", "[message]") { using f1 = field<"f1", std::uint32_t>::located; using f2 = field<"f2", std::uint32_t>::located; using m1 = message<"m1", f1, f2>; - using defn = combine<"defn", m1>; + using defn = overlay<"defn", m1>; using expected_defn = message<"defn", f1, f2>; STATIC_REQUIRE(std::is_same_v); } @@ -771,14 +771,14 @@ TEST_CASE("supplement message environment", "[message]") { STATIC_REQUIRE(custom(new_defn::env_t{}) == 18); } -TEST_CASE("combine appends environments", "[message]") { +TEST_CASE("overlay appends environments", "[message]") { using env1_t = stdx::make_env_t; using m1 = message<"m1", env1_t>; using env2_t = stdx::make_env_t; using m2 = message<"m2", env2_t>; - using defn = combine<"defn", m1, m2>; + using defn = overlay<"defn", m1, m2>; STATIC_REQUIRE(custom(defn::env_t{}) == 18); }