From ea93c44db7c23d49c6715d7b2ba3efe968132433 Mon Sep 17 00:00:00 2001 From: WarmUpTill <19472752+WarmUpTill@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:32:54 +0200 Subject: [PATCH] Add option to check macro conditions in parallel to other macros --- data/locale/en-US.ini | 5 +- lib/macro/macro-settings.cpp | 26 +++++++++- lib/macro/macro-settings.hpp | 3 ++ lib/macro/macro.cpp | 93 ++++++++++++++++++++++++++++++++---- lib/macro/macro.hpp | 6 +++ 5 files changed, 120 insertions(+), 13 deletions(-) diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 55ba257d..df2c0f66 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -199,8 +199,11 @@ AdvSceneSwitcher.macroTab.highlightTrueConditions="Highlight conditions of curre AdvSceneSwitcher.macroTab.highlightPerformedActions="Highlight recently performed actions of currently selected macro" AdvSceneSwitcher.macroTab.newMacroRegisterHotkey="Register hotkeys to control the pause state of new macros" AdvSceneSwitcher.macroTab.newMacroUseShortCircuitEvaluation="Enable short circuit evaluation of macro conditions for new macros" -AdvSceneSwitcher.macroTab.saveSettingsOnMacroChange="Save settings when selecting a new macro" +AdvSceneSwitcher.macroTab.newMacroCheckInParallel="Evaluate conditions of new macros in parallel to other macros" +AdvSceneSwitcher.macroTab.checkInParallel.tooltip="If this option is not enabled, this macro's conditions will be evaluated after the preceeding macro's condition check has completed.\nSo, if there are three macros - Macro A, Macro B, and Macro C - in the macro list the order the conditions in are evaluated is:\n • Macro A's conditions\n • Macro B's conditions\n • Macro C's conditions\nIf this option is enabled, the order might differ.\nEnabling this option can be useful for scenarios in which you have multiple long running condition checks, which you don't want to result in \"blocking\" other macros." +AdvSceneSwitcher.macroTab.saveSettingsOnMacroChange="Save settings when switching between macros" AdvSceneSwitcher.macroTab.saveSettingsOnMacroChange.tooltip="Saving the settings can be an expensive operation if you are working with a very large scene collection.\nIn this case, it might make sense to disable this option." +AdvSceneSwitcher.macroTab.currentCheckInParallel="Evaluate conditions of selected macro in parallel to other macros" AdvSceneSwitcher.macroTab.currentRegisterHotkeys="Register hotkeys to control the pause state of selected macro" AdvSceneSwitcher.macroTab.currentUseShortCircuitEvaluation="Enable short circuit evaluation of macro conditions for currently selected macro" AdvSceneSwitcher.macroTab.shortCircuit.tooltip="Enabling short circuit evaluation might improve the performance, as some condition checks are skipped, if the overall macro cannot be evaluated to \"true\" anymore.\nHowever, please note that condition checks, which are skipped over, will also not update their duration modifier checks." diff --git a/lib/macro/macro-settings.cpp b/lib/macro/macro-settings.cpp index 5cb84ae6..99771fdf 100644 --- a/lib/macro/macro-settings.cpp +++ b/lib/macro/macro-settings.cpp @@ -19,6 +19,8 @@ void GlobalMacroSettings::Save(obs_data_t *obj) const obs_data_set_bool(data, "highlightExecuted", _highlightExecuted); obs_data_set_bool(data, "highlightConditions", _highlightConditions); obs_data_set_bool(data, "highlightActions", _highlightActions); + obs_data_set_bool(data, "newMacroCheckInParallel", + _newMacroCheckInParallel); obs_data_set_bool(data, "newMacroRegisterHotkey", _newMacroRegisterHotkeys); obs_data_set_bool(data, "newMacroUseShortCircuitEvaluation", @@ -41,6 +43,8 @@ void GlobalMacroSettings::Load(obs_data_t *obj) _highlightExecuted = obs_data_get_bool(data, "highlightExecuted"); _highlightConditions = obs_data_get_bool(data, "highlightConditions"); _highlightActions = obs_data_get_bool(data, "highlightActions"); + _newMacroCheckInParallel = + obs_data_get_bool(data, "newMacroCheckInParallel"); _newMacroRegisterHotkeys = obs_data_get_bool(data, "newMacroRegisterHotkey"); _newMacroUseShortCircuitEvaluation = @@ -62,12 +66,16 @@ MacroSettingsDialog::MacroSettingsDialog(QWidget *parent, "AdvSceneSwitcher.macroTab.highlightTrueConditions"))), _highlightActions(new QCheckBox(obs_module_text( "AdvSceneSwitcher.macroTab.highlightPerformedActions"))), + _newMacroCheckInParallel(new QCheckBox(obs_module_text( + "AdvSceneSwitcher.macroTab.newMacroCheckInParallel"))), _newMacroRegisterHotkeys(new QCheckBox(obs_module_text( "AdvSceneSwitcher.macroTab.newMacroRegisterHotkey"))), _newMacroUseShortCircuitEvaluation(new QCheckBox(obs_module_text( "AdvSceneSwitcher.macroTab.newMacroUseShortCircuitEvaluation"))), _saveSettingsOnMacroChange(new QCheckBox(obs_module_text( "AdvSceneSwitcher.macroTab.saveSettingsOnMacroChange"))), + _currentCheckInParallel(new QCheckBox(obs_module_text( + "AdvSceneSwitcher.macroTab.currentCheckInParallel"))), _currentMacroRegisterHotkeys(new QCheckBox(obs_module_text( "AdvSceneSwitcher.macroTab.currentRegisterHotkeys"))), _currentUseShortCircuitEvaluation(new QCheckBox(obs_module_text( @@ -107,6 +115,11 @@ MacroSettingsDialog::MacroSettingsDialog(QWidget *parent, setWindowModality(Qt::WindowModality::WindowModal); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + _newMacroCheckInParallel->setToolTip(obs_module_text( + "AdvSceneSwitcher.macroTab.checkInParallel.tooltip")); + _currentCheckInParallel->setToolTip(obs_module_text( + "AdvSceneSwitcher.macroTab.checkInParallel.tooltip")); + _newMacroUseShortCircuitEvaluation->setToolTip(obs_module_text( "AdvSceneSwitcher.macroTab.shortCircuit.tooltip")); _saveSettingsOnMacroChange->setToolTip(obs_module_text( @@ -146,6 +159,8 @@ MacroSettingsDialog::MacroSettingsDialog(QWidget *parent, obs_module_text("AdvSceneSwitcher.macroTab.generalSettings")); auto generalLayout = new QVBoxLayout; generalLayout->addWidget(_saveSettingsOnMacroChange); + generalLayout->addWidget(_currentCheckInParallel); + generalLayout->addWidget(_newMacroCheckInParallel); generalLayout->addWidget(_currentSkipOnStartup); generalLayout->addWidget(_currentStopActionsIfNotDone); generalLayout->addWidget(_currentUseShortCircuitEvaluation); @@ -273,6 +288,7 @@ MacroSettingsDialog::MacroSettingsDialog(QWidget *parent, _highlightExecutedMacros->setChecked(settings._highlightExecuted); _highlightConditions->setChecked(settings._highlightConditions); _highlightActions->setChecked(settings._highlightActions); + _newMacroCheckInParallel->setChecked(settings._newMacroCheckInParallel); _newMacroRegisterHotkeys->setChecked(settings._newMacroRegisterHotkeys); _newMacroUseShortCircuitEvaluation->setChecked( settings._newMacroUseShortCircuitEvaluation); @@ -280,20 +296,23 @@ MacroSettingsDialog::MacroSettingsDialog(QWidget *parent, settings._saveSettingsOnMacroChange); if (!macro || macro->IsGroup()) { - hotkeyOptions->hide(); - // General group _currentSkipOnStartup->hide(); _currentStopActionsIfNotDone->hide(); _currentUseShortCircuitEvaluation->hide(); + _currentCheckInParallel->hide(); SetLayoutVisible(customConditionIntervalLayout, false); SetLayoutVisible(pauseStateSaveBehavorLayout, false); + // Hotkey group + _currentMacroRegisterHotkeys->hide(); + inputOptions->hide(); _dockOptions->hide(); return; } + _currentCheckInParallel->setChecked(macro->CheckInParallel()); _currentMacroRegisterHotkeys->setChecked(macro->PauseHotkeysEnabled()); _currentUseShortCircuitEvaluation->setChecked( macro->ShortCircuitEvaluationEnabled()); @@ -431,6 +450,8 @@ bool MacroSettingsDialog::AskForSettings(QWidget *parent, userInput._highlightConditions = dialog._highlightConditions->isChecked(); userInput._highlightActions = dialog._highlightActions->isChecked(); + userInput._newMacroCheckInParallel = + dialog._newMacroCheckInParallel->isChecked(); userInput._newMacroRegisterHotkeys = dialog._newMacroRegisterHotkeys->isChecked(); userInput._newMacroUseShortCircuitEvaluation = @@ -441,6 +462,7 @@ bool MacroSettingsDialog::AskForSettings(QWidget *parent, return true; } + macro->SetCheckInParallel(dialog._currentCheckInParallel->isChecked()); macro->EnablePauseHotkeys( dialog._currentMacroRegisterHotkeys->isChecked()); macro->SetShortCircuitEvaluation( diff --git a/lib/macro/macro-settings.hpp b/lib/macro/macro-settings.hpp index f8910631..beee699c 100644 --- a/lib/macro/macro-settings.hpp +++ b/lib/macro/macro-settings.hpp @@ -23,6 +23,7 @@ public: bool _highlightExecuted = false; bool _highlightConditions = false; bool _highlightActions = false; + bool _newMacroCheckInParallel = false; bool _newMacroRegisterHotkeys = true; bool _newMacroUseShortCircuitEvaluation = false; bool _saveSettingsOnMacroChange = true; @@ -52,10 +53,12 @@ private: QCheckBox *_highlightExecutedMacros; QCheckBox *_highlightConditions; QCheckBox *_highlightActions; + QCheckBox *_newMacroCheckInParallel; QCheckBox *_newMacroRegisterHotkeys; QCheckBox *_newMacroUseShortCircuitEvaluation; QCheckBox *_saveSettingsOnMacroChange; // Current macro specific settings + QCheckBox *_currentCheckInParallel; QCheckBox *_currentMacroRegisterHotkeys; QCheckBox *_currentUseShortCircuitEvaluation; QCheckBox *_currentUseCustomConditionCheckInterval; diff --git a/lib/macro/macro.cpp b/lib/macro/macro.cpp index d47a7ff3..6de3fda7 100644 --- a/lib/macro/macro.cpp +++ b/lib/macro/macro.cpp @@ -252,19 +252,52 @@ bool Macro::CheckConditions(bool ignorePause) return false; } - _stop = false; - _matched = false; - for (auto &condition : _conditions) { - if (!condition) { - continue; - } + const auto checkConditionsTask = + [this, + ignorePause](const std::deque> + &conditions) { + for (auto &condition : conditions) { + if (!condition) { + continue; + } - if (_paused && !ignorePause) { - vblog(LOG_INFO, "Macro %s is paused", _name.c_str()); + if (_paused && !ignorePause) { + vblog(LOG_INFO, "Macro %s is paused", + _name.c_str()); + return false; + } + + _matched = CheckConditionHelper(condition); + } + return _matched; + }; + + if (CheckInParallel()) { + if (!_conditionCheckFuture.valid()) { + _stop = false; + _matched = false; + _conditionCheckFuture = std::async( + std::launch::async, + [this, checkConditionsTask]() { + // Copy to avoid settings modifications + // causing issues + const auto conditionsCopy = _conditions; + checkConditionsTask(conditionsCopy); + }); return false; } - - _matched = CheckConditionHelper(condition); + if (_conditionCheckFuture.wait_for(std::chrono::seconds(0)) != + std::future_status::ready) { + vblog(LOG_INFO, + "Macro %s still waiting for condition check result", + _name.c_str()); + return false; + } + _conditionCheckFuture.get(); + } else { + _stop = false; + _matched = false; + _matched = checkConditionsTask(_conditions); } vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched); @@ -344,6 +377,13 @@ bool Macro::ConditionsShouldBeChecked() const bool Macro::ShouldRunActions() const { + if (CheckInParallel() && _conditionCheckFuture.valid()) { + vblog(LOG_INFO, + "%s not ready to perform actions as condition check is still running", + _name.c_str()); + return false; + } + const bool hasActionsToExecute = !_paused && (_matched || _elseActions.size() > 0) && (!_performActionsOnChange || _conditionSateChanged); @@ -519,6 +559,29 @@ void Macro::Stop() if (_backgroundThread.joinable()) { _backgroundThread.join(); } + if (_conditionCheckFuture.valid()) { + _conditionCheckFuture.get(); + } +} + +void Macro::SetCheckInParallel(bool parallel) +{ + _checkInParallel = parallel; + _conditionCheckFuture = {}; +} + +bool Macro::ParallelTasksCompleted() const +{ + if (!CheckInParallel() && !RunInParallel()) { + return true; + } + if (RunInParallel() && !_done) { + return false; + } + if (CheckInParallel() && _conditionCheckFuture.valid()) { + return false; + } + return true; } MacroInputVariables Macro::GetInputVariables() const @@ -720,6 +783,7 @@ bool Macro::Save(obs_data_t *obj, bool saveForCopy) const static_cast(_pauseSaveBehavior)); obs_data_set_bool(obj, "pause", _paused); obs_data_set_bool(obj, "parallel", _runInParallel); + obs_data_set_bool(obj, "checkConditionsInParallel", _checkInParallel); obs_data_set_bool(obj, "onChange", _performActionsOnChange); obs_data_set_bool(obj, "skipExecOnStart", _skipExecOnStart); obs_data_set_bool(obj, "stopActionsIfNotDone", _stopActionsIfNotDone); @@ -805,6 +869,7 @@ bool Macro::Load(obs_data_t *obj) break; } _runInParallel = obs_data_get_bool(obj, "parallel"); + _checkInParallel = obs_data_get_bool(obj, "checkConditionsInParallel"); _performActionsOnChange = obs_data_get_bool(obj, "onChange"); _skipExecOnStart = obs_data_get_bool(obj, "skipExecOnStart"); _stopActionsIfNotDone = obs_data_get_bool(obj, "stopActionsIfNotDone"); @@ -1476,6 +1541,14 @@ std::weak_ptr GetWeakMacroByName(const char *name) void InvalidateMacroTempVarValues() { for (const auto &m : macros) { + // Do not invalidate the temp vars set during condition checks + // or action executions running in parallel to the "main" macro + // loop, as otherwise access to the information stored in those + // variables might get lost while those checks or actions are + // still ongoing. + if (!m->ParallelTasksCompleted()) { + continue; + } m->InvalidateTempVarValues(); } } diff --git a/lib/macro/macro.hpp b/lib/macro/macro.hpp index 65ec057a..ba743237 100644 --- a/lib/macro/macro.hpp +++ b/lib/macro/macro.hpp @@ -7,6 +7,7 @@ #include "variable-string.hpp" #include "temp-variable.hpp" +#include #include #include #include @@ -78,6 +79,9 @@ public: void AddHelperThread(std::thread &&); void SetRunInParallel(bool parallel) { _runInParallel = parallel; } bool RunInParallel() const { return _runInParallel; } + bool CheckInParallel() const { return _checkInParallel; } + void SetCheckInParallel(bool parallel); + bool ParallelTasksCompleted() const; // Input variables MacroInputVariables GetInputVariables() const; @@ -205,7 +209,9 @@ private: bool _conditionSateChanged = false; bool _runInParallel = false; + bool _checkInParallel = false; bool _matched = false; + std::future _conditionCheckFuture; bool _lastMatched = false; bool _performActionsOnChange = true; bool _skipExecOnStart = false;