1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-31 07:03:55 +00:00
This commit is contained in:
J. Nick Koston
2025-10-29 16:08:27 -05:00
parent 3636ab68f3
commit f4d32c7def
16 changed files with 65 additions and 148 deletions

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
#include "esphome/components/binary_sensor/binary_sensor.h" #include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome { namespace esphome {

View File

@@ -2,7 +2,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
#include "esphome/components/cover/cover.h" #include "esphome/components/cover/cover.h"
namespace esphome { namespace esphome {

View File

@@ -9,7 +9,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/time.h" #include "esphome/core/time.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
namespace esphome { namespace esphome {
namespace template_ { namespace template_ {

View File

@@ -9,7 +9,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/time.h" #include "esphome/core/time.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
namespace esphome { namespace esphome {
namespace template_ { namespace template_ {

View File

@@ -9,7 +9,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/time.h" #include "esphome/core/time.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
namespace esphome { namespace esphome {
namespace template_ { namespace template_ {

View File

@@ -2,7 +2,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
#include "esphome/components/lock/lock.h" #include "esphome/components/lock/lock.h"
namespace esphome { namespace esphome {

View File

@@ -4,7 +4,7 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
namespace esphome { namespace esphome {
namespace template_ { namespace template_ {

View File

@@ -4,7 +4,7 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
namespace esphome { namespace esphome {
namespace template_ { namespace template_ {

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
namespace esphome { namespace esphome {

View File

@@ -2,7 +2,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
#include "esphome/components/switch/switch.h" #include "esphome/components/switch/switch.h"
namespace esphome { namespace esphome {

View File

@@ -0,0 +1,51 @@
#pragma once
#include "esphome/core/optional.h"
namespace esphome {
/** Lightweight wrapper for template platform lambdas (stateless function pointers only).
*
* This optimizes template platforms by storing only a function pointer (4 bytes on ESP32)
* instead of std::function (16-32 bytes).
*
* IMPORTANT: This only supports stateless lambdas (no captures). The set_template() method
* is an internal API used by YAML codegen, not intended for external use.
*
* Lambdas must return optional<T> to support the pattern:
* return {}; // Don't publish a value
* return 42.0; // Publish this value
*
* operator() returns optional<T>, returning nullopt when no lambda is set (nullptr check).
*
* @tparam T The return type (e.g., float for sensor values)
* @tparam Args Optional arguments for the lambda
*/
template<typename T, typename... Args> class TemplateLambda {
public:
TemplateLambda() : f_(nullptr) {}
/** Set the lambda function pointer.
* INTERNAL API: Only for use by YAML codegen.
* Only stateless lambdas (no captures) are supported.
*/
void set(optional<T> (*f)(Args...)) { this->f_ = f; }
/** Check if a lambda is set */
bool has_value() const { return this->f_ != nullptr; }
/** Call the lambda, returning nullopt if no lambda is set */
optional<T> operator()(Args... args) {
if (this->f_ == nullptr)
return nullopt;
return this->f_(args...);
}
/** Alias for operator() for compatibility */
optional<T> call(Args... args) { return (*this)(args...); }
protected:
optional<T> (*f_)(Args...); // Function pointer (4 bytes on ESP32)
};
} // namespace esphome

View File

@@ -4,7 +4,7 @@
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/preferences.h" #include "esphome/core/preferences.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
namespace esphome { namespace esphome {
namespace template_ { namespace template_ {

View File

@@ -2,7 +2,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
#include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/text_sensor/text_sensor.h"
namespace esphome { namespace esphome {

View File

@@ -2,7 +2,7 @@
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/automation.h" #include "esphome/core/automation.h"
#include "esphome/core/template_lambda.h" #include "../template_lambda.h"
#include "esphome/components/valve/valve.h" #include "esphome/components/valve/valve.h"
namespace esphome { namespace esphome {

View File

@@ -1,120 +0,0 @@
#pragma once
#include <functional>
#include <concepts>
#include "esphome/core/optional.h"
namespace esphome {
/** Helper class for template platforms that stores either a stateless lambda (function pointer)
* or a stateful lambda (std::function pointer).
*
* This provides backward compatibility with PR #11555 while maintaining the optimization:
* - Stateless lambdas (no capture) → function pointer (4 bytes on ESP32)
* - Stateful lambdas (with capture) → pointer to std::function (4 bytes on ESP32)
* Total size: enum (1 byte) + union (4 bytes) + padding = 8 bytes (same as PR #11555)
*
* Both lambda types must return optional<T> (as YAML codegen does) to support the pattern:
* return {}; // Don't publish a value
* return 42.0; // Publish this value
*
* operator() returns optional<T>, returning nullopt when no lambda is set (type == NONE).
* This eliminates redundant "is lambda set" checks by reusing optional's discriminator.
*
* @tparam T The return type (e.g., float for TemplateLambda<optional<float>>)
* @tparam Args Optional arguments for the lambda
*/
template<typename T, typename... Args> class TemplateLambda {
public:
TemplateLambda() : type_(NONE) {}
// For stateless lambdas: use function pointer
template<typename F>
requires std::invocable<F, Args...> && std::convertible_to < F, optional<T>(*)
(Args...) > void set(F f) {
this->reset_();
this->type_ = STATELESS_LAMBDA;
this->stateless_f_ = f; // Implicit conversion to function pointer
}
// For stateful lambdas: use std::function pointer
template<typename F>
requires std::invocable<F, Args...> &&
(!std::convertible_to<F, optional<T> (*)(Args...)>) &&std::convertible_to<std::invoke_result_t<F, Args...>,
optional<T>> void set(F &&f) {
this->reset_();
this->type_ = LAMBDA;
this->f_ = new std::function<optional<T>(Args...)>(std::forward<F>(f));
}
~TemplateLambda() { this->reset_(); }
// Copy constructor
TemplateLambda(const TemplateLambda &) = delete;
TemplateLambda &operator=(const TemplateLambda &) = delete;
// Move constructor
TemplateLambda(TemplateLambda &&other) noexcept : type_(other.type_) {
if (type_ == LAMBDA) {
this->f_ = other.f_;
other.f_ = nullptr;
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
}
other.type_ = NONE;
}
TemplateLambda &operator=(TemplateLambda &&other) noexcept {
if (this != &other) {
this->reset_();
this->type_ = other.type_;
if (type_ == LAMBDA) {
this->f_ = other.f_;
other.f_ = nullptr;
} else if (type_ == STATELESS_LAMBDA) {
this->stateless_f_ = other.stateless_f_;
}
other.type_ = NONE;
}
return *this;
}
bool has_value() const { return this->type_ != NONE; }
// Returns optional<T>, returning nullopt if no lambda is set
optional<T> operator()(Args... args) {
switch (this->type_) {
case STATELESS_LAMBDA:
return this->stateless_f_(args...); // Direct function pointer call
case LAMBDA:
return (*this->f_)(args...); // std::function call via pointer
case NONE:
default:
return nullopt; // No lambda set
}
}
optional<T> call(Args... args) { return (*this)(args...); }
protected:
void reset_() {
if (this->type_ == LAMBDA) {
delete this->f_;
this->f_ = nullptr;
}
this->type_ = NONE;
}
enum : uint8_t {
NONE,
STATELESS_LAMBDA,
LAMBDA,
} type_;
union {
optional<T> (*stateless_f_)(Args...); // Function pointer (4 bytes on ESP32)
std::function<optional<T>(Args...)> *f_; // Pointer to std::function (4 bytes on ESP32)
};
};
} // namespace esphome

View File

@@ -10,26 +10,12 @@ esphome:
state: !lambda "return 42.0;" state: !lambda "return 42.0;"
# Test C++ API: set_template() with stateless lambda (no captures) # Test C++ API: set_template() with stateless lambda (no captures)
# IMPORTANT: set_template() is an internal API. Only stateless lambdas are supported.
- lambda: |- - lambda: |-
id(template_sens).set_template([]() -> esphome::optional<float> { id(template_sens).set_template([]() -> esphome::optional<float> {
return 123.0f; return 123.0f;
}); });
# Test C++ API: set_template() with stateful lambda (with captures)
# This is the regression test for issue #11555
- lambda: |-
float captured_value = 456.0f;
id(template_sens).set_template([captured_value]() -> esphome::optional<float> {
return captured_value;
});
# Test C++ API: set_template() with more complex capture
- lambda: |-
auto sensor_id = id(template_sens);
id(template_number).set_template([sensor_id]() -> esphome::optional<float> {
return sensor_id->state * 2.0f;
});
- datetime.date.set: - datetime.date.set:
id: test_date id: test_date
date: date: