FastLED 3.10.8
Loading...
Searching...
No Matches
manager.cpp.hpp
Go to the documentation of this file.
1
3
6#include "fl/stl/singleton.h"
7#include "fl/log/log.h"
9#include "fl/stl/chrono.h"
10#include "fl/stl/algorithm.h"
11#include "fl/stl/move.h"
12#include "fl/system/trace.h"
13#include "fl/task/executor.h"
15#include "platforms/init_channel_driver.h"
16#include "platforms/is_platform.h"
17#include "fl/stl/noexcept.h"
18
19namespace fl {
20
24
26 auto& out = registry();
27 // Lazy initialization of platform-specific channel drivers
28 // C++11 guarantees thread-safe static initialization
29 static bool sInitialized = false; // okay static in header
30 if (!sInitialized) {
31 sInitialized = true;
32 platforms::initChannelDrivers();
33 }
34 return out;
35}
36
40 FL_DBG("ChannelManager: Initializing");
41
42 // Register as frame event listener for per-frame reset
44}
45
47 FL_DBG("ChannelManager: Destructor called");
48
49 // Remove self from EngineEvents listener list
51
52 for (auto& entry : mDrivers) {
53 if (entry.driver) {
54 entry.driver->setPollNeededCallback(IChannelDriver::PollNeededCallback());
55 }
56 }
57
58 // Shared drivers automatically cleaned up by shared_ptr destructors
59}
60
64
66 if (context == nullptr) {
67 return;
68 }
69 static_cast<ChannelManager*>(context)->notifyPollNeeded();
70}
71
73 return mPollNeededSignal.wait(timeoutMs);
74}
75
76u32 ChannelManager::pollNeededWaitSliceMs(u32 startTime, u32 timeoutMs) const FL_NO_EXCEPT {
77 constexpr u32 kPollNeededFallbackSliceMs = 1;
78 if (timeoutMs == 0) {
79 return kPollNeededFallbackSliceMs;
80 }
81 const u32 elapsed = millis() - startTime;
82 if (elapsed >= timeoutMs) {
83 return 0;
84 }
85 const u32 remaining = timeoutMs - elapsed;
86 return remaining < kPollNeededFallbackSliceMs ? remaining : kPollNeededFallbackSliceMs;
87}
88
90 int priority,
92 const fl::string* engineName,
95 FL_WARN("ChannelManager::addDriver() - Null driver provided");
96 return false;
97 }
98 if (reason == AddDriverSlowReason::EMPTY_NAME) {
99 FL_WARN("ChannelManager::addDriver() - Engine has empty name (driver->getName() returned empty string)");
100 return false;
101 }
102
103 for (const auto& entry : mDrivers) {
104 if (entry.name == *engineName) {
105 // True-duplicate fast path: same shared_ptr at same priority is
106 // a no-op (legacy clockless controllers may pre-bind the same
107 // driver singleton from many template instantiations). Skip the
108 // replace flow entirely so we don't waitForReady() or emit a
109 // spurious "Replacing" warning.
110 if (entry.driver == driver && entry.priority == priority) {
111 FL_DBG("ChannelManager::addDriver() - '" << engineName->c_str() << "' already registered at priority " << priority << " (idempotent no-op)");
112 return false;
113 }
114 FL_WARN("ChannelManager::addDriver() - Replacing existing driver '" << engineName->c_str() << "'");
115
116 FL_DBG("ChannelManager: Waiting for all drivers to become READY before replacement");
117 waitForReady();
118
119 // Re-scan after waiting: task pumping in waitForReady() can run
120 // callbacks, so do not retain an index or iterator across it.
121 for (size_t i = 0; i < mDrivers.size(); ++i) {
122 if (mDrivers[i].name != *engineName) {
123 continue;
124 }
125 FL_DBG("ChannelManager: Removing old driver '" << engineName->c_str() << "' (shared_ptr may delete)");
126 if (mDrivers[i].driver) {
127 mDrivers[i].driver->setPollNeededCallback(IChannelDriver::PollNeededCallback());
128 }
129 mDrivers.erase(mDrivers.begin() + i);
130 break;
131 }
132 return true;
133 }
134 }
135 return true;
136}
137
139 if (!driver) {
140 (void)addDriverSlow(priority, driver, nullptr, AddDriverSlowReason::NULL_DRIVER);
141 return;
142 }
143
144 // Get driver name from the driver itself
145 fl::string engineName = driver->getName();
146
147 // Reject drivers with empty names
148 if (engineName.empty()) {
149 (void)addDriverSlow(priority, driver, &engineName, AddDriverSlowReason::EMPTY_NAME);
150 return;
151 }
152
153 // A duplicate name takes the uncommon replacement/idempotency path.
154 for (const auto& entry : mDrivers) {
155 if (entry.name == engineName) {
156 if (!addDriverSlow(priority, driver, &engineName, AddDriverSlowReason::DUPLICATE_NAME)) {
157 return;
158 }
159 break;
160 }
161 }
162
163 // Respect exclusive driver mode: auto-disable if name doesn't match exclusive driver
164 bool enabled = true; // Default: enabled
165 if (!mExclusiveDriver.empty()) {
166 enabled = (engineName == mExclusiveDriver); // Only enable if matches exclusive driver
167 }
168
169 mDrivers.push_back({priority, driver, engineName, enabled});
170 driver->setPollNeededCallback(mPollNeededCallback);
171
172 // Build capability string for debug output. Gate the entire block behind
173 // FASTLED_HAS_DBG because the `capStr` exists ONLY to feed the FL_DBG
174 // line below. On release builds (FASTLED_HAS_DBG=0 — i.e. the default
175 // SKETCH_HAS_LARGE_MEMORY=0 path AND any -DFASTLED_LOG_VERBOSITY=0
176 // opt-in build via the gating in fl/log/log.h) the FL_DBG itself is a
177 // no-op, but without this guard the `fl::string capStr` allocation +
178 // two `if` branches still emitted code. See #2773 item 2.3 follow-up.
179#if FASTLED_HAS_DBG
180 IChannelDriver::Capabilities caps = driver->getCapabilities();
181 fl::string capStr;
182 if (caps.supportsClockless) {
183 capStr += "CLOCKLESS";
184 }
185 if (caps.supportsSpi) {
186 if (!capStr.empty()) capStr += "|";
187 capStr += "SPI";
188 }
189 if (capStr.empty()) {
190 capStr = "NONE";
191 }
192
193 FL_DBG("ChannelManager: Added driver '" << engineName.c_str() << "' (priority " << priority << ", caps: " << capStr.c_str() << ")");
194#endif
195
196 // Sort drivers by priority descending (higher values first) after each insertion
197 // Higher priority values = higher precedence (e.g., priority 50 selected over priority 10)
198 // Only 1-4 drivers expected — sort_small skips the quicksort_impl
199 // instantiation entirely (see #2907 for the bloat motivation).
200 fl::sort_small(mDrivers.begin(), mDrivers.end());
201}
202
204 if (!driver) {
205 FL_WARN("ChannelManager::removeDriver() - Null driver provided");
206 return false;
207 }
208
209 // Find and remove the driver from the list
210 for (size_t i = 0; i < mDrivers.size(); ++i) {
211 if (mDrivers[i].driver == driver) {
212 FL_DBG("ChannelManager: Removing driver '" << mDrivers[i].name << "'");
213
214 mDrivers[i].driver->setPollNeededCallback(IChannelDriver::PollNeededCallback());
215
216 // Remove using vector::erase (preserves sort order)
217 mDrivers.erase(mDrivers.begin() + i);
218 return true; // Engine found and removed
219 }
220 }
221
222 // Engine not found
223 FL_WARN("ChannelManager::removeDriver() - Engine " << driver.get() << " not found in registry");
224 return false;
225}
226
228 FL_DBG("ChannelManager: Waiting for all drivers to become READY before clearing");
229
230 // Wait for all drivers to become READY before clearing
231 // This prevents clearing drivers that are still transmitting
232 waitForReady();
233
234 FL_DBG("ChannelManager: Clearing " << mDrivers.size() << " drivers");
235
236 for (auto& entry : mDrivers) {
237 if (entry.driver) {
238 entry.driver->setPollNeededCallback(IChannelDriver::PollNeededCallback());
239 }
240 }
241
242 // Clear all drivers (shared_ptr handles cleanup automatically)
243 mDrivers.clear();
244
245 // Drop the exclusive-driver filter along with the registry it refers to.
246 // Leaving it set would name a driver that no longer exists, and
247 // addDriver() consults it -- so every driver registered afterwards would
248 // arrive silently disabled, with no diagnostic and nothing to clear it.
249 mExclusiveDriver.clear();
250}
251
252void ChannelManager::setDriverEnabled(const char* name, bool enabled) FL_NO_EXCEPT {
253 if (!name) {
254 FL_ERROR("ChannelManager::setDriverEnabled() - Null driver name provided");
255 return;
256 }
257
258 bool found = false;
259 for (auto& entry : mDrivers) {
260 if (entry.name == name) {
261 entry.enabled = enabled;
262 found = true;
263 FL_DBG("ChannelManager: Driver '" << name << "' " << (enabled ? "enabled" : "disabled"));
264 }
265 }
266
267 if (!found) {
268 FL_ERROR("ChannelManager::setDriverEnabled() - Driver '" << name << "' not found in registry");
269 }
270}
271
275
277 // Handle null or empty name: disable everything.
278 if (!name || !name[0]) {
279 FL_ERROR("ChannelManager::setExclusiveDriverByName() - Null or empty driver name provided");
280 mExclusiveDriver.clear();
281 for (auto& entry : mDrivers) {
282 entry.enabled = false;
283 }
284 return false;
285 }
286
287 // Store exclusive driver name for forward compatibility.
288 // When non-empty, addDriver() will auto-disable non-matching drivers.
289 mExclusiveDriver = name;
290
291 // Single-pass: enable only drivers matching the given name.
292 bool found = false;
293 for (auto& entry : mDrivers) {
294 entry.enabled = (entry.name == name);
295 found = found || entry.enabled;
296 }
297
298 if (!found) {
299 FL_ERROR("ChannelManager::setExclusiveDriverByName() - Driver '" << name << "' not found in registry");
300 }
301 return found;
302}
303
305 if (name.empty()) {
306 FL_ERROR("ChannelManager::setDriverPriority() - Empty driver name provided");
307 return false;
308 }
309
310 // Find driver and update priority
311 bool found = false;
312 for (auto& entry : mDrivers) {
313 if (entry.name == name) {
314 entry.priority = priority;
315 found = true;
316 FL_DBG("ChannelManager: Driver '" << name << "' priority changed to " << priority);
317 break;
318 }
319 }
320
321 if (!found) {
322 FL_ERROR("ChannelManager::setDriverPriority() - Driver '" << name << "' not found in registry");
323 return false;
324 }
325
326 // Re-sort drivers by priority (descending: higher values first).
327 // 1-4 drivers expected here too — sort_small avoids the quicksort body.
328 fl::sort_small(mDrivers.begin(), mDrivers.end());
329
330 FL_DBG("ChannelManager: Engine list re-sorted after priority change");
331 return true;
332}
333
335 for (const auto& entry : mDrivers) {
336 if (entry.driver.get() == &driver) {
337 return entry.enabled;
338 }
339 }
340 // Not registered by instance (e.g. replaced by a same-name driver).
341 return isDriverEnabled(driver.getName().c_str());
342}
343
344bool ChannelManager::isDriverEnabled(const char* name) const FL_NO_EXCEPT {
345 if (!name) {
346 FL_ERROR("ChannelManager::isDriverEnabled() - Null driver name provided");
347 return false;
348 }
349
350 for (const auto& entry : mDrivers) {
351 if (entry.name == name) {
352 return entry.enabled;
353 }
354 }
355
356 FL_ERROR("ChannelManager::isDriverEnabled() - Driver '" << name << "' not found in registry");
357 return false;
358}
359
361 if (name.empty()) {
363 }
364 for (const auto& entry : mDrivers) {
365 if (entry.name == name) {
366 return entry.enabled ? DriverStatus::STATUS_ENABLED
368 }
369 }
371}
372
374 return mDrivers.size();
375}
376
378 // Update cache with current driver state
379 mCachedDriverInfo.clear();
380 mCachedDriverInfo.reserve(mDrivers.size());
381
382 for (const auto& entry : mDrivers) {
383 // fl::string copy is cheap (shared pointer internally, no heap allocation)
384 mCachedDriverInfo.push_back({
385 entry.name,
386 entry.priority,
387 entry.enabled
388 });
389 }
390
391 return mCachedDriverInfo;
392}
393
395 if (name.empty()) {
397 }
398 for (const auto& entry : mDrivers) {
399 if (entry.enabled && entry.name == name) {
400 return entry.driver;
401 }
402 }
404}
405
407 if (name.empty()) {
408 FL_ERROR("ChannelManager::getDriverByName() - Empty driver name provided");
410 }
411 auto driver = findDriverByName(name);
412 if (!driver) {
413 FL_ERROR("ChannelManager::getDriverByName() - Driver '" << name.c_str() << "' not found or not enabled");
414 }
415 return driver;
416}
417
419 if (!data) {
420 FL_ERROR("ChannelManager::selectDriverForChannel() - Null channel data");
422 }
423
424 // If affinity is specified, look up by name. Misses fall through to
425 // priority dispatch below — per-frame logging is intentionally silent
426 // here because `Channel::showPixels` now emits a one-shot, actionable
427 // FL_ERROR with the enableDrivers<...>() / enableAllDrivers() hint
428 // (#2455). Use `findDriverByName` (silent) rather than `getDriverByName`
429 // (logs on miss) so the silent fall-through actually IS silent.
430 do {
431 if (affinity.empty()) {
432 break;
433 }
434 auto driver = findDriverByName(affinity);
435 if (!driver) {
436 break; // diagnostic emitted at the channel layer
437 }
438 if (!driver->canHandle(data)) {
439 FL_WARN_ONCE("ChannelManager: Affinity driver '" << affinity << "' cannot handle channel data (chipset/bus mismatch). Falling back to AUTO/priority dispatch.");
440 break;
441 }
442 return driver;
443 } while (false);
444
445
446 // No affinity: iterate drivers by priority (already sorted descending)
447 for (const auto& entry : mDrivers) {
448 if (!entry.enabled) continue;
449 if (entry.driver->canHandle(data)) {
450 return entry.driver; // Return shared_ptr
451 }
452 }
453
454 FL_ERROR("ChannelManager: No compatible driver found for channel data");
456}
457
458
459template<typename Condition>
460bool ChannelManager::waitForCondition(Condition condition, u32 timeoutMs) FL_NO_EXCEPT {
461 const u32 startTime = timeoutMs > 0 ? millis() : 0;
462
463 // Tier 1: instant non-blocking check (avoid micros() / millis() cost on
464 // the common already-ready path).
465 if (condition()) {
466 return true;
467 }
468
469 // Tier 2: bounded microsecond spin (#2818). Catches short DMA tails
470 // (APA102 small strips, WS2812B <=8 LEDs) without paying the >=1-tick
471 // floor of the cooperator yield below. Budget is runtime-tunable via
472 // FastLED.setWaitSpinBudgetUs(N); set to 0 to disable.
473 {
474 const u32 spinBudget = fl::detail::getWaitSpinBudgetUs();
475 if (spinBudget > 0) {
476 const u32 spinStart = fl::micros();
477 while ((fl::micros() - spinStart) < spinBudget) {
478 if (condition()) {
479 return true;
480 }
481 if (timeoutMs > 0 && (millis() - startTime) >= timeoutMs) {
482 FL_ERROR("ChannelManager: Timeout occurred while waiting for condition");
483 return false;
484 }
485 }
486 }
487 }
488
489 while (!condition()) {
490 // Check timeout if specified
491 if (timeoutMs > 0 && (millis() - startTime >= timeoutMs)) {
492 FL_ERROR("ChannelManager: Timeout occurred while waiting for condition");
493 return false; // Timeout occurred
494 }
495
496 const u32 sliceMs = pollNeededWaitSliceMs(startTime, timeoutMs);
497 if (sliceMs == 0) {
498 return false;
499 }
500 if (waitForPollNeededSignal(sliceMs)) {
501 continue;
502 }
503
504 // Adaptive yield (refs #2815, generalizes the #2493 ESP32-P4 carve-out):
505 //
506 // The 1-tick (>=1 ms at CONFIG_FREERTOS_HZ=1000) floor only exists to
507 // keep WiFi / lwIP / BT controller tasks alive while we are inside the
508 // channel wait loop (#2254). When no radio is actually up, that floor
509 // is pure timing drift -- visible as the regression reported in #2420
510 // and as the per-frame cost the #2493 ESP32-P4 carve-out was avoiding.
511 //
512 // NetworkDetector::isAnyNetworkActive() is the runtime version of the
513 // "is a radio up?" question. On non-ESP32 platforms and on ESP32-P4
514 // (no radio silicon) it folds to a constant `false`, so this is
515 // strictly a perf win for the common single-strip / no-WiFi case
516 // without losing the WiFi-friendly behavior when a radio is active.
518 // Radio active: keep WiFi/lwIP/BT alive with the deep yield.
520 } else {
521 // No radio: fast yield, no FreeRTOS tick floor.
523 }
524 }
525
526 return true; // Condition met
527}
528
530 // Poll all registered drivers and return aggregate state
531 // Priority order: ERROR > BUSY > DRAINING > READY
532 bool anyBusy = false;
533 bool anyDraining = false;
534 fl::string firstError;
535
536 for (auto& entry : mDrivers) {
537 IChannelDriver::DriverState result = entry.driver->poll();
539 anyBusy = true;
540 } else if (result.state == IChannelDriver::DriverState::DRAINING) {
541 anyDraining = true;
542 }
543 // Capture first error encountered
544 if (result.state == IChannelDriver::DriverState::ERROR && firstError.empty()) {
545 firstError = result.error;
546 }
547 }
548
549 // Return error if any driver reported error
550 if (!firstError.empty()) {
552 }
553
554 if (anyBusy) {
556 }
557 if (anyDraining) {
559 }
561}
562
564 bool ok = waitForCondition([this]() {
566 }, timeoutMs);
567 if (!ok) {
568 FL_ERROR("ChannelManager: Timeout occurred while waiting for READY state");
569 }
570 return ok;
571}
572
574 bool ok = waitForCondition([this]() {
575 auto state = poll();
576 bool draining_or_done = (
579 );
580 return draining_or_done;
581 }, timeoutMs);
582 if (!ok) {
583 FL_ERROR("ChannelManager: Timeout occurred while waiting for READY or DRAINING state");
584 }
585 return ok;
586}
587
588
590 waitForReady(); // Wait for all drivers to become READY before clearing previous frame state.
591}
592
594 // Call show() on all drivers to trigger transmission
595 // Channels have enqueued data directly to drivers during showPixels()
596 // Now we trigger transmission by calling show() on each driver
597 for (auto& entry : mDrivers) {
598 if (entry.enabled) {
599 entry.driver->show();
600 }
601 }
603}
604
606 // Allow all channel drivers to clean up
607 waitForReady();
608 FL_DBG("ChannelManager: reset() - all drivers ready");
609}
610
614
615} // namespace fl
TestState state
FastLED chrono implementation - duration types for time measurements.
void onEndFrame() FL_NO_EXCEPT override
Trigger transmission of batched channel data.
void setDriverEnabled(const char *name, bool enabled) FL_NO_EXCEPT
Enable or disable a driver by name at runtime.
bool waitForReadyOrDraining(u32 timeoutMs=1000) FL_NO_EXCEPT
bool setExclusiveDriverByName(const char *name) FL_NO_EXCEPT
Enable only one driver exclusively (disables all others) — by-name escape hatch.
u32 pollNeededWaitSliceMs(u32 startTime, u32 timeoutMs) const FL_NO_EXCEPT
void reset() FL_NO_EXCEPT
Reset bus manager state, clearing all enqueued and transmitting channels.
ChannelManager() FL_NO_EXCEPT
Constructor.
~ChannelManager() FL_NO_EXCEPT override
Destructor - cleanup shared drivers (automatic via shared_ptr)
void onBeginFrame() FL_NO_EXCEPT override
Poll drivers before frame starts to clear previous frame state.
static ChannelManager & registry() FL_NO_EXCEPT
Get the singleton WITHOUT registering platform default drivers.
fl::shared_ptr< IChannelDriver > findDriverByName(const fl::string &name) const FL_NO_EXCEPT
Silent counterpart to getDriverByName().
fl::size getDriverCount() const FL_NO_EXCEPT
Get count of registered drivers (including unnamed ones)
IChannelDriver::DriverState poll() FL_NO_EXCEPT
Poll all registered drivers and return aggregate state.
void clearAllDrivers() FL_NO_EXCEPT
Remove all drivers from the manager.
static ChannelManager & instance() FL_NO_EXCEPT
Get the global singleton instance.
IChannelDriver::PollNeededCallback mPollNeededCallback
Shared callback installed on drivers that can signal poll-needed events.
Definition manager.h:339
DriverStatus driverStatus(const fl::string &name) const FL_NO_EXCEPT
Look up a driver's registration status without logging on miss.
FL_NO_INLINE FL_COLD bool addDriverSlow(int priority, const fl::shared_ptr< IChannelDriver > &driver, const fl::string *engineName, AddDriverSlowReason reason) FL_NO_EXCEPT
Handle validation failures and the uncommon replacement flow outside the normal driver-registration p...
platforms::ChannelPollSignal mPollNeededSignal
Platform wait primitive owned by the manager.
Definition manager.h:342
fl::vector< DriverInfo > mCachedDriverInfo
Cached driver info for getDriverInfos() to avoid allocations.
Definition manager.h:331
DriverStatus
Registration status of a driver by name (silent lookup)
Definition manager.h:196
@ NOT_REGISTERED
No driver with that name is registered.
Definition manager.h:197
@ STATUS_ENABLED
Driver is registered and enabled.
Definition manager.h:199
@ STATUS_DISABLED
Driver is registered but disabled.
Definition manager.h:198
void setExclusiveDriver() FL_NO_EXCEPT
Register a single driver at a priority above the platform default and disable all others (compile-tim...
Definition manager.h:362
fl::shared_ptr< IChannelDriver > getDriverByName(const fl::string &name) const FL_NO_EXCEPT
Get driver by name for affinity binding.
bool waitForCondition(Condition condition, u32 timeoutMs=1000) FL_NO_EXCEPT
Wait until a condition is met, with check-pump-delay logic.
fl::shared_ptr< IChannelDriver > selectDriverForChannel(const ChannelDataPtr &data, const fl::string &affinity) FL_NO_EXCEPT
Select best driver for channel data (used by Channel::showPixels)
fl::vector< EngineEntry > mDrivers
Shared drivers sorted by priority descending (higher values first)
Definition manager.h:327
fl::string mExclusiveDriver
Exclusive driver name (empty if no exclusive mode)
Definition manager.h:336
static void notifyPollNeededThunk(void *context) FL_NO_EXCEPT
bool setDriverPriority(const fl::string &name, int priority) FL_NO_EXCEPT
Change the priority of a registered driver.
bool waitForPollNeededSignal(u32 timeoutMs) FL_NO_EXCEPT
void addDriver(int priority, fl::shared_ptr< IChannelDriver > driver) FL_NO_EXCEPT
Add a driver with priority (higher priority = preferred)
bool isDriverEnabled(const char *name) const FL_NO_EXCEPT
Check if a driver is enabled by name.
void notifyPollNeeded() FL_NO_EXCEPT
bool removeDriver(fl::shared_ptr< IChannelDriver > driver) FL_NO_EXCEPT
Remove a driver from the manager.
bool waitForReady(u32 timeoutMs=1000) FL_NO_EXCEPT
Wait for all drivers to become READY.
fl::span< const DriverInfo > getDriverInfos() const FL_NO_EXCEPT
Get full state of all registered drivers.
Unified channel manager with priority-based driver selection.
Definition manager.h:51
static void removeListener(Listener *listener) FL_NO_EXCEPT
static void addListener(Listener *listener, int priority=0) FL_NO_EXCEPT
Minimal interface for LED channel transmission drivers.
Definition driver.h:52
static T & instance() FL_NO_EXCEPT
Definition singleton.h:41
bool empty() const FL_NO_EXCEPT
const char * c_str() const FL_NO_EXCEPT
static bool isAnyNetworkActive() FL_NO_EXCEPT
#define FL_NO_INLINE
#define FL_COLD
Task executor — runs registered task runners and manages the run loop.
#define FL_WARN_ONCE(...)
Definition log.h:439
#define FL_WARN(...)
Definition log.h:437
#define FL_DBG
Definition log.h:553
#define FL_ERROR(...)
Definition log.h:373
Centralized logging categories for FastLED hardware interfaces and subsystems.
Unified manager for channel drivers with priority-based fallback.
void sort_small(Iterator first, Iterator last, Compare comp) FL_NO_EXCEPT
Definition algorithm.h:619
fl::u32 getWaitSpinBudgetUs() FL_NO_EXCEPT
Get the current tiered-wait spin budget (microseconds).
void yield_system(fl::u32 microseconds) FL_NO_EXCEPT
OS-level yield only: same behavior as run(us, ExecFlags::SYSTEM).
unsigned char u8
Definition stdint.h:131
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
ChannelManager & channelManager() FL_NO_EXCEPT
Get the global ChannelManager singleton instance.
fl::u32 millis() FL_NO_EXCEPT
Universal millisecond timer - returns milliseconds since system startup.
fl::u32 micros() FL_NO_EXCEPT
Universal microsecond timer - returns microseconds since system startup.
const char * busDriverName(Bus b, fl::u8 which=0, bool spi=false) FL_NO_EXCEPT
Concrete ChannelManager driver name for a portable bus.
Definition bus.h:170
Bus
Portable public bus selector.
Definition bus.h:27
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
Base definition for an LED controller.
Definition crgb.hpp:179
Cross-platform facade for runtime network activity detection.
bool supportsClockless
Supports clockless protocols (WS2812, SK6812, etc.)
Definition driver.h:155
bool supportsSpi
Supports SPI protocols (APA102, SK9822, etc.)
Definition driver.h:156
Driver capabilities.
Definition driver.h:154
@ READY
Hardware idle; ready to accept new transmissions.
Definition driver.h:170
@ DRAINING
All channels submitted; still transmitting.
Definition driver.h:172
@ ERROR
Driver encountered an error.
Definition driver.h:173
@ BUSY
Active: channels transmitting or queued.
Definition driver.h:171
Driver state with optional error message.
Definition driver.h:168
ISR-safe callback handle invoked when the manager should poll again.
Definition driver.h:61
Runtime-tunable microsecond spin budget for the channel-manager and driver wait loops (Phase 1 of #28...