mirror of
https://github.com/esphome/esphome.git
synced 2025-10-31 23:21:54 +00:00
Merge branch 'elimate_optional' into integration
This commit is contained in:
@@ -1,120 +1,51 @@
|
||||
#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).
|
||||
/** Lightweight wrapper for template platform lambdas (stateless function pointers only).
|
||||
*
|
||||
* 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)
|
||||
* This optimizes template platforms by storing only a function pointer (4 bytes on ESP32)
|
||||
* instead of std::function (16-32 bytes).
|
||||
*
|
||||
* Both lambda types must return optional<T> (as YAML codegen does) to support the pattern:
|
||||
* return {}; // Don't publish a value
|
||||
* 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 (type == NONE).
|
||||
* This eliminates redundant "is lambda set" checks by reusing optional's discriminator.
|
||||
* operator() returns optional<T>, returning nullopt when no lambda is set (nullptr check).
|
||||
*
|
||||
* @tparam T The return type (e.g., float for TemplateLambda<optional<float>>)
|
||||
* @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() : type_(NONE) {}
|
||||
TemplateLambda() : f_(nullptr) {}
|
||||
|
||||
// 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
|
||||
}
|
||||
/** 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; }
|
||||
|
||||
// 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));
|
||||
}
|
||||
/** Check if a lambda is set */
|
||||
bool has_value() const { return this->f_ != nullptr; }
|
||||
|
||||
~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
|
||||
/** Call the lambda, 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
|
||||
}
|
||||
if (this->f_ == nullptr)
|
||||
return nullopt;
|
||||
return this->f_(args...);
|
||||
}
|
||||
|
||||
/** Alias for operator() for compatibility */
|
||||
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)
|
||||
};
|
||||
optional<T> (*f_)(Args...); // Function pointer (4 bytes on ESP32)
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -10,26 +10,12 @@ esphome:
|
||||
state: !lambda "return 42.0;"
|
||||
|
||||
# Test C++ API: set_template() with stateless lambda (no captures)
|
||||
# NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break.
|
||||
- lambda: |-
|
||||
id(template_sens).set_template([]() -> esphome::optional<float> {
|
||||
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:
|
||||
id: test_date
|
||||
date:
|
||||
|
||||
Reference in New Issue
Block a user