mirror of
https://github.com/esphome/esphome.git
synced 2025-09-14 17:22:20 +01:00
Merge remote-tracking branch 'origin/loop_done' into integration
This commit is contained in:
@@ -256,6 +256,7 @@ void ESP32ImprovComponent::start() {
|
||||
|
||||
ESP_LOGD(TAG, "Setting Improv to start");
|
||||
this->should_start_ = true;
|
||||
this->enable_loop();
|
||||
}
|
||||
|
||||
void ESP32ImprovComponent::stop() {
|
||||
|
@@ -317,6 +317,7 @@ void I2SAudioMicrophone::stop_driver_() {
|
||||
ESP_LOGW(TAG, "Error uninstalling I2S driver - it may not have started: %s", esp_err_to_name(err));
|
||||
}
|
||||
#else
|
||||
if (this->rx_handle_ != nullptr) {
|
||||
/* Have to stop the channel before deleting it */
|
||||
err = i2s_channel_disable(this->rx_handle_);
|
||||
if (err != ESP_OK) {
|
||||
@@ -327,6 +328,8 @@ void I2SAudioMicrophone::stop_driver_() {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Error deleting I2S channel - it may not have started: %s", esp_err_to_name(err));
|
||||
}
|
||||
this->rx_handle_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
this->parent_->unlock();
|
||||
}
|
||||
|
@@ -97,11 +97,12 @@ void Application::loop() {
|
||||
// Feed WDT with time
|
||||
this->feed_wdt(last_op_end_time);
|
||||
|
||||
for (Component *component : this->looping_components_) {
|
||||
// Skip components that are done or failed
|
||||
if (component->should_skip_loop()) {
|
||||
continue;
|
||||
}
|
||||
// Mark that we're in the loop for safe reentrant modifications
|
||||
this->in_loop_ = true;
|
||||
|
||||
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
|
||||
this->loop_component_start_time_ = last_op_end_time;
|
||||
@@ -117,6 +118,8 @@ void Application::loop() {
|
||||
this->app_state_ |= new_app_state;
|
||||
this->feed_wdt(last_op_end_time);
|
||||
}
|
||||
|
||||
this->in_loop_ = false;
|
||||
this->app_state_ = new_app_state;
|
||||
|
||||
// Use the last component's end time instead of calling millis() again
|
||||
@@ -244,6 +247,51 @@ void Application::calculate_looping_components_() {
|
||||
if (obj->has_overridden_loop())
|
||||
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) {
|
||||
// This method must be reentrant - components can disable themselves during their own loop() call
|
||||
// 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_) {
|
||||
std::swap(this->looping_components_[i], this->looping_components_[this->looping_components_active_end_]);
|
||||
|
||||
// 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) {
|
||||
// This method must be reentrant - components can re-enable themselves during their own loop() call
|
||||
// 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_) {
|
||||
std::swap(this->looping_components_[i], this->looping_components_[this->looping_components_active_end_]);
|
||||
}
|
||||
this->looping_components_active_end_++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
|
@@ -585,13 +585,41 @@ class Application {
|
||||
|
||||
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_();
|
||||
|
||||
/// Perform a delay while also monitoring socket file descriptors for readiness
|
||||
void yield_with_select_(uint32_t delay_ms);
|
||||
|
||||
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_{};
|
||||
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
|
||||
std::vector<binary_sensor::BinarySensor *> binary_sensors_{};
|
||||
|
@@ -144,17 +144,21 @@ void Component::mark_failed() {
|
||||
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
||||
this->component_state_ |= COMPONENT_STATE_FAILED;
|
||||
this->status_set_error();
|
||||
// Also remove from loop since failed components shouldn't loop
|
||||
App.disable_component_loop_(this);
|
||||
}
|
||||
void Component::disable_loop() {
|
||||
ESP_LOGD(TAG, "%s loop disabled", this->get_component_source());
|
||||
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
||||
this->component_state_ |= COMPONENT_STATE_LOOP_DONE;
|
||||
App.disable_component_loop_(this);
|
||||
}
|
||||
void Component::enable_loop() {
|
||||
if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_LOOP_DONE) {
|
||||
ESP_LOGD(TAG, "%s loop enabled", this->get_component_source());
|
||||
this->component_state_ &= ~COMPONENT_STATE_MASK;
|
||||
this->component_state_ |= COMPONENT_STATE_LOOP;
|
||||
App.enable_component_loop_(this);
|
||||
}
|
||||
}
|
||||
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 ||
|
||||
(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::status_has_warning() const { return this->component_state_ & STATUS_LED_WARNING; }
|
||||
bool Component::status_has_error() const { return this->component_state_ & STATUS_LED_ERROR; }
|
||||
|
@@ -156,6 +156,9 @@ class Component {
|
||||
*
|
||||
* This is useful for components that only need to run for a certain period of time
|
||||
* 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();
|
||||
|
||||
@@ -163,6 +166,9 @@ class Component {
|
||||
*
|
||||
* This is useful for components that transition between active and inactive states
|
||||
* 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();
|
||||
|
||||
@@ -170,12 +176,6 @@ class Component {
|
||||
|
||||
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();
|
||||
|
||||
bool status_has_warning() const;
|
||||
|
Reference in New Issue
Block a user