mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Fix regression: BK7231N devices not returning entities via API
This commit is contained in:
		| @@ -1687,7 +1687,9 @@ void APIConnection::DeferredBatch::add_item(EntityBase *entity, MessageCreator c | |||||||
|   // O(n) but optimized for RAM and not performance. |   // O(n) but optimized for RAM and not performance. | ||||||
|   for (auto &item : items) { |   for (auto &item : items) { | ||||||
|     if (item.entity == entity && item.message_type == message_type) { |     if (item.entity == entity && item.message_type == message_type) { | ||||||
|       // Update the existing item with the new creator |       // Clean up old creator before replacing | ||||||
|  |       item.creator.cleanup(message_type); | ||||||
|  |       // Move assign the new creator | ||||||
|       item.creator = std::move(creator); |       item.creator = std::move(creator); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| @@ -1730,11 +1732,11 @@ void APIConnection::process_batch_() { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   size_t num_items = this->deferred_batch_.items.size(); |   size_t num_items = this->deferred_batch_.size(); | ||||||
|  |  | ||||||
|   // Fast path for single message - allocate exact size needed |   // Fast path for single message - allocate exact size needed | ||||||
|   if (num_items == 1) { |   if (num_items == 1) { | ||||||
|     const auto &item = this->deferred_batch_.items[0]; |     const auto &item = this->deferred_batch_[0]; | ||||||
|  |  | ||||||
|     // Let the creator calculate size and encode if it fits |     // Let the creator calculate size and encode if it fits | ||||||
|     uint16_t payload_size = |     uint16_t payload_size = | ||||||
| @@ -1764,7 +1766,8 @@ void APIConnection::process_batch_() { | |||||||
|  |  | ||||||
|   // Pre-calculate exact buffer size needed based on message types |   // Pre-calculate exact buffer size needed based on message types | ||||||
|   uint32_t total_estimated_size = 0; |   uint32_t total_estimated_size = 0; | ||||||
|   for (const auto &item : this->deferred_batch_.items) { |   for (size_t i = 0; i < this->deferred_batch_.size(); i++) { | ||||||
|  |     const auto &item = this->deferred_batch_[i]; | ||||||
|     total_estimated_size += get_estimated_message_size(item.message_type); |     total_estimated_size += get_estimated_message_size(item.message_type); | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -1785,7 +1788,8 @@ void APIConnection::process_batch_() { | |||||||
|   uint32_t current_offset = 0; |   uint32_t current_offset = 0; | ||||||
|  |  | ||||||
|   // Process items and encode directly to buffer |   // Process items and encode directly to buffer | ||||||
|   for (const auto &item : this->deferred_batch_.items) { |   for (size_t i = 0; i < this->deferred_batch_.size(); i++) { | ||||||
|  |     const auto &item = this->deferred_batch_[i]; | ||||||
|     // Try to encode message |     // Try to encode message | ||||||
|     // The creator will calculate overhead to determine if the message fits |     // The creator will calculate overhead to determine if the message fits | ||||||
|     uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type); |     uint16_t payload_size = item.creator(item.entity, this, remaining_size, false, item.message_type); | ||||||
| @@ -1840,17 +1844,15 @@ void APIConnection::process_batch_() { | |||||||
|   // Log messages after send attempt for VV debugging |   // Log messages after send attempt for VV debugging | ||||||
|   // It's safe to use the buffer for logging at this point regardless of send result |   // It's safe to use the buffer for logging at this point regardless of send result | ||||||
|   for (size_t i = 0; i < items_processed; i++) { |   for (size_t i = 0; i < items_processed; i++) { | ||||||
|     const auto &item = this->deferred_batch_.items[i]; |     const auto &item = this->deferred_batch_[i]; | ||||||
|     this->log_batch_item_(item); |     this->log_batch_item_(item); | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   // Handle remaining items more efficiently |   // Handle remaining items more efficiently | ||||||
|   if (items_processed < this->deferred_batch_.items.size()) { |   if (items_processed < this->deferred_batch_.size()) { | ||||||
|     // Remove processed items from the beginning |     // Remove processed items from the beginning with proper cleanup | ||||||
|     this->deferred_batch_.items.erase(this->deferred_batch_.items.begin(), |     this->deferred_batch_.remove_front(items_processed); | ||||||
|                                       this->deferred_batch_.items.begin() + items_processed); |  | ||||||
|  |  | ||||||
|     // Reschedule for remaining items |     // Reschedule for remaining items | ||||||
|     this->schedule_batch_(); |     this->schedule_batch_(); | ||||||
|   } else { |   } else { | ||||||
| @@ -1861,23 +1863,16 @@ void APIConnection::process_batch_() { | |||||||
|  |  | ||||||
| uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::MessageCreator::operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                                    bool is_single, uint16_t message_type) const { |                                                    bool is_single, uint16_t message_type) const { | ||||||
|   if (has_tagged_string_ptr_()) { |  | ||||||
|     // Handle string-based messages |  | ||||||
|     switch (message_type) { |  | ||||||
| #ifdef USE_EVENT | #ifdef USE_EVENT | ||||||
|       case EventResponse::MESSAGE_TYPE: { |   // Special case: EventResponse uses string pointer | ||||||
|  |   if (message_type == EventResponse::MESSAGE_TYPE) { | ||||||
|     auto *e = static_cast<event::Event *>(entity); |     auto *e = static_cast<event::Event *>(entity); | ||||||
|         return APIConnection::try_send_event_response(e, *get_string_ptr_(), conn, remaining_size, is_single); |     return APIConnection::try_send_event_response(e, *data_.string_ptr, conn, remaining_size, is_single); | ||||||
|   } |   } | ||||||
| #endif | #endif | ||||||
|       default: |  | ||||||
|         // Should not happen, return 0 to indicate no message |   // All other message types use function pointers | ||||||
|         return 0; |   return data_.function_ptr(entity, conn, remaining_size, is_single); | ||||||
|     } |  | ||||||
|   } else { |  | ||||||
|     // Function pointer case |  | ||||||
|     return data_.ptr(entity, conn, remaining_size, is_single); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_list_info_done(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|   | |||||||
| @@ -451,96 +451,53 @@ class APIConnection : public APIServerConnection { | |||||||
|   // Function pointer type for message encoding |   // Function pointer type for message encoding | ||||||
|   using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); |   using MessageCreatorPtr = uint16_t (*)(EntityBase *, APIConnection *, uint32_t remaining_size, bool is_single); | ||||||
|  |  | ||||||
|   // Optimized MessageCreator class using tagged pointer |  | ||||||
|   class MessageCreator { |   class MessageCreator { | ||||||
|     // Ensure pointer alignment allows LSB tagging |  | ||||||
|     static_assert(alignof(std::string *) > 1, "String pointer alignment must be > 1 for LSB tagging"); |  | ||||||
|  |  | ||||||
|    public: |    public: | ||||||
|     // Constructor for function pointer |     // Constructor for function pointer | ||||||
|     MessageCreator(MessageCreatorPtr ptr) { |     MessageCreator(MessageCreatorPtr ptr) { data_.function_ptr = ptr; } | ||||||
|       // Function pointers are always aligned, so LSB is 0 |  | ||||||
|       data_.ptr = ptr; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Constructor for string state capture |     // Constructor for string state capture | ||||||
|     explicit MessageCreator(const std::string &str_value) { |     explicit MessageCreator(const std::string &str_value) { data_.string_ptr = new std::string(str_value); } | ||||||
|       // Allocate string and tag the pointer |  | ||||||
|       auto *str = new std::string(str_value); |  | ||||||
|       // Set LSB to 1 to indicate string pointer |  | ||||||
|       data_.tagged = reinterpret_cast<uintptr_t>(str) | 1; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Destructor |     // No destructor - cleanup must be called explicitly with message_type | ||||||
|     ~MessageCreator() { |  | ||||||
|       if (has_tagged_string_ptr_()) { |  | ||||||
|         delete get_string_ptr_(); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Copy constructor |     // Delete copy operations - MessageCreator should only be moved | ||||||
|     MessageCreator(const MessageCreator &other) { |     MessageCreator(const MessageCreator &other) = delete; | ||||||
|       if (other.has_tagged_string_ptr_()) { |     MessageCreator &operator=(const MessageCreator &other) = delete; | ||||||
|         auto *str = new std::string(*other.get_string_ptr_()); |  | ||||||
|         data_.tagged = reinterpret_cast<uintptr_t>(str) | 1; |  | ||||||
|       } else { |  | ||||||
|         data_ = other.data_; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Move constructor |     // Move constructor | ||||||
|     MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.ptr = nullptr; } |     MessageCreator(MessageCreator &&other) noexcept : data_(other.data_) { other.data_.function_ptr = nullptr; } | ||||||
|  |  | ||||||
|     // Assignment operators (needed for batch deduplication) |  | ||||||
|     MessageCreator &operator=(const MessageCreator &other) { |  | ||||||
|       if (this != &other) { |  | ||||||
|         // Clean up current string data if needed |  | ||||||
|         if (has_tagged_string_ptr_()) { |  | ||||||
|           delete get_string_ptr_(); |  | ||||||
|         } |  | ||||||
|         // Copy new data |  | ||||||
|         if (other.has_tagged_string_ptr_()) { |  | ||||||
|           auto *str = new std::string(*other.get_string_ptr_()); |  | ||||||
|           data_.tagged = reinterpret_cast<uintptr_t>(str) | 1; |  | ||||||
|         } else { |  | ||||||
|           data_ = other.data_; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       return *this; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |     // Move assignment | ||||||
|     MessageCreator &operator=(MessageCreator &&other) noexcept { |     MessageCreator &operator=(MessageCreator &&other) noexcept { | ||||||
|       if (this != &other) { |       if (this != &other) { | ||||||
|         // Clean up current string data if needed |         // IMPORTANT: Caller must ensure cleanup() was called if this contains a string! | ||||||
|         if (has_tagged_string_ptr_()) { |         // In our usage, this happens in add_item() deduplication and vector::erase() | ||||||
|           delete get_string_ptr_(); |  | ||||||
|         } |  | ||||||
|         // Move data |  | ||||||
|         data_ = other.data_; |         data_ = other.data_; | ||||||
|         // Reset other to safe state |         other.data_.function_ptr = nullptr; | ||||||
|         other.data_.ptr = nullptr; |  | ||||||
|       } |       } | ||||||
|       return *this; |       return *this; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Call operator - now accepts message_type as parameter |     // Call operator - uses message_type to determine union type | ||||||
|     uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, |     uint16_t operator()(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, bool is_single, | ||||||
|                         uint16_t message_type) const; |                         uint16_t message_type) const; | ||||||
|  |  | ||||||
|    private: |     // Manual cleanup method - must be called before destruction for string types | ||||||
|     // Check if this contains a string pointer |     void cleanup(uint16_t message_type) { | ||||||
|     bool has_tagged_string_ptr_() const { return (data_.tagged & 1) != 0; } | #ifdef USE_EVENT | ||||||
|  |       if (message_type == EventResponse::MESSAGE_TYPE && data_.string_ptr != nullptr) { | ||||||
|     // Get the actual string pointer (clears the tag bit) |         delete data_.string_ptr; | ||||||
|     std::string *get_string_ptr_() const { |         data_.string_ptr = nullptr; | ||||||
|       // NOLINTNEXTLINE(performance-no-int-to-ptr) |       } | ||||||
|       return reinterpret_cast<std::string *>(data_.tagged & ~uintptr_t(1)); | #endif | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     union { |    private: | ||||||
|       MessageCreatorPtr ptr; |     union Data { | ||||||
|       uintptr_t tagged; |       MessageCreatorPtr function_ptr; | ||||||
|     } data_;  // 4 bytes on 32-bit |       std::string *string_ptr; | ||||||
|  |     } data_;  // 4 bytes on 32-bit, 8 bytes on 64-bit - same as before | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   // Generic batching mechanism for both state updates and entity info |   // Generic batching mechanism for both state updates and entity info | ||||||
| @@ -558,20 +515,46 @@ class APIConnection : public APIServerConnection { | |||||||
|     std::vector<BatchItem> items; |     std::vector<BatchItem> items; | ||||||
|     uint32_t batch_start_time{0}; |     uint32_t batch_start_time{0}; | ||||||
|  |  | ||||||
|  |    private: | ||||||
|  |     // Helper to cleanup items from the beginning | ||||||
|  |     void cleanup_items(size_t count) { | ||||||
|  |       for (size_t i = 0; i < count; i++) { | ||||||
|  |         items[i].creator.cleanup(items[i].message_type); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |    public: | ||||||
|     DeferredBatch() { |     DeferredBatch() { | ||||||
|       // Pre-allocate capacity for typical batch sizes to avoid reallocation |       // Pre-allocate capacity for typical batch sizes to avoid reallocation | ||||||
|       items.reserve(8); |       items.reserve(8); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     ~DeferredBatch() { | ||||||
|  |       // Ensure cleanup of any remaining items | ||||||
|  |       clear(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Add item to the batch |     // Add item to the batch | ||||||
|     void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type); |     void add_item(EntityBase *entity, MessageCreator creator, uint16_t message_type); | ||||||
|     // Add item to the front of the batch (for high priority messages like ping) |     // Add item to the front of the batch (for high priority messages like ping) | ||||||
|     void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type); |     void add_item_front(EntityBase *entity, MessageCreator creator, uint16_t message_type); | ||||||
|  |  | ||||||
|  |     // Clear all items with proper cleanup | ||||||
|     void clear() { |     void clear() { | ||||||
|  |       cleanup_items(items.size()); | ||||||
|       items.clear(); |       items.clear(); | ||||||
|       batch_start_time = 0; |       batch_start_time = 0; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // Remove processed items from the front with proper cleanup | ||||||
|  |     void remove_front(size_t count) { | ||||||
|  |       cleanup_items(count); | ||||||
|  |       items.erase(items.begin(), items.begin() + count); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     bool empty() const { return items.empty(); } |     bool empty() const { return items.empty(); } | ||||||
|  |     size_t size() const { return items.size(); } | ||||||
|  |     const BatchItem &operator[](size_t index) const { return items[index]; } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   // DeferredBatch here (16 bytes, 4-byte aligned) |   // DeferredBatch here (16 bytes, 4-byte aligned) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user