FastLED 3.10.9
Loading...
Searching...
No Matches
manager.cpp.hpp
Go to the documentation of this file.
1
3
7#include "fl/stl/singleton.h"
8#include "fl/log/log.h"
10#include "fl/stl/chrono.h"
11#include "fl/stl/algorithm.h"
12#include "fl/stl/move.h"
13#include "fl/system/trace.h"
14#include "fl/task/executor.h"
16#include "platforms/init_channel_driver.h"
17#include "platforms/is_platform.h"
18#include "fl/stl/noexcept.h"
19
20namespace fl {
21
24 // The optional thunk is retained only when the registry itself is used.
25 // Local manager instances must not replace the singleton completion hook.
26 detail::setManagedCompletionWait([](u32 timeoutMs) {
27 return Singleton<ChannelManager>::instance().waitForReady(timeoutMs);
28 });
29 return manager;
30}
31
33 auto& out = registry();
34 // Lazy initialization of platform-specific channel drivers
35 // C++11 guarantees thread-safe static initialization
36 static bool sInitialized = false; // okay static in header
37 if (!sInitialized) {
38 sInitialized = true;
39 platforms::initChannelDrivers();
40 }
41 return out;
42}
43
47 FL_DBG("ChannelManager: Initializing");
48
49 // Register as frame event listener for per-frame reset
51}
52
54 FL_DBG("ChannelManager: Destructor called");
55
56 // Remove self from EngineEvents listener list
58
59 for (auto& entry : mDrivers) {
60 if (entry.driver) {
61 entry.driver->setPollNeededCallback(IChannelDriver::PollNeededCallback());
62 }
63 }
64
65 // Shared drivers automatically cleaned up by shared_ptr destructors
66}
67
71
73 if (context == nullptr) {
74 return;
75 }
76 static_cast<ChannelManager*>(context)->notifyPollNeeded();
77}
78
80 return mPollNeededSignal.wait(timeoutMs);
81}
82
83u32 ChannelManager::pollNeededWaitSliceMs(u32 startTime, u32 timeoutMs) const FL_NO_EXCEPT {
84 constexpr u32 kPollNeededFallbackSliceMs = 1;
85 if (timeoutMs == 0) {
86 return kPollNeededFallbackSliceMs;
87 }
88 const u32 elapsed = millis() - startTime;
89 if (elapsed >= timeoutMs) {
90 return 0;
91 }
92 const u32 remaining = timeoutMs - elapsed;
93 return remaining < kPollNeededFallbackSliceMs ? remaining : kPollNeededFallbackSliceMs;
94}
95
97 int priority,
99 const fl::string* engineName,
101 if (reason == AddDriverSlowReason::NULL_DRIVER) {
102 FL_WARN("ChannelManager::addDriver() - Null driver provided");
103 return false;
104 }
105 if (reason == AddDriverSlowReason::EMPTY_NAME) {
106 FL_WARN("ChannelManager::addDriver() - Engine has empty name (driver->getName() returned empty string)");
107 return false;
108 }
109
110 for (const auto& entry : mDrivers) {
111 if (entry.name == *engineName) {
112 // True-duplicate fast path: same shared_ptr at same priority is
113 // a no-op (legacy clockless controllers may pre-bind the same
114 // driver singleton from many template instantiations). Skip the
115 // replace flow entirely so we don't waitForReady() or emit a
116 // spurious "Replacing" warning.
117 if (entry.driver == driver && entry.priority == priority) {
118 FL_DBG("ChannelManager::addDriver() - '" << engineName->c_str() << "' already registered at priority " << priority << " (idempotent no-op)");
119 return false;
120 }
121 FL_WARN("ChannelManager::addDriver() - Replacing existing driver '" << engineName->c_str() << "'");
122
123 FL_DBG("ChannelManager: Waiting for all drivers to become READY before replacement");
124 waitForReady();
125
126 // Re-scan after waiting: task pumping in waitForReady() can run
127 // callbacks, so do not retain an index or iterator across it.
128 for (size_t i = 0; i < mDrivers.size(); ++i) {
129 if (mDrivers[i].name != *engineName) {
130 continue;
131 }
132 FL_DBG("ChannelManager: Removing old driver '" << engineName->c_str() << "' (shared_ptr may delete)");
133 if (mDrivers[i].driver) {
134 mDrivers[i].driver->setPollNeededCallback(IChannelDriver::PollNeededCallback());
135 }
136 mDrivers.erase(mDrivers.begin() + i);
137 break;
138 }
139 return true;
140 }
141 }
142 return true;
143}
144
146 if (!driver) {
147 (void)addDriverSlow(priority, driver, nullptr, AddDriverSlowReason::NULL_DRIVER);
148 return;
149 }
150
151 // Get driver name from the driver itself
152 fl::string engineName = driver->getName();
153
154 // Reject drivers with empty names
155 if (engineName.empty()) {
156 (void)addDriverSlow(priority, driver, &engineName, AddDriverSlowReason::EMPTY_NAME);
157 return;
158 }
159
160 // A duplicate name takes the uncommon replacement/idempotency path.
161 for (const auto& entry : mDrivers) {
162 if (entry.name == engineName) {
163 if (!addDriverSlow(priority, driver, &engineName, AddDriverSlowReason::DUPLICATE_NAME)) {
164 return;
165 }
166 break;
167 }
168 }
169
170 // Respect exclusive driver mode: auto-disable if name doesn't match exclusive driver
171 bool enabled = true; // Default: enabled
172 if (!mExclusiveDriver.empty()) {
173 enabled = (engineName == mExclusiveDriver); // Only enable if matches exclusive driver
174 }
175
176 mDrivers.push_back({priority, driver, engineName, enabled});
177 driver->setPollNeededCallback(mPollNeededCallback);
178
179 // Build capability string for debug output. Gate the entire block behind
180 // FASTLED_HAS_DBG because the `capStr` exists ONLY to feed the FL_DBG
181 // line below. On release builds (FASTLED_HAS_DBG=0 — i.e. the default
182 // SKETCH_HAS_LARGE_MEMORY=0 path AND any -DFASTLED_LOG_VERBOSITY=0
183 // opt-in build via the gating in fl/log/log.h) the FL_DBG itself is a
184 // no-op, but without this guard the `fl::string capStr` allocation +
185 // two `if` branches still emitted code. See #2773 item 2.3 follow-up.
186#if FASTLED_HAS_DBG
187 IChannelDriver::Capabilities caps = driver->getCapabilities();
188 fl::string capStr;
189 if (caps.supportsClockless) {
190 capStr += "CLOCKLESS";
191 }
192 if (caps.supportsSpi) {
193 if (!capStr.empty()) capStr += "|";
194 capStr += "SPI";
195 }
196 if (capStr.empty()) {
197 capStr = "NONE";
198 }
199
200 FL_DBG("ChannelManager: Added driver '" << engineName.c_str() << "' (priority " << priority << ", caps: " << capStr.c_str() << ")");
201#endif
202
203 // Sort drivers by priority descending (higher values first) after each insertion
204 // Higher priority values = higher precedence (e.g., priority 50 selected over priority 10)
205 // Only 1-4 drivers expected — sort_small skips the quicksort_impl
206 // instantiation entirely (see #2907 for the bloat motivation).
207 fl::sort_small(mDrivers.begin(), mDrivers.end());
208}
209
211 if (!driver) {
212 FL_WARN("ChannelManager::removeDriver() - Null driver provided");
213 return false;
214 }
215
216 // Find and remove the driver from the list
217 for (size_t i = 0; i < mDrivers.size(); ++i) {
218 if (mDrivers[i].driver == driver) {
219 FL_DBG("ChannelManager: Removing driver '" << mDrivers[i].name << "'");
220
221 mDrivers[i].driver->setPollNeededCallback(IChannelDriver::PollNeededCallback());
222
223 // Remove using vector::erase (preserves sort order)
224 mDrivers.erase(mDrivers.begin() + i);
225 return true; // Engine found and removed
226 }
227 }
228
229 // Engine not found
230 FL_WARN("ChannelManager::removeDriver() - Engine " << driver.get() << " not found in registry");
231 return false;
232}
233
235 FL_DBG("ChannelManager: Waiting for all drivers to become READY before clearing");
236
237 // Wait for all drivers to become READY before clearing
238 // This prevents clearing drivers that are still transmitting
239 waitForReady();
240
241 FL_DBG("ChannelManager: Clearing " << mDrivers.size() << " drivers");
242
243 for (auto& entry : mDrivers) {
244 if (entry.driver) {
245 entry.driver->setPollNeededCallback(IChannelDriver::PollNeededCallback());
246 }
247 }
248
249 // Clear all drivers (shared_ptr handles cleanup automatically)
250 mDrivers.clear();
251
252 // Drop the exclusive-driver filter along with the registry it refers to.
253 // Leaving it set would name a driver that no longer exists, and
254 // addDriver() consults it -- so every driver registered afterwards would
255 // arrive silently disabled, with no diagnostic and nothing to clear it.
256 mExclusiveDriver.clear();
257}
258
259void ChannelManager::setDriverEnabled(const char* name, bool enabled) FL_NO_EXCEPT {
260 if (!name) {
261 FL_ERROR("ChannelManager::setDriverEnabled() - Null driver name provided");
262 return;
263 }
264
265 bool found = false;
266 for (auto& entry : mDrivers) {
267 if (entry.name == name) {
268 entry.enabled = enabled;
269 found = true;
270 FL_DBG("ChannelManager: Driver '" << name << "' " << (enabled ? "enabled" : "disabled"));
271 }
272 }
273
274 if (!found) {
275 FL_ERROR("ChannelManager::setDriverEnabled() - Driver '" << name << "' not found in registry");
276 }
277}
278
282
284 // Handle null or empty name: disable everything.
285 if (!name || !name[0]) {
286 FL_ERROR("ChannelManager::setExclusiveDriverByName() - Null or empty driver name provided");
287 mExclusiveDriver.clear();
288 for (auto& entry : mDrivers) {
289 entry.enabled = false;
290 }
291 return false;
292 }
293
294 // Store exclusive driver name for forward compatibility.
295 // When non-empty, addDriver() will auto-disable non-matching drivers.
296 mExclusiveDriver = name;
297
298 // Single-pass: enable only drivers matching the given name.
299 bool found = false;
300 for (auto& entry : mDrivers) {
301 entry.enabled = (entry.name == name);
302 found = found || entry.enabled;
303 }
304
305 if (!found) {
306 FL_ERROR("ChannelManager::setExclusiveDriverByName() - Driver '" << name << "' not found in registry");
307 }
308 return found;
309}
310
312 if (name.empty()) {
313 FL_ERROR("ChannelManager::setDriverPriority() - Empty driver name provided");
314 return false;
315 }
316
317 // Find driver and update priority
318 bool found = false;
319 for (auto& entry : mDrivers) {
320 if (entry.name == name) {
321 entry.priority = priority;
322 found = true;
323 FL_DBG("ChannelManager: Driver '" << name << "' priority changed to " << priority);
324 break;
325 }
326 }
327
328 if (!found) {
329 FL_ERROR("ChannelManager::setDriverPriority() - Driver '" << name << "' not found in registry");
330 return false;
331 }
332
333 // Re-sort drivers by priority (descending: higher values first).
334 // 1-4 drivers expected here too — sort_small avoids the quicksort body.
335 fl::sort_small(mDrivers.begin(), mDrivers.end());
336
337 FL_DBG("ChannelManager: Engine list re-sorted after priority change");
338 return true;
339}
340
342 for (const auto& entry : mDrivers) {
343 if (entry.driver.get() == &driver) {
344 return entry.enabled;
345 }
346 }
347 // Not registered by instance (e.g. replaced by a same-name driver).
348 return isDriverEnabled(driver.getName().c_str());
349}
350
351bool ChannelManager::isDriverEnabled(const char* name) const FL_NO_EXCEPT {
352 if (!name) {
353 FL_ERROR("ChannelManager::isDriverEnabled() - Null driver name provided");
354 return false;
355 }
356
357 for (const auto& entry : mDrivers) {
358 if (entry.name == name) {
359 return entry.enabled;
360 }
361 }
362
363 FL_ERROR("ChannelManager::isDriverEnabled() - Driver '" << name << "' not found in registry");
364 return false;
365}
366
368 if (name.empty()) {
370 }
371 for (const auto& entry : mDrivers) {
372 if (entry.name == name) {
373 return entry.enabled ? DriverStatus::STATUS_ENABLED
375 }
376 }
378}
379
381 return mDrivers.size();
382}
383
385 // Update cache with current driver state
386 mCachedDriverInfo.clear();
387 mCachedDriverInfo.reserve(mDrivers.size());
388
389 for (const auto& entry : mDrivers) {
390 // fl::string copy is cheap (shared pointer internally, no heap allocation)
391 mCachedDriverInfo.push_back({
392 entry.name,
393 entry.priority,
394 entry.enabled
395 });
396 }
397
398 return mCachedDriverInfo;
399}
400
402 if (name.empty()) {
404 }
405 for (const auto& entry : mDrivers) {
406 if (entry.enabled && entry.name == name) {
407 return entry.driver;
408 }
409 }
411}
412
414 if (name.empty()) {
415 FL_ERROR("ChannelManager::getDriverByName() - Empty driver name provided");
417 }
418 auto driver = findDriverByName(name);
419 if (!driver) {
420 FL_ERROR("ChannelManager::getDriverByName() - Driver '" << name.c_str() << "' not found or not enabled");
421 }
422 return driver;
423}
424
426 if (!data) {
427 FL_ERROR("ChannelManager::selectDriverForChannel() - Null channel data");
429 }
430
431 // If affinity is specified, look up by name. Misses fall through to
432 // priority dispatch below — per-frame logging is intentionally silent
433 // here because `Channel::showPixels` now emits a one-shot, actionable
434 // FL_ERROR with the enableDrivers<...>() / enableAllDrivers() hint
435 // (#2455). Use `findDriverByName` (silent) rather than `getDriverByName`
436 // (logs on miss) so the silent fall-through actually IS silent.
437 do {
438 if (affinity.empty()) {
439 break;
440 }
441 auto driver = findDriverByName(affinity);
442 if (!driver) {
443 break; // diagnostic emitted at the channel layer
444 }
445 if (!driver->canHandle(data)) {
446 FL_WARN_ONCE("ChannelManager: Affinity driver '" << affinity << "' cannot handle channel data (chipset/bus mismatch). Falling back to AUTO/priority dispatch.");
447 break;
448 }
449 return driver;
450 } while (false);
451
452
453 // No affinity: iterate drivers by priority (already sorted descending)
454 for (const auto& entry : mDrivers) {
455 if (!entry.enabled) continue;
456 if (entry.driver->canHandle(data)) {
457 return entry.driver; // Return shared_ptr
458 }
459 }
460
461 FL_ERROR("ChannelManager: No compatible driver found for channel data");
463}
464
465
466template<typename Condition>
467bool ChannelManager::waitForCondition(Condition condition, u32 timeoutMs) FL_NO_EXCEPT {
468 const u32 startTime = timeoutMs > 0 ? millis() : 0;
469
470 // Tier 1: instant non-blocking check (avoid micros() / millis() cost on
471 // the common already-ready path).
472 if (condition()) {
473 return true;
474 }
475
476 // Tier 2: bounded microsecond spin (#2818). Catches short DMA tails
477 // (APA102 small strips, WS2812B <=8 LEDs) without paying the >=1-tick
478 // floor of the cooperator yield below. Budget is runtime-tunable via
479 // FastLED.setWaitSpinBudgetUs(N); set to 0 to disable.
480 {
481 const u32 spinBudget = fl::detail::getWaitSpinBudgetUs();
482 if (spinBudget > 0) {
483 const u32 spinStart = fl::micros();
484 while ((fl::micros() - spinStart) < spinBudget) {
485 if (condition()) {
486 return true;
487 }
488 if (timeoutMs > 0 && (millis() - startTime) >= timeoutMs) {
489 FL_ERROR("ChannelManager: Timeout occurred while waiting for condition");
490 return false;
491 }
492 }
493 }
494 }
495
496 while (!condition()) {
497 // Check timeout if specified
498 if (timeoutMs > 0 && (millis() - startTime >= timeoutMs)) {
499 FL_ERROR("ChannelManager: Timeout occurred while waiting for condition");
500 return false; // Timeout occurred
501 }
502
503 const u32 sliceMs = pollNeededWaitSliceMs(startTime, timeoutMs);
504 if (sliceMs == 0) {
505 return false;
506 }
507 if (waitForPollNeededSignal(sliceMs)) {
508 continue;
509 }
510
511 // Adaptive yield (refs #2815, generalizes the #2493 ESP32-P4 carve-out):
512 //
513 // The 1-tick (>=1 ms at CONFIG_FREERTOS_HZ=1000) floor only exists to
514 // keep WiFi / lwIP / BT controller tasks alive while we are inside the
515 // channel wait loop (#2254). When no radio is actually up, that floor
516 // is pure timing drift -- visible as the regression reported in #2420
517 // and as the per-frame cost the #2493 ESP32-P4 carve-out was avoiding.
518 //
519 // NetworkDetector::isAnyNetworkActive() is the runtime version of the
520 // "is a radio up?" question. On non-ESP32 platforms and on ESP32-P4
521 // (no radio silicon) it folds to a constant `false`, so this is
522 // strictly a perf win for the common single-strip / no-WiFi case
523 // without losing the WiFi-friendly behavior when a radio is active.
525 // Radio active: keep WiFi/lwIP/BT alive with the deep yield.
527 } else {
528 // No radio: fast yield, no FreeRTOS tick floor.
530 }
531 }
532
533 return true; // Condition met
534}
535
537 // Poll all registered drivers and return aggregate state
538 // Priority order: ERROR > BUSY > DRAINING > READY
539 bool anyBusy = false;
540 bool anyDraining = false;
541 fl::string firstError;
542
543 for (auto& entry : mDrivers) {
544 IChannelDriver::DriverState result = entry.driver->poll();
546 anyBusy = true;
547 } else if (result.state == IChannelDriver::DriverState::DRAINING) {
548 anyDraining = true;
549 }
550 // Capture first error encountered
551 if (result.state == IChannelDriver::DriverState::ERROR && firstError.empty()) {
552 firstError = result.error;
553 }
554 }
555
556 // Return error if any driver reported error
557 if (!firstError.empty()) {
559 }
560
561 if (anyBusy) {
563 }
564 if (anyDraining) {
566 }
568}
569
571 bool ok = waitForCondition([this]() {
573 }, timeoutMs);
574 if (!ok) {
575 FL_ERROR("ChannelManager: Timeout occurred while waiting for READY state");
576 }
577 return ok;
578}
579
581 bool ok = waitForCondition([this]() {
582 auto state = poll();
583 bool draining_or_done = (
586 );
587 return draining_or_done;
588 }, timeoutMs);
589 if (!ok) {
590 FL_ERROR("ChannelManager: Timeout occurred while waiting for READY or DRAINING state");
591 }
592 return ok;
593}
594
595
597 waitForReady(); // Wait for all drivers to become READY before clearing previous frame state.
598}
599
601 // Call show() on all drivers to trigger transmission
602 // Channels have enqueued data directly to drivers during showPixels()
603 // Now we trigger transmission by calling show() on each driver
604 for (auto& entry : mDrivers) {
605 if (entry.enabled) {
606 entry.driver->show();
607 }
608 }
610}
611
613 // Allow all channel drivers to clean up
614 waitForReady();
615 FL_DBG("ChannelManager: reset() - all drivers ready");
616}
617
621
622} // 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
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).
DriverCompletionWait setManagedCompletionWait(DriverCompletionWait wait) FL_NO_EXCEPT
Internal singleton hooks; return the previous hook to allow scoped tests.
unsigned char u8
Definition s16x16x4.h:132
void yield_system(fl::u32 microseconds) FL_NO_EXCEPT
OS-level yield only: same behavior as run(us, ExecFlags::SYSTEM).
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:186
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.
Cross-platform facade for runtime network activity detection.
#define FL_NO_INLINE
#define FL_COLD
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...