FastLED 3.10.6
Loading...
Searching...
No Matches

◆ FL_DISABLE_WARNING()

FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING ( float- equal)

Definition at line 36 of file function.h.

38 {
39
40//----------------------------------------------------------------------------
41// is_function_pointer trait - detects function pointers like R(*)(Args...)
42//----------------------------------------------------------------------------
43template <typename T> struct is_function_pointer {
44 static constexpr bool value = false;
45};
46
47template <typename R, typename... Args>
48struct is_function_pointer<R(*)(Args...)> {
49 static constexpr bool value = true;
50};
51
52//----------------------------------------------------------------------------
53// function<R(Args...)>: type-erasing "std::function" replacement
54//
55// Design (FastLED #3235 Tier 1 item B, "Option B"):
56// Single small-buffer storage + two function pointers (invoker + manager).
57// The previous 5-alternative `fl::variant<>` based design emitted ~25 thunks
58// per signature; this collapses that to a single invoker pointer set at
59// construction and a single manager pointer that multiplexes copy/move/
60// destroy through an Op enum. Estimated cumulative flash savings: 25-35 KB
61// on Large-memory builds (audited across ~25-30 distinct signatures in
62// src/ + examples/).
63//
64// Supports free functions, lambdas/functors, and member functions (const &
65// non-const) -- the public API is unchanged from the legacy variant-based
66// implementation. Member-function support is provided via a thin wrapper
67// stored in the SBO; the legacy MemberCallableBase / NonConstMemberCallable /
68// ConstMemberCallable class hierarchy goes away.
69//----------------------------------------------------------------------------
70template <typename> class function;
71
72template <typename R, typename... Args>
73class FL_ALIGN function<R(Args...)> {
74private:
75 static constexpr fl::size kSboSize = FASTLED_INLINE_LAMBDA_SIZE;
76
77 // Multiplexed callable operations -- one entry per phase of the storage's
78 // lifecycle. Routed through a single Manager fn ptr to avoid having to
79 // emit separate copy/move/destroy thunks per signature.
80 enum class Op : fl::u8 { Destroy, Copy, Move };
81
82 // Invoker: knows how to call the stored callable with (Args...).
83 using Invoker = R (*)(const void* src, Args...);
84 // Manager: multiplexes copy/move/destroy. The semantics by op:
85 // Op::Destroy: invoke ~F() on the object at `dst` (storage bytes).
86 // `src` ignored.
87 // Op::Copy: copy-construct *this from *other.
88 // `dst` = this->mBytes, `src` = other.mBytes.
89 // Op::Move: move-construct *this from *other (and leave *other in
90 // a valid-but-unspecified state; reset() handles teardown).
91 // `dst` = this->mBytes, `src` = other.mBytes.
92 using Manager = void (*)(void* dst, void* src, Op op);
93
94 // SBO + bookkeeping
95 FL_ALIGN_MAX char mBytes[kSboSize];
96 Invoker mInvoker;
97 Manager mManager;
98 bool mHasValue;
99
100 // ---- Helpers: default-construct R for the "empty function called" path ---
101 // void R needs special handling because `return R{};` doesn't compile when
102 // R is void. Tag-dispatch on is_void<R>.
103 template <typename R2>
104 static typename enable_if<!is_void<R2>::value, R2>::type
105 null_return_impl() FL_NO_EXCEPT { return R2{}; }
106
107 template <typename R2>
108 static typename enable_if<is_void<R2>::value, R2>::type
109 null_return_impl() FL_NO_EXCEPT { return; }
110
111 static R null_invoker(const void* /*src*/, Args... /*args*/) FL_NO_EXCEPT {
112 return null_return_impl<R>();
113 }
114 static void null_manager(void* /*dst*/, void* /*src*/, Op /*op*/) FL_NO_EXCEPT {}
115
116 // ---- Per-F invoker --------------------------------------------------------
117 // Copies F out of storage so that mutable lambdas remain callable through a
118 // const operator(). The previous variant-based implementation used the same
119 // pattern.
120 template <typename Func>
121 static R functor_invoker(const void* src, Args... args) FL_NO_EXCEPT {
122 FL_ALIGN_AS(Func) char tmp[sizeof(Func)];
123 fl::memcpy(tmp, src, sizeof(Func));
124 Func* f = static_cast<Func*>(static_cast<void*>(tmp));
125 return (*f)(args...);
126 }
127
128 // ---- Trivial manager: F is trivially copyable; memcpy semantics. ----------
129 // Shared across all trivially-copyable types of the same size, so the
130 // linker collapses many F's into one body (per-size, not per-F).
131 template <fl::size N>
132 static void trivial_manager(void* dst, void* src, Op op) FL_NO_EXCEPT {
133 switch (op) {
134 case Op::Destroy:
135 // Trivially destructible: nothing to do.
136 break;
137 case Op::Copy:
138 case Op::Move:
139 fl::memcpy(dst, src, N);
140 break;
141 }
142 }
143
144 // ---- Non-trivial manager: real placement-new + destructor. ----------------
145 template <typename Func>
146 static void non_trivial_manager(void* dst, void* src, Op op) FL_NO_EXCEPT {
147 switch (op) {
148 case Op::Destroy: {
149 Func* f = static_cast<Func*>(dst);
150 f->~Func();
151 break;
152 }
153 case Op::Copy: {
154 const Func* sf = static_cast<const Func*>(static_cast<const void*>(src));
155 new (dst) Func(*sf);
156 break;
157 }
158 case Op::Move: {
159 Func* sf = static_cast<Func*>(src);
160 new (dst) Func(fl::move(*sf));
161 break;
162 }
163 }
164 }
165
166 // ---- Heap fallback: shared_ptr-wrapping holder for over-SBO callables. ---
167 // When the user passes a callable bigger than kSboSize, we wrap it in a
168 // `HeapHolder<Func>` and store *that* in the SBO. The holder is small
169 // (sizeof(shared_ptr<Func>) ~= 8-16 B) and its operator() dispatches through
170 // the shared_ptr to call the heap-allocated F. Copy/move/destroy of
171 // HeapHolder are handled by shared_ptr's own refcount machinery, so the
172 // non_trivial_manager template handles it cleanly.
173 template <typename Func>
174 struct HeapHolder {
175 fl::shared_ptr<Func> ptr;
176 R operator()(Args... args) const FL_NO_EXCEPT {
177 return (*ptr)(args...);
178 }
179 };
180
181 // ---- init_with: place F into storage, wire up invoker/manager ------------
182 // Tag-dispatches between in-SBO storage (when sizeof(F) fits) and the
183 // HeapHolder fallback for larger callables. Both paths go through the same
184 // functor_invoker / manager dispatch.
185 template <typename Func>
186 void init_with(Func&& f) FL_NO_EXCEPT {
187 using FBare = typename remove_reference<Func>::type;
188 FL_STATIC_ASSERT(alignof(FBare) <= alignof(max_align_t),
189 "Callable requires stricter alignment than SBO provides.");
190 init_with_impl(fl::forward<Func>(f),
191 integral_constant<bool, (sizeof(FBare) <= kSboSize)>{});
192 }
193
194 template <typename Func>
195 void init_with_impl(Func&& f, true_type /* fits in SBO */) FL_NO_EXCEPT {
196 using FBare = typename remove_reference<Func>::type;
197 new (mBytes) FBare(fl::forward<Func>(f));
198 mInvoker = &functor_invoker<FBare>;
199 mManager = is_trivially_copyable<FBare>::value
200 ? &trivial_manager<sizeof(FBare)>
201 : &non_trivial_manager<FBare>;
202 mHasValue = true;
203 }
204
205 template <typename Func>
206 void init_with_impl(Func&& f, false_type /* over-SBO; use HeapHolder */) FL_NO_EXCEPT {
207 using FBare = typename remove_reference<Func>::type;
208#ifdef FL_FUNCTION_NO_HEAP_FALLBACK
209 // Opt-out for memory-constrained users (FastLED #3237). The default
210 // heap-fallback path is suppressed and over-SBO callables fail at
211 // compile time. Suggested fixes are baked into the message.
212 (void)f;
213 FL_STATIC_ASSERT(sizeof(FBare) <= kSboSize,
214 "fl::function: capture exceeds FASTLED_INLINE_LAMBDA_SIZE "
215 "and FL_FUNCTION_NO_HEAP_FALLBACK is defined. "
216 "Either reduce the captured state, or bump "
217 "FASTLED_INLINE_LAMBDA_SIZE for this build, or "
218 "undef FL_FUNCTION_NO_HEAP_FALLBACK to re-enable "
219 "the shared_ptr-backed heap fallback.");
220#else
221 using Holder = HeapHolder<FBare>;
222 FL_STATIC_ASSERT(sizeof(Holder) <= kSboSize,
223 "shared_ptr too large for SBO; bump FASTLED_INLINE_LAMBDA_SIZE.");
224 new (mBytes) Holder{fl::make_shared<FBare>(fl::forward<Func>(f))};
225 mInvoker = &functor_invoker<Holder>;
226 // shared_ptr is not trivially copyable (its copy ctor touches the
227 // refcount), so always use the non_trivial_manager path here.
228 mManager = &non_trivial_manager<Holder>;
229 mHasValue = true;
230#endif
231 }
232
233 // Reset to empty state; calls destructor on the stored callable if any.
234 void reset() FL_NO_EXCEPT {
235 if (mHasValue) {
236 mManager(mBytes, nullptr, Op::Destroy);
237 mHasValue = false;
238 }
239 mInvoker = &null_invoker;
240 mManager = &null_manager;
241 }
242
243 // ---- Member-function wrappers (stored in the SBO like any other functor) -
244 // Each member-fn constructor packages (obj, mf) into a wrapper struct that
245 // the SBO path treats as a regular callable. The struct is at class scope
246 // so it has external linkage and template instantiation rules are clean.
247 template <typename C>
248 struct NonConstMemberWrapper {
249 C* obj;
250 R (C::*mf)(Args...);
251 R operator()(Args... args) const FL_NO_EXCEPT {
252 return (obj->*mf)(args...);
253 }
254 };
255
256 template <typename C>
257 struct ConstMemberWrapper {
258 const C* obj;
259 R (C::*mf)(Args...) const;
260 R operator()(Args... args) const FL_NO_EXCEPT {
261 return (obj->*mf)(args...);
262 }
263 };
264
265public:
266 function() FL_NO_EXCEPT
267 : mInvoker(&null_invoker), mManager(&null_manager), mHasValue(false) {
268 // Initialize storage to zero so a copy of an empty function doesn't
269 // memcpy uninitialized memory. (Matches the legacy behavior.)
270 fl::memset(mBytes, 0, kSboSize);
271 }
272
273 // Copy constructor
274 function(const function& other) FL_NO_EXCEPT
275 : mInvoker(other.mInvoker), mManager(other.mManager), mHasValue(other.mHasValue) {
276 fl::memset(mBytes, 0, kSboSize);
277 if (mHasValue) {
278 // other's bytes are logically const here, but the Manager signature
279 // takes void* for the unified copy/move/destroy contract. Casting
280 // away const is safe because Op::Copy reads only from src.
281 mManager(mBytes, const_cast<char*>(other.mBytes), Op::Copy);
282 }
283 }
284
285 // Move constructor
286 function(function&& other) FL_NO_EXCEPT
287 : mInvoker(other.mInvoker), mManager(other.mManager), mHasValue(other.mHasValue) {
288 fl::memset(mBytes, 0, kSboSize);
289 if (mHasValue) {
290 mManager(mBytes, other.mBytes, Op::Move);
291 // Destroy the moved-from object's storage and leave *other empty.
292 other.reset();
293 }
294 }
295
296 // Copy assignment
297 function& operator=(const function& other) FL_NO_EXCEPT {
298 if (this == &other) return *this;
299 reset();
300 mInvoker = other.mInvoker;
301 mManager = other.mManager;
302 mHasValue = other.mHasValue;
303 if (mHasValue) {
304 mManager(mBytes, const_cast<char*>(other.mBytes), Op::Copy);
305 }
306 return *this;
307 }
308
309 // Move assignment
310 function& operator=(function&& other) FL_NO_EXCEPT {
311 if (this == &other) return *this;
312 reset();
313 mInvoker = other.mInvoker;
314 mManager = other.mManager;
315 mHasValue = other.mHasValue;
316 if (mHasValue) {
317 mManager(mBytes, other.mBytes, Op::Move);
318 other.reset();
319 }
320 return *this;
321 }
322
323 ~function() FL_NO_EXCEPT {
324 reset();
325 }
326
327 // 1) Free function pointer
328 function(R (*fp)(Args...)) FL_NO_EXCEPT
329 : mInvoker(&null_invoker), mManager(&null_manager), mHasValue(false) {
330 fl::memset(mBytes, 0, kSboSize);
331 if (fp) {
332 init_with(fp);
333 }
334 }
335
336 // 2) Lambda / functor (SFINAE excludes fn ptrs, member fn ptrs, and self
337 // to avoid hijacking copy/move construction)
338 template <typename Func,
339 typename = enable_if_t<!is_member_function_pointer<Func>::value &&
340 !is_function_pointer<Func>::value &&
341 !is_same<typename remove_reference<Func>::type, function>::value>>
342 function(Func f) FL_NO_EXCEPT
343 : mInvoker(&null_invoker), mManager(&null_manager), mHasValue(false) {
344 fl::memset(mBytes, 0, kSboSize);
345 init_with(fl::move(f));
346 }
347
348 // 3) Non-const member function
349 template <typename C>
350 function(R (C::*mf)(Args...), C* obj) FL_NO_EXCEPT
351 : mInvoker(&null_invoker), mManager(&null_manager), mHasValue(false) {
352 fl::memset(mBytes, 0, kSboSize);
353 init_with(NonConstMemberWrapper<C>{obj, mf});
354 }
355
356 // 4) Const member function
357 template <typename C>
358 function(R (C::*mf)(Args...) const, const C* obj) FL_NO_EXCEPT
359 : mInvoker(&null_invoker), mManager(&null_manager), mHasValue(false) {
360 fl::memset(mBytes, 0, kSboSize);
361 init_with(ConstMemberWrapper<C>{obj, mf});
362 }
363
364 R operator()(Args... args) const FL_NO_EXCEPT {
365 return mInvoker(mBytes, args...);
366 }
367
368 explicit operator bool() const FL_NO_EXCEPT { return mHasValue; }
369
370 void clear() FL_NO_EXCEPT { reset(); }
371
372 bool operator==(const function& o) const FL_NO_EXCEPT {
373 // Mirrors legacy semantics: just empty / non-empty equality.
374 return mHasValue == o.mHasValue;
375 }
376
377 bool operator!=(const function& o) const FL_NO_EXCEPT { return !(*this == o); }
378};
379
380//----------------------------------------------------------------------------
381// function_list: Container for managing multiple callbacks with add/remove
382//----------------------------------------------------------------------------
383
384// Primary template declaration with static assertion for invalid usage
385// Only specializations for function signatures (e.g., function_list<void(Args...)>) are valid
386template <typename T>
387class function_list {
389 "function_list requires a void returning function signature.");
390};
391
392// Partial specialization for function signature syntax: function_list<void(Args...)>
393// Supports: function_list<void()>, function_list<void(float)>, function_list<void(u8, float, float)>, etc.
394template <typename... Args>
395class function_list<void(Args...)> {
396 private:
397 using FunctionType = function<void(Args...)>;
398
399 struct FunctionEntry {
400 int id;
401 int priority;
402 FunctionType fn;
403
404 FunctionEntry() FL_NO_EXCEPT : id(0), priority(0), fn() {}
405 FunctionEntry(int idParam, int priorityParam, FunctionType fnParam) FL_NO_EXCEPT
406 : id(idParam), priority(priorityParam), fn(fnParam) {}
407 };
408
409 using FunctionVector = fl::vector<FunctionEntry>;
410
411 FunctionVector mFunctions;
412 int mIdCounter = 0;
413
414 bool mNeedsCompact = false; // True when functions have been cleared during invocation
415
416 public:
417 function_list() FL_NO_EXCEPT : mFunctions(), mIdCounter(0), mNeedsCompact(false) {}
418 function_list(const function_list& other) FL_NO_EXCEPT
419 : mFunctions(other.mFunctions), mIdCounter(other.mIdCounter), mNeedsCompact(other.mNeedsCompact) {}
420 function_list(function_list&& other) FL_NO_EXCEPT
421 : mFunctions(fl::move(other.mFunctions)), mIdCounter(other.mIdCounter), mNeedsCompact(other.mNeedsCompact) {}
422 function_list& operator=(const function_list& other) FL_NO_EXCEPT {
423 if (this != &other) {
424 mFunctions = other.mFunctions;
425 mIdCounter = other.mIdCounter;
426 mNeedsCompact = other.mNeedsCompact;
427 }
428 return *this;
429 }
430 function_list& operator=(function_list&& other) FL_NO_EXCEPT {
431 if (this != &other) {
432 mFunctions = fl::move(other.mFunctions);
433 mIdCounter = other.mIdCounter;
434 mNeedsCompact = other.mNeedsCompact;
435 }
436 return *this;
437 }
438 ~function_list() = default;
439
440 int add(function<void(Args...)> fn, int priority = 0) FL_NO_EXCEPT {
441 int id = mIdCounter++;
442 mFunctions.push_back(FunctionEntry(id, priority, fn));
443 return id;
444 }
445
446 void remove(int id) FL_NO_EXCEPT {
447 // During invocation: clear the function for deferred removal
448 for (size_t i = 0; i < mFunctions.size(); ++i) {
449 if (mFunctions[i].id == id) {
450 mFunctions[i].fn.clear();
451 mNeedsCompact = true;
452 }
453 }
454 }
455
456 void clear() FL_NO_EXCEPT {
457 mFunctions.clear();
458 }
459
460 // Compact the storage by removing invalid (cleared) callbacks
461 // Used internally after invocation if removals occurred
462 void compact() FL_NO_EXCEPT {
463 if (!mNeedsCompact) return;
464 size_t write_pos = 0;
465 for (size_t read_pos = 0; read_pos < mFunctions.size(); ++read_pos) {
466 if (mFunctions[read_pos].fn) { // Check if function is still valid
467 if (write_pos != read_pos) {
468 mFunctions[write_pos] = mFunctions[read_pos];
469 }
470 write_pos++;
471 }
472 }
473 mFunctions.resize(write_pos);
474 mNeedsCompact = false;
475 }
476
477 // Size information - counts only valid (non-cleared) functions
478 fl::size size() const FL_NO_EXCEPT {
479 fl::size count = 0;
480 for (const auto& entry : mFunctions) {
481 if (entry.fn) {
482 ++count;
483 }
484 }
485 return count;
486 }
487 bool empty() const FL_NO_EXCEPT { return size() == 0; }
488
489 // Boolean conversion for if (callback) checks
490 explicit operator bool() const FL_NO_EXCEPT { return !empty(); }
491
492 void invoke(Args... args) FL_NO_EXCEPT {
493 if (mFunctions.empty()) return;
494 // Compact the storage by removing invalid (cleared) callbacks
495 compact();
496 // Save size at start - newly added callbacks won't execute until next call
497 const size_t invoke_size = mFunctions.size();
498 // Early return if no callbacks
499 if (invoke_size == 0) {
500 return;
501 }
502
503 // Collect unique priorities into a small vector (usually very few unique values)
505 for (size_t i = 0; i < invoke_size; ++i) {
506 if (!mFunctions[i].fn) continue; // Skip already-cleared functions
507
508 int p = mFunctions[i].priority;
509 // Only add if not already present
510 if (priorities.find(p) == priorities.end()) {
511 priorities.push_back(p);
512 }
513 }
514
515 // Sort priorities (higher priority first) - cheap since there are usually very few unique priorities
516 fl::sort(priorities.begin(), priorities.end(), [](int a, int b) FL_NO_EXCEPT { return a > b; });
517
518 // Iterate through priorities (highest first), then through functions matching each priority
519 for (size_t p_idx = 0; p_idx < priorities.size(); ++p_idx) {
520 int current_priority = priorities[p_idx];
521 for (size_t i = 0; i < invoke_size; ++i) {
522 if (mFunctions.empty()) {
523 return; // Clear happened.
524 }
525 if (mFunctions[i].fn && mFunctions[i].priority == current_priority) {
526 mFunctions[i].fn(args...);
527 }
528 }
529 }
530 compact();
531 }
532
533 // Call operator - syntactic sugar for invoke()
534 void operator()(Args... args) FL_NO_EXCEPT {
535 invoke(args...);
536 }
537};
538
539// Partial specialization for non-void return types: function_list<R(Args...)> where R != void
540// Triggers a compile-time error when attempting to use non-void return types
541template <typename R, typename... Args>
542class function_list<R(Args...)> {
544 "function_list only supports void return type. "
545 "Use function_list<void(Args...)> instead of function_list<ReturnType(Args...)>.");
546};
547
548} // namespace fl
fl::size size() const FL_NO_EXCEPT
void push_back(const T &value) FL_NO_EXCEPT
Definition vector.h:624
iterator find(const T &value) FL_NO_EXCEPT
Definition vector.h:682
iterator end() FL_NO_EXCEPT
Definition vector.h:661
iterator begin() FL_NO_EXCEPT
Definition vector.h:655
#define FASTLED_INLINE_LAMBDA_SIZE
Definition function.h:19
unsigned char u8
Definition stdint.h:131
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition move.h:28
void clear(CRGB(&arr)[N])
Definition clear.h:12
void * memcpy(void *dest, const void *src, size_t n) FL_NO_EXCEPT
Iterator remove(Iterator first, Iterator last, const T &value) FL_NO_EXCEPT
Definition algorithm.h:265
FASTLED_FORCE_INLINE bool operator==(const CRGB &lhs, const CRGB &rhs) FL_NO_EXCEPT
Check if two CRGB objects have the same color data.
Definition crgb.h:740
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
Definition shared_ptr.h:414
void * memset(void *s, int c, size_t n) FL_NO_EXCEPT
constexpr T && forward(typename remove_reference< T >::type &t) FL_NO_EXCEPT
float add(float &a, float &b)
auto invoke(F &&f, T1 &&t1, Args &&... args) FL_NO_EXCEPT -> enable_if_t< is_member_function_pointer< typename remove_reference< F >::type >::value &&!detail::use_pointer_syntax< T1 >::value, decltype((fl::forward< T1 >(t1).*f)(fl::forward< Args >(args)...))>
Definition functional.h:43
VectorN< T, INLINED_SIZE > vector_inlined
Definition vector.h:1137
void sort(Iterator first, Iterator last, Compare comp) FL_NO_EXCEPT
Definition algorithm.h:564
FASTLED_FORCE_INLINE bool operator!=(const CRGB &lhs, const CRGB &rhs) FL_NO_EXCEPT
Check if two CRGB objects do not have the same color data.
Definition crgb.h:746
corkscrew_args args
Definition old.h:149
#define FL_ALIGN_AS(T)
#define FL_ALIGN_MAX
#define FL_NO_EXCEPT
#define FL_ALIGN
#define FL_STATIC_ASSERT(...)

References args, fl::vector< T >::begin(), fl::vector< T >::end(), FASTLED_INLINE_LAMBDA_SIZE, fl::vector< T >::find(), FL_ALIGN, FL_ALIGN_AS, FL_ALIGN_MAX, FL_NO_EXCEPT, FL_STATIC_ASSERT, fl::forward(), fl::make_shared(), fl::memcpy(), fl::memset(), fl::move(), fl::vector< T >::push_back(), fl::vector_basic::size(), fl::sort(), fl::type_rank< T >::value, and fl::is_same< T, U >::value.

+ Here is the call graph for this function: