mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'template_value_func_pointers' into integration
This commit is contained in:
		| @@ -16,12 +16,7 @@ from esphome.const import ( | ||||
|     CONF_UPDATE_INTERVAL, | ||||
| ) | ||||
| from esphome.core import ID | ||||
| from esphome.cpp_generator import ( | ||||
|     LambdaExpression, | ||||
|     MockObj, | ||||
|     MockObjClass, | ||||
|     TemplateArgsType, | ||||
| ) | ||||
| from esphome.cpp_generator import MockObj, MockObjClass, TemplateArgsType | ||||
| from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor | ||||
| from esphome.types import ConfigType | ||||
| from esphome.util import Registry | ||||
| @@ -92,7 +87,6 @@ def validate_potentially_or_condition(value): | ||||
|  | ||||
| DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component) | ||||
| LambdaAction = cg.esphome_ns.class_("LambdaAction", Action) | ||||
| StatelessLambdaAction = cg.esphome_ns.class_("StatelessLambdaAction", Action) | ||||
| IfAction = cg.esphome_ns.class_("IfAction", Action) | ||||
| WhileAction = cg.esphome_ns.class_("WhileAction", Action) | ||||
| RepeatAction = cg.esphome_ns.class_("RepeatAction", Action) | ||||
| @@ -103,40 +97,9 @@ ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action) | ||||
| Automation = cg.esphome_ns.class_("Automation") | ||||
|  | ||||
| LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition) | ||||
| StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition) | ||||
| ForCondition = cg.esphome_ns.class_("ForCondition", Condition, cg.Component) | ||||
|  | ||||
|  | ||||
| def new_lambda_pvariable( | ||||
|     id_obj: ID, | ||||
|     lambda_expr: LambdaExpression, | ||||
|     stateless_class: MockObjClass, | ||||
|     template_arg: cg.TemplateArguments | None = None, | ||||
| ) -> MockObj: | ||||
|     """Create Pvariable for lambda, using stateless class if applicable. | ||||
|  | ||||
|     Combines ID selection and Pvariable creation in one call. For stateless | ||||
|     lambdas (empty capture), uses function pointer instead of std::function. | ||||
|  | ||||
|     Args: | ||||
|         id_obj: The ID object (action_id, condition_id, or filter_id) | ||||
|         lambda_expr: The lambda expression object | ||||
|         stateless_class: The stateless class to use for stateless lambdas | ||||
|         template_arg: Optional template arguments (for actions/conditions) | ||||
|  | ||||
|     Returns: | ||||
|         The created Pvariable | ||||
|     """ | ||||
|     # For stateless lambdas, use function pointer instead of std::function | ||||
|     if lambda_expr.capture == "": | ||||
|         id_obj = id_obj.copy() | ||||
|         id_obj.type = stateless_class | ||||
|  | ||||
|     if template_arg is not None: | ||||
|         return cg.new_Pvariable(id_obj, template_arg, lambda_expr) | ||||
|     return cg.new_Pvariable(id_obj, lambda_expr) | ||||
|  | ||||
|  | ||||
| def validate_automation(extra_schema=None, extra_validators=None, single=False): | ||||
|     if extra_schema is None: | ||||
|         extra_schema = {} | ||||
| @@ -277,9 +240,7 @@ async def lambda_condition_to_code( | ||||
|     args: TemplateArgsType, | ||||
| ) -> MockObj: | ||||
|     lambda_ = await cg.process_lambda(config, args, return_type=bool) | ||||
|     return new_lambda_pvariable( | ||||
|         condition_id, lambda_, StatelessLambdaCondition, template_arg | ||||
|     ) | ||||
|     return cg.new_Pvariable(condition_id, template_arg, lambda_) | ||||
|  | ||||
|  | ||||
| @register_condition( | ||||
| @@ -445,7 +406,7 @@ async def lambda_action_to_code( | ||||
|     args: TemplateArgsType, | ||||
| ) -> MockObj: | ||||
|     lambda_ = await cg.process_lambda(config, args, return_type=cg.void) | ||||
|     return new_lambda_pvariable(action_id, lambda_, StatelessLambdaAction, template_arg) | ||||
|     return cg.new_Pvariable(action_id, template_arg, lambda_) | ||||
|  | ||||
|  | ||||
| @register_action( | ||||
|   | ||||
| @@ -155,7 +155,6 @@ DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Compon | ||||
| InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter) | ||||
| AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter, cg.Component) | ||||
| LambdaFilter = binary_sensor_ns.class_("LambdaFilter", Filter) | ||||
| StatelessLambdaFilter = binary_sensor_ns.class_("StatelessLambdaFilter", Filter) | ||||
| SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter, cg.Component) | ||||
|  | ||||
| _LOGGER = getLogger(__name__) | ||||
| @@ -300,7 +299,7 @@ async def lambda_filter_to_code(config, filter_id): | ||||
|     lambda_ = await cg.process_lambda( | ||||
|         config, [(bool, "x")], return_type=cg.optional.template(bool) | ||||
|     ) | ||||
|     return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter) | ||||
|     return cg.new_Pvariable(filter_id, lambda_) | ||||
|  | ||||
|  | ||||
| @register_filter( | ||||
|   | ||||
| @@ -111,21 +111,6 @@ class LambdaFilter : public Filter { | ||||
|   std::function<optional<bool>(bool)> f_; | ||||
| }; | ||||
|  | ||||
| /** Optimized lambda filter 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). | ||||
|  */ | ||||
| class StatelessLambdaFilter : public Filter { | ||||
|  public: | ||||
|   explicit StatelessLambdaFilter(optional<bool> (*f)(bool)) : f_(f) {} | ||||
|  | ||||
|   optional<bool> new_value(bool value) override { return this->f_(value); } | ||||
|  | ||||
|  protected: | ||||
|   optional<bool> (*f_)(bool); | ||||
| }; | ||||
|  | ||||
| class SettleFilter : public Filter, public Component { | ||||
|  public: | ||||
|   optional<bool> new_value(bool value) override; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import re | ||||
|  | ||||
| from esphome import automation | ||||
| from esphome.automation import LambdaAction, StatelessLambdaAction | ||||
| from esphome.automation import LambdaAction | ||||
| import esphome.codegen as cg | ||||
| from esphome.components.esp32 import add_idf_sdkconfig_option, get_esp32_variant | ||||
| from esphome.components.esp32.const import ( | ||||
| @@ -430,9 +430,7 @@ async def logger_log_action_to_code(config, action_id, template_arg, args): | ||||
|     text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_))) | ||||
|  | ||||
|     lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) | ||||
|     return automation.new_lambda_pvariable( | ||||
|         action_id, lambda_, StatelessLambdaAction, template_arg | ||||
|     ) | ||||
|     return cg.new_Pvariable(action_id, template_arg, lambda_) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
| @@ -457,9 +455,7 @@ async def logger_set_level_to_code(config, action_id, template_arg, args): | ||||
|         text = str(cg.statement(logger.set_log_level(level))) | ||||
|  | ||||
|     lambda_ = await cg.process_lambda(Lambda(text), args, return_type=cg.void) | ||||
|     return automation.new_lambda_pvariable( | ||||
|         action_id, lambda_, StatelessLambdaAction, template_arg | ||||
|     ) | ||||
|     return cg.new_Pvariable(action_id, template_arg, lambda_) | ||||
|  | ||||
|  | ||||
| FILTER_SOURCE_FILES = filter_source_files_from_platform( | ||||
|   | ||||
| @@ -261,7 +261,6 @@ ExponentialMovingAverageFilter = sensor_ns.class_( | ||||
| ) | ||||
| ThrottleAverageFilter = sensor_ns.class_("ThrottleAverageFilter", Filter, cg.Component) | ||||
| LambdaFilter = sensor_ns.class_("LambdaFilter", Filter) | ||||
| StatelessLambdaFilter = sensor_ns.class_("StatelessLambdaFilter", Filter) | ||||
| OffsetFilter = sensor_ns.class_("OffsetFilter", Filter) | ||||
| MultiplyFilter = sensor_ns.class_("MultiplyFilter", Filter) | ||||
| ValueListFilter = sensor_ns.class_("ValueListFilter", Filter) | ||||
| @@ -574,7 +573,7 @@ async def lambda_filter_to_code(config, filter_id): | ||||
|     lambda_ = await cg.process_lambda( | ||||
|         config, [(float, "x")], return_type=cg.optional.template(float) | ||||
|     ) | ||||
|     return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter) | ||||
|     return cg.new_Pvariable(filter_id, lambda_) | ||||
|  | ||||
|  | ||||
| DELTA_SCHEMA = cv.Schema( | ||||
|   | ||||
| @@ -296,21 +296,6 @@ class LambdaFilter : public Filter { | ||||
|   lambda_filter_t lambda_filter_; | ||||
| }; | ||||
|  | ||||
| /** Optimized lambda filter 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). | ||||
|  */ | ||||
| class StatelessLambdaFilter : public Filter { | ||||
|  public: | ||||
|   explicit StatelessLambdaFilter(optional<float> (*lambda_filter)(float)) : lambda_filter_(lambda_filter) {} | ||||
|  | ||||
|   optional<float> new_value(float value) override { return this->lambda_filter_(value); } | ||||
|  | ||||
|  protected: | ||||
|   optional<float> (*lambda_filter_)(float); | ||||
| }; | ||||
|  | ||||
| /// A simple filter that adds `offset` to each value it receives. | ||||
| class OffsetFilter : public Filter { | ||||
|  public: | ||||
|   | ||||
| @@ -57,7 +57,6 @@ validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) | ||||
| # Filters | ||||
| Filter = text_sensor_ns.class_("Filter") | ||||
| LambdaFilter = text_sensor_ns.class_("LambdaFilter", Filter) | ||||
| StatelessLambdaFilter = text_sensor_ns.class_("StatelessLambdaFilter", Filter) | ||||
| ToUpperFilter = text_sensor_ns.class_("ToUpperFilter", Filter) | ||||
| ToLowerFilter = text_sensor_ns.class_("ToLowerFilter", Filter) | ||||
| AppendFilter = text_sensor_ns.class_("AppendFilter", Filter) | ||||
| @@ -71,7 +70,7 @@ async def lambda_filter_to_code(config, filter_id): | ||||
|     lambda_ = await cg.process_lambda( | ||||
|         config, [(cg.std_string, "x")], return_type=cg.optional.template(cg.std_string) | ||||
|     ) | ||||
|     return automation.new_lambda_pvariable(filter_id, lambda_, StatelessLambdaFilter) | ||||
|     return cg.new_Pvariable(filter_id, lambda_) | ||||
|  | ||||
|  | ||||
| @FILTER_REGISTRY.register("to_upper", ToUpperFilter, {}) | ||||
|   | ||||
| @@ -62,21 +62,6 @@ class LambdaFilter : public Filter { | ||||
|   lambda_filter_t lambda_filter_; | ||||
| }; | ||||
|  | ||||
| /** Optimized lambda filter 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). | ||||
|  */ | ||||
| class StatelessLambdaFilter : public Filter { | ||||
|  public: | ||||
|   explicit StatelessLambdaFilter(optional<std::string> (*lambda_filter)(std::string)) : lambda_filter_(lambda_filter) {} | ||||
|  | ||||
|   optional<std::string> new_value(std::string value) override { return this->lambda_filter_(value); } | ||||
|  | ||||
|  protected: | ||||
|   optional<std::string> (*lambda_filter_)(std::string); | ||||
| }; | ||||
|  | ||||
| /// A simple filter that converts all text to uppercase | ||||
| class ToUpperFilter : public Filter { | ||||
|  public: | ||||
|   | ||||
| @@ -27,11 +27,21 @@ 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> | ||||
|   requires(!std::invocable<F, X...>) TemplatableValue(F value) : 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> | ||||
|   requires std::invocable<F, X...> && std::convertible_to<F, T (*)(X...)> TemplatableValue(F f) | ||||
|       : 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> | ||||
|   requires std::invocable<F, X...> &&(!std::convertible_to<F, T (*)(X...)>) TemplatableValue(F f) : type_(LAMBDA) { | ||||
|     this->f_ = new std::function<T(X...)>(std::move(f)); | ||||
|   } | ||||
|  | ||||
| @@ -41,6 +51,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 +63,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 +92,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 +130,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,18 +79,6 @@ 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) {} | ||||
| @@ -202,19 +190,6 @@ 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