FastLED 3.9.15
Loading...
Searching...
No Matches

◆ FL_DISABLE_WARNING()

FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING ( float- equal)

Definition at line 43 of file function.h.

45 {
46
47//----------------------------------------------------------------------------
48// is_function_pointer trait - detects function pointers like R(*)(Args...)
49//----------------------------------------------------------------------------
50template <typename T> struct is_function_pointer {
51 static constexpr bool value = false;
52};
53
54template <typename R, typename... Args>
55struct is_function_pointer<R(*)(Args...)> {
56 static constexpr bool value = true;
57};
58
59//----------------------------------------------------------------------------
60// More or less a drop in replacement for std::function
61// function<R(Args...)>: type‐erasing "std::function" replacement
62// Supports free functions, lambdas/functors, member functions (const &
63// non‑const)
64//
65// NEW: Uses inline storage for member functions, free functions, and small
66// lambdas/functors. Only large lambdas/functors use heap allocation.
67//----------------------------------------------------------------------------
68template <typename> class function;
69
70template <typename R, typename... Args>
71class FL_ALIGN function<R(Args...)> {
72private:
73#if FL_FUNCTION_FULL_VARIANT
74 struct CallableBase {
75 virtual R invoke(Args... args) FL_NOEXCEPT = 0;
76 virtual ~CallableBase() FL_NOEXCEPT = default;
77 };
78
79 template <typename F>
80 struct Callable : CallableBase {
81 F f;
82 Callable(F fn) FL_NOEXCEPT : f(fn) {}
83 R invoke(Args... args) FL_NOEXCEPT override { return f(args...); }
84 };
85#endif // FL_FUNCTION_FULL_VARIANT
86
87 // Type-erased free function callable - stored inline!
88 struct FreeFunctionCallable {
89 R (*func_ptr)(Args...);
90
91 FreeFunctionCallable(R (*fp)(Args...)) FL_NOEXCEPT : func_ptr(fp) {}
92
93 R invoke(Args... args) const FL_NOEXCEPT {
94 return func_ptr(args...);
95 }
96 };
97
98 // Type-erased small lambda/functor callable - stored inline!
99 // Size limit for inline storage - configurable via preprocessor define
100
101 static constexpr fl::size kInlineLambdaSize = FASTLED_INLINE_LAMBDA_SIZE;
102
103 struct InlinedLambda {
104 // Storage for the lambda/functor object
105 // Use aligned storage to ensure proper alignment for any type
106 FL_ALIGN_MAX char bytes[kInlineLambdaSize];
107
108 // Type-erased invoker and destructor function pointers
109 R (*invoker)(const InlinedLambda& storage, Args... args);
110 void (*destructor)(InlinedLambda& storage);
111
112 template <typename Function>
113 InlinedLambda(Function f) FL_NOEXCEPT {
114 FL_STATIC_ASSERT(sizeof(Function) <= kInlineLambdaSize,
115 "Lambda/functor too large for inline storage");
116 FL_STATIC_ASSERT(alignof(Function) <= alignof(max_align_t),
117 "Lambda/functor requires stricter alignment than storage provides");
118
119 // Initialize the entire storage to zero to avoid copying uninitialized memory
120 fl::memset(bytes, 0, kInlineLambdaSize);
121
122 // Construct the lambda/functor in-place
123 new (bytes) Function(fl::move(f));
124
125 // Set up type-erased function pointers
126 invoker = &invoke_lambda<Function>;
127 destructor = &destroy_lambda<Function>;
128 }
129
130 // Copy constructor
131 InlinedLambda(const InlinedLambda& other) FL_NOEXCEPT
132 : invoker(other.invoker), destructor(other.destructor) {
133 // This is tricky - we need to copy the stored object
134 // For now, we'll use memcopy (works for trivially copyable types)
135 fl::memcpy(bytes, other.bytes, kInlineLambdaSize);
136 }
137
138 // Move constructor
139 InlinedLambda(InlinedLambda&& other) FL_NOEXCEPT
140 : invoker(other.invoker), destructor(other.destructor) {
141 fl::memcpy(bytes, other.bytes, kInlineLambdaSize);
142 // Reset the other object to prevent double destruction
143 other.destructor = nullptr;
144 }
145
146 ~InlinedLambda() FL_NOEXCEPT {
147 if (destructor) {
148 destructor(*this);
149 }
150 }
151
152 template <typename FUNCTOR>
153 static R invoke_lambda(const InlinedLambda& storage, Args... args) FL_NOEXCEPT {
154 // Use placement new to safely access the stored lambda
155 FL_ALIGN_AS(FUNCTOR) char temp_storage[sizeof(FUNCTOR)];
156 // Copy the lambda from storage
157 fl::memcpy(temp_storage, storage.bytes, sizeof(FUNCTOR));
158 // Get a properly typed pointer to the copied lambda (non-const for mutable lambdas)
159 FUNCTOR* f = static_cast<FUNCTOR*>(static_cast<void*>(temp_storage));
160 // Invoke the lambda
161 return (*f)(args...);
162 }
163
164 template <typename FUNCTOR>
165 static void destroy_lambda(InlinedLambda& storage) FL_NOEXCEPT {
166 // For destruction, we need to call the destructor on the actual object
167 // that was constructed with placement new in storage.bytes
168 // We use the standard library approach: create a properly typed pointer
169 // using placement new, then call the destructor through that pointer
170
171 // This is the standard-compliant way to get a properly typed pointer
172 // to an object that was constructed with placement new
173 FUNCTOR* obj_ptr = static_cast<FUNCTOR*>(static_cast<void*>(storage.bytes));
174 obj_ptr->~FUNCTOR();
175 }
176
177 R invoke(Args... args) const FL_NOEXCEPT {
178 return invoker(*this, args...);
179 }
180 };
181
182#if FL_FUNCTION_FULL_VARIANT
183 // Type-erased member function callable base
184 struct MemberCallableBase {
185 virtual R invoke(Args... args) const FL_NOEXCEPT = 0;
186 virtual ~MemberCallableBase() FL_NOEXCEPT = default;
187 };
188
189 // Type-erased non-const member function callable
190 struct NonConstMemberCallable : MemberCallableBase {
191 void* obj;
192 // Union to store member function pointer as raw bytes
193 union MemberFuncStorage {
194 char bytes[sizeof(R (NonConstMemberCallable::*)(Args...))];
195 // Ensure proper alignment
196 void* alignment_dummy;
197 } member_func_storage;
198
199 // Type-erased invoker function - set at construction time
200 R (*invoker)(void* obj, const MemberFuncStorage& mfp, Args... args);
201
202 template <typename C>
203 NonConstMemberCallable(C* o, R (C::*mf)(Args...)) FL_NOEXCEPT : obj(o) {
204 // Store the member function pointer as raw bytes
205 FL_STATIC_ASSERT(sizeof(mf) <= sizeof(member_func_storage),
206 "Member function pointer too large");
207 fl::memcpy(member_func_storage.bytes, &mf, sizeof(mf));
208 // Set the invoker to a function that knows how to cast back and call
209 invoker = &invoke_nonconst_member<C>;
210 }
211
212 template <typename C>
213 static R invoke_nonconst_member(void* obj, const MemberFuncStorage& mfp, Args... args) FL_NOEXCEPT {
214 C* typed_obj = static_cast<C*>(obj);
215 R (C::*typed_mf)(Args...);
216 fl::memcpy(&typed_mf, mfp.bytes, sizeof(typed_mf));
217 return (typed_obj->*typed_mf)(args...);
218 }
219
220 R invoke(Args... args) const FL_NOEXCEPT override {
221 return invoker(obj, member_func_storage, args...);
222 }
223 };
224
225 // Type-erased const member function callable
226 struct ConstMemberCallable : MemberCallableBase {
227 const void* obj;
228 // Union to store member function pointer as raw bytes
229 union MemberFuncStorage {
230 char bytes[sizeof(R (ConstMemberCallable::*)(Args...) const)];
231 // Ensure proper alignment
232 void* alignment_dummy;
233 } member_func_storage;
234
235 // Type-erased invoker function - set at construction time
236 R (*invoker)(const void* obj, const MemberFuncStorage& mfp, Args... args);
237
238 template <typename C>
239 ConstMemberCallable(const C* o, R (C::*mf)(Args...) const) FL_NOEXCEPT : obj(o) {
240 // Store the member function pointer as raw bytes
241 FL_STATIC_ASSERT(sizeof(mf) <= sizeof(member_func_storage),
242 "Member function pointer too large");
243 fl::memcpy(member_func_storage.bytes, &mf, sizeof(mf));
244 // Set the invoker to a function that knows how to cast back and call
245 invoker = &invoke_const_member<C>;
246 }
247
248 template <typename C>
249 static R invoke_const_member(const void* obj, const MemberFuncStorage& mfp, Args... args) FL_NOEXCEPT {
250 const C* typed_obj = static_cast<const C*>(obj);
251 R (C::*typed_mf)(Args...) const;
252 fl::memcpy(&typed_mf, mfp.bytes, sizeof(typed_mf));
253 return (typed_obj->*typed_mf)(args...);
254 }
255
256 R invoke(Args... args) const FL_NOEXCEPT override {
257 return invoker(obj, member_func_storage, args...);
258 }
259 };
260#endif // FL_FUNCTION_FULL_VARIANT
261
262 // variant to store any of our callable types inline.
263 // Low-memory targets drop the heap-fallback CallableBase and the two
264 // MemberCallable alternatives (audited dead on those targets) to save
265 // ~3 KB of per-signature variant codegen tax. See FastLED #3077.
266#if FL_FUNCTION_FULL_VARIANT
267 using Storage = variant<fl::shared_ptr<CallableBase>, FreeFunctionCallable, InlinedLambda, NonConstMemberCallable, ConstMemberCallable>;
268#else
269 using Storage = variant<FreeFunctionCallable, InlinedLambda>;
270#endif
271 Storage mStorage;
272
273 // Helper function to handle default return value for void and non-void types
274 template<typename ReturnType>
275 typename enable_if<!is_void<ReturnType>::value, ReturnType>::type
276 default_return_helper() const FL_NOEXCEPT {
277 return ReturnType{};
278 }
279
280 template<typename ReturnType>
281 typename enable_if<is_void<ReturnType>::value, ReturnType>::type
282 default_return_helper() const FL_NOEXCEPT {
283 return;
284 }
285
286public:
287 function() FL_NOEXCEPT = default;
288
289 // Copy constructor - properly handle variant alignment
290 function(const function& other) FL_NOEXCEPT : mStorage(other.mStorage) {}
291
292 // Move constructor - properly handle variant alignment
293 function(function&& other) FL_NOEXCEPT : mStorage(fl::move(other.mStorage)) {}
294
295 // Copy assignment
296 function& operator=(const function& other) FL_NOEXCEPT {
297 if (this != &other) {
298 mStorage = other.mStorage;
299 }
300 return *this;
301 }
302
303 // Move assignment
304 function& operator=(function&& other) FL_NOEXCEPT {
305 if (this != &other) {
306 mStorage = fl::move(other.mStorage);
307 }
308 return *this;
309 }
310
311 // 1) Free function constructor - stored inline!
312 function(R (*fp)(Args...)) FL_NOEXCEPT {
313 mStorage = FreeFunctionCallable(fp);
314 }
315
316 // 2) Lambda/functor constructor - inline if small, heap if large
317 template <typename F, typename = enable_if_t<!is_member_function_pointer<F>::value && !is_function_pointer<F>::value>>
318 function(F f) FL_NOEXCEPT {
319 // Use template specialization instead of if constexpr for C++14 compatibility
320 construct_lambda_or_functor(fl::move(f), typename conditional<sizeof(F) <= kInlineLambdaSize, true_type, false_type>::type{});
321 }
322
323#if FL_FUNCTION_FULL_VARIANT
324 // 3) non‑const member function - stored inline!
325 template <typename C>
326 function(R (C::*mf)(Args...), C* obj) FL_NOEXCEPT {
327 mStorage = NonConstMemberCallable(obj, mf);
328 }
329
330 // 4) const member function - stored inline!
331 template <typename C>
332 function(R (C::*mf)(Args...) const, const C* obj) FL_NOEXCEPT {
333 mStorage = ConstMemberCallable(obj, mf);
334 }
335#endif // FL_FUNCTION_FULL_VARIANT
336
337 R operator()(Args... args) const FL_NOEXCEPT {
338 // Direct dispatch using type checking - efficient and simple
339#if FL_FUNCTION_FULL_VARIANT
340 if (auto* heap_callable = mStorage.template ptr<fl::shared_ptr<CallableBase>>()) {
341 return (*heap_callable)->invoke(args...);
342 } else if (auto* free_func = mStorage.template ptr<FreeFunctionCallable>()) {
343#else
344 if (auto* free_func = mStorage.template ptr<FreeFunctionCallable>()) {
345#endif
346 return free_func->invoke(args...);
347 } else if (auto* inlined_lambda = mStorage.template ptr<InlinedLambda>()) {
348 return inlined_lambda->invoke(args...);
349#if FL_FUNCTION_FULL_VARIANT
350 } else if (auto* nonconst_member = mStorage.template ptr<NonConstMemberCallable>()) {
351 return nonconst_member->invoke(args...);
352 } else if (auto* const_member = mStorage.template ptr<ConstMemberCallable>()) {
353 return const_member->invoke(args...);
354#endif
355 }
356 // This should never happen if the function is properly constructed
357 return default_return_helper<R>();
358 }
359
360 explicit operator bool() const FL_NOEXCEPT {
361 return !mStorage.empty();
362 }
363
364 void clear() FL_NOEXCEPT {
365 mStorage = Storage{}; // Reset to empty variant
366 }
367
368 bool operator==(const function& o) const FL_NOEXCEPT {
369 // For simplicity, just check if both are empty or both are non-empty
370 // Full equality would require more complex comparison logic
371 return mStorage.empty() == o.mStorage.empty();
372 }
373
374 bool operator!=(const function& o) const FL_NOEXCEPT {
375 return !(*this == o);
376 }
377
378private:
379 // Helper for small lambdas/functors - inline storage
380 template <typename F>
381 void construct_lambda_or_functor(F f, true_type /* small */) FL_NOEXCEPT {
382 mStorage = InlinedLambda(fl::move(f));
383 }
384
385#if FL_FUNCTION_FULL_VARIANT
386 // Helper for large lambdas/functors - heap storage
387 template <typename F>
388 void construct_lambda_or_functor(F f, false_type /* large */) FL_NOEXCEPT {
389 mStorage = fl::shared_ptr<CallableBase>(fl::make_shared<Callable<F>>(fl::move(f)));
390 }
391#else
392 // Low-memory targets: heap fallback dropped per FastLED #3077 to save flash.
393 // Large lambdas (>kInlineLambdaSize) compile but leave the storage empty;
394 // operator() returns the default-constructed R. This degrades gracefully
395 // for link-dead code paths (e.g. UIButton::onClicked which the LPC845
396 // LowMemory build never reaches), and the linker DCE's the unreached
397 // instantiations. Code paths reachable at runtime should pre-validate that
398 // their lambdas fit in kInlineLambdaSize.
399 template <typename F>
400 void construct_lambda_or_functor(F /*f*/, false_type /* large */) FL_NOEXCEPT {
401 mStorage = Storage{};
402 }
403#endif
404};
405
406//----------------------------------------------------------------------------
407// function_list: Container for managing multiple callbacks with add/remove
408//----------------------------------------------------------------------------
409
410// Primary template declaration with static assertion for invalid usage
411// Only specializations for function signatures (e.g., function_list<void(Args...)>) are valid
412template <typename T>
413class function_list {
415 "function_list requires a void returning function signature.");
416};
417
418// Partial specialization for function signature syntax: function_list<void(Args...)>
419// Supports: function_list<void()>, function_list<void(float)>, function_list<void(u8, float, float)>, etc.
420template <typename... Args>
421class function_list<void(Args...)> {
422 private:
423 using FunctionType = function<void(Args...)>;
424
425 struct FunctionEntry {
426 int id;
427 int priority;
428 FunctionType fn;
429
430 FunctionEntry() FL_NOEXCEPT : id(0), priority(0), fn() {}
431 FunctionEntry(int idParam, int priorityParam, FunctionType fnParam) FL_NOEXCEPT
432 : id(idParam), priority(priorityParam), fn(fnParam) {}
433 };
434
435 using FunctionVector = fl::vector<FunctionEntry>;
436
437 FunctionVector mFunctions;
438 int mIdCounter = 0;
439
440 bool mNeedsCompact = false; // True when functions have been cleared during invocation
441
442 public:
443 function_list() FL_NOEXCEPT : mFunctions(), mIdCounter(0), mNeedsCompact(false) {}
444 function_list(const function_list& other) FL_NOEXCEPT
445 : mFunctions(other.mFunctions), mIdCounter(other.mIdCounter), mNeedsCompact(other.mNeedsCompact) {}
446 function_list(function_list&& other) FL_NOEXCEPT
447 : mFunctions(fl::move(other.mFunctions)), mIdCounter(other.mIdCounter), mNeedsCompact(other.mNeedsCompact) {}
448 function_list& operator=(const function_list& other) FL_NOEXCEPT {
449 if (this != &other) {
450 mFunctions = other.mFunctions;
451 mIdCounter = other.mIdCounter;
452 mNeedsCompact = other.mNeedsCompact;
453 }
454 return *this;
455 }
456 function_list& operator=(function_list&& other) FL_NOEXCEPT {
457 if (this != &other) {
458 mFunctions = fl::move(other.mFunctions);
459 mIdCounter = other.mIdCounter;
460 mNeedsCompact = other.mNeedsCompact;
461 }
462 return *this;
463 }
464 ~function_list() = default;
465
466 int add(function<void(Args...)> fn, int priority = 0) FL_NOEXCEPT {
467 int id = mIdCounter++;
468 mFunctions.push_back(FunctionEntry(id, priority, fn));
469 return id;
470 }
471
472 void remove(int id) FL_NOEXCEPT {
473 // During invocation: clear the function for deferred removal
474 for (size_t i = 0; i < mFunctions.size(); ++i) {
475 if (mFunctions[i].id == id) {
476 mFunctions[i].fn.clear();
477 mNeedsCompact = true;
478 }
479 }
480 }
481
482 void clear() FL_NOEXCEPT {
483 mFunctions.clear();
484 }
485
486 // Compact the storage by removing invalid (cleared) callbacks
487 // Used internally after invocation if removals occurred
488 void compact() FL_NOEXCEPT {
489 if (!mNeedsCompact) return;
490 size_t write_pos = 0;
491 for (size_t read_pos = 0; read_pos < mFunctions.size(); ++read_pos) {
492 if (mFunctions[read_pos].fn) { // Check if function is still valid
493 if (write_pos != read_pos) {
494 mFunctions[write_pos] = mFunctions[read_pos];
495 }
496 write_pos++;
497 }
498 }
499 mFunctions.resize(write_pos);
500 mNeedsCompact = false;
501 }
502
503 // Size information - counts only valid (non-cleared) functions
504 fl::size size() const FL_NOEXCEPT {
505 fl::size count = 0;
506 for (const auto& entry : mFunctions) {
507 if (entry.fn) {
508 ++count;
509 }
510 }
511 return count;
512 }
513 bool empty() const FL_NOEXCEPT { return size() == 0; }
514
515 // Boolean conversion for if (callback) checks
516 explicit operator bool() const FL_NOEXCEPT { return !empty(); }
517
518 void invoke(Args... args) FL_NOEXCEPT {
519 if (mFunctions.empty()) return;
520 // Compact the storage by removing invalid (cleared) callbacks
521 compact();
522 // Save size at start - newly added callbacks won't execute until next call
523 const size_t invoke_size = mFunctions.size();
524 // Early return if no callbacks
525 if (invoke_size == 0) {
526 return;
527 }
528
529 // Collect unique priorities into a small vector (usually very few unique values)
531 for (size_t i = 0; i < invoke_size; ++i) {
532 if (!mFunctions[i].fn) continue; // Skip already-cleared functions
533
534 int p = mFunctions[i].priority;
535 // Only add if not already present
536 if (priorities.find(p) == priorities.end()) {
537 priorities.push_back(p);
538 }
539 }
540
541 // Sort priorities (higher priority first) - cheap since there are usually very few unique priorities
542 fl::sort(priorities.begin(), priorities.end(), [](int a, int b) FL_NOEXCEPT { return a > b; });
543
544 // Iterate through priorities (highest first), then through functions matching each priority
545 for (size_t p_idx = 0; p_idx < priorities.size(); ++p_idx) {
546 int current_priority = priorities[p_idx];
547 for (size_t i = 0; i < invoke_size; ++i) {
548 if (mFunctions.empty()) {
549 return; // Clear happened.
550 }
551 if (mFunctions[i].fn && mFunctions[i].priority == current_priority) {
552 mFunctions[i].fn(args...);
553 }
554 }
555 }
556 compact();
557 }
558
559 // Call operator - syntactic sugar for invoke()
560 void operator()(Args... args) FL_NOEXCEPT {
561 invoke(args...);
562 }
563};
564
565// Partial specialization for non-void return types: function_list<R(Args...)> where R != void
566// Triggers a compile-time error when attempting to use non-void return types
567template <typename R, typename... Args>
568class function_list<R(Args...)> {
570 "function_list only supports void return type. "
571 "Use function_list<void(Args...)> instead of function_list<ReturnType(Args...)>.");
572};
573
574} // namespace fl
fl::size size() const FL_NOEXCEPT
iterator begin() FL_NOEXCEPT
Definition vector.h:655
iterator find(const T &value) FL_NOEXCEPT
Definition vector.h:682
iterator end() FL_NOEXCEPT
Definition vector.h:661
void push_back(const T &value) FL_NOEXCEPT
Definition vector.h:624
#define FASTLED_INLINE_LAMBDA_SIZE
Definition function.h:39
void * memcpy(void *dest, const void *src, size_t n) FL_NOEXCEPT
constexpr remove_reference< T >::type && move(T &&t) FL_NOEXCEPT
Definition move.h:28
void clear(CRGB(&arr)[N])
Definition clear.h:12
void * memset(void *s, int c, size_t n) FL_NOEXCEPT
FASTLED_FORCE_INLINE bool operator!=(const CRGB &lhs, const CRGB &rhs) FL_NOEXCEPT
Check if two CRGB objects do not have the same color data.
Definition crgb.h:739
FASTLED_FORCE_INLINE bool operator==(const CRGB &lhs, const CRGB &rhs) FL_NOEXCEPT
Check if two CRGB objects have the same color data.
Definition crgb.h:733
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
float add(float &a, float &b)
auto invoke(F &&f, T1 &&t1, Args &&... args) FL_NOEXCEPT -> 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
void sort(Iterator first, Iterator last, Compare comp) FL_NOEXCEPT
Definition algorithm.h:564
VectorN< T, INLINED_SIZE > vector_inlined
Definition vector.h:1133
Iterator remove(Iterator first, Iterator last, const T &value) FL_NOEXCEPT
Definition algorithm.h:265
corkscrew_args args
Definition old.h:149
#define FL_STATIC_ASSERT(...)
#define FL_ALIGN_AS(T)
#define FL_ALIGN_MAX
#define FL_NOEXCEPT
#define FL_ALIGN

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_DISABLE_WARNING_POP, FL_NOEXCEPT, FL_STATIC_ASSERT, 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: