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