FastLED 3.10.8
Loading...
Searching...
No Matches
executor.cpp.hpp
Go to the documentation of this file.
1#include "fl/task/executor.h"
2#include "fl/stl/functional.h"
3#include "fl/stl/singleton.h"
4#include "fl/stl/scope_exit.h"
5#include "fl/stl/algorithm.h"
6#include "fl/task/task.h"
7#include "fl/stl/chrono.h"
8#include "fl/log/log.h"
9#include "fl/log/log.h"
10
11#include "fl/stl/new.h"
12#include "fl/system/yield.h"
13#include "platforms/coroutine_runtime.h"
14#include "fl/stl/noexcept.h"
15
16namespace fl {
17namespace task {
18
19namespace detail {
20
21
27} // namespace detail
28
32
34 if (r && fl::find(mRunners.begin(), mRunners.end(), r) == mRunners.end()) {
35 mRunners.push_back(r);
36 }
37}
38
40 auto it = fl::find(mRunners.begin(), mRunners.end(), r);
41 if (it != mRunners.end()) {
42 mRunners.erase(it);
43 }
44}
45
47 // Update all registered runners
48 for (auto* r : mRunners) {
49 if (r) {
50 r->update();
51 }
52 }
53}
54
56 for (const auto* r : mRunners) {
57 if (r && r->has_active_tasks()) {
58 return true;
59 }
60 }
61 return false;
62}
63
65 size_t total = 0;
66 for (const auto* r : mRunners) {
67 if (r) {
68 total += r->active_task_count();
69 }
70 }
71 return total;
72}
73
74// Public API functions
75
76void run(fl::u32 microseconds, ExecFlags flags) FL_NO_EXCEPT {
77 // Re-entrancy guard: detect if run is called from within run
79 if (running) {
80 FL_WARN_ONCE("task::run re-entrancy detected, skipping nested call");
81 return;
82 }
83 running = true;
84 auto guard = fl::make_scope_exit([&running]() { running = false; });
85
86 const bool do_tasks = flags & ExecFlags::TASKS;
87 const bool do_coroutines = flags & ExecFlags::COROUTINES;
88 const bool do_system = flags & ExecFlags::SYSTEM;
89
90 // Calculate start time with rollover protection
91 fl::u32 begin_time = fl::micros();
92
93 // Lambda to get elapsed time (rollover-safe)
94 auto elapsed = [begin_time]() {
95 return fl::micros() - begin_time;
96 };
97
98 // Lambda to get remaining time until deadline expires
99 auto remaining = [elapsed, microseconds]() -> fl::u32 {
100 fl::u32 e = elapsed();
101 if (e >= microseconds) {
102 return 0;
103 }
104 return microseconds - e;
105 };
106
107 // Lambda to check if deadline has expired
108 auto expired = [remaining]() {
109 return remaining() == 0;
110 };
111
112 do {
113 // TASKS: Scheduler (fl::task timers) + Executor (fetch, HTTP server, audio)
114 if (do_tasks) {
117 }
118
119 // SYSTEM: OS-level yield.
120 //
121 // When the caller provided a non-zero microseconds budget we treat
122 // this as an explicit spin-wait on hardware (typical DMA / driver
123 // wait loops). In that case we MUST use a deep yield (>= 1 FreeRTOS
124 // tick on ESP32) so that lower-priority network tasks — WiFi,
125 // Ethernet lwIP, etc. — actually get CPU time. Previously this
126 // path was gated on an active WiFi mode check, which missed
127 // Ethernet-only deployments and also the pre-connection window
128 // where `esp_wifi_get_mode` still returns WIFI_MODE_NULL. That
129 // regression manifested as the ESP32-S3 I2S-vs-websockets
130 // starvation reported in https://github.com/FastLED/FastLED/issues/2254
131 // (Issue 1).
132 //
133 // When microseconds == 0 the caller wants the cheapest possible
134 // yield (we are between other pumped subsystems and will loop
135 // again immediately), so we keep the lightweight fl::yield() path.
136 //
137 // The sleep is clamped to whatever is left of the caller's budget,
138 // mirroring the COROUTINES branch below. This used to be a hardcoded
139 // pumpCoroutines(1000) that ignored `microseconds` entirely, so a
140 // caller asking for run(250, SYSTEM) slept 1000 us and overshot its
141 // own deadline by 750 us on every platform where pumpCoroutines
142 // honors the requested duration (host/stub -- CoroutineRunner::run).
143 //
144 // ESP32 behavior is unchanged: pumpCoroutines() there converts us to
145 // FreeRTOS ticks and floors at one tick for ANY non-zero request
146 // (coroutine_esp32.impl.hpp, the `if (ticks == 0) ticks = 1` branch
147 // added for #2254), so 250 and 1000 both yield exactly one tick. The
148 // deep-yield guarantee #2254 depends on still holds.
149 if (do_system) {
150 if (microseconds > 0) {
151 const fl::u32 time_left = remaining();
152 if (time_left) {
153 fl::platforms::ICoroutineRuntime::instance().pumpCoroutines(
154 fl::min(1000u, time_left));
155 }
156 } else {
157 fl::yield();
158 }
159 }
160
161 // COROUTINES: Platform cooperative coroutines (pumpCoroutines)
162 if (do_coroutines) {
163 auto time_left = remaining();
164 if (time_left) {
165 fl::u32 sleep_us = fl::min(1000u, time_left);
166 fl::platforms::ICoroutineRuntime::instance().pumpCoroutines(sleep_us);
167 }
168 }
169 } while (!expired());
170}
171
172void yield_system(fl::u32 microseconds) FL_NO_EXCEPT {
173 // Mirrors the SYSTEM branch of run() without naming Scheduler/Executor.
174 const fl::u32 begin_time = fl::micros();
175 do {
176 if (microseconds > 0) {
177 const fl::u32 e = fl::micros() - begin_time;
178 if (e < microseconds) {
179 fl::platforms::ICoroutineRuntime::instance().pumpCoroutines(
180 fl::min(1000u, microseconds - e));
181 }
182 } else {
183 fl::yield();
184 }
185 } while (fl::micros() - begin_time < microseconds);
186}
187
191
195
196
197} // namespace task
198} // namespace fl
FastLED chrono implementation - duration types for time measurements.
static T & instance() FL_NO_EXCEPT
Definition singleton.h:41
static T & instance() FL_NO_EXCEPT
Definition singleton.h:133
size_t total_active_tasks() const FL_NO_EXCEPT
Get total number of active tasks across all runners.
fl::vector< Runner * > mRunners
Definition executor.h:121
void register_runner(Runner *r) FL_NO_EXCEPT
Register a runner.
bool has_active_tasks() const FL_NO_EXCEPT
Check if there are any active tasks.
void unregister_runner(Runner *r) FL_NO_EXCEPT
Unregister a runner.
static Executor & instance() FL_NO_EXCEPT
void update_all() FL_NO_EXCEPT
Update all registered runners.
Task executor (singleton) — manages registered runners.
Definition executor.h:101
Generic task runner interface.
Definition executor.h:86
static Scheduler & instance() FL_NO_EXCEPT
void update() FL_NO_EXCEPT
Task executor — runs registered task runners and manages the run loop.
#define FL_WARN_ONCE(...)
Definition log.h:439
Centralized logging categories for FastLED hardware interfaces and subsystems.
Iterator find(Iterator first, Iterator last, const T &value) FL_NO_EXCEPT
Definition algorithm.h:215
int & await_depth_tls() FL_NO_EXCEPT
Get reference to thread-local await recursion depth.
void yield_system(fl::u32 microseconds) FL_NO_EXCEPT
OS-level yield only: same behavior as run(us, ExecFlags::SYSTEM).
size_t active_tasks() FL_NO_EXCEPT
Get the number of active tasks across all systems.
void run(fl::u32 microseconds, ExecFlags flags) FL_NO_EXCEPT
Run selected task subsystems.
bool has_tasks() FL_NO_EXCEPT
Check if any systems have active tasks.
ExecFlags
Flags controlling which subsystems run() pumps.
Definition executor.h:64
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
FL_DISABLE_WARNING_PUSH U constexpr common_type_t< T, U > min(T a, U b) FL_NO_EXCEPT
Definition math.h:71
fl::u32 micros() FL_NO_EXCEPT
Universal microsecond timer - returns microseconds since system startup.
scope_exit< decay_t< EF > > make_scope_exit(EF &&f)
Factory for C++11/14 (no CTAD).
Definition scope_exit.h:48
void yield() FL_NO_EXCEPT
Yield to the platform scheduler.
Definition yield.cpp.hpp:32
Base definition for an LED controller.
Definition crgb.hpp:179
RAII scope guard — executes a callable on scope exit.
Platform-aware cooperative yield for FastLED.