mirror of
https://github.com/esphome/esphome.git
synced 2025-10-31 23:21:54 +00:00
Merge branch 'integration' into memory_api
This commit is contained in:
@@ -1,120 +1,51 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <concepts>
|
|
||||||
#include "esphome/core/optional.h"
|
#include "esphome/core/optional.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
|
||||||
/** Helper class for template platforms that stores either a stateless lambda (function pointer)
|
/** Lightweight wrapper for template platform lambdas (stateless function pointers only).
|
||||||
* or a stateful lambda (std::function pointer).
|
|
||||||
*
|
*
|
||||||
* This provides backward compatibility with PR #11555 while maintaining the optimization:
|
* This optimizes template platforms by storing only a function pointer (4 bytes on ESP32)
|
||||||
* - Stateless lambdas (no capture) → function pointer (4 bytes on ESP32)
|
* instead of std::function (16-32 bytes).
|
||||||
* - 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:
|
* IMPORTANT: This only supports stateless lambdas (no captures). The set_template() method
|
||||||
* return {}; // Don't publish a value
|
* 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
|
* return 42.0; // Publish this value
|
||||||
*
|
*
|
||||||
* operator() returns optional<T>, returning nullopt when no lambda is set (type == NONE).
|
* operator() returns optional<T>, returning nullopt when no lambda is set (nullptr check).
|
||||||
* 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 T The return type (e.g., float for sensor values)
|
||||||
* @tparam Args Optional arguments for the lambda
|
* @tparam Args Optional arguments for the lambda
|
||||||
*/
|
*/
|
||||||
template<typename T, typename... Args> class TemplateLambda {
|
template<typename T, typename... Args> class TemplateLambda {
|
||||||
public:
|
public:
|
||||||
TemplateLambda() : type_(NONE) {}
|
TemplateLambda() : f_(nullptr) {}
|
||||||
|
|
||||||
// For stateless lambdas: use function pointer
|
/** Set the lambda function pointer.
|
||||||
template<typename F>
|
* INTERNAL API: Only for use by YAML codegen.
|
||||||
requires std::invocable<F, Args...> && std::convertible_to < F, optional<T>(*)
|
* Only stateless lambdas (no captures) are supported.
|
||||||
(Args...) > void set(F f) {
|
*/
|
||||||
this->reset_();
|
void set(optional<T> (*f)(Args...)) { this->f_ = f; }
|
||||||
this->type_ = STATELESS_LAMBDA;
|
|
||||||
this->stateless_f_ = f; // Implicit conversion to function pointer
|
|
||||||
}
|
|
||||||
|
|
||||||
// For stateful lambdas: use std::function pointer
|
/** Check if a lambda is set */
|
||||||
template<typename F>
|
bool has_value() const { return this->f_ != nullptr; }
|
||||||
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_(); }
|
/** Call the lambda, returning nullopt if no lambda is set */
|
||||||
|
|
||||||
// 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) {
|
optional<T> operator()(Args... args) {
|
||||||
switch (this->type_) {
|
if (this->f_ == nullptr)
|
||||||
case STATELESS_LAMBDA:
|
return nullopt;
|
||||||
return this->stateless_f_(args...); // Direct function pointer call
|
return this->f_(args...);
|
||||||
case LAMBDA:
|
|
||||||
return (*this->f_)(args...); // std::function call via pointer
|
|
||||||
case NONE:
|
|
||||||
default:
|
|
||||||
return nullopt; // No lambda set
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Alias for operator() for compatibility */
|
||||||
optional<T> call(Args... args) { return (*this)(args...); }
|
optional<T> call(Args... args) { return (*this)(args...); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void reset_() {
|
optional<T> (*f_)(Args...); // Function pointer (4 bytes on ESP32)
|
||||||
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
|
} // namespace esphome
|
||||||
|
|||||||
@@ -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)
|
||||||
|
# NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break.
|
||||||
- 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:
|
||||||
|
|||||||
Reference in New Issue
Block a user