FastLED 3.9.15
Loading...
Searching...
No Matches
async_log_queue.cpp.hpp
Go to the documentation of this file.
1
3
5#include "fl/stl/string.h"
7#include "fl/stl/string.h"
8#include "fl/math/math.h"
9
10namespace fl {
11
12// NOTE: critical_section implementation in fl/stl/isr/critical_section.cpp.hpp
13
14// ============================================================================
15// AsyncLogQueue::Descriptor implementation
16// ============================================================================
17
18template <fl::size DescriptorCount, fl::size ArenaSize>
21
22// ============================================================================
23// AsyncLogQueue public methods
24// ============================================================================
25
26template <fl::size DescriptorCount, fl::size ArenaSize>
28 : mHead(0), mTail(0), mArenaHead(0), mArenaTail(0), mDropped(0) {
29 // Initialize all descriptors to zero (optional, for debugging)
30 for (fl::size i = 0; i < DescriptorCount; i++) {
32 }
33}
34
35template <fl::size DescriptorCount, fl::size ArenaSize>
37 fl::size len = msg.length();
38 if (len > MAX_MESSAGE_LENGTH) {
40 }
41 return push(msg.c_str(), static_cast<fl::u16>(len));
42}
44template <fl::size DescriptorCount, fl::size ArenaSize>
46 fl::u16 len = boundedStrlen(str, MAX_MESSAGE_LENGTH);
47 return push(str, len);
48}
50template <fl::size DescriptorCount, fl::size ArenaSize>
51bool AsyncLogQueue<DescriptorCount, ArenaSize>::tryPop(const char** outPtr, fl::u16* outLen) {
52 // Read head with memory barrier (acquire semantics)
53 fl::u32 head = loadHead();
54 fl::u32 tail = mTail;
56 if (tail == head) {
57 return false; // Queue empty
58 }
59
60 // Read descriptor at tail position
61 const Descriptor& desc = mDescriptors[tail];
62
63 // Return pointer into arena (contiguous due to padding at wrap)
64 *outPtr = &mArena[desc.mStartIdx];
65 *outLen = desc.mLength;
66
67 return true;
68}
69
70template <fl::size DescriptorCount, fl::size ArenaSize>
72 fl::u32 tail = mTail;
73 const Descriptor& desc = mDescriptors[tail];
75 // Free arena space by advancing arena tail
76 fl::u32 newArenaTail = (mArenaTail + desc.mLength) & (ArenaSize - 1);
77
78 {
79 fl::isr::critical_section cs; // Protect arena tail update
80 mArenaTail = newArenaTail;
81 }
82
83 // Clear descriptor (optional, for debugging/sentinel semantics)
84 mDescriptors[tail] = Descriptor();
85
86 // Advance tail (consumer publishes completion)
87 fl::u32 newTail = (tail + 1) & (DescriptorCount - 1);
88
89 {
90 fl::isr::critical_section cs; // Protect tail update
91 mTail = newTail;
92 }
93}
94
95template <fl::size DescriptorCount, fl::size ArenaSize>
99
100template <fl::size DescriptorCount, fl::size ArenaSize>
102 fl::u32 head = loadHead();
103 fl::u32 tail = mTail;
104 return (head - tail) & (DescriptorCount - 1);
105}
106
107template <fl::size DescriptorCount, fl::size ArenaSize>
111
112// ============================================================================
113// AsyncLogQueue private methods
114// ============================================================================
115
116template <fl::size DescriptorCount, fl::size ArenaSize>
117bool AsyncLogQueue<DescriptorCount, ArenaSize>::push(const char* str, fl::u16 len) {
118 if (len == 0) {
119 return true; // Empty message, accept but don't store
120 }
121
122 // Load indices (head is producer-owned, tail needs barrier)
123 fl::u32 head = mHead;
124 fl::u32 tail = loadTail();
125 fl::u32 next = (head + 1) & (DescriptorCount - 1);
126
127 // Check if descriptor ring is full
128 if (next == tail) {
130 return false;
131 }
132
133 // Check if arena has enough free space
134 fl::u32 aHead = mArenaHead;
135 fl::u32 aTail = loadArenaTail();
136
137 if (!arenaHasSpace(aHead, aTail, len)) {
139 return false;
140 }
141
142 // Allocate space in arena
143 fl::u32 start = aHead;
144
145 // Check if message would wrap past arena end
146 if (start + len > ArenaSize) {
147 // Insert padding to wrap to start of arena (contiguous records)
148 fl::u32 padding = ArenaSize - start;
149
150 // Check if padding + message fits
151 if (!arenaHasSpace(aHead, aTail, static_cast<fl::u16>(padding + len))) {
153 return false;
154 }
155
156 // Advance arena head to skip padding bytes
157 aHead = (aHead + padding) & (ArenaSize - 1); // Should wrap to 0
158 // Store message at wrapped position
159 start = aHead;
160 }
161
162 // Copy message into arena (the only potentially slow operation in ISR)
163 for (fl::u16 i = 0; i < len; i++) {
164 mArena[start + i] = str[i];
165 }
166
167 // Advance arena head by message length
168 fl::u32 newArenaHead = (aHead + len) & (ArenaSize - 1);
169 {
170 fl::isr::critical_section cs; // Protect arena head update
171 mArenaHead = newArenaHead;
172 }
173
174 // Write descriptor (data is already copied, safe to publish)
175 mDescriptors[head].mStartIdx = start;
176 mDescriptors[head].mLength = len;
177
178 // Publish by advancing head (critical section for memory ordering)
179 {
181 mHead = next;
182 }
183
184 return true;
185}
186
187template <fl::size DescriptorCount, fl::size ArenaSize>
188fl::u16 AsyncLogQueue<DescriptorCount, ArenaSize>::boundedStrlen(const char* str, fl::u16 maxLen) {
189 fl::u16 len = 0;
190 while (len < maxLen && str[len] != '\0') {
191 len++;
192 }
193 return len;
194}
195
196template <fl::size DescriptorCount, fl::size ArenaSize>
197bool AsyncLogQueue<DescriptorCount, ArenaSize>::arenaHasSpace(fl::u32 aHead, fl::u32 aTail, fl::u16 len) const {
198 // Calculate free space in ring buffer
199 fl::u32 used = (aHead - aTail) & (ArenaSize - 1);
200 fl::u32 free = ArenaSize - used - 1; // Reserve 1 byte to distinguish full/empty
201
202 return len <= free;
203}
204
205template <fl::size DescriptorCount, fl::size ArenaSize>
210
211template <fl::size DescriptorCount, fl::size ArenaSize>
216
217template <fl::size DescriptorCount, fl::size ArenaSize>
222
223template <fl::size DescriptorCount, fl::size ArenaSize>
226 mDropped = mDropped + 1; // C++20-compliant volatile increment
227}
228
229// ============================================================================
230// Explicit template instantiations for common sizes
231// ============================================================================
232
233// Default size (128 descriptors, 4KB arena)
234template class AsyncLogQueue<128, 4096>;
235
236// Small test size (8 descriptors, 64 bytes arena)
237template class AsyncLogQueue<8, 64>;
238
239// Medium test size (16 descriptors, 256 bytes arena)
240template class AsyncLogQueue<16, 256>;
241
242// Large test size (128 descriptors, 1KB arena)
243template class AsyncLogQueue<128, 1024>;
244
245} // namespace fl
High-performance ISR-safe async logging queue (SPSC ring buffer) - declarations only.
bool push(const fl::string &msg)
Push a message from fl::string (ISR-safe)
volatile fl::u32 mArenaHead
Producer write position (arena)
volatile fl::u32 mTail
Consumer read position (descriptor ring)
static fl::u16 boundedStrlen(const char *str, fl::u16 maxLen)
volatile fl::u32 mDropped
Count of dropped messages (overflow)
fl::u32 loadArenaTail() const
bool empty() const
Check if queue is empty.
fl::size size() const
Get current number of messages in queue.
bool tryPop(const char **outPtr, fl::u16 *outLen)
Consumer: Try to pop one message (main thread only)
fl::u32 droppedCount() const
Get number of messages dropped due to overflow.
bool arenaHasSpace(fl::u32 aHead, fl::u32 aTail, fl::u16 len) const
void commit()
Consumer: Commit the popped message to free space (main thread only)
Descriptor mDescriptors[DescriptorCount]
Ring of message descriptors.
volatile fl::u32 mHead
Producer write position (descriptor ring)
volatile fl::u32 mArenaTail
Consumer read position (arena)
High-performance SPSC async log queue.
fl::size length() const FL_NOEXCEPT
const char * c_str() const FL_NOEXCEPT
RAII helper for critical sections (interrupt disable/enable) Automatically disables interrupts on con...
RAII critical section helper and interrupt control declarations.
void free(void *ptr)
Base definition for an LED controller.
Definition crgb.hpp:179
fl::u32 mStartIdx
Offset into arena where message starts.
fl::u16 mLength
Length of message in bytes.
fl::u16 mPadding
Reserved for alignment (unused)
Descriptor for one log message.