From dee78b8a8e48e543f8d5f096d56f37e1bf9721fe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 3 Apr 2026 22:39:19 -1000 Subject: [PATCH] fix slowdown --- esphome/components/api/api_connection.cpp | 42 +------------------ esphome/components/api/api_connection.h | 50 +++++++++++++++++++++-- 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index a25be074e30..9b8dce7b41a 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -1996,47 +1996,7 @@ bool APIConnection::send_message_(uint32_t payload_size, uint8_t message_type, M encode_fn(msg, buffer); return this->send_buffer(ProtoWriteBuffer{&shared_buf}, message_type); } -// Encodes a message to the buffer and returns the total number of bytes used, -// including header and footer overhead. Returns 0 if the message doesn't fit. -uint16_t APIConnection::encode_to_buffer(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg, - APIConnection *conn, uint32_t remaining_size) { -#ifdef HAS_PROTO_MESSAGE_DUMP - if (conn->flags_.log_only_mode) { - auto *proto_msg = static_cast(msg); - DumpBuffer dump_buf; - conn->log_send_message_(proto_msg->message_name(), proto_msg->dump_to(dump_buf)); - return 1; - } -#endif - // Cache frame sizes to avoid repeated virtual calls - const uint8_t header_padding = conn->helper_->frame_header_padding(); - const uint8_t footer_size = conn->helper_->frame_footer_size(); - - // Calculate total size with padding for buffer allocation - size_t total_calculated_size = calculated_size + header_padding + footer_size; - - // Check if it fits - if (total_calculated_size > remaining_size) - return 0; // Doesn't fit - - auto &shared_buf = conn->parent_->get_shared_buffer_ref(); - - // First message: padding already in buffer, only add payload. - // Subsequent messages: use exact header size for gap-free packing. - bool first = conn->flags_.batch_first_message; - if (first) - conn->flags_.batch_first_message = false; - conn->batch_header_size_ = - first ? header_padding : conn->helper_->frame_header_size(calculated_size, conn->batch_message_type_); - size_t to_add = first ? calculated_size : (calculated_size + conn->batch_header_size_ + footer_size); - - shared_buf.resize(shared_buf.size() + to_add); - ProtoWriteBuffer buffer{&shared_buf, shared_buf.size() - calculated_size}; - encode_fn(msg, buffer); - - // Return total size (header + payload + footer) - return static_cast(total_calculated_size); -} +// encode_to_buffer is defined inline in api_connection.h (ESPHOME_ALWAYS_INLINE) bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) { const bool is_log_message = (message_type == SubscribeLogsResponse::MESSAGE_TYPE); diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index d0bccf418ff..037b13d17c1 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -402,9 +402,53 @@ class APIConnection final : public APIServerConnectionBase { // Non-template buffer management for send_message bool send_message_(uint32_t payload_size, uint8_t message_type, MessageEncodeFn encode_fn, const void *msg); - // Non-template buffer management for batch encoding - static uint16_t encode_to_buffer(uint32_t calculated_size, MessageEncodeFn encode_fn, const void *msg, - APIConnection *conn, uint32_t remaining_size); + // Non-template buffer management for batch encoding. + // ALWAYS_INLINE so it merges into each call site (encode_message_to_buffer, + // fill_and_encode_entity_state) — the compiler can then inline the encode_fn + // callback and eliminate the indirect call. + static inline uint16_t ESPHOME_ALWAYS_INLINE encode_to_buffer(uint32_t calculated_size, MessageEncodeFn encode_fn, + const void *msg, APIConnection *conn, + uint32_t remaining_size) { +#ifdef HAS_PROTO_MESSAGE_DUMP + if (conn->flags_.log_only_mode) { + auto *proto_msg = static_cast(msg); + DumpBuffer dump_buf; + conn->log_send_message_(proto_msg->message_name(), proto_msg->dump_to(dump_buf)); + return 1; + } +#endif + // Cache frame sizes to avoid repeated virtual calls + const uint8_t header_padding = conn->helper_->frame_header_padding(); + const uint8_t footer_size = conn->helper_->frame_footer_size(); + + // Calculate total size with padding for buffer allocation + size_t total_calculated_size = calculated_size + header_padding + footer_size; + + // Check if it fits + if (total_calculated_size > remaining_size) + return 0; // Doesn't fit + + auto &shared_buf = conn->parent_->get_shared_buffer_ref(); + + // First message: padding already in buffer, only add payload. + // Subsequent messages: use exact header size for gap-free packing. + size_t to_add; + if (conn->flags_.batch_first_message) { + conn->flags_.batch_first_message = false; + conn->batch_header_size_ = header_padding; + to_add = calculated_size; + } else { + conn->batch_header_size_ = conn->helper_->frame_header_size(calculated_size, conn->batch_message_type_); + to_add = calculated_size + conn->batch_header_size_ + footer_size; + } + + shared_buf.resize(shared_buf.size() + to_add); + ProtoWriteBuffer buffer{&shared_buf, shared_buf.size() - calculated_size}; + encode_fn(msg, buffer); + + // Return total size (header + payload + footer) + return static_cast(total_calculated_size); + } // Thin template wrapper — computes size, delegates buffer work to non-template helper template static uint16_t encode_message_to_buffer(T &msg, APIConnection *conn, uint32_t remaining_size) {