1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-14 17:22:20 +01:00

Merge branch 'loop_done' into integration

This commit is contained in:
J. Nick Koston
2025-06-15 18:40:46 -05:00
5 changed files with 93 additions and 15 deletions

View File

@@ -256,6 +256,7 @@ void ESP32ImprovComponent::start() {
ESP_LOGD(TAG, "Setting Improv to start"); ESP_LOGD(TAG, "Setting Improv to start");
this->should_start_ = true; this->should_start_ = true;
this->enable_loop();
} }
void ESP32ImprovComponent::stop() { void ESP32ImprovComponent::stop() {

View File

@@ -97,11 +97,12 @@ void Application::loop() {
// Feed WDT with time // Feed WDT with time
this->feed_wdt(last_op_end_time); this->feed_wdt(last_op_end_time);
for (Component *component : this->looping_components_) { // Mark that we're in the loop for safe reentrant modifications
// Skip components that are done or failed this->in_loop_ = true;
if (component->should_skip_loop()) {
continue; for (this->current_loop_index_ = 0; this->current_loop_index_ < this->looping_components_active_end_;
} this->current_loop_index_++) {
Component *component = this->looping_components_[this->current_loop_index_];
// Update the cached time before each component runs // Update the cached time before each component runs
this->loop_component_start_time_ = last_op_end_time; this->loop_component_start_time_ = last_op_end_time;
@@ -117,6 +118,8 @@ void Application::loop() {
this->app_state_ |= new_app_state; this->app_state_ |= new_app_state;
this->feed_wdt(last_op_end_time); this->feed_wdt(last_op_end_time);
} }
this->in_loop_ = false;
this->app_state_ = new_app_state; this->app_state_ = new_app_state;
// Use the last component's end time instead of calling millis() again // Use the last component's end time instead of calling millis() again
@@ -244,6 +247,52 @@ void Application::calculate_looping_components_() {
if (obj->has_overridden_loop()) if (obj->has_overridden_loop())
this->looping_components_.push_back(obj); this->looping_components_.push_back(obj);
} }
// Initially all components are active
this->looping_components_active_end_ = this->looping_components_.size();
}
void Application::disable_component_loop(Component *component) {
// Linear search to find component in active section
// Most configs have 10-30 looping components (30 is on the high end)
// O(n) is acceptable here as we optimize for memory, not complexity
for (uint16_t i = 0; i < this->looping_components_active_end_; i++) {
if (this->looping_components_[i] == component) {
// Move last active component to this position
this->looping_components_active_end_--;
if (i != this->looping_components_active_end_) {
this->looping_components_[i] = this->looping_components_[this->looping_components_active_end_];
this->looping_components_[this->looping_components_active_end_] = component;
// If we're currently iterating and just swapped the current position
if (this->in_loop_ && i == this->current_loop_index_) {
// Decrement so we'll process the swapped component next
this->current_loop_index_--;
}
}
return;
}
}
}
void Application::enable_component_loop(Component *component) {
// Single pass through all components to find and move if needed
// With typical 10-30 components, O(n) is faster than maintaining a map
const uint16_t size = this->looping_components_.size();
for (uint16_t i = 0; i < size; i++) {
if (this->looping_components_[i] == component) {
if (i < this->looping_components_active_end_) {
return; // Already active
}
// Found in inactive section - move to active
if (i != this->looping_components_active_end_) {
Component *temp = this->looping_components_[this->looping_components_active_end_];
this->looping_components_[this->looping_components_active_end_] = component;
this->looping_components_[i] = temp;
}
this->looping_components_active_end_++;
return;
}
}
} }
#ifdef USE_SOCKET_SELECT_SUPPORT #ifdef USE_SOCKET_SELECT_SUPPORT

View File

@@ -585,13 +585,41 @@ class Application {
void calculate_looping_components_(); void calculate_looping_components_();
// These methods are called by Component::disable_loop() and Component::enable_loop()
// Components should not call these directly - use this->disable_loop() or this->enable_loop()
// to ensure component state is properly updated along with the loop partition
void disable_component_loop(Component *component);
void enable_component_loop(Component *component);
void feed_wdt_arch_(); void feed_wdt_arch_();
/// Perform a delay while also monitoring socket file descriptors for readiness /// Perform a delay while also monitoring socket file descriptors for readiness
void yield_with_select_(uint32_t delay_ms); void yield_with_select_(uint32_t delay_ms);
std::vector<Component *> components_{}; std::vector<Component *> components_{};
// Partitioned vector design for looping components
// =================================================
// Components are partitioned into [active | inactive] sections:
//
// looping_components_: [A, B, C, D | E, F]
// ^
// looping_components_active_end_ (4)
//
// - Components A,B,C,D are active and will be called in loop()
// - Components E,F are inactive (disabled/failed) and won't be called
// - No flag checking needed during iteration - just loop 0 to active_end_
// - When a component is disabled, it's swapped with the last active component
// and active_end_ is decremented
// - When a component is enabled, it's swapped with the first inactive component
// and active_end_ is incremented
// - This eliminates branch mispredictions from flag checking in the hot loop
std::vector<Component *> looping_components_{}; std::vector<Component *> looping_components_{};
uint16_t looping_components_active_end_{0};
// For safe reentrant modifications during iteration
uint16_t current_loop_index_{0};
bool in_loop_{false};
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
std::vector<binary_sensor::BinarySensor *> binary_sensors_{}; std::vector<binary_sensor::BinarySensor *> binary_sensors_{};

View File

@@ -144,17 +144,21 @@ void Component::mark_failed() {
this->component_state_ &= ~COMPONENT_STATE_MASK; this->component_state_ &= ~COMPONENT_STATE_MASK;
this->component_state_ |= COMPONENT_STATE_FAILED; this->component_state_ |= COMPONENT_STATE_FAILED;
this->status_set_error(); this->status_set_error();
// Also remove from loop since failed components shouldn't loop
App.disable_component_loop(this);
} }
void Component::disable_loop() { void Component::disable_loop() {
ESP_LOGD(TAG, "%s loop disabled", this->get_component_source()); ESP_LOGD(TAG, "%s loop disabled", this->get_component_source());
this->component_state_ &= ~COMPONENT_STATE_MASK; this->component_state_ &= ~COMPONENT_STATE_MASK;
this->component_state_ |= COMPONENT_STATE_LOOP_DONE; this->component_state_ |= COMPONENT_STATE_LOOP_DONE;
App.disable_component_loop(this);
} }
void Component::enable_loop() { void Component::enable_loop() {
if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE) { if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE) {
ESP_LOGD(TAG, "%s loop enabled", this->get_component_source()); ESP_LOGD(TAG, "%s loop enabled", this->get_component_source());
this->component_state_ &= ~COMPONENT_STATE_MASK; this->component_state_ &= ~COMPONENT_STATE_MASK;
this->component_state_ |= COMPONENT_STATE_LOOP; this->component_state_ |= COMPONENT_STATE_LOOP;
App.enable_component_loop(this);
} }
} }
void Component::reset_to_construction_state() { void Component::reset_to_construction_state() {
@@ -193,10 +197,6 @@ bool Component::is_ready() const {
return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP || return (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP ||
(this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_SETUP; (this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_SETUP;
} }
bool Component::should_skip_loop() const {
uint8_t state = this->component_state_ & COMPONENT_STATE_MASK;
return state == COMPONENT_STATE_FAILED || state == COMPONENT_STATE_LOOP_DONE;
}
bool Component::can_proceed() { return true; } bool Component::can_proceed() { return true; }
bool Component::status_has_warning() const { return this->component_state_ & STATUS_LED_WARNING; } bool Component::status_has_warning() const { return this->component_state_ & STATUS_LED_WARNING; }
bool Component::status_has_error() const { return this->component_state_ & STATUS_LED_ERROR; } bool Component::status_has_error() const { return this->component_state_ & STATUS_LED_ERROR; }

View File

@@ -156,6 +156,9 @@ class Component {
* *
* This is useful for components that only need to run for a certain period of time * This is useful for components that only need to run for a certain period of time
* or when inactive, saving CPU cycles. * or when inactive, saving CPU cycles.
*
* @note Components should call this->disable_loop() on themselves, not on other components.
* This ensures the component's state is properly updated along with the loop partition.
*/ */
void disable_loop(); void disable_loop();
@@ -163,6 +166,9 @@ class Component {
* *
* This is useful for components that transition between active and inactive states * This is useful for components that transition between active and inactive states
* and need to re-enable their loop() method when becoming active again. * and need to re-enable their loop() method when becoming active again.
*
* @note Components should call this->enable_loop() on themselves, not on other components.
* This ensures the component's state is properly updated along with the loop partition.
*/ */
void enable_loop(); void enable_loop();
@@ -170,12 +176,6 @@ class Component {
bool is_ready() const; bool is_ready() const;
/** Check if this component should skip its loop execution.
*
* @return True if the component is in FAILED or LOOP_DONE state
*/
bool should_skip_loop() const;
virtual bool can_proceed(); virtual bool can_proceed();
bool status_has_warning() const; bool status_has_warning() const;