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:
@@ -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() {
|
||||||
|
@@ -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
|
||||||
|
@@ -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_{};
|
||||||
|
@@ -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; }
|
||||||
|
@@ -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;
|
||||||
|
Reference in New Issue
Block a user