mirror of
https://github.com/esphome/esphome.git
synced 2026-02-08 16:51:52 +00:00
Add stateless lambda infrastructure to TemplatableValue, Action, and Condition
Add STATELESS_LAMBDA support to TemplatableValue class using C++20 concepts: - Automatically detects stateless lambdas (convertible to function pointers) - Uses 4-byte function pointer instead of 32-byte std::function - Falls back to std::function for stateful lambdas Add StatelessLambdaAction and StatelessLambdaCondition classes for automation: - Memory: 4 bytes vs 32 bytes per lambda - Direct function pointer calls (no std::function overhead) This infrastructure enables template platforms to use function pointers for stateless lambdas, saving ~28 bytes per lambda instance.
This commit is contained in:
@@ -15,7 +15,10 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_characteristic_on_w
|
||||
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
|
||||
new Trigger<std::vector<uint8_t>, uint16_t>();
|
||||
characteristic->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
|
||||
// Convert span to vector for trigger
|
||||
// Convert span to vector for trigger - copy is necessary because:
|
||||
// 1. Trigger stores the data for use in automation actions that execute later
|
||||
// 2. The span is only valid during this callback (points to temporary BLE stack data)
|
||||
// 3. User lambdas in automations need persistent data they can access asynchronously
|
||||
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
|
||||
});
|
||||
return on_write_trigger;
|
||||
@@ -27,7 +30,10 @@ Trigger<std::vector<uint8_t>, uint16_t> *BLETriggers::create_descriptor_on_write
|
||||
Trigger<std::vector<uint8_t>, uint16_t> *on_write_trigger = // NOLINT(cppcoreguidelines-owning-memory)
|
||||
new Trigger<std::vector<uint8_t>, uint16_t>();
|
||||
descriptor->on_write([on_write_trigger](std::span<const uint8_t> data, uint16_t id) {
|
||||
// Convert span to vector for trigger
|
||||
// Convert span to vector for trigger - copy is necessary because:
|
||||
// 1. Trigger stores the data for use in automation actions that execute later
|
||||
// 2. The span is only valid during this callback (points to temporary BLE stack data)
|
||||
// 3. User lambdas in automations need persistent data they can access asynchronously
|
||||
on_write_trigger->trigger(std::vector<uint8_t>(data.begin(), data.end()), id);
|
||||
});
|
||||
return on_write_trigger;
|
||||
|
||||
@@ -27,11 +27,20 @@ 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> TemplatableValue(F value) requires(!std::invocable<F, X...>) : 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>
|
||||
TemplatableValue(F f) requires std::invocable<F, X...> && std::convertible_to<F, T (*)(X...)>
|
||||
: 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>
|
||||
TemplatableValue(F f) requires std::invocable<F, X...> &&(!std::convertible_to<F, T (*)(X...)>) : type_(LAMBDA) {
|
||||
this->f_ = new std::function<T(X...)>(std::move(f));
|
||||
}
|
||||
|
||||
@@ -41,6 +50,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 +62,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 +91,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 +129,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,6 +79,18 @@ 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) {}
|
||||
@@ -190,6 +202,19 @@ 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