CommRaT 2.0.0
C++20 Real-Time Messaging Framework
Loading...
Searching...
No Matches
message_registry.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "../messages.hpp"
4#include "message_id.hpp"
5#include <type_traits>
6#include <tuple>
7#include <optional>
8#include <span>
9
10namespace commrat {
11
12// Forward declaration for Module template alias
13template<typename UserRegistry, typename OutputSpec_, typename InputSpec_, typename... CommandTypes>
14class Module;
15
16// ============================================================================
17// Message ID Auto-Increment System
18// ============================================================================
19
25template<typename... MessageDefs>
26struct AutoAssignIDs;
27
28template<>
29struct AutoAssignIDs<> {
30 using Result = std::tuple<>;
31};
32
33// Forward declarations
34template<typename ProcessedTuple, typename... Remaining>
36
37// Base case: no more messages to process
38template<typename... ProcessedDefs>
39struct AutoAssignIDsProcess<std::tuple<ProcessedDefs...>> {
40 using Result = std::tuple<>;
41};
42
43// Recursive case: process First, then Rest
44template<typename... ProcessedDefs, typename First, typename... Rest>
45struct AutoAssignIDsProcess<std::tuple<ProcessedDefs...>, First, Rest...> {
46private:
47 // Track highest ID for this prefix/subprefix
48 template<typename MessageDef>
49 struct HighestID {
50 static constexpr uint16_t value = []() constexpr {
51 if constexpr (sizeof...(ProcessedDefs) == 0) {
52 return 0;
53 } else {
54 uint16_t max_id = 0;
55 ((max_id = (ProcessedDefs::prefix == MessageDef::prefix &&
56 ProcessedDefs::subprefix == MessageDef::subprefix &&
57 ProcessedDefs::local_id > max_id) ?
58 ProcessedDefs::local_id : max_id), ...);
59 return max_id;
60 }
61 }();
62 };
63
64 // Assign ID to current message
65 using CurrentProcessed = std::conditional_t<
66 First::needs_auto_id,
68 typename First::Payload,
69 First::prefix,
70 First::subprefix,
71 HighestID<First>::value + 1
72 >,
73 First
74 >;
75
76 using RestResult = typename AutoAssignIDsProcess<std::tuple<ProcessedDefs..., CurrentProcessed>, Rest...>::Result;
77
78public:
79 using Result = decltype(std::tuple_cat(
80 std::declval<std::tuple<CurrentProcessed>>(),
81 std::declval<RestResult>()
82 ));
83};
84
85template<typename... MessageDefs>
87 using Result = typename AutoAssignIDsProcess<std::tuple<>, MessageDefs...>::Result;
88};
89
90// ============================================================================
91// Compile-Time Message ID Collision Detection
92// ============================================================================
93
94template<typename... MessageDefs>
96 static constexpr bool check() {
97 if constexpr (sizeof...(MessageDefs) <= 1) {
98 return true;
99 } else {
100 return check_all_pairs<MessageDefs...>();
101 }
102 }
103
104private:
105 template<typename First, typename Second, typename... Rest>
106 static constexpr bool check_all_pairs() {
107 constexpr uint32_t id1 = make_message_id(
108 static_cast<uint8_t>(First::prefix),
109 First::subprefix,
110 First::local_id
111 );
112 constexpr uint32_t id2 = make_message_id(
113 static_cast<uint8_t>(Second::prefix),
114 Second::subprefix,
115 Second::local_id
116 );
117
118 static_assert(id1 != id2, "Message ID collision detected!");
119
120 if constexpr (sizeof...(Rest) > 0) {
121 return check_all_pairs<First, Rest...>() && check_all_pairs<Second, Rest...>();
122 }
123 return true;
124 }
125};
126
127// ============================================================================
128// Compile-Time Message Type Registry
129// ============================================================================
130
147template<typename... MessageDefs>
149private:
150 // Auto-assign IDs where needed
151 using ProcessedDefs = typename AutoAssignIDs<MessageDefs...>::Result;
152
153 // Extract payload types from MessageDefinitions
154 template<typename Tuple>
155 struct ExtractPayloads;
156
157 template<typename... Defs>
158 struct ExtractPayloads<std::tuple<Defs...>> {
159 using PayloadTypes = std::tuple<typename Defs::Payload...>;
160 };
161
162 // Check for ID collisions at compile-time
163 static constexpr bool collisions_checked = CheckCollisions<MessageDefs...>::check();
164
165public:
166 // Payload types tuple - exposed for introspection
167 using PayloadTypes = typename ExtractPayloads<ProcessedDefs>::PayloadTypes;
168
169 // Helper to check if a payload type is in the registry
170 template<typename T, typename Tuple>
171 struct IsInTuple;
172
173 template<typename T, typename... Types>
174 struct IsInTuple<T, std::tuple<Types...>> {
175 static constexpr bool value = (std::is_same_v<T, Types> || ...);
176 };
177
178 template<typename T>
180
181 // Number of registered message types
182 static constexpr size_t num_types = sizeof...(MessageDefs);
183
184 // Maximum message size across all registered types (for buffer allocation)
185 // NOTE: We calculate size of TimsMessage<Payload> not just Payload, because that's what gets serialized
186 template<typename... Payloads>
187 static constexpr size_t calc_max_size(std::tuple<Payloads...>*) {
188 return std::max({sertial::Message<TimsMessage<Payloads>>::max_buffer_size...});
189 }
190
191 static constexpr size_t max_message_size = calc_max_size(static_cast<PayloadTypes*>(nullptr));
192
209 template<typename... SpecificTypes>
210 static constexpr size_t max_size_for_types() {
211 static_assert(sizeof...(SpecificTypes) > 0,
212 "max_size_for_types requires at least one type");
213
214 // Validate all types are registered
215 static_assert((is_registered_v<SpecificTypes> && ...),
216 "All types must be registered in the message registry");
217
218 // Calculate max size for the specified types
219 return std::max({sertial::Message<TimsMessage<SpecificTypes>>::max_buffer_size...});
220 }
221
222private:
223 // Helper to get MessageDefinition for a payload type
224 template<typename PayloadT, typename Tuple>
225 struct FindMessageDef;
226
227 template<typename PayloadT, typename... Defs>
228 struct FindMessageDef<PayloadT, std::tuple<Defs...>> {
229 private:
230 template<typename Def>
231 static constexpr bool matches = std::is_same_v<typename Def::Payload, PayloadT>;
232
233 template<typename First, typename... Rest>
234 static constexpr auto find_impl() {
235 if constexpr (matches<First>) {
236 return First{};
237 } else if constexpr (sizeof...(Rest) > 0) {
238 return find_impl<Rest...>();
239 } else {
240 return void{};
241 }
242 }
243
244 public:
245 using type = decltype(find_impl<Defs...>());
246 };
247
248 // Helper to get message ID for a payload type
249 template<typename PayloadT>
250 static constexpr uint32_t get_message_id_for() {
251 using MessageDef = typename FindMessageDef<PayloadT, ProcessedDefs>::type;
252 return make_message_id(
253 static_cast<uint8_t>(MessageDef::prefix),
254 MessageDef::subprefix,
255 MessageDef::local_id
256 );
257 }
258
259 // Helper to get index of payload type in tuple
260 template<typename T, typename Tuple, size_t Index = 0>
261 struct TypeIndex;
262
263 template<typename T, typename... Types, size_t Index>
264 struct TypeIndex<T, std::tuple<Types...>, Index> {
265 static constexpr size_t value = []() constexpr {
266 size_t idx = num_types;
267 size_t current = 0;
268 ((std::is_same_v<T, Types> ? (idx = current, 0) : (++current, 0)), ...);
269 return idx;
270 }();
271 };
272
273 template<typename T>
274 static constexpr size_t type_index() {
275 return TypeIndex<T, PayloadTypes>::value;
276 }
277
278 // Helper to get payload type by message ID at runtime
279 template<uint32_t ID, size_t Index = 0, typename Tuple = ProcessedDefs>
280 struct TypeByID;
281
282 template<uint32_t ID, size_t Index, typename... Defs>
283 struct TypeByID<ID, Index, std::tuple<Defs...>> {
284 using type = void;
285 static constexpr bool found = false;
286 };
287
288 template<uint32_t ID, size_t Index, typename... Defs>
289 requires (Index < sizeof...(Defs))
290 struct TypeByID<ID, Index, std::tuple<Defs...>> {
291 private:
292 using CurrentDef = std::tuple_element_t<Index, std::tuple<Defs...>>;
293 static constexpr uint32_t current_id = make_message_id(
294 static_cast<uint8_t>(CurrentDef::prefix),
295 CurrentDef::subprefix,
296 CurrentDef::local_id
297 );
298
299 public:
300 using type = std::conditional_t<
301 current_id == ID,
302 typename CurrentDef::Payload,
303 typename TypeByID<ID, Index + 1>::type
304 >;
305 static constexpr bool found = (current_id == ID) ||
306 TypeByID<ID, Index + 1>::found;
307 };
308
309public:
310 // ========================================================================
311 // Type Traits
312 // ========================================================================
313
317 template<typename T>
318 static constexpr bool is_registered = is_registered_v<T>;
319
323 template<typename T>
324 requires is_registered_v<T>
325 static constexpr uint32_t get_message_id() {
326 return get_message_id_for<T>();
327 }
328
332 template<uint32_t ID>
333 using PayloadTypeFor = typename TypeByID<ID, 0>::type;
334
338 template<uint32_t ID>
339 static constexpr bool has_message_id = TypeByID<ID, 0>::found;
340
341 // ========================================================================
342 // Serialization Interface (Compile-Time Type-Safe)
343 // ========================================================================
344
354 template<typename T>
355 requires is_registered_v<T>
356 static auto serialize(T& message) {
357 // Set message ID from MessageDefinition
358 message.header.msg_type = get_message_id<T>();
359
360 // Use SeRTial's serialization
361 auto result = serialize_message(message);
362
363 // Update header with actual serialized size
364 message.header.msg_size = static_cast<uint32_t>(result.size);
365
366 return result;
367 }
368
376 template<typename PayloadT>
377 requires is_registered_v<PayloadT>
378 static auto serialize(TimsMessage<PayloadT>& message) {
379 // Set message ID from MessageDefinition of payload type
380 message.header.msg_type = get_message_id<PayloadT>();
381
382 // Use SeRTial directly (bypass message_type check in serialize_message)
383 auto result = sertial::Message<TimsMessage<PayloadT>>::serialize(message);
384
385 // Update header with actual serialized size
386 message.header.msg_size = static_cast<uint32_t>(result.size);
387
388 return result;
389 }
390
400 template<typename T>
401 requires is_registered_v<T> && (!requires { typename T::payload_type; })
402 static auto deserialize(std::span<const std::byte> data) {
403 // T is a payload type - deserialize to TimsMessage<T> and extract payload
404 using MsgType = TimsMessage<T>;
405 auto msg_result = sertial::Message<MsgType>::deserialize(data);
406
407 // Transform DeserializeResult<TimsMessage<T>> to DeserializeResult<T>
408 if (msg_result) {
409 // Extract payload and return it wrapped
410 return std::make_optional(std::move(msg_result->payload));
411 }
412
413 // Return empty optional
414 return std::optional<T>{};
415 }
416
424 template<typename MsgT>
425 requires requires { typename MsgT::payload_type; } &&
426 is_registered_v<typename MsgT::payload_type>
427 static auto deserialize(std::span<const std::byte> data) {
428 // Use SeRTial directly to deserialize TimsMessage (bypass message_type check)
429 return sertial::Message<MsgT>::deserialize(data);
430 }
431
432 // ========================================================================
433 // Runtime Dispatch (Visitor Pattern)
434 // ========================================================================
435
454 template<typename Visitor>
455 static bool visit(uint32_t msg_id, std::span<const std::byte> data, Visitor&& visitor) {
456 return visit_impl<0>(msg_id, data, std::forward<Visitor>(visitor));
457 }
458
469 template<typename Callback>
470 static bool dispatch(uint32_t msg_id, std::span<const std::byte> data, Callback&& callback) {
471 return visit(msg_id, data, std::forward<Callback>(callback));
472 }
473
474 // ========================================================================
475 // Compile-Time Information
476 // ========================================================================
477
481 static constexpr size_t max_buffer_size() {
482 return max_message_size;
483 }
484
488 static constexpr size_t size() {
489 return num_types;
490 }
491
495 template<typename... Defs>
496 static constexpr auto get_all_ids_impl(std::tuple<Defs...>*) {
497 return std::array<uint32_t, sizeof...(Defs)>{
499 static_cast<uint8_t>(Defs::prefix),
500 Defs::subprefix,
501 Defs::local_id
502 )...
503 };
504 }
505
506 static constexpr auto message_ids() {
507 return get_all_ids_impl(static_cast<ProcessedDefs*>(nullptr));
508 }
509
510 // ========================================================================
511 // Phase 6 Multi-Input Helpers
512 // ========================================================================
513
517 template<std::size_t I>
518 using type_at = std::tuple_element_t<I, PayloadTypes>;
519
523 template<typename T>
524 static constexpr std::size_t get_type_index() {
525 return type_index<T>();
526 }
527
528private:
529 // Recursive visitor implementation
530 template<size_t Index, typename Visitor, typename... Defs>
531 static bool visit_impl_helper(uint32_t msg_id, std::span<const std::byte> data,
532 Visitor&& visitor, std::tuple<Defs...>*) {
533 if constexpr (Index >= sizeof...(Defs)) {
534 // Message ID not found in registry
535 return false;
536 } else {
537 using CurrentDef = std::tuple_element_t<Index, std::tuple<Defs...>>;
538 using CurrentPayload = typename CurrentDef::Payload;
539
540 constexpr uint32_t current_id = make_message_id(
541 static_cast<uint8_t>(CurrentDef::prefix),
542 CurrentDef::subprefix,
543 CurrentDef::local_id
544 );
545
546 if (msg_id == current_id) {
547 // Found matching ID - deserialize TimsMessage<Payload> wrapper using Registry
548 auto result = deserialize<TimsMessage<CurrentPayload>>(data);
549 if (result) {
550 // Visit with the full TimsMessage wrapper
551 std::forward<Visitor>(visitor)(*result);
552 return true;
553 }
554 return false;
555 }
556
557 // Try next type
558 return visit_impl_helper<Index + 1>(msg_id, data,
559 std::forward<Visitor>(visitor),
560 static_cast<std::tuple<Defs...>*>(nullptr));
561 }
562 }
563
564 template<size_t Index, typename Visitor>
565 static bool visit_impl(uint32_t msg_id, std::span<const std::byte> data, Visitor&& visitor) {
566 return visit_impl_helper<Index>(msg_id, data, std::forward<Visitor>(visitor),
567 static_cast<ProcessedDefs*>(nullptr));
568 }
569};
570
571// ============================================================================
572// Helper Functions for Convenience
573// ============================================================================
574
584template<typename... MessageDefs>
585using make_registry = MessageRegistry<MessageDefs...>;
586
590template<typename... MessageDefs>
591using CustomRegistry = MessageRegistry<MessageDefs...>;
592
593} // namespace commrat
Compile-time message type registry using MessageDefinition templates.
static constexpr auto get_all_ids_impl(std::tuple< Defs... > *)
Get list of all message IDs in the registry.
static auto serialize(TimsMessage< PayloadT > &message)
Serialize a TimsMessage<PayloadT> wrapper.
static auto serialize(T &message)
Serialize a message with automatic type registration check.
std::tuple_element_t< I, PayloadTypes > type_at
Get payload type at index I.
static constexpr bool is_registered_v
static bool dispatch(uint32_t msg_id, std::span< const std::byte > data, Callback &&callback)
Deserialize message by ID and dispatch to callback.
static constexpr size_t calc_max_size(std::tuple< Payloads... > *)
static constexpr auto message_ids()
static constexpr bool has_message_id
Check if a message ID is registered.
static constexpr size_t max_message_size
static constexpr size_t size()
Get number of registered message types.
typename TypeByID< ID, 0 >::type PayloadTypeFor
Get the payload type by message ID (compile-time)
static constexpr bool is_registered
Check if a payload type is registered.
static constexpr size_t max_buffer_size()
Get maximum buffer size needed for any message in the registry.
static constexpr size_t max_size_for_types()
Calculate maximum message size for specific payload types.
static constexpr std::size_t get_type_index()
Get type index for payload type T.
static constexpr uint32_t get_message_id()
Get the message ID for a given payload type.
static bool visit(uint32_t msg_id, std::span< const std::byte > data, Visitor &&visitor)
Visit a message by its message ID using a visitor.
typename ExtractPayloads< ProcessedDefs >::PayloadTypes PayloadTypes
static auto deserialize(std::span< const std::byte > data)
Deserialize a message with known type at compile time.
static constexpr size_t num_types
static auto deserialize(std::span< const std::byte > data)
Deserialize a TimsMessage<PayloadT> wrapper.
CommRaT - Modern C++ Real-Time Communication Framework.
constexpr uint32_t make_message_id(uint8_t prefix, uint8_t subprefix, uint16_t id)
Compile-time message ID construction.
auto serialize_message(T &message) -> typename sertial::Message< T >::Result
Definition messages.hpp:209
decltype(std::tuple_cat(std::declval< std::tuple< CurrentProcessed > >(), std::declval< RestResult >())) Result
Helper to assign auto-incremented IDs to messages marked with needs_auto_id.
typename AutoAssignIDsProcess< std::tuple<>, MessageDefs... >::Result Result
static constexpr bool check()
Message definition with compile-time ID assignment.