[api] Extract cold code from APIConnection::loop() hot path (#13901)

This commit is contained in:
J. Nick Koston
2026-02-10 21:30:34 -06:00
committed by GitHub
parent e3bafc1b45
commit 5281fd3273
5 changed files with 56 additions and 44 deletions
+45 -29
View File
@@ -219,35 +219,8 @@ void APIConnection::loop() {
this->process_batch_(); this->process_batch_();
} }
switch (this->active_iterator_) { if (this->active_iterator_ != ActiveIterator::NONE) {
case ActiveIterator::LIST_ENTITIES: this->process_active_iterator_();
if (this->iterator_storage_.list_entities.completed()) {
this->destroy_active_iterator_();
if (this->flags_.state_subscription) {
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
}
} else {
this->process_iterator_batch_(this->iterator_storage_.list_entities);
}
break;
case ActiveIterator::INITIAL_STATE:
if (this->iterator_storage_.initial_state.completed()) {
this->destroy_active_iterator_();
// Process any remaining batched messages immediately
if (!this->deferred_batch_.empty()) {
this->process_batch_();
}
// Now that everything is sent, enable immediate sending for future state changes
this->flags_.should_try_send_immediately = true;
// Release excess memory from buffers that grew during initial sync
this->deferred_batch_.release_buffer();
this->helper_->release_buffers();
} else {
this->process_iterator_batch_(this->iterator_storage_.initial_state);
}
break;
case ActiveIterator::NONE:
break;
} }
if (this->flags_.sent_ping) { if (this->flags_.sent_ping) {
@@ -283,6 +256,49 @@ void APIConnection::loop() {
#endif #endif
} }
void APIConnection::process_active_iterator_() {
// Caller ensures active_iterator_ != NONE
if (this->active_iterator_ == ActiveIterator::LIST_ENTITIES) {
if (this->iterator_storage_.list_entities.completed()) {
this->destroy_active_iterator_();
if (this->flags_.state_subscription) {
this->begin_iterator_(ActiveIterator::INITIAL_STATE);
}
} else {
this->process_iterator_batch_(this->iterator_storage_.list_entities);
}
} else { // INITIAL_STATE
if (this->iterator_storage_.initial_state.completed()) {
this->destroy_active_iterator_();
// Process any remaining batched messages immediately
if (!this->deferred_batch_.empty()) {
this->process_batch_();
}
// Now that everything is sent, enable immediate sending for future state changes
this->flags_.should_try_send_immediately = true;
// Release excess memory from buffers that grew during initial sync
this->deferred_batch_.release_buffer();
this->helper_->release_buffers();
} else {
this->process_iterator_batch_(this->iterator_storage_.initial_state);
}
}
}
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_() { bool APIConnection::send_disconnect_response_() {
// remote initiated disconnect_client // remote initiated disconnect_client
// don't close yet, we still need to send the disconnect response // don't close yet, we still need to send the disconnect response
+10 -13
View File
@@ -15,6 +15,10 @@
#include <limits> #include <limits>
#include <vector> #include <vector>
namespace esphome {
class ComponentIterator;
} // namespace esphome
namespace esphome::api { namespace esphome::api {
// Keepalive timeout in milliseconds // Keepalive timeout in milliseconds
@@ -366,20 +370,13 @@ class APIConnection final : public APIServerConnectionBase {
return this->client_supports_api_version(1, 14) ? MAX_INITIAL_PER_BATCH : MAX_INITIAL_PER_BATCH_LEGACY; 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 // Process active iterator (list_entities/initial_state) during connection setup.
template<typename Iterator> void process_iterator_batch_(Iterator &iterator) { // Extracted from loop() — only runs during initial handshake, NONE in steady state.
size_t initial_size = this->deferred_batch_.size(); void __attribute__((noinline)) process_active_iterator_();
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 // Helper method to process multiple entities from an iterator in a batch.
// Note: iterator.advance() already calls schedule_batch_() via schedule_message_() // Takes ComponentIterator base class reference to avoid duplicate template instantiations.
if (this->deferred_batch_.size() >= max_batch) { void process_iterator_batch_(ComponentIterator &iterator);
this->process_batch_();
}
}
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size); static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size);
-1
View File
@@ -94,7 +94,6 @@ class ListEntitiesIterator : public ComponentIterator {
bool on_update(update::UpdateEntity *entity) override; bool on_update(update::UpdateEntity *entity) override;
#endif #endif
bool on_end() override; bool on_end() override;
bool completed() { return this->state_ == IteratorState::NONE; }
protected: protected:
APIConnection *client_; APIConnection *client_;
-1
View File
@@ -88,7 +88,6 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_UPDATE #ifdef USE_UPDATE
bool on_update(update::UpdateEntity *entity) override; bool on_update(update::UpdateEntity *entity) override;
#endif #endif
bool completed() { return this->state_ == IteratorState::NONE; }
protected: protected:
APIConnection *client_; APIConnection *client_;
+1
View File
@@ -26,6 +26,7 @@ class ComponentIterator {
public: public:
void begin(bool include_internal = false); void begin(bool include_internal = false);
void advance(); void advance();
bool completed() const { return this->state_ == IteratorState::NONE; }
virtual bool on_begin(); virtual bool on_begin();
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
virtual bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) = 0; virtual bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) = 0;