1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-20 08:46:01 +00:00

actions: fix loop re-entry (#7972)

Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
Kjell Braden
2025-11-03 00:02:13 +01:00
committed by GitHub
parent 8a8a80e107
commit 86402be9e3
5 changed files with 321 additions and 32 deletions

View File

@@ -2,6 +2,7 @@
#include <memory>
#include <tuple>
#include <forward_list>
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
@@ -264,10 +265,22 @@ template<class C, typename... Ts> class IsRunningCondition : public Condition<Ts
C *parent_;
};
/** Wait for a script to finish before continuing.
*
* Uses queue-based storage to safely handle concurrent executions.
* While concurrent execution from the same trigger is uncommon, it's possible
* (e.g., rapid button presses, high-frequency sensor updates), so we use
* queue-based storage for correctness.
*/
template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>, public Component {
public:
ScriptWaitAction(C *script) : script_(script) {}
void setup() override {
// Start with loop disabled - only enable when there's work to do
this->disable_loop();
}
void play_complex(Ts... x) override {
this->num_running_++;
// Check if we can continue immediately.
@@ -275,7 +288,11 @@ template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>,
this->play_next_(x...);
return;
}
this->var_ = std::make_tuple(x...);
// Store parameters for later execution
this->param_queue_.emplace_front(x...);
// Enable loop now that we have work to do
this->enable_loop();
this->loop();
}
@@ -286,15 +303,30 @@ template<class C, typename... Ts> class ScriptWaitAction : public Action<Ts...>,
if (this->script_->is_running())
return;
this->play_next_tuple_(this->var_);
while (!this->param_queue_.empty()) {
auto &params = this->param_queue_.front();
this->play_next_tuple_(params, typename gens<sizeof...(Ts)>::type());
this->param_queue_.pop_front();
}
// Queue is now empty - disable loop until next play_complex
this->disable_loop();
}
void play(Ts... x) override { /* ignore - see play_complex */
}
void stop() override {
this->param_queue_.clear();
this->disable_loop();
}
protected:
template<int... S> void play_next_tuple_(const std::tuple<Ts...> &tuple, seq<S...> /*unused*/) {
this->play_next_(std::get<S>(tuple)...);
}
C *script_;
std::tuple<Ts...> var_{};
std::forward_list<std::tuple<Ts...>> param_queue_;
};
} // namespace script