[core] Add zero-allocation object_id methods (#12578)
This commit is contained in:
@@ -323,17 +323,10 @@ class APIConnection final : public APIServerConnection {
|
||||
APIConnection *conn, uint32_t remaining_size, bool is_single) {
|
||||
// Set common fields that are shared by all entity types
|
||||
msg.key = entity->get_object_id_hash();
|
||||
// Try to use static reference first to avoid allocation
|
||||
StringRef static_ref = entity->get_object_id_ref_for_api_();
|
||||
// Store dynamic string outside the if-else to maintain lifetime
|
||||
std::string object_id;
|
||||
if (!static_ref.empty()) {
|
||||
msg.set_object_id(static_ref);
|
||||
} else {
|
||||
// Dynamic case - need to allocate
|
||||
object_id = entity->get_object_id();
|
||||
msg.set_object_id(StringRef(object_id));
|
||||
}
|
||||
// Get object_id with zero heap allocation
|
||||
// Static case returns direct reference, dynamic case uses buffer
|
||||
char object_id_buf[OBJECT_ID_MAX_LEN];
|
||||
msg.set_object_id(entity->get_object_id_to(object_id_buf));
|
||||
|
||||
if (entity->has_own_name()) {
|
||||
msg.set_name(entity->get_name());
|
||||
|
||||
@@ -404,9 +404,11 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) {
|
||||
|
||||
// Helper functions to reduce code size by avoiding macro expansion
|
||||
static void set_json_id(JsonObject &root, EntityBase *obj, const char *prefix, JsonDetail start_config) {
|
||||
char id_buf[160]; // object_id can be up to 128 chars + prefix + dash + null
|
||||
const auto &object_id = obj->get_object_id();
|
||||
snprintf(id_buf, sizeof(id_buf), "%s-%s", prefix, object_id.c_str());
|
||||
char id_buf[160]; // prefix + dash + object_id (up to 128) + null
|
||||
size_t len = strlen(prefix);
|
||||
memcpy(id_buf, prefix, len); // NOLINT(bugprone-not-null-terminated-result) - null added by write_object_id_to
|
||||
id_buf[len++] = '-';
|
||||
obj->write_object_id_to(id_buf + len, sizeof(id_buf) - len);
|
||||
root[ESPHOME_F("id")] = id_buf;
|
||||
if (start_config == DETAIL_ALL) {
|
||||
root[ESPHOME_F("name")] = obj->get_name();
|
||||
|
||||
@@ -52,14 +52,10 @@ struct UrlMatch {
|
||||
}
|
||||
|
||||
bool id_equals_entity(EntityBase *entity) const {
|
||||
// Zero-copy comparison using StringRef
|
||||
StringRef static_ref = entity->get_object_id_ref_for_api_();
|
||||
if (!static_ref.empty()) {
|
||||
return id && id_len == static_ref.size() && memcmp(id, static_ref.c_str(), id_len) == 0;
|
||||
}
|
||||
// Fallback to allocation (rare)
|
||||
const auto &obj_id = entity->get_object_id();
|
||||
return id && id_len == obj_id.length() && memcmp(id, obj_id.c_str(), id_len) == 0;
|
||||
// Get object_id with zero heap allocation
|
||||
char object_id_buf[OBJECT_ID_MAX_LEN];
|
||||
StringRef object_id = entity->get_object_id_to(object_id_buf);
|
||||
return id && id_len == object_id.size() && memcmp(id, object_id.c_str(), id_len) == 0;
|
||||
}
|
||||
|
||||
bool method_equals(const char *str) const {
|
||||
|
||||
@@ -15,7 +15,8 @@ void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &
|
||||
stream->print("\" id=\"");
|
||||
stream->print(klass.c_str());
|
||||
stream->print("-");
|
||||
stream->print(obj->get_object_id().c_str());
|
||||
char object_id_buf[OBJECT_ID_MAX_LEN];
|
||||
stream->print(obj->get_object_id_to(object_id_buf).c_str());
|
||||
stream->print("\"><td>");
|
||||
stream->print(obj->get_name().c_str());
|
||||
stream->print("</td><td></td><td>");
|
||||
|
||||
@@ -60,15 +60,6 @@ std::string EntityBase::get_object_id() const {
|
||||
// `App.get_friendly_name()` is constant.
|
||||
return this->object_id_c_str_ == nullptr ? "" : this->object_id_c_str_;
|
||||
}
|
||||
StringRef EntityBase::get_object_id_ref_for_api_() const {
|
||||
static constexpr auto EMPTY_STRING = StringRef::from_lit("");
|
||||
// Return empty for dynamic case (MAC suffix)
|
||||
if (this->is_object_id_dynamic_()) {
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
// For static case, return the string or empty if null
|
||||
return this->object_id_c_str_ == nullptr ? EMPTY_STRING : StringRef(this->object_id_c_str_);
|
||||
}
|
||||
void EntityBase::set_object_id(const char *object_id) {
|
||||
this->object_id_c_str_ = object_id;
|
||||
this->calc_object_id_();
|
||||
@@ -82,8 +73,41 @@ void EntityBase::set_name_and_object_id(const char *name, const char *object_id)
|
||||
|
||||
// Calculate Object ID Hash from Entity Name
|
||||
void EntityBase::calc_object_id_() {
|
||||
this->object_id_hash_ =
|
||||
fnv1_hash(this->is_object_id_dynamic_() ? this->get_object_id().c_str() : this->object_id_c_str_);
|
||||
char buf[OBJECT_ID_MAX_LEN];
|
||||
StringRef object_id = this->get_object_id_to(buf);
|
||||
this->object_id_hash_ = fnv1_hash(object_id.c_str());
|
||||
}
|
||||
|
||||
// Format dynamic object_id: sanitized snake_case of friendly_name
|
||||
static size_t format_dynamic_object_id(char *buf, size_t buf_size) {
|
||||
const std::string &name = App.get_friendly_name();
|
||||
size_t len = std::min(name.size(), buf_size - 1);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
buf[i] = to_sanitized_char(to_snake_case_char(name[i]));
|
||||
}
|
||||
buf[len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t EntityBase::write_object_id_to(char *buf, size_t buf_size) const {
|
||||
if (this->is_object_id_dynamic_()) {
|
||||
return format_dynamic_object_id(buf, buf_size);
|
||||
}
|
||||
const char *src = this->object_id_c_str_ == nullptr ? "" : this->object_id_c_str_;
|
||||
size_t len = strlen(src);
|
||||
if (len >= buf_size)
|
||||
len = buf_size - 1;
|
||||
memcpy(buf, src, len);
|
||||
buf[len] = '\0';
|
||||
return len;
|
||||
}
|
||||
|
||||
StringRef EntityBase::get_object_id_to(std::span<char, OBJECT_ID_MAX_LEN> buf) const {
|
||||
if (this->is_object_id_dynamic_()) {
|
||||
size_t len = format_dynamic_object_id(buf.data(), buf.size());
|
||||
return StringRef(buf.data(), len);
|
||||
}
|
||||
return this->object_id_c_str_ == nullptr ? StringRef() : StringRef(this->object_id_c_str_);
|
||||
}
|
||||
|
||||
uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; }
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include "string_ref.h"
|
||||
#include "helpers.h"
|
||||
#include "log.h"
|
||||
@@ -12,14 +13,8 @@
|
||||
|
||||
namespace esphome {
|
||||
|
||||
// Forward declaration for friend access
|
||||
namespace api {
|
||||
class APIConnection;
|
||||
} // namespace api
|
||||
|
||||
namespace web_server {
|
||||
struct UrlMatch;
|
||||
} // namespace web_server
|
||||
// Maximum size for object_id buffer (friendly_name max ~120 + margin)
|
||||
static constexpr size_t OBJECT_ID_MAX_LEN = 128;
|
||||
|
||||
enum EntityCategory : uint8_t {
|
||||
ENTITY_CATEGORY_NONE = 0,
|
||||
@@ -47,6 +42,15 @@ class EntityBase {
|
||||
// Get the unique Object ID of this Entity
|
||||
uint32_t get_object_id_hash();
|
||||
|
||||
/// Get object_id with zero heap allocation
|
||||
/// For static case: returns StringRef to internal storage (buffer unused)
|
||||
/// For dynamic case: formats into buffer and returns StringRef to buffer
|
||||
StringRef get_object_id_to(std::span<char, OBJECT_ID_MAX_LEN> buf) const;
|
||||
|
||||
/// Write object_id directly to buffer, returns length written (excluding null)
|
||||
/// Useful for building compound strings without intermediate buffer
|
||||
size_t write_object_id_to(char *buf, size_t buf_size) const;
|
||||
|
||||
// Get/set whether this Entity should be hidden outside ESPHome
|
||||
bool is_internal() const { return this->flags_.internal; }
|
||||
void set_internal(bool internal) { this->flags_.internal = internal; }
|
||||
@@ -125,13 +129,6 @@ class EntityBase {
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class api::APIConnection;
|
||||
friend struct web_server::UrlMatch;
|
||||
|
||||
// Get object_id as StringRef when it's static (for API usage)
|
||||
// Returns empty StringRef if object_id is dynamic (needs allocation)
|
||||
StringRef get_object_id_ref_for_api_() const;
|
||||
|
||||
void calc_object_id_();
|
||||
|
||||
/// Check if the object_id is dynamic (changes with MAC suffix)
|
||||
|
||||
@@ -189,14 +189,6 @@ template<int (*fn)(int)> std::string str_ctype_transform(const std::string &str)
|
||||
}
|
||||
std::string str_lower_case(const std::string &str) { return str_ctype_transform<std::tolower>(str); }
|
||||
std::string str_upper_case(const std::string &str) { return str_ctype_transform<std::toupper>(str); }
|
||||
// Convert char to snake_case: lowercase and spaces to underscores
|
||||
static constexpr char to_snake_case_char(char c) {
|
||||
return (c == ' ') ? '_' : (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c;
|
||||
}
|
||||
// Sanitize char: keep alphanumerics, dashes, underscores; replace others with underscore
|
||||
static constexpr char to_sanitized_char(char c) {
|
||||
return (c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ? c : '_';
|
||||
}
|
||||
std::string str_snake_case(const std::string &str) {
|
||||
std::string result = str;
|
||||
for (char &c : result) {
|
||||
|
||||
@@ -516,9 +516,16 @@ std::string str_until(const std::string &str, char ch);
|
||||
std::string str_lower_case(const std::string &str);
|
||||
/// Convert the string to upper case.
|
||||
std::string str_upper_case(const std::string &str);
|
||||
|
||||
/// Convert a single char to snake_case: lowercase and space to underscore.
|
||||
constexpr char to_snake_case_char(char c) { return (c == ' ') ? '_' : (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; }
|
||||
/// Convert the string to snake case (lowercase with underscores).
|
||||
std::string str_snake_case(const std::string &str);
|
||||
|
||||
/// Sanitize a single char: keep alphanumerics, dashes, underscores; replace others with underscore.
|
||||
constexpr char to_sanitized_char(char c) {
|
||||
return (c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ? c : '_';
|
||||
}
|
||||
/// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores.
|
||||
std::string str_sanitize(const std::string &str);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user