From 9bf90eff01b0959f463a5c62b8da06c998b92792 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 9 Feb 2026 19:57:29 -0600 Subject: [PATCH] [api] De-duplicate iterator batch processing in APIConnection::loop() Replace the process_iterator_batch_ template (instantiated separately for ListEntitiesIterator and InitialStateIterator) with a single non-template method taking a ComponentIterator& base class reference. Both iterators inherit from ComponentIterator, and the template only called completed() and advance() which are both base class methods. The two template instantiations generated ~160 bytes of duplicate code in APIConnection::loop(). The single non-template version is 79 bytes, saving 61 bytes net flash and removing 140 bytes of cold connect/reconnect code from the hot loop path. Also move the duplicated completed() method from ListEntitiesIterator and InitialStateIterator to the ComponentIterator base class where state_ is defined. --- esphome/components/api/api_connection.cpp | 14 ++++++++++++++ esphome/components/api/api_connection.h | 17 +++-------------- esphome/components/api/list_entities.h | 1 - esphome/components/api/subscribe_state.h | 1 - esphome/core/component_iterator.h | 1 + 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index ddc24a7e2c..96a6ed7ae9 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -283,6 +283,20 @@ void APIConnection::loop() { #endif } +void APIConnection::process_iterator_batch_(ComponentIterator &iterator) { + size_t initial_size = this->deferred_batch_.size(); + size_t max_batch = this->get_max_batch_size_(); + while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) { + iterator.advance(); + } + + // If the batch is full, process it immediately + // Note: iterator.advance() already calls schedule_batch_() via schedule_message_() + if (this->deferred_batch_.size() >= max_batch) { + this->process_batch_(); + } +} + bool APIConnection::send_disconnect_response_() { // remote initiated disconnect_client // don't close yet, we still need to send the disconnect response diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 7f738a9bfd..3115507fa3 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -364,20 +364,9 @@ class APIConnection final : public APIServerConnectionBase { return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY; } - // Helper method to process multiple entities from an iterator in a batch - template void process_iterator_batch_(Iterator &iterator) { - size_t initial_size = this->deferred_batch_.size(); - size_t max_batch = this->get_max_batch_size_(); - while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < max_batch) { - iterator.advance(); - } - - // If the batch is full, process it immediately - // Note: iterator.advance() already calls schedule_batch_() via schedule_message_() - if (this->deferred_batch_.size() >= max_batch) { - this->process_batch_(); - } - } + // Helper method to process multiple entities from an iterator in a batch. + // Takes ComponentIterator base class reference to avoid duplicate template instantiations. + void process_iterator_batch_(ComponentIterator &iterator); #ifdef USE_BINARY_SENSOR static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index bef36dd015..90769f9a81 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -94,7 +94,6 @@ class ListEntitiesIterator : public ComponentIterator { bool on_update(update::UpdateEntity *entity) override; #endif bool on_end() override; - bool completed() { return this->state_ == IteratorState::NONE; } protected: APIConnection *client_; diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index 3c9f33835a..6f8577ca7b 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -88,7 +88,6 @@ class InitialStateIterator : public ComponentIterator { #ifdef USE_UPDATE bool on_update(update::UpdateEntity *entity) override; #endif - bool completed() { return this->state_ == IteratorState::NONE; } protected: APIConnection *client_; diff --git a/esphome/core/component_iterator.h b/esphome/core/component_iterator.h index e13d81a8e4..6c03b74a17 100644 --- a/esphome/core/component_iterator.h +++ b/esphome/core/component_iterator.h @@ -26,6 +26,7 @@ class ComponentIterator { public: void begin(bool include_internal = false); void advance(); + bool completed() const { return this->state_ == IteratorState::NONE; } virtual bool on_begin(); #ifdef USE_BINARY_SENSOR virtual bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) = 0;