Skip to content

Commit c2639b4

Browse files
authored
Merge pull request #655 from elbeno/combine-messages
✨ Add message packing
2 parents aa250b4 + f82e5bb commit c2639b4

File tree

4 files changed

+216
-68
lines changed

4 files changed

+216
-68
lines changed

docs/message.adoc

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,54 @@ using msg1_defn = extend<header_defn, "msg", type_f::with_required<1>, payload_f
136136
using msg2_defn = extend<header_defn, "msg", type_f::with_required<2>, payload_f>;
137137
----
138138

139+
==== Combining and packing messages
140+
141+
It is sometimes useful to combine multiple message definitions, to avoid
142+
repetition. For example, adding a payload message to a header:
143+
144+
[source,cpp]
145+
----
146+
using type_f = field<"type", std::uint32_t>::located<at{0_dw, 7_msb, 0_lsb}>;
147+
using header_defn = message<"header", type_f>;
148+
149+
using data_f = field<"data">, std::uint32_t>::located<at{0_dw, 15_msb, 7_lsb}>;
150+
using payload_defn = message<"payload", data_f>;
151+
152+
using msg_defn = extend<
153+
combine<"msg", header_defn, payload_defn>,
154+
type_f::with_required<1>>;
155+
----
156+
157+
The combined definition incorporates all the fields of the messages. And as
158+
shown, the combination might typically be `extend`ed with a constraint on the
159+
header field.
160+
161+
Other times it is useful to automatically concatenate or `pack` messages
162+
together, where the field locations in each message start at 0.
163+
164+
[source,cpp]
165+
----
166+
using type_f = field<"type", std::uint32_t>::located<at{0_dw, 5_msb, 0_lsb}>;
167+
using header_defn = message<"header", type_f>;
168+
169+
// note: data_f collides with type_f under a naive combination
170+
using data_f = field<"data">, std::uint32_t>::located<at{0_dw, 7_msb, 0_lsb}>;
171+
using payload_defn = message<"payload", data_f>;
172+
173+
using msg_defn = extend<
174+
pack<"msg", std::uint8_t, header_defn, payload_defn>,
175+
type_f::with_required<1>>;
176+
177+
// resulting message layout:
178+
// byte 0 1
179+
// bit 01234567 01234567
180+
// field |type|xx |data |
181+
----
182+
183+
The second parameter to `pack` (`std::uint8_t` in the example above) defines how
184+
the messages are packed together - in this case, each subsequent message is
185+
byte-aligned.
186+
139187
==== Owning vs view types
140188

141189
An owning message uses underlying storage: by default, this is a `std::array` of

include/msg/field.hpp

Lines changed: 35 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,6 @@ template <typename T, typename Spec>
6464
concept field_locator_for =
6565
field_extractor_for<T, Spec> and field_inserter_for<T, Spec>;
6666

