diff --git a/esphome/automation.py b/esphome/automation.py index cfe0af1b59..99def9f273 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -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( diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 8892b57e6e..26e784a0b8 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -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( diff --git a/esphome/components/binary_sensor/filter.h b/esphome/components/binary_sensor/filter.h index 2d473c3b64..a7eb080feb 100644 --- a/esphome/components/binary_sensor/filter.h +++ b/esphome/components/binary_sensor/filter.h @@ -111,21 +111,6 @@ class LambdaFilter : public Filter { std::function(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 (*f)(bool)) : f_(f) {} - - optional new_value(bool value) override { return this->f_(value); } - - protected: - optional (*f_)(bool); -}; - class SettleFilter : public Filter, public Component { public: optional new_value(bool value) override; diff --git a/esphome/components/logger/__init__.py b/esphome/components/logger/__init__.py index 22bf3d2f4c..1d02073d27 100644 --- a/esphome/components/logger/__init__.py +++ b/esphome/components/logger/__init__.py @@ -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( diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 41ac3516b9..93283e4d47 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -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( diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 75e28a1efe..ecd55308d1 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -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 (*lambda_filter)(float)) : lambda_filter_(lambda_filter) {} - - optional new_value(float value) override { return this->lambda_filter_(value); } - - protected: - optional (*lambda_filter_)(float); -}; - /// A simple filter that adds `offset` to each value it receives. class OffsetFilter : public Filter { public: diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index adc8a76fcd..7a9e947abd 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -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, {}) diff --git a/esphome/components/text_sensor/filter.h b/esphome/components/text_sensor/filter.h index 85acac5c8d..c77c221235 100644 --- a/esphome/components/text_sensor/filter.h +++ b/esphome/components/text_sensor/filter.h @@ -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 (*lambda_filter)(std::string)) : lambda_filter_(lambda_filter) {} - - optional new_value(std::string value) override { return this->lambda_filter_(value); } - - protected: - optional (*lambda_filter_)(std::string); -}; - /// A simple filter that converts all text to uppercase class ToUpperFilter : public Filter { public: diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 0512752d50..d357c6f58e 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -27,11 +27,21 @@ template class TemplatableValue { public: TemplatableValue() : type_(NONE) {} - template::value, int> = 0> TemplatableValue(F value) : type_(VALUE) { + template + requires(!std::invocable) TemplatableValue(F value) : type_(VALUE) { new (&this->value_) T(std::move(value)); } - template::value, int> = 0> TemplatableValue(F f) : type_(LAMBDA) { + // For stateless lambdas (convertible to function pointer): use function pointer + template + requires std::invocable && std::convertible_to 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 + requires std::invocable &&(!std::convertible_to) TemplatableValue(F f) : type_(LAMBDA) { this->f_ = new std::function(std::move(f)); } @@ -41,6 +51,8 @@ template class TemplatableValue { new (&this->value_) T(other.value_); } else if (type_ == LAMBDA) { this->f_ = new std::function(*other.f_); + } else if (type_ == STATELESS_LAMBDA) { + this->stateless_f_ = other.stateless_f_; } } @@ -51,6 +63,8 @@ template 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 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 optional_value(X... x) { @@ -109,11 +130,13 @@ template class TemplatableValue { NONE, VALUE, LAMBDA, + STATELESS_LAMBDA, } type_; union { T value_; std::function *f_; + T (*stateless_f_)(X...); }; }; diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 1c60dd1c7a..af8cde971b 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -79,18 +79,6 @@ template class LambdaCondition : public Condition { std::function 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 class StatelessLambdaCondition : public Condition { - public: - explicit StatelessLambdaCondition(bool (*f)(Ts...)) : f_(f) {} - bool check(Ts... x) override { return this->f_(x...); } - - protected: - bool (*f_)(Ts...); -}; - template class ForCondition : public Condition, public Component { public: explicit ForCondition(Condition<> *condition) : condition_(condition) {} @@ -202,19 +190,6 @@ template class LambdaAction : public Action { std::function 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 class StatelessLambdaAction : public Action { - public: - explicit StatelessLambdaAction(void (*f)(Ts...)) : f_(f) {} - - void play(Ts... x) override { this->f_(x...); } - - protected: - void (*f_)(Ts...); -}; - template class IfAction : public Action { public: explicit IfAction(Condition *condition) : condition_(condition) {}