diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 5fd94fcd..36486759 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -1628,6 +1628,8 @@ AdvSceneSwitcher.actionQueues.invalid="Invalid action queue selection" AdvSceneSwitcher.actionQueues.name="Name:" AdvSceneSwitcher.actionQueues.runOnStartup="Run action queue when starting the plugin" AdvSceneSwitcher.actionQueues.resolveVariablesOnAdd="Resolve variables when action is inserted into the queue" +AdvSceneSwitcher.actionQueues.cloneVariableContext="Clone variable context when action is inserted into the queue" +AdvSceneSwitcher.actionQueues.cloneVariableContext.tooltip="Captures the current values of all variables when the action is added to the queue.\nDuring execution, actions read from and write to this snapshot instead of the global variables.\nUnlike \"Resolve variables\", changes made by one action in the queue are visible to subsequent actions." AdvSceneSwitcher.actionQueues.running="Queue is running" AdvSceneSwitcher.actionQueues.stopped="Queue is stopped" AdvSceneSwitcher.actionQueues.start="Start action queue" diff --git a/lib/queue/action-queue.cpp b/lib/queue/action-queue.cpp index a9131911..ab0b2cea 100644 --- a/lib/queue/action-queue.cpp +++ b/lib/queue/action-queue.cpp @@ -45,6 +45,7 @@ void ActionQueue::Save(obs_data_t *obj) const obs_data_set_string(obj, "name", _name.c_str()); obs_data_set_bool(obj, "runOnStartup", _runOnStartup); obs_data_set_bool(obj, "resolveVariablesOnAdd", _resolveVariablesOnAdd); + obs_data_set_bool(obj, "cloneVariableContext", _cloneVariableContext); } void ActionQueue::Load(obs_data_t *obj) @@ -54,6 +55,7 @@ void ActionQueue::Load(obs_data_t *obj) _runOnStartup = obs_data_get_bool(obj, "runOnStartup"); _resolveVariablesOnAdd = obs_data_get_bool(obj, "resolveVariablesOnAdd"); + _cloneVariableContext = obs_data_get_bool(obj, "cloneVariableContext"); if (_runOnStartup) { Start(); @@ -114,9 +116,17 @@ void ActionQueue::Add(const std::shared_ptr &action) copy->PostLoad(); RunAndClearPostLoadSteps(); copy->ResolveVariablesToFixedValues(); - _actions.emplace_back(copy); + _actions.push_back({copy, {}}); + } else if (_cloneVariableContext) { + auto copy = action->Copy(); + OBSDataAutoRelease data = obs_data_create(); + action->Save(data); + copy->Load(data); + copy->PostLoad(); + RunAndClearPostLoadSteps(); + _actions.push_back({copy, CreateVariableContext()}); } else { - _actions.emplace_back(action); + _actions.push_back({action, {}}); } _cv.notify_all(); } @@ -143,7 +153,7 @@ size_t ActionQueue::Size() void ActionQueue::RunActions() { - std::shared_ptr action; + QueueEntry entry; while (true) { { // Grab next action to run std::unique_lock lock(_mutex); @@ -156,20 +166,25 @@ void ActionQueue::RunActions() if (_stop) { return; } - action = _actions.front(); + entry = _actions.front(); _actions.pop_front(); } - if (!action) { + if (!entry.action) { continue; } if (ActionLoggingEnabled()) { blog(LOG_INFO, "Performing action '%s' in queue '%s'", - action->GetId().c_str(), _name.c_str()); - action->LogAction(); + entry.action->GetId().c_str(), _name.c_str()); + entry.action->LogAction(); } - action->PerformAction(); + + if (entry.context) { + SetActiveVariableContext(&*entry.context); + } + entry.action->PerformAction(); + SetActiveVariableContext(nullptr); } } @@ -187,6 +202,7 @@ ActionQueueSettingsDialog::ActionQueueSettingsDialog(QWidget *parent, obs_module_text("AdvSceneSwitcher.actionQueues.clear"))), _runOnStartup(new QCheckBox()), _resolveVariablesOnAdd(new QCheckBox()), + _cloneVariableContext(new QCheckBox()), _queue(settings) { QWidget::connect(_startStopToggle, SIGNAL(clicked()), this, @@ -195,6 +211,7 @@ ActionQueueSettingsDialog::ActionQueueSettingsDialog(QWidget *parent, _runOnStartup->setChecked(settings._runOnStartup); _resolveVariablesOnAdd->setChecked(settings._resolveVariablesOnAdd); + _cloneVariableContext->setChecked(settings._cloneVariableContext); UpdateLabels(); auto layout = new QGridLayout(); @@ -224,6 +241,14 @@ ActionQueueSettingsDialog::ActionQueueSettingsDialog(QWidget *parent, _resolveVariablesOnAdd->setToolTip(obs_module_text( "AdvSceneSwitcher.actionQueues.resolveVariablesOnAdd")); ++row; + layout->addWidget( + new QLabel(obs_module_text( + "AdvSceneSwitcher.actionQueues.cloneVariableContext")), + row, 0); + layout->addWidget(_cloneVariableContext, row, 1); + _cloneVariableContext->setToolTip(obs_module_text( + "AdvSceneSwitcher.actionQueues.cloneVariableContext.tooltip")); + ++row; layout->addWidget(_queueRunStatus, row, 0); layout->addWidget(_startStopToggle, row, 1); ++row; @@ -253,6 +278,8 @@ bool ActionQueueSettingsDialog::AskForSettings(QWidget *parent, settings._runOnStartup = dialog._runOnStartup->isChecked(); settings._resolveVariablesOnAdd = dialog._resolveVariablesOnAdd->isChecked(); + settings._cloneVariableContext = + dialog._cloneVariableContext->isChecked(); return true; } diff --git a/lib/queue/action-queue.hpp b/lib/queue/action-queue.hpp index bf448c06..07ebb760 100644 --- a/lib/queue/action-queue.hpp +++ b/lib/queue/action-queue.hpp @@ -1,11 +1,13 @@ #pragma once #include "item-selection-helpers.hpp" #include "macro-action.hpp" +#include "variable.hpp" #include #include #include #include +#include #include #include @@ -17,6 +19,11 @@ class ActionQueueSettingsDialog; class ActionQueue : public Item { using TimePoint = std::chrono::high_resolution_clock::time_point; + struct QueueEntry { + std::shared_ptr action; + std::optional context; + }; + public: ActionQueue(); ~ActionQueue(); @@ -43,11 +50,12 @@ private: bool _runOnStartup = true; bool _resolveVariablesOnAdd = true; + bool _cloneVariableContext = false; std::atomic_bool _stop = {true}; std::mutex _mutex; std::condition_variable _cv; std::thread _thread; - std::deque> _actions; + std::deque _actions; TimePoint _lastEmpty; friend ActionQueueSelection; @@ -73,6 +81,7 @@ private: QPushButton *_clear; QCheckBox *_runOnStartup; QCheckBox *_resolveVariablesOnAdd; + QCheckBox *_cloneVariableContext; ActionQueue &_queue; }; diff --git a/lib/variables/variable.cpp b/lib/variables/variable.cpp index 249aab1d..94bb1b05 100644 --- a/lib/variables/variable.cpp +++ b/lib/variables/variable.cpp @@ -18,6 +18,30 @@ static std::deque> variables; static std::mutex lastVariableChangeMutex; static std::chrono::high_resolution_clock::time_point lastVariableChange{}; +// When set, Variable::Value() and Variable::SetValue() operate on this context +// instead of the global variable state. Used by action queues to isolate +// variable reads and writes to a snapshot taken at the time the action was +// added to the queue, so that actions can modify variables without affecting +// the global state or other queue entries. +thread_local static VariableContext *activeVarContext = nullptr; + +VariableContext CreateVariableContext() +{ + VariableContext context; + for (const auto &v : variables) { + const auto &var = std::dynamic_pointer_cast(v); + if (var) { + context[var->Name()] = var->Value(false); + } + } + return context; +} + +void SetActiveVariableContext(VariableContext *context) +{ + activeVarContext = context; +} + static bool setup() { AddEarlySaveStep(SaveVariables); @@ -76,6 +100,14 @@ void Variable::Save(obs_data_t *obj) const std::string Variable::Value(bool updateLastUsed) const { + if (activeVarContext) { + auto it = activeVarContext->find(Name()); + if (it == activeVarContext->end()) { + return ""; + } + return it->second; + } + std::lock_guard lock(_mutex); if (updateLastUsed) { UpdateLastUsed(); @@ -108,6 +140,16 @@ std::optional Variable::IntValue() const void Variable::SetValue(const std::string &value) { + if (activeVarContext) { + auto it = activeVarContext->find(Name()); + if (it == activeVarContext->end()) { + return; + } + it->second = value; + setLastVariableChangeTime(); + return; + } + { std::lock_guard lock(_mutex); _previousValue = _value; @@ -121,6 +163,7 @@ void Variable::SetValue(const std::string &value) } setLastVariableChangeTime(); } + _cv.notify_all(); } diff --git a/lib/variables/variable.hpp b/lib/variables/variable.hpp index 5d70f096..22997d35 100644 --- a/lib/variables/variable.hpp +++ b/lib/variables/variable.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace advss { @@ -116,6 +117,10 @@ signals: void Remove(const QString &); }; +using VariableContext = std::unordered_map; +VariableContext CreateVariableContext(); +void SetActiveVariableContext(VariableContext *context); + std::deque> &GetVariables(); EXPORT Variable *GetVariableByName(const std::string &name); EXPORT Variable *GetVariableByQString(const QString &name);