mirror of
https://github.com/esphome/esphome.git
synced 2026-06-02 03:02:19 +08:00
[api] Reduce heap usage for Home Assistant service call string storage (#12151)
This commit is contained in:
@@ -17,6 +17,12 @@
|
|||||||
namespace esphome::api {
|
namespace esphome::api {
|
||||||
|
|
||||||
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
|
template<typename... X> class TemplatableStringValue : public TemplatableValue<std::string, X...> {
|
||||||
|
// Verify that const char* uses the base class STATIC_STRING optimization (no heap allocation)
|
||||||
|
// rather than being wrapped in a lambda. The base class constructor for const char* is more
|
||||||
|
// specialized than the templated constructor here, so it should be selected.
|
||||||
|
static_assert(std::is_constructible_v<TemplatableValue<std::string, X...>, const char *>,
|
||||||
|
"Base class must have const char* constructor for STATIC_STRING optimization");
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Helper to convert value to string - handles the case where value is already a string
|
// Helper to convert value to string - handles the case where value is already a string
|
||||||
template<typename T> static std::string value_to_string(T &&val) { return to_string(std::forward<T>(val)); }
|
template<typename T> static std::string value_to_string(T &&val) { return to_string(std::forward<T>(val)); }
|
||||||
@@ -47,10 +53,10 @@ template<typename... Ts> class TemplatableKeyValuePair {
|
|||||||
|
|
||||||
// Keys are always string literals from YAML dictionary keys (e.g., "code", "event")
|
// Keys are always string literals from YAML dictionary keys (e.g., "code", "event")
|
||||||
// and never templatable values or lambdas. Only the value parameter can be a lambda/template.
|
// and never templatable values or lambdas. Only the value parameter can be a lambda/template.
|
||||||
// Using pass-by-value with std::move allows optimal performance for both lvalues and rvalues.
|
// Using const char* avoids std::string heap allocation - keys remain in flash.
|
||||||
template<typename T> TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
|
template<typename T> TemplatableKeyValuePair(const char *key, T value) : key(key), value(value) {}
|
||||||
|
|
||||||
std::string key;
|
const char *key{nullptr};
|
||||||
TemplatableStringValue<Ts...> value;
|
TemplatableStringValue<Ts...> value;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,14 +115,15 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
|
|
||||||
// Keys are always string literals from the Python code generation (e.g., cg.add(var.add_data("tag_id", templ))).
|
// Keys are always string literals from the Python code generation (e.g., cg.add(var.add_data("tag_id", templ))).
|
||||||
// The value parameter can be a lambda/template, but keys are never templatable.
|
// The value parameter can be a lambda/template, but keys are never templatable.
|
||||||
template<typename K, typename V> void add_data(K &&key, V &&value) {
|
// Using const char* for keys avoids std::string heap allocation - keys remain in flash.
|
||||||
this->add_kv_(this->data_, std::forward<K>(key), std::forward<V>(value));
|
template<typename V> void add_data(const char *key, V &&value) {
|
||||||
|
this->add_kv_(this->data_, key, std::forward<V>(value));
|
||||||
}
|
}
|
||||||
template<typename K, typename V> void add_data_template(K &&key, V &&value) {
|
template<typename V> void add_data_template(const char *key, V &&value) {
|
||||||
this->add_kv_(this->data_template_, std::forward<K>(key), std::forward<V>(value));
|
this->add_kv_(this->data_template_, key, std::forward<V>(value));
|
||||||
}
|
}
|
||||||
template<typename K, typename V> void add_variable(K &&key, V &&value) {
|
template<typename V> void add_variable(const char *key, V &&value) {
|
||||||
this->add_kv_(this->variables_, std::forward<K>(key), std::forward<V>(value));
|
this->add_kv_(this->variables_, key, std::forward<V>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
|
||||||
@@ -189,10 +196,11 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Helper to add key-value pairs to FixedVectors with perfect forwarding to avoid copies
|
// Helper to add key-value pairs to FixedVectors
|
||||||
template<typename K, typename V> void add_kv_(FixedVector<TemplatableKeyValuePair<Ts...>> &vec, K &&key, V &&value) {
|
// Keys are always string literals (const char*), values can be lambdas/templates
|
||||||
|
template<typename V> void add_kv_(FixedVector<TemplatableKeyValuePair<Ts...>> &vec, const char *key, V &&value) {
|
||||||
auto &kv = vec.emplace_back();
|
auto &kv = vec.emplace_back();
|
||||||
kv.key = std::forward<K>(key);
|
kv.key = key;
|
||||||
kv.value = std::forward<V>(value);
|
kv.value = std::forward<V>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
public:
|
public:
|
||||||
TemplatableValue() : type_(NONE) {}
|
TemplatableValue() : type_(NONE) {}
|
||||||
|
|
||||||
|
// For const char* when T is std::string: store pointer directly, no heap allocation
|
||||||
|
// String remains in flash and is only converted to std::string when value() is called
|
||||||
|
TemplatableValue(const char *str) requires std::same_as<T, std::string> : type_(STATIC_STRING) {
|
||||||
|
this->static_str_ = str;
|
||||||
|
}
|
||||||
|
|
||||||
template<typename F> TemplatableValue(F value) requires(!std::invocable<F, X...>) : type_(VALUE) {
|
template<typename F> TemplatableValue(F value) requires(!std::invocable<F, X...>) : type_(VALUE) {
|
||||||
new (&this->value_) T(std::move(value));
|
new (&this->value_) T(std::move(value));
|
||||||
}
|
}
|
||||||
@@ -64,24 +70,28 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
|
|
||||||
// Copy constructor
|
// Copy constructor
|
||||||
TemplatableValue(const TemplatableValue &other) : type_(other.type_) {
|
TemplatableValue(const TemplatableValue &other) : type_(other.type_) {
|
||||||
if (type_ == VALUE) {
|
if (this->type_ == VALUE) {
|
||||||
new (&this->value_) T(other.value_);
|
new (&this->value_) T(other.value_);
|
||||||
} else if (type_ == LAMBDA) {
|
} else if (this->type_ == LAMBDA) {
|
||||||
this->f_ = new std::function<T(X...)>(*other.f_);
|
this->f_ = new std::function<T(X...)>(*other.f_);
|
||||||
} else if (type_ == STATELESS_LAMBDA) {
|
} else if (this->type_ == STATELESS_LAMBDA) {
|
||||||
this->stateless_f_ = other.stateless_f_;
|
this->stateless_f_ = other.stateless_f_;
|
||||||
|
} else if (this->type_ == STATIC_STRING) {
|
||||||
|
this->static_str_ = other.static_str_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move constructor
|
// Move constructor
|
||||||
TemplatableValue(TemplatableValue &&other) noexcept : type_(other.type_) {
|
TemplatableValue(TemplatableValue &&other) noexcept : type_(other.type_) {
|
||||||
if (type_ == VALUE) {
|
if (this->type_ == VALUE) {
|
||||||
new (&this->value_) T(std::move(other.value_));
|
new (&this->value_) T(std::move(other.value_));
|
||||||
} else if (type_ == LAMBDA) {
|
} else if (this->type_ == LAMBDA) {
|
||||||
this->f_ = other.f_;
|
this->f_ = other.f_;
|
||||||
other.f_ = nullptr;
|
other.f_ = nullptr;
|
||||||
} else if (type_ == STATELESS_LAMBDA) {
|
} else if (this->type_ == STATELESS_LAMBDA) {
|
||||||
this->stateless_f_ = other.stateless_f_;
|
this->stateless_f_ = other.stateless_f_;
|
||||||
|
} else if (this->type_ == STATIC_STRING) {
|
||||||
|
this->static_str_ = other.static_str_;
|
||||||
}
|
}
|
||||||
other.type_ = NONE;
|
other.type_ = NONE;
|
||||||
}
|
}
|
||||||
@@ -104,12 +114,12 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
~TemplatableValue() {
|
~TemplatableValue() {
|
||||||
if (type_ == VALUE) {
|
if (this->type_ == VALUE) {
|
||||||
this->value_.~T();
|
this->value_.~T();
|
||||||
} else if (type_ == LAMBDA) {
|
} else if (this->type_ == LAMBDA) {
|
||||||
delete this->f_;
|
delete this->f_;
|
||||||
}
|
}
|
||||||
// STATELESS_LAMBDA/NONE: no cleanup needed (function pointer or empty, not heap-allocated)
|
// STATELESS_LAMBDA/STATIC_STRING/NONE: no cleanup needed (pointers, not heap-allocated)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool has_value() { return this->type_ != NONE; }
|
bool has_value() { return this->type_ != NONE; }
|
||||||
@@ -122,6 +132,13 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
return (*this->f_)(x...); // std::function call
|
return (*this->f_)(x...); // std::function call
|
||||||
case VALUE:
|
case VALUE:
|
||||||
return this->value_;
|
return this->value_;
|
||||||
|
case STATIC_STRING:
|
||||||
|
// if constexpr required: code must compile for all T, but STATIC_STRING
|
||||||
|
// can only be set when T is std::string (enforced by constructor constraint)
|
||||||
|
if constexpr (std::same_as<T, std::string>) {
|
||||||
|
return std::string(this->static_str_);
|
||||||
|
}
|
||||||
|
__builtin_unreachable();
|
||||||
case NONE:
|
case NONE:
|
||||||
default:
|
default:
|
||||||
return T{};
|
return T{};
|
||||||
@@ -148,12 +165,14 @@ template<typename T, typename... X> class TemplatableValue {
|
|||||||
VALUE,
|
VALUE,
|
||||||
LAMBDA,
|
LAMBDA,
|
||||||
STATELESS_LAMBDA,
|
STATELESS_LAMBDA,
|
||||||
|
STATIC_STRING, // For const char* when T is std::string - avoids heap allocation
|
||||||
} type_;
|
} type_;
|
||||||
|
|
||||||
union {
|
union {
|
||||||
T value_;
|
T value_;
|
||||||
std::function<T(X...)> *f_;
|
std::function<T(X...)> *f_;
|
||||||
T (*stateless_f_)(X...);
|
T (*stateless_f_)(X...);
|
||||||
|
const char *static_str_; // For STATIC_STRING type
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user