CommRaT 2.0.0
C++20 Real-Time Messaging Framework
Loading...
Searching...
No Matches
mailbox.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "../platform/tims_wrapper.hpp"
4#include "../messages.hpp"
5#include "../messaging/message_registry.hpp"
6#include "../messaging/message_id.hpp"
7#include "../platform/threading.hpp"
8#include <expected>
9#include <optional>
10#include <chrono> // Keep for std::chrono::milliseconds in API
11#include <functional>
12#include <concepts>
13#include <span>
14#include <queue>
15
16namespace commrat {
17
18// ============================================================================
19// Error Types
20// ============================================================================
21
34
35// Convert error to string
36constexpr const char* to_string(MailboxError error) {
37 switch (error) {
38 case MailboxError::NotInitialized: return "Mailbox not initialized";
39 case MailboxError::InvalidMessage: return "Invalid message";
40 case MailboxError::Timeout: return "Operation timed out";
41 case MailboxError::QueueFull: return "Message queue is full";
42 case MailboxError::QueueEmpty: return "Message queue is empty";
43 case MailboxError::SerializationError: return "Serialization failed";
44 case MailboxError::NetworkError: return "Network error";
45 case MailboxError::InvalidDestination: return "Invalid destination mailbox";
46 case MailboxError::AlreadyRunning: return "Mailbox is already running";
47 case MailboxError::NotRunning: return "Mailbox is not running";
48 }
49 return "Unknown error";
50}
51
52// ============================================================================
53// Result Type (C++20-compatible alternative to std::expected)
54// ============================================================================
55
56template<typename T>
58private:
59 std::optional<T> value_;
60 std::optional<MailboxError> error_;
61
62public:
63 // Success constructor
64 MailboxResult(T value) : value_(std::move(value)), error_(std::nullopt) {}
65
66 // Error constructor
67 MailboxResult(MailboxError error) : value_(std::nullopt), error_(error) {}
68
69 // Check if result contains a value
70 explicit operator bool() const { return value_.has_value(); }
71 bool has_value() const { return value_.has_value(); }
72
73 // Access the value
74 T& operator*() & { return *value_; }
75 const T& operator*() const & { return *value_; }
76 T&& operator*() && { return std::move(*value_); }
77
78 T* operator->() { return &(*value_); }
79 const T* operator->() const { return &(*value_); }
80
81 T& value() & { return *value_; }
82 const T& value() const & { return *value_; }
83 T&& value() && { return std::move(*value_); }
84
85 // Access the error
86 MailboxError error() const { return *error_; }
87};
88
89// Specialization for void
90template<>
91class MailboxResult<void> {
92private:
93 std::optional<MailboxError> error_;
94
95public:
96 // Success constructor
97 MailboxResult() : error_(std::nullopt) {}
98
99 // Error constructor
101
102 // Static factory methods
105
106 // Check if result is success
107 explicit operator bool() const { return !error_.has_value(); }
108 bool has_value() const { return !error_.has_value(); }
109
110 // Access the error
111 MailboxError get_error() const { return *error_; }
112};
113
114// ============================================================================
115// Raw Message Receipt (for unknown message types)
116// ============================================================================
117
120 std::vector<std::byte> buffer; // Message data including header
121 int32_t type; // Message type ID
122 uint32_t sender_id; // Sender mailbox ID
123 size_t size; // Total message size
124 uint64_t timestamp; // Message timestamp
125
126 // Header accessor for compatibility
127 struct {
128 uint32_t msg_type;
130
132};
133// ============================================================================
134// Mailbox Configuration
135// ============================================================================
136
138 uint32_t mailbox_id;
139 size_t message_slots = 10;
140 size_t max_message_size = 4096;
141 uint8_t send_priority = 10;
142 bool realtime = false;
143 std::string mailbox_name = "";
144};
145
146// ============================================================================
147// Mailbox Template Class
148// ============================================================================
149
176template<typename... MessageDefs>
177class Mailbox {
178private:
179 // Internal registry for this mailbox's supported types
180 using Registry = MessageRegistry<MessageDefs...>;
181
182 // Helper to check if type is a MessageDefinition
183 template<typename T>
184 struct is_message_definition : std::false_type {};
185
186 template<typename PayloadT, MessagePrefix Prefix, auto SubPrefix, uint16_t ID>
187 struct is_message_definition<MessageDefinition<PayloadT, Prefix, SubPrefix, ID>> : std::true_type {};
188
189 // Validate that all types are MessageDefinition
190 static_assert((is_message_definition<MessageDefs>::value && ...),
191 "All template parameters must be MessageDefinition types");
192
193public:
194 // ========================================================================
195 // Construction and Lifecycle
196 // ========================================================================
197
202 explicit Mailbox(const MailboxConfig& config)
203 : config_(config)
204 , tims_(create_tims_config(config))
205 , running_(false) {
206 }
207
212 stop();
213 }
214
215 // Non-copyable
216 Mailbox(const Mailbox&) = delete;
217 Mailbox& operator=(const Mailbox&) = delete;
218
219 // Movable
220 Mailbox(Mailbox&& other) noexcept
221 : config_(std::move(other.config_))
222 , tims_(std::move(other.tims_))
223 , running_(other.running_.load()) {
224 other.running_ = false;
225 }
226
227 Mailbox& operator=(Mailbox&& other) noexcept {
228 if (this != &other) {
229 stop();
230 config_ = std::move(other.config_);
231 tims_ = std::move(other.tims_);
232 running_ = other.running_.load();
233 other.running_ = false;
234 }
235 return *this;
236 }
237
238 // ========================================================================
239 // Lifecycle Management
240 // ========================================================================
241
247 if (running_) {
249 }
250
251 auto result = tims_.initialize();
252 if (result != TimsResult::SUCCESS) {
254 }
255
256 // Receive buffer initialized
257 running_ = true;
258 return MailboxResult<void>();
259 }
260
264 void stop() {
265 if (running_) {
266 running_ = false;
267 tims_.shutdown();
268 }
269 }
270
274 bool is_running() const {
275 return running_;
276 }
277
278 // ========================================================================
279 // Type Validation
280 // ========================================================================
281
286 template<typename T>
287 static constexpr bool is_registered = []() constexpr {
288 // Check if T is TimsMessage<PayloadT>
289 if constexpr (requires { typename T::payload_type; }) {
290 return Registry::template is_registered<typename T::payload_type>;
291 } else {
292 return Registry::template is_registered<T>;
293 }
294 }();
295
299 static constexpr size_t num_message_types() {
300 return sizeof...(MessageDefs);
301 }
302
303 // ========================================================================
304 // Send Operations
305 // ========================================================================
306
315 template<typename T>
316 requires is_registered<T>
317 auto send(T& message, uint32_t dest_mailbox) -> MailboxResult<void> {
318 if (!running_) {
320 }
321
322 if (dest_mailbox == 0) {
324 }
325
326 // Registry::serialize expects TimsMessage<PayloadT>& (full message with header)
327 [[maybe_unused]] auto result = Registry::serialize(message);
328
329 // Send via TiMS
330 auto tims_result = tims_.send(message, dest_mailbox);
331
332 if (tims_result != TimsResult::SUCCESS) {
333 std::cerr << "[Mailbox] TiMS send failed with code: " << static_cast<int>(tims_result) << std::endl;
335 }
336
337 return MailboxResult<void>();
338 }
339
340 // ========================================================================
341 // Receive Operations
342 // ========================================================================
343
350 template<typename T>
351 requires is_registered<T>
353 if (!running_) {
355 }
356
357 // Receive raw bytes from TiMS
358 // Use SeRTial's buffer_type for TimsMessage<T> (includes header + payload)
359 typename sertial::Message<TimsMessage<T>>::buffer_type buffer;
360 auto bytes = tims_.receive_raw_bytes(buffer, std::chrono::seconds(1));
361
362 if (bytes <= 0) {
364 }
365
366 // Deserialize full TimsMessage<T> (header + payload)
367 auto result = sertial::Message<TimsMessage<T>>::deserialize(
368 std::span<const std::byte>(buffer.data(), bytes)
369 );
370 if (!result) {
372 }
373
374 // Return TimsMessage directly - no need for wrapper
375 return std::move(*result);
376 }
377
384 template<typename T>
385 requires is_registered<T>
386 auto try_receive() -> std::optional<TimsMessage<T>> {
387 // Use -1 to indicate non-blocking mode (TIMS_NONBLOCK)
388 // This gets converted to -1ns which TiMS recognizes
389 auto result = receive_for<T>(std::chrono::milliseconds(-1));
390 if (result) {
391 return *result;
392 }
393 return std::nullopt;
394 }
395
403 template<typename T>
404 requires is_registered<T>
405 auto receive_for(std::chrono::milliseconds timeout) -> MailboxResult<TimsMessage<T>> {
406 if (!running_) {
408 }
409
410 // Receive with timeout from TiMS
411 // Use SeRTial's buffer_type for TimsMessage<T> (includes header + payload)
412 typename sertial::Message<TimsMessage<T>>::buffer_type buffer;
413 auto bytes = tims_.receive_raw_bytes(buffer, timeout);
414
415 if (bytes == 0) {
417 }
418
419 if (bytes < 0) {
421 }
422
423 // Deserialize full TimsMessage<T> (header + payload)
424 auto result = sertial::Message<TimsMessage<T>>::deserialize(
425 std::span<const std::byte>(buffer.data(), bytes)
426 );
427 if (!result) {
429 }
430
431 // Return TimsMessage directly - no need for wrapper
432 return std::move(*result);
433 }
434
444 auto receive_any_raw(std::chrono::milliseconds timeout = std::chrono::milliseconds{-1})
446 if (!running_) {
448 }
449
450 // Receive raw bytes
451 constexpr size_t buffer_size = Registry::max_message_size;
452 std::array<std::byte, buffer_size> buffer;
453 ssize_t bytes = tims_.receive_raw_bytes(buffer, timeout);
454
455 if (bytes < 0) {
456 if (timeout.count() == -1 || timeout == std::chrono::milliseconds{0}) {
457 // Non-blocking receive with no message
459 }
461 }
462
463 // Parse header
464 if (static_cast<size_t>(bytes) < sizeof(TimsHeader)) {
466 }
467
468 TimsHeader header;
469 std::memcpy(&header, buffer.data(), sizeof(TimsHeader));
470
471 // TODO: Extract sender ID from TIMS (not currently exposed in API)
472 uint32_t sender_id = 0; // Placeholder
473
474 // Copy to vector for easy handling
475 RawReceivedMessage raw;
476 raw.buffer = std::vector<std::byte>(buffer.begin(), buffer.begin() + bytes);
477 raw.type = static_cast<int32_t>(header.msg_type);
478 raw.sender_id = sender_id;
479 raw.size = bytes;
480 raw.timestamp = header.timestamp;
481 raw.header.msg_type = header.msg_type;
482
483 return raw;
484 }
485
505 template<typename Visitor>
506 auto receive_any(Visitor&& visitor) -> MailboxResult<void> {
507 if (!running_) {
509 }
510
511 // Receive raw bytes
512 // Use largest message size from registry since we don't know type in advance
513 constexpr size_t buffer_size = Registry::max_message_size;
514 std::array<std::byte, buffer_size> buffer;
515 auto bytes = tims_.receive_raw_bytes(buffer, std::chrono::seconds(1));
516
517 if (bytes <= 0) {
519 }
520
521 // Parse header to get message type
522 if (static_cast<size_t>(bytes) < sizeof(TimsHeader)) {
524 }
525
526 TimsHeader header;
527 std::memcpy(&header, buffer.data(), sizeof(TimsHeader));
528 MessageType msg_type = static_cast<MessageType>(header.msg_type);
529
530 // Use registry to dispatch based on runtime type
531 bool success = Registry::visit(msg_type,
532 std::span<const std::byte>(buffer.data(), bytes),
533 [&visitor](auto&& tims_msg) {
534 // Visitor receives TimsMessage<PayloadType> directly
535 std::forward<Visitor>(visitor)(std::forward<decltype(tims_msg)>(tims_msg));
536 });
537
538 if (!success) {
540 }
541
542 return MailboxResult<void>();
543 }
544
545 // ========================================================================
546 // Utility Operations
547 // ========================================================================
548
554 if (!running_) {
556 }
557
558 // TiMS doesn't expose clean directly, so we drain messages
559 // Use largest message size from registry to handle any message type
560 constexpr size_t buffer_size = Registry::max_message_size;
561 std::array<std::byte, buffer_size> buffer;
562 while (tims_.receive_raw_bytes(buffer, std::chrono::milliseconds(10)) > 0) {
563 // Discard messages
564 }
565
566 return MailboxResult<void>();
567 }
568
572 uint32_t mailbox_id() const {
573 return config_.mailbox_id;
574 }
575
579 uint64_t messages_sent() const {
580 return tims_.get_messages_sent();
581 }
582
586 uint64_t messages_received() const {
587 return tims_.get_messages_received();
588 }
589
590private:
591 // Convert MailboxConfig to TimsConfig
592 static TimsConfig create_tims_config(const MailboxConfig& config) {
593 TimsConfig tims_config;
594 tims_config.mailbox_id = config.mailbox_id;
595 tims_config.mailbox_name = config.mailbox_name.empty()
596 ? ("mailbox_" + std::to_string(config.mailbox_id))
597 : config.mailbox_name;
598 tims_config.max_msg_size = config.max_message_size;
599 tims_config.priority = config.send_priority;
600 tims_config.realtime = config.realtime;
601 return tims_config;
602 }
603
604 MailboxConfig config_;
605 TimsWrapper tims_;
606 std::atomic<bool> running_;
607};
608
609// ============================================================================
610// Convenience Type Aliases
611// ============================================================================
612
623>;
624
628template<typename... CustomTypes>
634 CustomTypes...
635>;
636
637} // namespace commrat
MailboxResult(MailboxError error)
Definition mailbox.hpp:100
static MailboxResult< void > error(MailboxError err)
Definition mailbox.hpp:104
static MailboxResult< void > ok()
Definition mailbox.hpp:103
MailboxError get_error() const
Definition mailbox.hpp:111
MailboxResult(MailboxError error)
Definition mailbox.hpp:67
const T & operator*() const &
Definition mailbox.hpp:75
const T & value() const &
Definition mailbox.hpp:82
bool has_value() const
Definition mailbox.hpp:71
const T * operator->() const
Definition mailbox.hpp:79
MailboxError error() const
Definition mailbox.hpp:86
Strongly-typed mailbox for message-based communication.
Definition mailbox.hpp:177
auto receive_any(Visitor &&visitor) -> MailboxResult< void >
Receive any registered message type using a visitor.
Definition mailbox.hpp:506
static constexpr bool is_registered
Check if a type is registered with this mailbox at compile time For TimsMessage<PayloadT>,...
Definition mailbox.hpp:287
auto receive() -> MailboxResult< TimsMessage< T > >
Receive a message of specific type (blocking)
Definition mailbox.hpp:352
bool is_running() const
Check if mailbox is running.
Definition mailbox.hpp:274
uint64_t messages_received() const
Get number of messages received.
Definition mailbox.hpp:586
uint32_t mailbox_id() const
Get the mailbox ID.
Definition mailbox.hpp:572
Mailbox(Mailbox &&other) noexcept
Definition mailbox.hpp:220
auto send(T &message, uint32_t dest_mailbox) -> MailboxResult< void >
Send a message to a destination mailbox.
Definition mailbox.hpp:317
Mailbox & operator=(Mailbox &&other) noexcept
Definition mailbox.hpp:227
~Mailbox()
Destructor - automatically stops and cleans up.
Definition mailbox.hpp:211
Mailbox(const MailboxConfig &config)
Construct a mailbox with the given configuration.
Definition mailbox.hpp:202
void stop()
Stop the mailbox.
Definition mailbox.hpp:264
Mailbox & operator=(const Mailbox &)=delete
auto receive_any_raw(std::chrono::milliseconds timeout=std::chrono::milliseconds{-1}) -> MailboxResult< RawReceivedMessage >
Receive any message without knowing its type.
Definition mailbox.hpp:444
auto start() -> MailboxResult< void >
Start the mailbox (initialize TiMS connection)
Definition mailbox.hpp:246
Mailbox(const Mailbox &)=delete
auto clean() -> MailboxResult< void >
Clear all pending messages from the mailbox.
Definition mailbox.hpp:553
static constexpr size_t num_message_types()
Get number of registered message types.
Definition mailbox.hpp:299
auto try_receive() -> std::optional< TimsMessage< T > >
Try to receive a message without blocking.
Definition mailbox.hpp:386
uint64_t messages_sent() const
Get number of messages sent.
Definition mailbox.hpp:579
auto receive_for(std::chrono::milliseconds timeout) -> MailboxResult< TimsMessage< T > >
Receive a message with timeout.
Definition mailbox.hpp:405
static auto serialize(T &message)
Serialize a message with automatic type registration check.
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.
uint64_t get_messages_sent() const
uint64_t get_messages_received() const
ssize_t receive_raw_bytes(std::span< std::byte > buffer, Milliseconds timeout)
TimsResult send(T &message, uint32_t dest_mailbox_id)
TimsResult initialize()
CommRaT - Modern C++ Real-Time Communication Framework.
TimsMessage< SensorPayload > SensorMessage
Definition messages.hpp:133
TimsMessage< ErrorPayload > ErrorMessage
Definition messages.hpp:131
constexpr const char * to_string(MailboxError error)
Definition mailbox.hpp:36
TimsMessage< AckPayload > AckMessage
Definition messages.hpp:132
uint32_t MessageType
Definition messages.hpp:25
TimsMessage< StatusPayload > StatusMessage
Definition messages.hpp:130
auto deserialize(std::span< const std::byte > data) -> sertial::DeserializeResult< T >
Definition messages.hpp:192
TimsMessage< CommandPayload > CommandMessage
Definition messages.hpp:129
std::string mailbox_name
Definition mailbox.hpp:143
Message definition with compile-time ID assignment.
Raw received message with type info.
Definition mailbox.hpp:119
std::vector< std::byte > buffer
Definition mailbox.hpp:120
struct commrat::RawReceivedMessage::@0 header
std::string mailbox_name