| 422 | |
| 423 | template<typename Condition> |
| 424 | bool ChannelManager::waitForCondition(Condition condition, u32 timeoutMs) { |
| 425 | const u32 startTime = timeoutMs > 0 ? millis() : 0; |
| 426 | |
| 427 | // Tier 1: instant non-blocking check (avoid micros() / millis() cost on |
| 428 | // the common already-ready path). |
| 429 | if (condition()) { |
| 430 | return true; |
| 431 | } |
| 432 | |
| 433 | // Tier 2: bounded microsecond spin (#2818). Catches short DMA tails |
| 434 | // (APA102 small strips, WS2812B <=8 LEDs) without paying the >=1-tick |
| 435 | // floor of the cooperator yield below. Budget is runtime-tunable via |
| 436 | // FastLED.setWaitSpinBudgetUs(N); set to 0 to disable. |
| 437 | { |
| 438 | const u32 spinBudget = fl::detail::getWaitSpinBudgetUs(); |
| 439 | if (spinBudget > 0) { |
| 440 | const u32 spinStart = fl::micros(); |
| 441 | while ((fl::micros() - spinStart) < spinBudget) { |
| 442 | if (condition()) { |
| 443 | return true; |
| 444 | } |
| 445 | if (timeoutMs > 0 && (millis() - startTime) >= timeoutMs) { |
| 446 | FL_ERROR("ChannelManager: Timeout occurred while waiting for condition"); |
| 447 | return false; |
| 448 | } |
| 449 | } |
| 450 | } |
| 451 | } |
| 452 | |
| 453 | while (!condition()) { |
| 454 | // Check timeout if specified |
| 455 | if (timeoutMs > 0 && (millis() - startTime >= timeoutMs)) { |
| 456 | FL_ERROR("ChannelManager: Timeout occurred while waiting for condition"); |
| 457 | return false; // Timeout occurred |
| 458 | } |
| 459 | |
| 460 | const u32 sliceMs = pollNeededWaitSliceMs(startTime, timeoutMs); |
| 461 | if (sliceMs == 0) { |
| 462 | return false; |
| 463 | } |
| 464 | if (waitForPollNeededSignal(sliceMs)) { |
| 465 | continue; |
| 466 | } |
| 467 | |
| 468 | // Adaptive yield (refs #2815, generalizes the #2493 ESP32-P4 carve-out): |
| 469 | // |
| 470 | // The 1-tick (>=1 ms at CONFIG_FREERTOS_HZ=1000) floor only exists to |
| 471 | // keep WiFi / lwIP / BT controller tasks alive while we are inside the |
| 472 | // channel wait loop (#2254). When no radio is actually up, that floor |
| 473 | // is pure timing drift -- visible as the regression reported in #2420 |
| 474 | // and as the per-frame cost the #2493 ESP32-P4 carve-out was avoiding. |
| 475 | // |
| 476 | // NetworkDetector::isAnyNetworkActive() is the runtime version of the |
| 477 | // "is a radio up?" question. On non-ESP32 platforms and on ESP32-P4 |
| 478 | // (no radio silicon) it folds to a constant `false`, so this is |
| 479 | // strictly a perf win for the common single-strip / no-WiFi case |
| 480 | // without losing the WiFi-friendly behavior when a radio is active. |
| 481 | if (fl::NetworkDetector::isAnyNetworkActive()) { |
nothing calls this directly
no test coverage detected