67-
template <typename T>
68-
concept field_location = requires(T const &t) {
69-
{ t.index() } -> std::same_as<std::uint32_t>;
70-
{ t.lsb() } -> std::same_as<std::uint32_t>;
71-
{ t.size() } -> std::same_as<std::uint32_t>;
72-
{ t.valid() } -> std::same_as<bool>;
73-
{ t.sort_key() } -> std::same_as<std::uint32_t>;
74-
};
75-
7667
namespace detail {
7768
template <typename Name, typename T, std::uint32_t BitSize>
7869
struct field_spec_t {
@@ -305,62 +296,33 @@ CONSTEVAL auto operator""_msb(unsigned long long int v) -> msb_t {
305296
}
306297
} // namespace literals
307298

308-
template <typename...> struct at;
309-
310-
template <> struct at<dword_index_t, msb_t, lsb_t> {
311-
dword_index_t index_{};
312-
msb_t msb_{};
313-
lsb_t lsb_{};
299+
struct bit_unit {};
314300

315-
[[nodiscard]] constexpr auto
316-
index() const -> std::underlying_type_t<dword_index_t> {
317-
return stdx::to_underlying(index_);
318-
}
319-
[[nodiscard]] constexpr auto lsb() const -> std::underlying_type_t<lsb_t> {
320-
return stdx::to_underlying(lsb_);
321-
}
322-
[[nodiscard]] constexpr auto size() const -> std::underlying_type_t<lsb_t> {
323-
return stdx::to_underlying(msb_) - lsb() + 1;
324-
}
325-
[[nodiscard]] constexpr auto valid() const -> bool {
326-
return size() <= 64 and
327-
stdx::to_underlying(msb_) >= stdx::to_underlying(lsb_);
328-
}
329-
[[nodiscard]] constexpr auto
330-
sort_key() const -> std::underlying_type_t<lsb_t> {
331-
return index() * 32u + stdx::to_underlying(lsb_);
301+
template <typename Unit, typename T>
302+
constexpr auto unit_bit_size(T t) -> std::uint32_t {
303+
if constexpr (std::same_as<Unit, bit_unit>) {
304+
return static_cast<std::uint32_t>(t);
305+
} else {
306+
return static_cast<std::uint32_t>(t) * sizeof(Unit) * CHAR_BIT;
332307
}
333-
};
308+
}
334309

335-
template <> struct at<byte_index_t, msb_t, lsb_t> {
336-
byte_index_t index_{};
310+
struct at {
337311
msb_t msb_{};
338312
lsb_t lsb_{};
339313

340-
[[nodiscard]] constexpr auto
341-
index() const -> std::underlying_type_t<byte_index_t> {
342-
return stdx::to_underlying(index_) / 4;
343-
}
344-
[[nodiscard]] constexpr auto lsb() const -> std::underlying_type_t<lsb_t> {
345-
return (stdx::to_underlying(index_) * 8) % 32 +
346-
stdx::to_underlying(lsb_);
347-
}
348-
[[nodiscard]] constexpr auto size() const -> std::underlying_type_t<lsb_t> {
349-
return stdx::to_underlying(msb_) - stdx::to_underlying(lsb_) + 1;
350-
}
351-
[[nodiscard]] constexpr auto valid() const -> bool {
352-
return size() <= 8 and
353-
stdx::to_underlying(msb_) >= stdx::to_underlying(lsb_);
354-
}
355-
[[nodiscard]] constexpr auto
356-
sort_key() const -> std::underlying_type_t<lsb_t> {
357-
return index() * 32u + stdx::to_underlying(lsb_);
358-
}
359-
};
360-
361-
template <> struct at<msb_t, lsb_t> {
362-
msb_t msb_{};
363-
lsb_t lsb_{};
314+
constexpr at() = default;
315+
constexpr at(msb_t m, lsb_t l) : msb_{m}, lsb_{l} {}
316+
constexpr at(dword_index_t di, msb_t m, lsb_t l)
317+
: msb_{unit_bit_size<std::uint32_t>(stdx::to_underlying(di)) +
318+
stdx::to_underlying(m)},
319+
lsb_{unit_bit_size<std::uint32_t>(stdx::to_underlying(di)) +
320+
stdx::to_underlying(l)} {}
321+
constexpr at(byte_index_t bi, msb_t m, lsb_t l)
322+
: msb_{unit_bit_size<std::uint8_t>(stdx::to_underlying(bi)) +
323+
stdx::to_underlying(m)},
324+
lsb_{unit_bit_size<std::uint8_t>(stdx::to_underlying(bi)) +
325+
stdx::to_underlying(l)} {}
364326

365327
[[nodiscard]] constexpr auto
366328
index() const -> std::underlying_type_t<lsb_t> {
@@ -380,13 +342,14 @@ template <> struct at<msb_t, lsb_t> {
380342
sort_key() const -> std::underlying_type_t<lsb_t> {
381343
return stdx::to_underlying(lsb_);
382344
}
345+
[[nodiscard]] constexpr auto shifted_by(auto n) const -> at {
346+
return {msb_t{stdx::to_underlying(msb_) + n},
347+
lsb_t{stdx::to_underlying(lsb_) + n}};
348+
}
383349
};
384350

385-
template <typename... Ts> at(Ts...) -> at<Ts...>;
386-
387351
namespace detail {
388-
template <auto... Ats>
389-
requires(... and field_location<decltype(Ats)>)
352+
template <at... Ats>
390353
using locator_for =
391354
field_locator_t<bits_locator_t<Ats.index(), Ats.size(), Ats.lsb()>...>;
392355

@@ -428,9 +391,8 @@ struct field_id_t {};
428391

429392
template <typename Name, typename T = std::uint32_t,
430393
typename Default = detail::with_default<T>,
431-
match::matcher M = match::always_t, auto... Ats>
432-
requires std::is_trivially_copyable_v<T> and
433-
(... and field_location<decltype(Ats)>)
394+
match::matcher M = match::always_t, at... Ats>
395+
requires std::is_trivially_copyable_v<T>
434396
class field_t : public field_spec_t<Name, T, detail::field_size<Ats...>>,
435397
public detail::locator_for<Ats...>,
436398
public Default {
@@ -543,6 +505,12 @@ class field_t : public field_spec_t<Name, T, detail::field_size<Ats...>>,
543505
using with_required = field_t<Name, T, detail::with_const_default<T, V>,
544506
msg::equal_to_t<field_t, V>, Ats...>;
545507

508+
// ======================================================================
509+
// shift a field
510+
template <auto N, typename Unit = bit_unit>
511+
using shifted_by =
512+
field_t<Name, T, Default, M, Ats.shifted_by(unit_bit_size<Unit>(N))...>;
513+
546514
// ======================================================================
547515
// legacy aliases
548516
template <typename X> using WithMatch = with_matcher<X>;
@@ -560,8 +528,7 @@ template <stdx::ct_string Name, typename T = std::uint32_t,
560528
typename Default = detail::with_default<T>,
561529
match::matcher M = match::always_t>
562530
struct field {
563-
template <auto... Ats>
564-
requires(... and field_location<decltype(Ats)>)
531+
template <at... Ats>
565532
using located = detail::field_t<
566533
decltype(stdx::ct_string_to_type<Name, sc::string_constant>()), T,
567534
Default, M, Ats...>;

include/msg/message.hpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <stdx/utility.hpp>
2020

2121
#include <boost/mp11/algorithm.hpp>
22+
#include <boost/mp11/function.hpp>
2223
#include <boost/mp11/list.hpp>
2324
#include <boost/mp11/set.hpp>
2425

@@ -339,6 +340,10 @@ template <stdx::ct_string Name, typename... Fields> struct message {
339340
template <template <typename, std::size_t> typename C, typename T>
340341
using custom_storage_t = typename access_t::template storage_t<C, T>;
341342

343+
template <auto N, typename Unit = bit_unit>
344+
using shifted_by =
345+
message<Name, typename Fields::template shifted_by<N, Unit>...>;
346+
342347
template <typename S>
343348
constexpr static auto fits_inside =
344349
(... and Fields::template fits_inside<S>());
@@ -652,4 +657,49 @@ call_with_message(F &&f, S &&s, Args &&...args) -> decltype(auto) {
652657
std::forward<Args>(args)...);
653658
}
654659
}
660+
661+
namespace detail {
662+
template <typename AlignTo, typename... Msgs>
663+
using msg_sizes = stdx::type_list<typename Msgs::template size<AlignTo>...>;
664+
665+
template <typename S>
666+
using negated_size = std::integral_constant<std::size_t, -S::value>;
667+
668+
template <typename AlignTo, typename... Msgs>
669+
using msg_offsets = boost::mp11::mp_partial_sum<
670+
msg_sizes<AlignTo, Msgs...>,
671+
negated_size<typename boost::mp11::mp_first<
672+
stdx::type_list<Msgs...>>::template size<AlignTo>>,
673+
boost::mp11::mp_plus>;
674+
675+
template <typename AlignTo> struct shift_msg_q {
676+
template <typename Offset, typename Msg>
677+
using fn = typename Msg::template shifted_by<Offset::value, AlignTo>;
678+
};
679+
680+
template <typename AlignTo, typename... Msgs>
681+
using shifted_msgs = boost::mp11::mp_transform_q<shift_msg_q<AlignTo>,
682+
msg_offsets<AlignTo, Msgs...>,
683+
stdx::type_list<Msgs...>>;
684+
685+
template <stdx::ct_string Name> struct combiner {
686+
template <typename... Fields> using fn = msg::message<Name, Fields...>;
687+
};
688+
689+
template <stdx::ct_string Name> struct combine_q {
690+
template <typename... Msgs>
691+
requires(sizeof...(Msgs) > 0)
692+
using fn = boost::mp11::mp_apply_q<
693+
combiner<Name>, boost::mp11::mp_append<typename Msgs::fields_t...>>;
694+
};
695+
} // namespace detail
696+
697+
template <stdx::ct_string Name, typename... Msgs>
698+
requires(sizeof...(Msgs) > 0)
699+
using combine = typename detail::combine_q<Name>::template fn<Msgs...>;
700+
701+
template <stdx::ct_string Name, typename AlignTo, typename... Msgs>
702+
requires(sizeof...(Msgs) > 0)
703+
using pack = boost::mp11::mp_apply_q<detail::combine_q<Name>,
704+
detail::shifted_msgs<AlignTo, Msgs...>>;
655705
} // namespace msg

test/msg/message.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,3 +538,86 @@ TEST_CASE("message equivalence matcher", "[message]") {
538538
"f3"_field = 0xd00d};
539539
CHECK_THAT(m2, not MsgEquiv(m));
540540
}
541+
542+
TEST_CASE("message reports size", "[message]") {
543+
static_assert(msg_defn::size<std::uint32_t>::value == 2);
544+
static_assert(msg_defn::size<std::uint16_t>::value == 4);
545+
static_assert(msg_defn::size<std::uint8_t>::value == 7);
546+
}
547+
548+
TEST_CASE("shift a field by bit offset", "[message]") {
549+
using new_field = id_field::shifted_by<3>;
550+
using expected_field =
551+
field<"id", std::uint32_t>::located<at{34_msb, 27_lsb}>;
552+
static_assert(std::is_same_v<new_field, expected_field>);
553+
}
554+
555+
TEST_CASE("shift a field by byte offset", "[message]") {
556+
using new_field = id_field::shifted_by<1, std::uint8_t>;
557+
using expected_field =
558+
field<"id", std::uint32_t>::located<at{39_msb, 32_lsb}>;
559+
static_assert(std::is_same_v<new_field, expected_field>);
560+
}
561+
562+
TEST_CASE("shift all fields in a message", "[message]") {
563+
using base_defn = message<"msg", id_field, field1>;
564+
using defn = base_defn::shifted_by<1, std::uint8_t>;
565+
566+
using shifted_id = id_field::shifted_by<1, std::uint8_t>;
567+
using shifted_field1 = field1::shifted_by<1, std::uint8_t>;
568+
using expected_defn = message<"msg", shifted_id, shifted_field1>;
569+
static_assert(std::is_same_v<defn, expected_defn>);
570+
}
571+
572+
TEST_CASE("combine messages", "[message]") {
573+
using f1 = field<"f1", std::uint32_t>::located<at{23_msb, 16_lsb}>;
574+
using f2 = field<"f2", std::uint32_t>::located<at{15_msb, 0_lsb}>;
575+
using m1 = message<"m1", f1, f2>;
576+
577+
using f3 = field<"f3", std::uint32_t>::located<at{23_msb, 16_lsb}>;
578+
using f4 = field<"f4", std::uint32_t>::located<at{15_msb, 0_lsb}>;
579+
using m2 = message<"m2", f3, f4>;
580+
581+
using defn = combine<"defn", m1, m2::shifted_by<1, std::uint32_t>>;
582+
using expected_defn =
583+
message<"defn", f1, f2, f3::shifted_by<1, std::uint32_t>,
584+
f4::shifted_by<1, std::uint32_t>>;
585+
static_assert(std::is_same_v<defn, expected_defn>);
586+
}
587+
588+
TEST_CASE("combine 1 message", "[message]") {
589+
using f1 = field<"f1", std::uint32_t>::located<at{15_msb, 0_lsb}>;
590+
using f2 = field<"f2", std::uint32_t>::located<at{23_msb, 16_lsb}>;
591+
using m1 = message<"m1", f1, f2>;
592+
593+
using defn = combine<"defn", m1>;
594+
using expected_defn = message<"defn", f1, f2>;
595+
static_assert(std::is_same_v<defn, expected_defn>);
596+
}
597+
598+
TEST_CASE("pack messages", "[message]") {
599+
using f1 = field<"f1", std::uint32_t>::located<at{15_msb, 0_lsb}>;
600+
using f2 = field<"f2", std::uint32_t>::located<at{23_msb, 16_lsb}>;
601+
using m1 = message<"m1", f1, f2>;
602+
603+
using f3 = field<"f3", std::uint32_t>::located<at{15_msb, 0_lsb}>;
604+
using f4 = field<"f4", std::uint32_t>::located<at{23_msb, 16_lsb}>;
605+
using m2 = message<"m2", f3, f4>;
606+
607+
using defn = pack<"defn", std::uint8_t, m1, m2>;
608+
using expected_defn =
609+
message<"defn", f1, f2,
610+
f3::shifted_by<m1::size<std::uint8_t>::value, std::uint8_t>,
611+
f4::shifted_by<m1::size<std::uint8_t>::value, std::uint8_t>>;
612+
static_assert(std::is_same_v<defn, expected_defn>);
613+
}
614+
615+
TEST_CASE("pack 1 message", "[message]") {
616+
using f1 = field<"f1", std::uint32_t>::located<at{15_msb, 0_lsb}>;
617+
using f2 = field<"f2", std::uint32_t>::located<at{23_msb, 16_lsb}>;
618+
using m1 = message<"m1", f1, f2>;
619+
620+
using defn = pack<"defn", std::uint8_t, m1>;
621+
using expected_defn = message<"defn", f1, f2>;
622+
static_assert(std::is_same_v<defn, expected_defn>);
623+
}

0 commit comments

Comments
 (0)