mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Add stateless lambda infrastructure to TemplatableValue, Action, and Condition
Add STATELESS_LAMBDA support to TemplatableValue class using C++20 concepts: - Automatically detects stateless lambdas (convertible to function pointers) - Uses 4-byte function pointer instead of 32-byte std::function - Falls back to std::function for stateful lambdas Add StatelessLambdaAction and StatelessLambdaCondition classes for automation: - Memory: 4 bytes vs 32 bytes per lambda - Direct function pointer calls (no std::function overhead) This infrastructure enables template platforms to use function pointers for stateless lambdas, saving ~28 bytes per lambda instance.
This commit is contained in:
		| @@ -15,7 +15,10 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_characteristic_on_w | ||||
|   Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger =  // NOLINT(cppcoreguidelines-owning-memory) | ||||
|       new Trigger<std::vector<uint8_t>, uint16_t>(); | ||||
|   characteristic->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) { | ||||
|     // Convert span to vector for trigger | ||||
|     // Convert span to vector for trigger - copy is necessary because: | ||||
|     // 1. Trigger stores the data for use in automation actions that execute later | ||||
|     // 2. The span is only valid during this callback (points to temporary BLE stack data) | ||||
|     // 3. User lambdas in automations need persistent data they can access asynchronously | ||||
|     on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id); | ||||
|   }); | ||||
|   return on_write_trigger; | ||||
| @@ -27,7 +30,10 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write | ||||
|   Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger =  // NOLINT(cppcoreguidelines-owning-memory) | ||||
|       new Trigger<std::vector<uint8_t>, uint16_t>(); | ||||
|   descriptor->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) { | ||||
|     // Convert span to vector for trigger | ||||
|     // Convert span to vector for trigger - copy is necessary because: | ||||
|     // 1. Trigger stores the data for use in automation actions that execute later | ||||
|     // 2. The span is only valid during this callback (points to temporary BLE stack data) | ||||
|     // 3. User lambdas in automations need persistent data they can access asynchronously | ||||
|     on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id); | ||||
|   }); | ||||
|   return on_write_trigger; | ||||
|   | ||||
| @@ -27,11 +27,20 @@ template<typename T, typename... X> class TemplatableValue { | ||||
|  public: | ||||
|   TemplatableValue() : type_(NONE) {} | ||||
|  | ||||
|   template<typename F, enable_if_t<!is_invocable<F, X...>::value, int> = 0> TemplatableValue(F value) : type_(VALUE) { | ||||
|   template<typename F> TemplatableValue(F value) requires(!std::invocable<F, X...>) : type_(VALUE) { | ||||
|     new (&this->value_) T(std::move(value)); | ||||
|   } | ||||
|  | ||||
|   template<typename F, enable_if_t<is_invocable<F, X...>::value, int> = 0> TemplatableValue(F f) : type_(LAMBDA) { | ||||
|   // For stateless lambdas (convertible to function pointer): use function pointer | ||||
|   template<typename F> | ||||
|   TemplatableValue(F f) requires std::invocable<F, X...> && std::convertible_to<F, T (*)(X...)> | ||||
|       : type_(STATELESS_LAMBDA) { | ||||
|     this->stateless_f_ = f;  // Implicit conversion to function pointer | ||||
|   } | ||||
|  | ||||
|   // For stateful lambdas (not convertible to function pointer): use std::function | ||||
|   template<typename F> | ||||
|   TemplatableValue(F f) requires std::invocable<F, X...> &&(!std::convertible_to<F, T (*)(X...)>) : type_(LAMBDA) { | ||||
|     this->f_ = new std::function<T(X...)>(std::move(f)); | ||||
|   } | ||||
|  | ||||
| @@ -41,6 +50,8 @@ template<typename T, typename... X> class TemplatableValue { | ||||
|       new (&this->value_) T(other.value_); | ||||
|     } else if (type_ == LAMBDA) { | ||||
|       this->f_ = new std::function<T(X...)>(*other.f_); | ||||
|     } else if (type_ == STATELESS_LAMBDA) { | ||||
|       this->stateless_f_ = other.stateless_f_; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -51,6 +62,8 @@ template<typename T, typename... X> class TemplatableValue { | ||||
|     } else if (type_ == LAMBDA) { | ||||
|       this->f_ = other.f_; | ||||
|       other.f_ = nullptr; | ||||
|     } else if (type_ == STATELESS_LAMBDA) { | ||||
|       this->stateless_f_ = other.stateless_f_; | ||||
|     } | ||||
|     other.type_ = NONE; | ||||
|   } | ||||
| @@ -78,16 +91,23 @@ template<typename T, typename... X> class TemplatableValue { | ||||
|     } else if (type_ == LAMBDA) { | ||||
|       delete this->f_; | ||||
|     } | ||||
|     // STATELESS_LAMBDA/NONE: no cleanup needed (function pointer or empty, not heap-allocated) | ||||
|   } | ||||
|  | ||||
|   bool has_value() { return this->type_ != NONE; } | ||||
|  | ||||
|   T value(X... x) { | ||||
|     if (this->type_ == LAMBDA) { | ||||
|       return (*this->f_)(x...); | ||||
|     switch (this->type_) { | ||||
|       case STATELESS_LAMBDA: | ||||
|         return this->stateless_f_(x...);  // Direct function pointer call | ||||
|       case LAMBDA: | ||||
|         return (*this->f_)(x...);  // std::function call | ||||
|       case VALUE: | ||||
|         return this->value_; | ||||
|       case NONE: | ||||
|       default: | ||||
|         return T{}; | ||||
|     } | ||||
|     // return value also when none | ||||
|     return this->type_ == VALUE ? this->value_ : T{}; | ||||
|   } | ||||
|  | ||||
|   optional<T> optional_value(X... x) { | ||||
| @@ -109,11 +129,13 @@ template<typename T, typename... X> class TemplatableValue { | ||||
|     NONE, | ||||
|     VALUE, | ||||
|     LAMBDA, | ||||
|     STATELESS_LAMBDA, | ||||
|   } type_; | ||||
|  | ||||
|   union { | ||||
|     T value_; | ||||
|     std::function<T(X...)> *f_; | ||||
|     T (*stateless_f_)(X...); | ||||
|   }; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -79,6 +79,18 @@ template<typename... Ts> class LambdaCondition : public Condition<Ts...> { | ||||
|   std::function<bool(Ts...)> f_; | ||||
| }; | ||||
|  | ||||
| /// Optimized lambda condition for stateless lambdas (no capture). | ||||
| /// Uses function pointer instead of std::function to reduce memory overhead. | ||||
| /// Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||
| template<typename... Ts> class StatelessLambdaCondition : public Condition<Ts...> { | ||||
|  public: | ||||
|   explicit StatelessLambdaCondition(bool (*f)(Ts...)) : f_(f) {} | ||||
|   bool check(Ts... x) override { return this->f_(x...); } | ||||
|  | ||||
|  protected: | ||||
|   bool (*f_)(Ts...); | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class ForCondition : public Condition<Ts...>, public Component { | ||||
|  public: | ||||
|   explicit ForCondition(Condition<> *condition) : condition_(condition) {} | ||||
| @@ -190,6 +202,19 @@ template<typename... Ts> class LambdaAction : public Action<Ts...> { | ||||
|   std::function<void(Ts...)> f_; | ||||
| }; | ||||
|  | ||||
| /// Optimized lambda action for stateless lambdas (no capture). | ||||
| /// Uses function pointer instead of std::function to reduce memory overhead. | ||||
| /// Memory: 4 bytes (function pointer on 32-bit) vs 32 bytes (std::function). | ||||
| template<typename... Ts> class StatelessLambdaAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit StatelessLambdaAction(void (*f)(Ts...)) : f_(f) {} | ||||
|  | ||||
|   void play(Ts... x) override { this->f_(x...); } | ||||
|  | ||||
|  protected: | ||||
|   void (*f_)(Ts...); | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class IfAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit IfAction(Condition<Ts...> *condition) : condition_(condition) {} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user