diff --git a/esphome/components/api/proto.cpp b/esphome/components/api/proto.cpp index 89fdd8c8b2..bbd2595b7b 100644 --- a/esphome/components/api/proto.cpp +++ b/esphome/components/api/proto.cpp @@ -24,7 +24,7 @@ std::string ProtoMessage::dump() const { // ============================================================================ // Helper to get vector size for any repeated field type -static size_t get_vector_size(ProtoFieldType type, const void *field_addr) { +static inline size_t get_vector_size(ProtoFieldType type, const void *field_addr) { switch (type) { case ProtoFieldType::TYPE_BOOL: return static_cast *>(field_addr)->size(); @@ -52,7 +52,7 @@ static size_t get_vector_size(ProtoFieldType type, const void *field_addr) { } // Helper to get a pointer to the nth element in a vector (const version) -static const void *get_vector_element(ProtoFieldType type, const void *field_addr, size_t index) { +static inline const void *get_vector_element(ProtoFieldType type, const void *field_addr, size_t index) { switch (type) { case ProtoFieldType::TYPE_BOOL: { // std::vector is special - we need to handle it differently @@ -84,8 +84,8 @@ static const void *get_vector_element(ProtoFieldType type, const void *field_add } // Unified encode function that works for both single fields and repeated fields -static void encode_field(ProtoWriteBuffer &buffer, ProtoFieldType type, uint8_t field_num, const void *field_addr, - bool force) { +static inline void encode_field(ProtoWriteBuffer &buffer, ProtoFieldType type, uint8_t field_num, + const void *field_addr, bool force) { switch (type) { case ProtoFieldType::TYPE_BOOL: buffer.encode_bool(field_num, *static_cast(field_addr), force); @@ -132,8 +132,8 @@ static void encode_field(ProtoWriteBuffer &buffer, ProtoFieldType type, uint8_t } // Unified size calculation -static void calculate_field_size(uint32_t &total_size, ProtoFieldType type, uint8_t precalc_size, - const void *field_addr, bool force) { +static inline void calculate_field_size(uint32_t &total_size, ProtoFieldType type, uint8_t precalc_size, + const void *field_addr, bool force) { switch (type) { case ProtoFieldType::TYPE_BOOL: ProtoSize::add_bool_field(total_size, precalc_size, *static_cast(field_addr), force); @@ -176,7 +176,7 @@ static void calculate_field_size(uint32_t &total_size, ProtoFieldType type, uint } // Decode varint for single fields -static bool decode_varint_field(ProtoFieldType type, void *field_addr, const ProtoVarInt &value) { +static inline bool decode_varint_field(ProtoFieldType type, void *field_addr, const ProtoVarInt &value) { switch (type) { case ProtoFieldType::TYPE_BOOL: *static_cast(field_addr) = value.as_bool(); @@ -206,7 +206,7 @@ static bool decode_varint_field(ProtoFieldType type, void *field_addr, const Pro } // Decode varint for repeated fields -static bool decode_repeated_varint_field(ProtoFieldType type, void *field_addr, const ProtoVarInt &value) { +static inline bool decode_repeated_varint_field(ProtoFieldType type, void *field_addr, const ProtoVarInt &value) { switch (type) { case ProtoFieldType::TYPE_BOOL: static_cast *>(field_addr)->push_back(value.as_bool()); @@ -236,7 +236,7 @@ static bool decode_repeated_varint_field(ProtoFieldType type, void *field_addr, } // Decode 32-bit for single fields -static bool decode_32bit_field(ProtoFieldType type, void *field_addr, const Proto32Bit &value) { +static inline bool decode_32bit_field(ProtoFieldType type, void *field_addr, const Proto32Bit &value) { switch (type) { case ProtoFieldType::TYPE_FLOAT: *static_cast(field_addr) = value.as_float(); @@ -253,7 +253,7 @@ static bool decode_32bit_field(ProtoFieldType type, void *field_addr, const Prot } // Decode 32-bit for repeated fields -static bool decode_repeated_32bit_field(ProtoFieldType type, void *field_addr, const Proto32Bit &value) { +static inline bool decode_repeated_32bit_field(ProtoFieldType type, void *field_addr, const Proto32Bit &value) { switch (type) { case ProtoFieldType::TYPE_FLOAT: static_cast *>(field_addr)->push_back(value.as_float()); @@ -270,8 +270,8 @@ static bool decode_repeated_32bit_field(ProtoFieldType type, void *field_addr, c } // Decode length-delimited for single fields -static bool decode_length_field(ProtoFieldType type, void *field_addr, const ProtoLengthDelimited &value, - uint8_t message_type_id) { +static inline bool decode_length_field(ProtoFieldType type, void *field_addr, const ProtoLengthDelimited &value, + uint8_t message_type_id) { switch (type) { case ProtoFieldType::TYPE_STRING: case ProtoFieldType::TYPE_BYTES: @@ -288,8 +288,8 @@ static bool decode_length_field(ProtoFieldType type, void *field_addr, const Pro } // Decode length-delimited for repeated fields -static bool decode_repeated_length_field(ProtoFieldType type, void *field_addr, const ProtoLengthDelimited &value, - uint8_t message_type_id) { +static inline bool decode_repeated_length_field(ProtoFieldType type, void *field_addr, + const ProtoLengthDelimited &value, uint8_t message_type_id) { switch (type) { case ProtoFieldType::TYPE_STRING: case ProtoFieldType::TYPE_BYTES: @@ -486,8 +486,13 @@ void ProtoMessage::encode(ProtoWriteBuffer buffer) const { REPEATED_MESSAGE_HANDLERS[handler_id].encode(buffer, field_addr, repeated_fields[i].field_num); } } else { - // Iterate through the vector and encode each element using the same function! + // Early exit for empty vectors size_t count = get_vector_size(repeated_fields[i].get_type(), field_addr); + if (count == 0) { + continue; + } + + // Iterate through the vector and encode each element using the same function! for (size_t j = 0; j < count; j++) { const void *element = get_vector_element(repeated_fields[i].get_type(), field_addr, j); if (element != nullptr) { @@ -535,10 +540,18 @@ void ProtoMessage::calculate_size(uint32_t &total_size) const { ProtoFieldType type = repeated_fields[i].get_type(); size_t count = get_vector_size(type, field_addr); + // Early exit for empty vectors + if (count == 0) { + continue; + } + // For fixed-size types, we can calculate size more efficiently if (type == ProtoFieldType::TYPE_FIXED32 || type == ProtoFieldType::TYPE_SFIXED32 || type == ProtoFieldType::TYPE_FLOAT) { total_size += count * (repeated_fields[i].get_precalced_size() + 4); + } else if (type == ProtoFieldType::TYPE_BOOL) { + // Booleans are always 1 byte when encoded + total_size += count * (repeated_fields[i].get_precalced_size() + 1); } else { // For variable-size types, calculate each element for (size_t j = 0; j < count; j++) { diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index 4dcc25451a..051e07be42 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -40,7 +40,7 @@ enum class ProtoFieldType : uint8_t { }; // Helper to get wire type from field type -constexpr uint8_t get_wire_type(ProtoFieldType type) { +inline constexpr uint8_t get_wire_type(ProtoFieldType type) { switch (type) { case ProtoFieldType::TYPE_BOOL: case ProtoFieldType::TYPE_INT32: @@ -113,20 +113,20 @@ class ProtoVarInt { return {}; // Incomplete or invalid varint } - uint16_t as_uint16() const { return this->value_; } - uint32_t as_uint32() const { return this->value_; } - uint64_t as_uint64() const { return this->value_; } - bool as_bool() const { return this->value_; } - template T as_enum() const { return static_cast(this->as_uint32()); } - int32_t as_int32() const { + inline uint16_t as_uint16() const { return this->value_; } + inline uint32_t as_uint32() const { return this->value_; } + inline uint64_t as_uint64() const { return this->value_; } + inline bool as_bool() const { return this->value_; } + template inline T as_enum() const { return static_cast(this->as_uint32()); } + inline int32_t as_int32() const { // Not ZigZag encoded return static_cast(this->as_int64()); } - int64_t as_int64() const { + inline int64_t as_int64() const { // Not ZigZag encoded return static_cast(this->value_); } - int32_t as_sint32() const { + inline int32_t as_sint32() const { // with ZigZag encoding if (this->value_ & 1) { return static_cast(~(this->value_ >> 1)); @@ -134,7 +134,7 @@ class ProtoVarInt { return static_cast(this->value_ >> 1); } } - int64_t as_sint64() const { + inline int64_t as_sint64() const { // with ZigZag encoding if (this->value_ & 1) { return static_cast(~(this->value_ >> 1)); @@ -194,8 +194,10 @@ class ProtoVarInt { class ProtoLengthDelimited { public: explicit ProtoLengthDelimited(const uint8_t *value, size_t length) : value_(value), length_(length) {} - std::string as_string() const { return std::string(reinterpret_cast(this->value_), this->length_); } - template C as_message() const { + inline std::string as_string() const { + return std::string(reinterpret_cast(this->value_), this->length_); + } + template inline C as_message() const { auto msg = C(); msg.decode(this->value_, this->length_); return msg; @@ -209,9 +211,9 @@ class ProtoLengthDelimited { class Proto32Bit { public: explicit Proto32Bit(uint32_t value) : value_(value) {} - uint32_t as_fixed32() const { return this->value_; } - int32_t as_sfixed32() const { return static_cast(this->value_); } - float as_float() const { + inline uint32_t as_fixed32() const { return this->value_; } + inline int32_t as_sfixed32() const { return static_cast(this->value_); } + inline float as_float() const { union { uint32_t raw; float value; @@ -267,9 +269,9 @@ struct FieldMeta { }; // Helper methods - ProtoFieldType get_type() const { return static_cast(type_and_size & 0x1F); } - uint8_t get_precalced_size() const { return ((type_and_size >> 5) & 0x03) + 1; } - uint8_t get_wire_type() const { + inline ProtoFieldType get_type() const { return static_cast(type_and_size & 0x1F); } + inline uint8_t get_precalced_size() const { return ((type_and_size >> 5) & 0x03) + 1; } + inline uint8_t get_wire_type() const { // Wire type is encoded as: 0=varint, 2=length-delimited, 5=32-bit // We only need 1 bit to distinguish between 0/2 and 5 (32-bit) // If bit 7 is set, it's wire type 5, otherwise check the field type @@ -279,7 +281,7 @@ struct FieldMeta { ProtoFieldType t = get_type(); return (t >= ProtoFieldType::TYPE_STRING) ? 2 : 0; // length-delimited : varint } - uint16_t get_offset() const { + inline uint16_t get_offset() const { if (get_type() == ProtoFieldType::TYPE_MESSAGE) { // Reconstruct full offset from packed fields (10-bit offset) // Bits 0-7 from offset_low, bits 8-9 from lower 2 bits of message_type_id @@ -287,7 +289,7 @@ struct FieldMeta { } return offset; } - uint8_t get_message_type_id() const { return message_type_id >> 2; } // Upper 6 bits for type ID (0-63) + inline uint8_t get_message_type_id() const { return message_type_id >> 2; } // Upper 6 bits for type ID (0-63) }; // Optimized repeated field metadata (4 bytes - no padding on 32-bit architectures) @@ -303,9 +305,9 @@ struct RepeatedFieldMeta { }; // Helper methods - ProtoFieldType get_type() const { return static_cast(type_and_size & 0x1F); } - uint8_t get_precalced_size() const { return ((type_and_size >> 5) & 0x03) + 1; } - uint8_t get_wire_type() const { + inline ProtoFieldType get_type() const { return static_cast(type_and_size & 0x1F); } + inline uint8_t get_precalced_size() const { return ((type_and_size >> 5) & 0x03) + 1; } + inline uint8_t get_wire_type() const { // Wire type is encoded as: 0=varint, 2=length-delimited, 5=32-bit // We only need 1 bit to distinguish between 0/2 and 5 (32-bit) // If bit 7 is set, it's wire type 5, otherwise check the field type @@ -315,7 +317,7 @@ struct RepeatedFieldMeta { ProtoFieldType t = get_type(); return (t >= ProtoFieldType::TYPE_STRING) ? 2 : 0; // length-delimited : varint } - uint16_t get_offset() const { + inline uint16_t get_offset() const { if (get_type() == ProtoFieldType::TYPE_MESSAGE) { // Reconstruct full offset from packed fields (10-bit offset) // Bits 0-7 from offset_low, bits 8-9 from lower 2 bits of message_type_id @@ -323,7 +325,7 @@ struct RepeatedFieldMeta { } return offset; } - uint8_t get_message_type_id() const { return message_type_id >> 2; } // Upper 6 bits for type ID (0-63) + inline uint8_t get_message_type_id() const { return message_type_id >> 2; } // Upper 6 bits for type ID (0-63) }; // Binary search for field lookup - optimized for performance @@ -371,9 +373,9 @@ inline const FieldMeta *find_field_binary(const FieldMeta *fields, uint8_t count class ProtoWriteBuffer { public: ProtoWriteBuffer(std::vector *buffer) : buffer_(buffer) {} - void write(uint8_t value) { this->buffer_->push_back(value); } - void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); } - void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); } + inline void write(uint8_t value) { this->buffer_->push_back(value); } + inline void encode_varint_raw(ProtoVarInt value) { value.encode(*this->buffer_); } + inline void encode_varint_raw(uint32_t value) { this->encode_varint_raw(ProtoVarInt(value)); } /** * Encode a field key (tag/wire type combination). * @@ -386,11 +388,11 @@ class ProtoWriteBuffer { * * Following https://protobuf.dev/programming-guides/encoding/#structure */ - void encode_field_raw(uint32_t field_id, uint32_t type) { + inline void encode_field_raw(uint32_t field_id, uint32_t type) { uint32_t val = (field_id << 3) | (type & 0b111); this->encode_varint_raw(val); } - void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) { + inline void encode_string(uint32_t field_id, const char *string, size_t len, bool force = false) { if (len == 0 && !force) return; @@ -399,25 +401,25 @@ class ProtoWriteBuffer { auto *data = reinterpret_cast(string); this->buffer_->insert(this->buffer_->end(), data, data + len); } - void encode_string(uint32_t field_id, const std::string &value, bool force = false) { + inline void encode_string(uint32_t field_id, const std::string &value, bool force = false) { this->encode_string(field_id, value.data(), value.size(), force); } - void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) { + inline void encode_bytes(uint32_t field_id, const uint8_t *data, size_t len, bool force = false) { this->encode_string(field_id, reinterpret_cast(data), len, force); } - void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) { + inline void encode_uint32(uint32_t field_id, uint32_t value, bool force = false) { if (value == 0 && !force) return; this->encode_field_raw(field_id, 0); // type 0: Varint - uint32 this->encode_varint_raw(value); } - void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) { + inline void encode_uint64(uint32_t field_id, uint64_t value, bool force = false) { if (value == 0 && !force) return; this->encode_field_raw(field_id, 0); // type 0: Varint - uint64 this->encode_varint_raw(ProtoVarInt(value)); } - void encode_bool(uint32_t field_id, bool value, bool force = false) { + inline void encode_bool(uint32_t field_id, bool value, bool force = false) { if (!value && !force) return; this->encode_field_raw(field_id, 0); // type 0: Varint - bool @@ -428,15 +430,15 @@ class ProtoWriteBuffer { return; this->encode_field_raw(field_id, 5); // type 5: 32-bit fixed32 - this->write((value >> 0) & 0xFF); - this->write((value >> 8) & 0xFF); - this->write((value >> 16) & 0xFF); - this->write((value >> 24) & 0xFF); + // Reserve space and write all 4 bytes at once for better performance + size_t pos = this->buffer_->size(); + this->buffer_->resize(pos + 4); + (*this->buffer_)[pos] = (value >> 0) & 0xFF; + (*this->buffer_)[pos + 1] = (value >> 8) & 0xFF; + (*this->buffer_)[pos + 2] = (value >> 16) & 0xFF; + (*this->buffer_)[pos + 3] = (value >> 24) & 0xFF; } - template void encode_enum(uint32_t field_id, T value, bool force = false) { - this->encode_uint32(field_id, static_cast(value), force); - } - void encode_float(uint32_t field_id, float value, bool force = false) { + inline void encode_float(uint32_t field_id, float value, bool force = false) { if (value == 0.0f && !force) return; @@ -447,7 +449,7 @@ class ProtoWriteBuffer { val.value = value; this->encode_fixed32(field_id, val.raw); } - void encode_int32(uint32_t field_id, int32_t value, bool force = false) { + inline void encode_int32(uint32_t field_id, int32_t value, bool force = false) { if (value < 0) { // negative int32 is always 10 byte long this->encode_int64(field_id, value, force); @@ -455,10 +457,10 @@ class ProtoWriteBuffer { } this->encode_uint32(field_id, static_cast(value), force); } - void encode_int64(uint32_t field_id, int64_t value, bool force = false) { + inline void encode_int64(uint32_t field_id, int64_t value, bool force = false) { this->encode_uint64(field_id, static_cast(value), force); } - void encode_sint32(uint32_t field_id, int32_t value, bool force = false) { + inline void encode_sint32(uint32_t field_id, int32_t value, bool force = false) { uint32_t uvalue; if (value < 0) { uvalue = ~(value << 1); @@ -467,7 +469,7 @@ class ProtoWriteBuffer { } this->encode_uint32(field_id, uvalue, force); } - void encode_sint64(uint32_t field_id, int64_t value, bool force = false) { + inline void encode_sint64(uint32_t field_id, int64_t value, bool force = false) { uint64_t uvalue; if (value < 0) { uvalue = ~(value << 1); @@ -476,7 +478,7 @@ class ProtoWriteBuffer { } this->encode_uint64(field_id, uvalue, force); } - void encode_sfixed32(uint32_t field_id, int32_t value, bool force = false) { + inline void encode_sfixed32(uint32_t field_id, int32_t value, bool force = false) { if (!force && value == 0) return; this->encode_fixed32(field_id, static_cast(value), force);