From f82e5bb2008e03aa082b851b79339b4f438e7811 Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Mon, 2 Dec 2024 10:48:24 -0700 Subject: [PATCH] :sparkles: Add message packing Problem: - Message definitions can't easily be combined, especially when the locations of fields are fixed in definitions and it would be useful to pack two or more messages together. - See issue #653 Solution: - Add primitives for combining messages: - `shifted_by` on fields and messages to increment their location(s) - `msg::combine` to naively combine multiple messages together - `msg::pack` to concatenate messages according to desired alignment Notes: - `at` has been simplified --- docs/message.adoc | 48 +++++++++++++++++++ include/msg/field.hpp | 103 ++++++++++++++-------------------------- include/msg/message.hpp | 50 +++++++++++++++++++ test/msg/message.cpp | 83 ++++++++++++++++++++++++++++++++ 4 files changed, 216 insertions(+), 68 deletions(-) diff --git a/docs/message.adoc b/docs/message.adoc index 265aa5a0..df502348 100644 --- a/docs/message.adoc +++ b/docs/message.adoc @@ -136,6 +136,54 @@ using msg1_defn = extend, payload_f using msg2_defn = extend, payload_f>; ---- +==== Combining and packing messages + +It is sometimes useful to combine multiple message definitions, to avoid +repetition. For example, adding a payload message to a header: + +[source,cpp] +---- +using type_f = field<"type", std::uint32_t>::located; +using header_defn = message<"header", type_f>; + +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>, + type_f::with_required<1>>; +---- + +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. + +Other times it is useful to automatically concatenate or `pack` messages +together, where the field locations in each message start at 0. + +[source,cpp] +---- +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 payload_defn = message<"payload", data_f>; + +using msg_defn = extend< + pack<"msg", std::uint8_t, header_defn, payload_defn>, + type_f::with_required<1>>; + +// resulting message layout: +// byte 0 1 +// bit 01234567 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. + ==== Owning vs view types An owning message uses underlying storage: by default, this is a `std::array` of diff --git a/include/msg/field.hpp b/include/msg/field.hpp index 4ea8d070..557c251c 100644 --- a/include/msg/field.hpp +++ b/include/msg/field.hpp @@ -64,15 +64,6 @@ template concept field_locator_for = field_extractor_for and field_inserter_for; -template -concept field_location = requires(T const &t) { - { t.index() } -> std::same_as; - { t.lsb() } -> std::same_as; - { t.size() } -> std::same_as; - { t.valid() } -> std::same_as; - { t.sort_key() } -> std::same_as; -}; - namespace detail { template struct field_spec_t { @@ -305,62 +296,33 @@ CONSTEVAL auto operator""_msb(unsigned long long int v) -> msb_t { } } // namespace literals -template struct at; - -template <> struct at { - dword_index_t index_{}; - msb_t msb_{}; - lsb_t lsb_{}; +struct bit_unit {}; - [[nodiscard]] constexpr auto - index() const -> std::underlying_type_t { - return stdx::to_underlying(index_); - } - [[nodiscard]] constexpr auto lsb() const -> std::underlying_type_t { - return stdx::to_underlying(lsb_); - } - [[nodiscard]] constexpr auto size() const -> std::underlying_type_t { - return stdx::to_underlying(msb_) - lsb() + 1; - } - [[nodiscard]] constexpr auto valid() const -> bool { - return size() <= 64 and - stdx::to_underlying(msb_) >= stdx::to_underlying(lsb_); - } - [[nodiscard]] constexpr auto - sort_key() const -> std::underlying_type_t { - return index() * 32u + stdx::to_underlying(lsb_); +template +constexpr auto unit_bit_size(T t) -> std::uint32_t { + if constexpr (std::same_as) { + return static_cast(t); + } else { + return static_cast(t) * sizeof(Unit) * CHAR_BIT; } -}; +} -template <> struct at { - byte_index_t index_{}; +struct at { msb_t msb_{}; lsb_t lsb_{}; - [[nodiscard]] constexpr auto - index() const -> std::underlying_type_t { - return stdx::to_underlying(index_) / 4; - } - [[nodiscard]] constexpr auto lsb() const -> std::underlying_type_t { - return (stdx::to_underlying(index_) * 8) % 32 + - stdx::to_underlying(lsb_); - } - [[nodiscard]] constexpr auto size() const -> std::underlying_type_t { - return stdx::to_underlying(msb_) - stdx::to_underlying(lsb_) + 1; - } - [[nodiscard]] constexpr auto valid() const -> bool { - return size() <= 8 and - stdx::to_underlying(msb_) >= stdx::to_underlying(lsb_); - } - [[nodiscard]] constexpr auto - sort_key() const -> std::underlying_type_t { - return index() * 32u + stdx::to_underlying(lsb_); - } -}; - -template <> struct at { - msb_t msb_{}; - lsb_t lsb_{}; + constexpr at() = default; + constexpr at(msb_t m, lsb_t l) : msb_{m}, lsb_{l} {} + constexpr at(dword_index_t di, msb_t m, lsb_t l) + : msb_{unit_bit_size(stdx::to_underlying(di)) + + stdx::to_underlying(m)}, + lsb_{unit_bit_size(stdx::to_underlying(di)) + + stdx::to_underlying(l)} {} + constexpr at(byte_index_t bi, msb_t m, lsb_t l) + : msb_{unit_bit_size(stdx::to_underlying(bi)) + + stdx::to_underlying(m)}, + lsb_{unit_bit_size(stdx::to_underlying(bi)) + + stdx::to_underlying(l)} {} [[nodiscard]] constexpr auto index() const -> std::underlying_type_t { @@ -380,13 +342,14 @@ template <> struct at { sort_key() const -> std::underlying_type_t { return stdx::to_underlying(lsb_); } + [[nodiscard]] constexpr auto shifted_by(auto n) const -> at { + return {msb_t{stdx::to_underlying(msb_) + n}, + lsb_t{stdx::to_underlying(lsb_) + n}}; + } }; -template at(Ts...) -> at; - namespace detail { -template - requires(... and field_location) +template using locator_for = field_locator_t...>; @@ -428,9 +391,8 @@ struct field_id_t {}; template , - match::matcher M = match::always_t, auto... Ats> - requires std::is_trivially_copyable_v and - (... and field_location) + match::matcher M = match::always_t, at... Ats> + requires std::is_trivially_copyable_v class field_t : public field_spec_t>, public detail::locator_for, public Default { @@ -543,6 +505,12 @@ class field_t : public field_spec_t>, using with_required = field_t, msg::equal_to_t, Ats...>; + // ====================================================================== + // shift a field + template + using shifted_by = + field_t(N))...>; + // ====================================================================== // legacy aliases template using WithMatch = with_matcher; @@ -560,8 +528,7 @@ template , match::matcher M = match::always_t> struct field { - template - requires(... and field_location) + template using located = detail::field_t< decltype(stdx::ct_string_to_type()), T, Default, M, Ats...>; diff --git a/include/msg/message.hpp b/include/msg/message.hpp index 3b27afc1..53700636 100644 --- a/include/msg/message.hpp +++ b/include/msg/message.hpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -339,6 +340,10 @@ template struct message { template