SuperTinyKernel™ RTOS 1.06.x
Lightweight, high-performance, deterministic, bare-metal C++ RTOS for resource-constrained embedded systems. MIT Open Source License.
Loading...
Searching...
No Matches
stk_sync_msgqueue.h
Go to the documentation of this file.
1/*
2 * SuperTinyKernel(TM) RTOS: Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems.
3 *
4 * Source: https://github.com/SuperTinyKernel-RTOS
5 *
6 * Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com>. All Rights Reserved.
7 * License: MIT License, see LICENSE for a full text.
8 */
9
10#ifndef STK_SYNC_MSGQUEUE_H_
11#define STK_SYNC_MSGQUEUE_H_
12
13#include "stk_sync_cv.h"
14
20
21namespace stk {
22namespace sync {
23
56{
57public:
60 static const size_t CAPACITY_MAX = 0xFFFEU;
61
68 explicit MessageQueue(uint8_t *buf, size_t capacity, size_t msg_size);
69
77
91 bool Put(const void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE);
92
101 bool TryPut(const void *msg_ptr) { return Put(msg_ptr, NO_WAIT); }
102
117 bool PutFront(const void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE);
118
128 bool TryPutFront(const void *msg_ptr) { return PutFront(msg_ptr, NO_WAIT); }
129
144 bool Get(void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE);
145
154 bool TryGet(void *msg_ptr) { return Get(msg_ptr, NO_WAIT); }
155
172 bool Peek(void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE);
173
183 bool TryPeek(void *msg_ptr) { return Peek(msg_ptr, NO_WAIT); }
184
201 bool PeekFront(void *msg_ptr, Timeout timeout_ticks = WAIT_INFINITE);
202
212 bool TryPeekFront(void *msg_ptr) { return PeekFront(msg_ptr, NO_WAIT); }
213
222 void Reset();
223
228 size_t GetCapacity() const { return m_capacity; }
229
234 size_t GetMsgSize() const { return m_msg_size; }
235
241 size_t GetCount() const { return m_count; }
242
247 size_t GetSpace() const { return (m_capacity - m_count); }
248
253 uint8_t *GetBuffer() { return m_buffer; }
254
259 bool IsEmpty() const { return (m_count == 0U); }
260
265 bool IsFull() const { return (m_count == m_capacity); }
266
275 bool IsStorageValid() const { return (m_buffer != nullptr); }
276
277private:
279
280 // Get pointer to the raw storage for slot index \a idx.
281 uint8_t *Slot(size_t idx) const { return m_buffer + (idx * m_msg_size); }
282
283 // Advance a ring-buffer index forward by one with wrap-around.
284 size_t Next(size_t idx) const { return (idx + 1U) % m_capacity; }
285
286 // Retreat a ring-buffer index backward by one with wrap-around.
287 // Uses a branch instead of modular arithmetic to avoid unsigned underflow.
288 size_t Prev(size_t idx) const { return (idx == 0U) ? (m_capacity - 1U) : (idx - 1U); }
289
290 uint8_t *m_buffer;
291 const size_t m_capacity;
292 const size_t m_msg_size;
293 size_t m_count;
294 size_t m_head;
295 size_t m_tail;
298};
299
300// ---------------------------------------------------------------------------
301// Constructor
302// ---------------------------------------------------------------------------
303
304inline MessageQueue::MessageQueue(uint8_t *buf, size_t capacity, size_t msg_size)
305: m_buffer(buf),
306 m_capacity(capacity),
307 m_msg_size(msg_size),
308 m_count(0U),
309 m_head(0U),
310 m_tail(0U)
311{
312 STK_ASSERT(buf != nullptr);
313 STK_ASSERT(capacity >= 1U);
314 STK_ASSERT(capacity <= CAPACITY_MAX);
315 STK_ASSERT(msg_size >= 1U);
316}
317
318// ---------------------------------------------------------------------------
319// Put
320// ---------------------------------------------------------------------------
321
322inline bool MessageQueue::Put(const void *msg_ptr, Timeout timeout_ticks)
323{
324 STK_ASSERT(msg_ptr != nullptr); // API contract: msg_ptr must not be null
325 STK_ASSERT(m_count <= (CAPACITY_MAX - 1U)); // API contract: must not exceed capacity
326
328 bool success = true;
329
330 while (m_count == m_capacity)
331 {
332 if (!m_cv_not_full.Wait(cs_, timeout_ticks))
333 {
334 success = false;
335 break;
336 }
337 }
338
339 if (success)
340 {
341 STK_MEMCPY(Slot(m_head), msg_ptr, m_msg_size);
342 m_head = Next(m_head);
343 m_count++;
344
345 m_cv_not_empty.NotifyOne_CS();
346 }
347
348 return success;
349}
350
351// ---------------------------------------------------------------------------
352// PutFront
353// ---------------------------------------------------------------------------
354
355inline bool MessageQueue::PutFront(const void *msg_ptr, Timeout timeout_ticks)
356{
357 STK_ASSERT(msg_ptr != nullptr); // API contract: msg_ptr must not be null
358 STK_ASSERT(m_count <= (CAPACITY_MAX - 1U)); // API contract: must not exceed capacity
359
361 bool success = true;
362
363 while (m_count == m_capacity)
364 {
365 if (!m_cv_not_full.Wait(cs_, timeout_ticks))
366 {
367 success = false;
368 break;
369 }
370 }
371
372 if (success)
373 {
374 // retreat the tail pointer to claim the slot that Get() would read next,
375 // then write the message there; this makes the new message the head of
376 // the logical sequence without touching m_head at all
377 m_tail = Prev(m_tail);
378 STK_MEMCPY(Slot(m_tail), msg_ptr, m_msg_size);
379 m_count++;
380
381 m_cv_not_empty.NotifyOne_CS();
382 }
383
384 return success;
385}
386
387// ---------------------------------------------------------------------------
388// Get
389// ---------------------------------------------------------------------------
390
391inline bool MessageQueue::Get(void *msg_ptr, Timeout timeout_ticks)
392{
393 STK_ASSERT(msg_ptr != nullptr); // API contract: msg_ptr must not be null
394
396 bool success = true;
397
398 while (m_count == 0U)
399 {
400 if (!m_cv_not_empty.Wait(cs_, timeout_ticks))
401 {
402 success = false;
403 break;
404 }
405 }
406
407 if (success)
408 {
409 STK_MEMCPY(msg_ptr, Slot(m_tail), m_msg_size);
410 m_tail = Next(m_tail);
411 m_count--;
412
413 m_cv_not_full.NotifyOne_CS();
414 }
415
416 return success;
417}
418
419// ---------------------------------------------------------------------------
420// Peek
421// ---------------------------------------------------------------------------
422
423inline bool MessageQueue::Peek(void *msg_ptr, Timeout timeout_ticks)
424{
425 STK_ASSERT(msg_ptr != nullptr); // API contract: msg_ptr must not be null
426
428 bool success = true;
429
430 while (m_count == 0U)
431 {
432 if (!m_cv_not_empty.Wait(cs_, timeout_ticks))
433 {
434 success = false;
435 break;
436 }
437 }
438
439 if (success)
440 {
441 // copy from the tail slot without advancing the index or decrementing
442 // the count, so the message remains available for the next Get()
443 STK_MEMCPY(msg_ptr, Slot(m_tail), m_msg_size);
444 }
445
446 return success;
447}
448
449// ---------------------------------------------------------------------------
450// PeekFront
451// ---------------------------------------------------------------------------
452
453inline bool MessageQueue::PeekFront(void *msg_ptr, Timeout timeout_ticks)
454{
455 STK_ASSERT(msg_ptr != nullptr); // API contract: msg_ptr must not be null
456
458 bool success = true;
459
460 while (m_count == 0U)
461 {
462 if (!m_cv_not_empty.Wait(cs_, timeout_ticks))
463 {
464 success = false;
465 break;
466 }
467 }
468
469 if (success)
470 {
471 // the front-inserted message is at m_tail (PutFront retreats m_tail then
472 // writes, so the newly placed message is always at the current m_tail);
473 // for a pure-Put queue this is equally correct: m_tail is the oldest slot
474 STK_MEMCPY(msg_ptr, Slot(m_tail), m_msg_size);
475 }
476
477 return success;
478}
479
480// ---------------------------------------------------------------------------
481// Reset
482// ---------------------------------------------------------------------------
483
485{
486 const ScopedCriticalSection cs_;
487
488 m_count = 0U;
489 m_head = 0U;
490 m_tail = 0U;
491
492 // wake all blocked producers: the queue is now entirely empty
493 m_cv_not_full.NotifyAll_CS();
494}
495
496// ---------------------------------------------------------------------------
497
534template <size_t N, size_t MSG>
536{
537public:
538 STK_STATIC_ASSERT_DESC(N >= 1U, "MessageQueueT: capacity N must be at least 1");
539 STK_STATIC_ASSERT_DESC(N <= CAPACITY_MAX, "MessageQueueT: capacity N must not exceed CAPACITY_MAX");
540 STK_STATIC_ASSERT_DESC(MSG >= 1U, "MessageQueueT: message size MSG must be at least 1");
541
546
554
555private:
557
558 alignas(alignof(max_align_t)) uint8_t m_storage[N * MSG];
559};
560
561} // namespace sync
562} // namespace stk
563
564#endif /* STK_SYNC_MSGQUEUE_H_ */
static __stk_forceinline void STK_MEMCPY(void *const dest, const void *const src, const size_t size)
A wrapper for a built-in memcpy, redefine to your own if required.
Definition stk_arch.h:540
#define STK_NONCOPYABLE_CLASS(TYPE)
Disables copy construction and assignment for a class.
Definition stk_defs.h:601
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:409
#define STK_STATIC_ASSERT_DESC(X, DESC)
Compile-time assertion with a custom error description. Produces a compilation error if X is false.
Definition stk_defs.h:429
#define STK_VIRT_DTOR
Makes destructors virtual and compliant to strict rules if STK_STRICT_COMPLIANCY=0.
Definition stk_defs.h:159
Implementation of synchronization primitive: stk::sync::ConditionVariable.
Namespace of STK package.
constexpr Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:210
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:146
constexpr Timeout WAIT_INFINITE
Timeout value: block indefinitely until the synchronization object is signaled.
Definition stk_common.h:204
Synchronization primitives for task coordination and resource protection.
Traceable object.
Definition stk_common.h:410
RAII-style low-level synchronization primitive for atomic code execution. Used as building brick for ...
Definition stk_sync_cs.h:54
Condition Variable primitive for signaling between tasks based on specific predicates.
Definition stk_sync_cv.h:68
bool TryPeek(void *msg_ptr)
Attempt to peek at the next message without blocking.
bool TryPeekFront(void *msg_ptr)
Attempt to peek at the front message without blocking.
bool IsEmpty() const
Check whether the queue is currently empty.
bool PeekFront(void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Peek at the most recently front-inserted message (front of the FIFO) without removing it.
size_t GetCapacity() const
Get the maximum number of messages the queue can hold.
static const size_t CAPACITY_MAX
Max capacity supported (number of messages).
bool TryPutFront(const void *msg_ptr)
Attempt to put a message into the front of the queue without blocking.
size_t m_tail
read index (next slot to be read by Get())
size_t Next(size_t idx) const
size_t GetMsgSize() const
Get the size of each message in bytes.
bool TryPut(const void *msg_ptr)
Attempt to put a message into the back of the queue without blocking.
~MessageQueue()=default
Destructor.
size_t GetSpace() const
Get the number of free slots currently available.
uint8_t * Slot(size_t idx) const
const size_t m_capacity
maximum number of messages stored in the queue
size_t Prev(size_t idx) const
size_t GetCount() const
Get the current number of messages in the queue.
bool TryGet(void *msg_ptr)
Attempt to get a message from the queue without blocking.
uint8_t * GetBuffer()
Get pointer to the message buffer.
size_t m_head
write index (next slot to be written by Put())
bool Peek(void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Peek at the next message to be delivered (back of the FIFO) without removing it.
uint8_t * m_buffer
flat byte ring-buffer: capacity slots of msg_size bytes each
ConditionVariable m_cv_not_full
signaled by Get()/Reset() when the queue is no longer full
bool PutFront(const void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Put a message into the front of the queue (LIFO / priority-insert order).
bool IsStorageValid() const
Verify that the backing storage is valid and the pool is ready for use.
size_t m_count
current number of messages stored in the queue
bool Put(const void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Put a message into the back of the queue (FIFO order).
bool Get(void *msg_ptr, Timeout timeout_ticks=WAIT_INFINITE)
Get a message from the queue.
bool IsFull() const
Check whether the queue is currently full.
ConditionVariable m_cv_not_empty
signaled by Put() when the queue transitions from empty
void Reset()
Discard all messages and reset the queue to the empty state.
MessageQueue(uint8_t *buf, size_t capacity, size_t msg_size)
Constructor.
const size_t m_msg_size
size of each message in bytes
uint8_t m_storage[N *MSG]
flat byte ring-buffer: N slots of MSG bytes each
~MessageQueueT()=default
Destructor.