mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 09:01:49 +00: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");
 | 
			
		||||
  this->should_start_ = true;
 | 
			
		||||
  this->enable_loop();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ESP32ImprovComponent::stop() {
 | 
			
		||||
 
 | 
			
		||||
@@ -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,52 @@ 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) {
 | 
			
		||||
  // 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
 | 
			
		||||
 
 | 
			
		||||
@@ -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