diff --git a/esphome/core/automation.h b/esphome/core/automation.h index aace7889f0..c22b3ca0e3 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -220,6 +220,7 @@ template class Action { protected: friend ActionList; + template friend class ContinuationAction; virtual void play(Ts... x) = 0; void play_next_(Ts... x) { diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h index 1c60dd1c7a..685f3ab8ae 100644 --- a/esphome/core/base_automation.h +++ b/esphome/core/base_automation.h @@ -171,9 +171,22 @@ template class DelayAction : public Action, public Compon TEMPLATABLE_VALUE(uint32_t, delay) void play_complex(Ts... x) override { - auto f = std::bind(&DelayAction::play_next_, this, x...); this->num_running_++; + // Store parameters in shared_ptr for this timer instance + // This avoids std::bind bloat while supporting parallel script mode + // shared_ptr is used (vs unique_ptr) because std::function requires copyability + auto params = std::make_shared>(x...); + + // Lambda captures only 'this' and the shared_ptr (8-16 bytes total) + // vs std::bind which captures 'this' + copies of all x... + bind overhead + // This eliminates ~200-300 bytes of std::bind template instantiation code + auto f = [this, params]() { + if (this->num_running_ > 0) { + std::apply([this](auto &&...args) { this->play_next_(args...); }, *params); + } + }; + // If num_running_ > 1, we have multiple instances running in parallel // In single/restart/queued modes, only one instance runs at a time // Parallel mode uses skip_cancel=true to allow multiple delays to coexist @@ -215,18 +228,46 @@ template class StatelessLambdaAction : public Action { void (*f_)(Ts...); }; +/// Simple continuation action that calls play_next_ on a parent action. +/// Used internally by IfAction, WhileAction, RepeatAction, etc. to chain actions. +/// Memory: 4-8 bytes (parent pointer) vs 40 bytes (LambdaAction with std::function). +template class ContinuationAction : public Action { + public: + explicit ContinuationAction(Action *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->play_next_(x...); } + + protected: + Action *parent_; +}; + +// Forward declaration for WhileLoopContinuation +template class WhileAction; + +/// Loop continuation for WhileAction that checks condition and repeats or continues. +/// Memory: 4-8 bytes (parent pointer) vs 40 bytes (LambdaAction with std::function). +template class WhileLoopContinuation : public Action { + public: + explicit WhileLoopContinuation(WhileAction *parent) : parent_(parent) {} + + void play(Ts... x) override; + + protected: + WhileAction *parent_; +}; + template class IfAction : public Action { public: explicit IfAction(Condition *condition) : condition_(condition) {} void add_then(const std::initializer_list *> &actions) { this->then_.add_actions(actions); - this->then_.add_action(new LambdaAction([this](Ts... x) { this->play_next_(x...); })); + this->then_.add_action(new ContinuationAction(this)); } void add_else(const std::initializer_list *> &actions) { this->else_.add_actions(actions); - this->else_.add_action(new LambdaAction([this](Ts... x) { this->play_next_(x...); })); + this->else_.add_action(new ContinuationAction(this)); } void play_complex(Ts... x) override { @@ -267,19 +308,11 @@ template class WhileAction : public Action { void add_then(const std::initializer_list *> &actions) { this->then_.add_actions(actions); - this->then_.add_action(new LambdaAction([this](Ts... x) { - if (this->num_running_ > 0 && this->condition_->check_tuple(this->var_)) { - // play again - if (this->num_running_ > 0) { - this->then_.play_tuple(this->var_); - } - } else { - // condition false, play next - this->play_next_tuple_(this->var_); - } - })); + this->then_.add_action(new WhileLoopContinuation(this)); } + friend class WhileLoopContinuation; + void play_complex(Ts... x) override { this->num_running_++; // Store loop parameters @@ -308,22 +341,45 @@ template class WhileAction : public Action { std::tuple var_{}; }; +// Implementation of WhileLoopContinuation::play +template void WhileLoopContinuation::play(Ts... x) { + if (this->parent_->num_running_ > 0 && this->parent_->condition_->check_tuple(this->parent_->var_)) { + // play again + if (this->parent_->num_running_ > 0) { + this->parent_->then_.play_tuple(this->parent_->var_); + } + } else { + // condition false, play next + this->parent_->play_next_tuple_(this->parent_->var_); + } +} + +// Forward declaration for RepeatLoopContinuation +template class RepeatAction; + +/// Loop continuation for RepeatAction that increments iteration and repeats or continues. +/// Memory: 4-8 bytes (parent pointer) vs 40 bytes (LambdaAction with std::function). +template class RepeatLoopContinuation : public Action { + public: + explicit RepeatLoopContinuation(RepeatAction *parent) : parent_(parent) {} + + void play(uint32_t iteration, Ts... x) override; + + protected: + RepeatAction *parent_; +}; + template class RepeatAction : public Action { public: TEMPLATABLE_VALUE(uint32_t, count) void add_then(const std::initializer_list *> &actions) { this->then_.add_actions(actions); - this->then_.add_action(new LambdaAction([this](uint32_t iteration, Ts... x) { - iteration++; - if (iteration >= this->count_.value(x...)) { - this->play_next_tuple_(this->var_); - } else { - this->then_.play(iteration, x...); - } - })); + this->then_.add_action(new RepeatLoopContinuation(this)); } + friend class RepeatLoopContinuation; + void play_complex(Ts... x) override { this->num_running_++; this->var_ = std::make_tuple(x...); @@ -344,6 +400,16 @@ template class RepeatAction : public Action { std::tuple var_; }; +// Implementation of RepeatLoopContinuation::play +template void RepeatLoopContinuation::play(uint32_t iteration, Ts... x) { + iteration++; + if (iteration >= this->parent_->count_.value(x...)) { + this->parent_->play_next_tuple_(this->parent_->var_); + } else { + this->parent_->then_.play(iteration, x...); + } +} + template class WaitUntilAction : public Action, public Component { public: WaitUntilAction(Condition *condition) : condition_(condition) {} @@ -362,7 +428,10 @@ template class WaitUntilAction : public Action, public Co this->var_ = std::make_tuple(x...); if (this->timeout_value_.has_value()) { - auto f = std::bind(&WaitUntilAction::play_next_, this, x...); + // Lambda captures only 'this' to reference stored var_ + // vs std::bind which duplicates storage of x... (already in var_) + // This eliminates ~100-200 bytes of std::bind template instantiation code + auto f = [this]() { this->play_next_tuple_(this->var_); }; this->set_timeout("timeout", this->timeout_value_.value(x...), f); }