From 36a598fa40e16f49ec6eba5cc5ebef6e23f39b7b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 3 Mar 2026 22:22:51 -1000 Subject: [PATCH 1/2] [core] Call loop() directly in main loop, bypass call() indirection In the main loop, components in looping_components_ active section are guaranteed to be in LOOP state. The call() method's state machine dispatch (checking CONSTRUCTION, SETUP, FAILED, LOOP_DONE) is only needed during Application::setup(). In the main loop it adds two unnecessary function call frames per component per iteration (call() -> call_loop_() -> loop()). This became dead weight when looping_components_ partitioning was introduced in June 2025 (8a06c4380d). Before that, Application::loop() iterated components_[] which contained all states, so the state check was necessary. --- esphome/core/application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 8c2ba58c86e..f827783503f 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -173,7 +173,7 @@ void Application::loop() { { this->set_current_component(component); WarnIfComponentBlockingGuard guard{component, last_op_end_time}; - component->call(); + component->loop(); // Use the finish method to get the current time as the end time last_op_end_time = guard.finish(); } From fe0436166c12eff9672f58d38a9fed64d30504e8 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 3 Mar 2026 22:31:04 -1000 Subject: [PATCH 2/2] =?UTF-8?q?[core]=20Ensure=20SETUP=E2=86=92LOOP=20tran?= =?UTF-8?q?sition=20before=20main=20loop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Components after the last blocking component in setup only receive one call() (CONSTRUCTION→SETUP) and never get the second call() that would transition them to LOOP state. Explicitly transition all active looping components to LOOP state at the end of setup() so the main loop can call loop() directly without the call() state machine wrapper. --- esphome/core/application.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index f827783503f..db1c8a0c0a1 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -153,6 +153,14 @@ void Application::setup() { this->setup_wake_loop_threadsafe_(); #endif + // Ensure all active looping components are in LOOP state. + // Components after the last blocking component only got one call() during setup + // (CONSTRUCTION→SETUP) and never received the second call() (SETUP→LOOP). + // The main loop calls loop() directly, bypassing call()'s state machine. + for (uint16_t i = 0; i < this->looping_components_active_end_; i++) { + this->looping_components_[i]->set_component_state_(COMPONENT_STATE_LOOP); + } + this->schedule_dump_config(); } void Application::loop() {