From cde4e24cc4721a67dd49b482bf5087fde88dbb3f Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Fri, 29 Dec 2023 18:56:38 +0100 Subject: [PATCH] Add action queues This is intended to be used to execute long running actions which shall not be run in parallel to one another. Multiple different action queues can be created. The "Queue" action and condition can be used to modify and check the status and size of action queues. --- CMakeLists.txt | 8 +- data/locale/en-US.ini | 27 ++ src/advanced-scene-switcher.cpp | 7 + src/macro-core/action-queue.cpp | 380 +++++++++++++++++++++++ src/macro-core/action-queue.hpp | 105 +++++++ src/macro-core/macro-action-queue.cpp | 218 +++++++++++++ src/macro-core/macro-action-queue.hpp | 75 +++++ src/macro-core/macro-condition-queue.cpp | 163 ++++++++++ src/macro-core/macro-condition-queue.hpp | 74 +++++ 9 files changed, 1056 insertions(+), 1 deletion(-) create mode 100644 src/macro-core/action-queue.cpp create mode 100644 src/macro-core/action-queue.hpp create mode 100644 src/macro-core/macro-action-queue.cpp create mode 100644 src/macro-core/macro-action-queue.hpp create mode 100644 src/macro-core/macro-condition-queue.cpp create mode 100644 src/macro-core/macro-condition-queue.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f3da0a71..437e7f0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,7 +87,9 @@ target_sources( # Maro sources target_sources( ${LIB_NAME} - PRIVATE src/macro-core/macro-action-audio.cpp + PRIVATE src/macro-core/action-queue.cpp + src/macro-core/action-queue.hpp + src/macro-core/macro-action-audio.cpp src/macro-core/macro-action-audio.hpp src/macro-core/macro-action-clipboard.cpp src/macro-core/macro-action-clipboard.hpp @@ -115,6 +117,8 @@ target_sources( src/macro-core/macro-action-profile.hpp src/macro-core/macro-action-projector.cpp src/macro-core/macro-action-projector.hpp + src/macro-core/macro-action-queue.cpp + src/macro-core/macro-action-queue.hpp src/macro-core/macro-action-random.cpp src/macro-core/macro-action-random.hpp src/macro-core/macro-action-recording.cpp @@ -193,6 +197,8 @@ target_sources( src/macro-core/macro-condition-process.hpp src/macro-core/macro-condition-profile.cpp src/macro-core/macro-condition-profile.hpp + src/macro-core/macro-condition-queue.cpp + src/macro-core/macro-condition-queue.hpp src/macro-core/macro-condition-recording.cpp src/macro-core/macro-condition-recording.hpp src/macro-core/macro-condition-replay-buffer.cpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 40371f9d..5426a81f 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -557,6 +557,12 @@ AdvSceneSwitcher.condition.twitch.entry="Channel{{channel}}{{conditions}}{{point AdvSceneSwitcher.condition.twitch.entry.account="Check using account{{account}}" AdvSceneSwitcher.condition.twitch.tokenPermissionsInsufficient="Permissions of selected token are insufficient to perform selected action!" AdvSceneSwitcher.condition.twitch.title.title="Enter title" +AdvSceneSwitcher.condition.queue="Queue" +AdvSceneSwitcher.condition.queue.type.started="Started" +AdvSceneSwitcher.condition.queue.type.stopped="Stopped" +AdvSceneSwitcher.condition.queue.type.size="Size" +AdvSceneSwitcher.condition.queue.entry.startStop="Queue{{queues}}is{{conditions}}" +AdvSceneSwitcher.condition.queue.entry.size="Queue{{queues}}{{conditions}}is less than{{size}}" ; Macro Actions AdvSceneSwitcher.action.scene="Switch scene" @@ -897,6 +903,13 @@ AdvSceneSwitcher.action.clipboard.type.copy.image="Copy image" AdvSceneSwitcher.action.clipboard.copy.text.text.placeholder="Enter text" AdvSceneSwitcher.action.clipboard.copy.image.url.placeholder="Enter direct image URL" AdvSceneSwitcher.action.clipboard.copy.image.url.tooltip="Currently supported formats: PNG, JPG/JPEG, BMP, GIF." +AdvSceneSwitcher.action.queue="Queue" +AdvSceneSwitcher.action.queue.type.add="Add" +AdvSceneSwitcher.action.queue.type.clear="Clear" +AdvSceneSwitcher.action.queue.type.start="Start" +AdvSceneSwitcher.action.queue.type.stop="Stop" +AdvSceneSwitcher.action.queue.entry.add="{{actions}}actions of macro{{macros}}to queue{{queues}}" +AdvSceneSwitcher.action.queue.entry.other="{{actions}}queue{{queues}}" ; Hotkey AdvSceneSwitcher.hotkey.startSwitcherHotkey="Start the Advanced Scene Switcher" @@ -962,6 +975,20 @@ AdvSceneSwitcher.connection.status.connecting="Connecting" AdvSceneSwitcher.connection.status.connected="Connected, but not authenticated" AdvSceneSwitcher.connection.status.authenticated="Connected and authenticated" +AdvSceneSwitcher.actionQueues.select="--select queue--" +AdvSceneSwitcher.actionQueues.add="Add new action queue" +AdvSceneSwitcher.actionQueues.nameNotAvailable="Action queue name already in use" +AdvSceneSwitcher.actionQueues.configure="Configure action queue settings" +AdvSceneSwitcher.actionQueues.invalid="Invalid action queue selection" +AdvSceneSwitcher.actionQueues.name="Name:" +AdvSceneSwitcher.actionQueues.runOnStartup="Run action queue when starting the plugin" +AdvSceneSwitcher.actionQueues.running="Queue is running" +AdvSceneSwitcher.actionQueues.stopped="Queue is stopped" +AdvSceneSwitcher.actionQueues.start="Start action queue" +AdvSceneSwitcher.actionQueues.stop="Stop action queue" +AdvSceneSwitcher.actionQueues.size="Current size: %1" +AdvSceneSwitcher.actionQueues.clear="Clear action queue" + AdvSceneSwitcher.regex.enable="Enable regular expressions" AdvSceneSwitcher.regex.configure="Configure regular expression settings" AdvSceneSwitcher.regex.partialMatch="Allow partial match" diff --git a/src/advanced-scene-switcher.cpp b/src/advanced-scene-switcher.cpp index 92735351..f021daeb 100644 --- a/src/advanced-scene-switcher.cpp +++ b/src/advanced-scene-switcher.cpp @@ -434,11 +434,14 @@ static void ResetMacros() } } +void AutoStartActionQueues(); + void SwitcherData::Start() { if (!(th && th->isRunning())) { ResetForNextInterval(); ResetMacros(); + AutoStartActionQueues(); stop = false; th = new SwitcherThread(); @@ -465,6 +468,7 @@ void SwitcherData::Start() } bool CloseAllInputDialogs(); +void StopAndClearAllActionQueues(); void SwitcherData::Stop() { @@ -474,6 +478,7 @@ void SwitcherData::Stop() SetMacroAbortWait(true); GetMacroWaitCV().notify_all(); GetMacroTransitionCV().notify_all(); + StopAndClearAllActionQueues(); // Not waiting if a dialog was closed is a workaround to avoid // deadlocks when a variable input dialog is opened while Stop() @@ -750,6 +755,7 @@ QWidget *GetSettingsWindow() void SetupConnectionManager(); void SetupWebsocketHelpers(); +void SetupActionQueues(); extern "C" void InitSceneSwitcher(obs_module_t *module, translateFunc translate) { @@ -763,6 +769,7 @@ extern "C" void InitSceneSwitcher(obs_module_t *module, translateFunc translate) SetupDock(); SetupConnectionManager(); SetupWebsocketHelpers(); + SetupActionQueues(); obs_frontend_add_save_callback(SaveSceneSwitcher, nullptr); obs_frontend_add_event_callback(OBSEvent, switcher); diff --git a/src/macro-core/action-queue.cpp b/src/macro-core/action-queue.cpp new file mode 100644 index 00000000..8975377e --- /dev/null +++ b/src/macro-core/action-queue.cpp @@ -0,0 +1,380 @@ +#include "action-queue.hpp" +#include "plugin-state-helpers.hpp" + +namespace advss { + +static std::deque> queues; + +void SetupActionQueues() +{ + static bool done = false; + if (done) { + return; + } + AddSaveStep(SaveActionQueues); + AddLoadStep(LoadActionQueues); + done = true; +} + +ActionQueue::ActionQueue() : Item() {} + +ActionQueue::~ActionQueue() +{ + Stop(); +} + +std::shared_ptr ActionQueue::Create() +{ + return std::make_shared(); +} + +void ActionQueue::Save(obs_data_t *obj) const +{ + obs_data_set_string(obj, "name", _name.c_str()); + obs_data_set_bool(obj, "runOnStartup", _runOnStartup); +} + +void ActionQueue::Load(obs_data_t *obj) +{ + std::lock_guard lock(_mutex); + _name = obs_data_get_string(obj, "name"); + _runOnStartup = obs_data_get_bool(obj, "runOnStartup"); + + if (_runOnStartup) { + Start(); + } +} + +void ActionQueue::Start() +{ + if (_thread.joinable()) { + return; + } + _stop = false; + _thread = std::thread(&ActionQueue::RunActions, this); +} + +void ActionQueue::Stop() +{ + _stop = true; + _cv.notify_all(); + if (_thread.joinable()) { + _thread.join(); + } +} + +bool ActionQueue::IsRunning() const +{ + return _thread.joinable(); +} + +bool ActionQueue::RunsOnStartup() const +{ + return _runOnStartup; +} + +void ActionQueue::Clear() +{ + std::lock_guard lock(_mutex); + _actions.clear(); +} + +void ActionQueue::Add(const std::shared_ptr &actions) +{ + std::lock_guard lock(_mutex); + _actions.emplace_back(actions); + _cv.notify_all(); +} + +bool ActionQueue::IsEmpty() +{ + std::lock_guard lock(_mutex); + return _actions.empty(); +} + +size_t ActionQueue::Size() +{ + std::lock_guard lock(_mutex); + return _actions.size(); +} + +void ActionQueue::RunActions() +{ + std::shared_ptr action; + while (true) { + { // Grab next action to run + std::unique_lock lock(_mutex); + while (_actions.empty() && !_stop) { + _cv.wait(lock); + } + + if (_stop) { + return; + } + action = _actions.front(); + _actions.pop_front(); + } + + if (!action) { + continue; + } + + vblog(LOG_INFO, "Performing action '%s' in queue '%s'", + action->GetId().c_str(), _name.c_str()); + action->PerformAction(); + } +} + +ActionQueueSettingsDialog::ActionQueueSettingsDialog(QWidget *parent, + ActionQueue &settings) + : ItemSettingsDialog(settings, queues, + "AdvSceneSwitcher.actionQueues.select", + "AdvSceneSwitcher.actionQueues.add", + "AdvSceneSwitcher.actionQueues.nameNotAvailable", + parent), + _queueRunStatus(new QLabel()), + _startStopToggle(new QPushButton()), + _queueSize(new QLabel()), + _clear(new QPushButton( + obs_module_text("AdvSceneSwitcher.actionQueues.clear"))), + _runOnStartup(new QCheckBox()), + _queue(settings) +{ + QWidget::connect(_startStopToggle, SIGNAL(clicked()), this, + SLOT(StartStopClicked())); + QWidget::connect(_clear, SIGNAL(clicked()), this, SLOT(ClearClicked())); + + _runOnStartup->setChecked(settings._runOnStartup); + UpdateLabels(); + + auto layout = new QGridLayout(); + int row = 0; + layout->addWidget(new QLabel(obs_module_text( + "AdvSceneSwitcher.actionQueues.name")), + row, 0); + auto nameLayout = new QHBoxLayout(); + nameLayout->setContentsMargins(0, 0, 0, 0); + nameLayout->addWidget(_name); + nameLayout->addWidget(_nameHint); + layout->addLayout(nameLayout, row, 1); + ++row; + layout->addWidget( + new QLabel(obs_module_text( + "AdvSceneSwitcher.actionQueues.runOnStartup")), + row, 0); + layout->addWidget(_runOnStartup, row, 1); + _runOnStartup->setToolTip( + obs_module_text("AdvSceneSwitcher.actionQueues.runOnStartup")); + ++row; + layout->addWidget(_queueRunStatus, row, 0); + layout->addWidget(_startStopToggle, row, 1); + ++row; + layout->addWidget(_queueSize, row, 0); + layout->addWidget(_clear, row, 1); + ++row; + layout->addWidget(_buttonbox, row, 0, 1, -1); + layout->setSizeConstraint(QLayout::SetFixedSize); + setLayout(layout); + + auto timer = new QTimer(this); + QWidget::connect(timer, &QTimer::timeout, this, + &ActionQueueSettingsDialog::UpdateLabels); + timer->start(300); +} + +bool ActionQueueSettingsDialog::AskForSettings(QWidget *parent, + ActionQueue &settings) +{ + ActionQueueSettingsDialog dialog(parent, settings); + dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle")); + if (dialog.exec() != DialogCode::Accepted) { + return false; + } + + settings._name = dialog._name->text().toStdString(); + settings._runOnStartup = dialog._runOnStartup->isChecked(); + return true; +} + +void ActionQueueSettingsDialog::StartStopClicked() +{ + if (_queue.IsRunning()) { + _queue.Stop(); + } else { + _queue.Start(); + } + UpdateLabels(); +} + +void ActionQueueSettingsDialog::ClearClicked() +{ + _queue.Clear(); +} + +void ActionQueueSettingsDialog::UpdateLabels() +{ + _queueRunStatus->setText(obs_module_text( + _queue.IsRunning() ? "AdvSceneSwitcher.actionQueues.running" + : "AdvSceneSwitcher.actionQueues.stopped")); + _startStopToggle->setText( + _queue.IsRunning() + ? obs_module_text("AdvSceneSwitcher.actionQueues.stop") + : obs_module_text( + "AdvSceneSwitcher.actionQueues.start")); + _queueSize->setText( + QString(obs_module_text("AdvSceneSwitcher.actionQueues.size")) + .arg(QString::number(_queue.Size()))); +} + +static bool AskForSettingsWrapper(QWidget *parent, Item &settings) +{ + ActionQueue &queue = dynamic_cast(settings); + if (ActionQueueSettingsDialog::AskForSettings(parent, queue)) { + return true; + } + return false; +} + +void ActionQueueSelection::SetActionQueue( + const std::weak_ptr &queue_) +{ + const QSignalBlocker blocker(_selection); + auto queue = queue_.lock(); + if (queue) { + _selection->setCurrentText( + QString::fromStdString(queue->Name())); + } else { + _selection->setCurrentIndex(-1); + } +} + +ActionQueueSelection::ActionQueueSelection(QWidget *parent) + : ItemSelection(queues, ActionQueue::Create, AskForSettingsWrapper, + "AdvSceneSwitcher.actionQueues.select", + "AdvSceneSwitcher.actionQueues.add", + "AdvSceneSwitcher.item.nameNotAvailable", + "AdvSceneSwitcher.actionQueues.configure", parent) +{ + // Connect to slots + QWidget::connect(ActionQueueSignalManager::Instance(), + SIGNAL(Rename(const QString &, const QString &)), this, + SLOT(RenameItem(const QString &, const QString &))); + QWidget::connect(ActionQueueSignalManager::Instance(), + SIGNAL(Add(const QString &)), this, + SLOT(AddItem(const QString &))); + QWidget::connect(ActionQueueSignalManager::Instance(), + SIGNAL(Remove(const QString &)), this, + SLOT(RemoveItem(const QString &))); + + // Forward signals + QWidget::connect(this, + SIGNAL(ItemRenamed(const QString &, const QString &)), + ActionQueueSignalManager::Instance(), + SIGNAL(Rename(const QString &, const QString &))); + QWidget::connect(this, SIGNAL(ItemAdded(const QString &)), + ActionQueueSignalManager::Instance(), + SIGNAL(Add(const QString &))); + QWidget::connect(this, SIGNAL(ItemRemoved(const QString &)), + ActionQueueSignalManager::Instance(), + SIGNAL(Remove(const QString &))); +} + +ActionQueueSignalManager::ActionQueueSignalManager(QObject *parent) + : QObject(parent) +{ + QWidget::connect(this, SIGNAL(Add(const QString &)), this, + SLOT(StartNewQueue(const QString &))); +} + +ActionQueueSignalManager *ActionQueueSignalManager::Instance() +{ + static ActionQueueSignalManager manager; + return &manager; +} + +void ActionQueueSignalManager::StartNewQueue(const QString &name) +{ + auto weakQueue = GetWeakActionQueueByQString(name); + auto queue = weakQueue.lock(); + if (!queue) { + return; + } + if (queue->RunsOnStartup()) { + queue->Start(); + } +} + +void SaveActionQueues(obs_data_t *obj) +{ + OBSDataArrayAutoRelease array = obs_data_array_create(); + for (const auto &queue : queues) { + OBSDataAutoRelease obj = obs_data_create(); + queue->Save(obj); + obs_data_array_push_back(array, obj); + } + obs_data_set_array(obj, "actionQueues", array); +} + +void LoadActionQueues(obs_data_t *obj) +{ + queues.clear(); + + OBSDataArrayAutoRelease array = obs_data_get_array(obj, "actionQueues"); + size_t count = obs_data_array_count(array); + + for (size_t i = 0; i < count; i++) { + OBSDataAutoRelease obj = obs_data_array_item(array, i); + auto queue = ActionQueue::Create(); + queues.emplace_back(queue); + queues.back()->Load(obj); + } +} + +std::weak_ptr GetWeakActionQueueByName(const std::string &name) +{ + for (const auto &queue : queues) { + if (queue->Name() == name) { + std::weak_ptr wp = + std::dynamic_pointer_cast(queue); + return wp; + } + } + return std::weak_ptr(); +} + +std::weak_ptr GetWeakActionQueueByQString(const QString &name) +{ + return GetWeakActionQueueByName(name.toStdString()); +} + +std::string GetActionQueueName(const std::weak_ptr &queue_) +{ + auto queue = queue_.lock(); + if (!queue) { + return obs_module_text("AdvSceneSwitcher.actionQueues.invalid"); + } + return queue->Name(); +} + +void AutoStartActionQueues() +{ + for (auto &queue : queues) { + auto actionQueue = + std::dynamic_pointer_cast(queue); + if (actionQueue->RunsOnStartup()) { + actionQueue->Start(); + } + } +} + +void StopAndClearAllActionQueues() +{ + for (auto &queue : queues) { + auto actionQueue = + std::dynamic_pointer_cast(queue); + actionQueue->Stop(); + actionQueue->Clear(); + } +} + +} // namespace advss diff --git a/src/macro-core/action-queue.hpp b/src/macro-core/action-queue.hpp new file mode 100644 index 00000000..fa27c45b --- /dev/null +++ b/src/macro-core/action-queue.hpp @@ -0,0 +1,105 @@ +#pragma once +#include "item-selection-helpers.hpp" +#include "macro-action.hpp" + +#include +#include +#include +#include +#include + +namespace advss { + +class ActionQueueSelection; +class ActionQueueSettingsDialog; + +class ActionQueue : public Item { +public: + ActionQueue(); + ~ActionQueue(); + + static std::shared_ptr Create(); + + void Save(obs_data_t *obj) const; + void Load(obs_data_t *obj); + + void Start(); + void Stop(); + bool IsRunning() const; + bool RunsOnStartup() const; + + void Clear(); + bool IsEmpty(); + + void Add(const std::shared_ptr &); + size_t Size(); + +private: + void RunActions(); + + bool _runOnStartup = true; + std::atomic_bool _stop = {false}; + std::mutex _mutex; + std::condition_variable _cv; + std::thread _thread; + std::deque> _actions; + + friend ActionQueueSelection; + friend ActionQueueSettingsDialog; +}; + +class ActionQueueSettingsDialog : public ItemSettingsDialog { + Q_OBJECT + +public: + ActionQueueSettingsDialog(QWidget *parent, ActionQueue &); + static bool AskForSettings(QWidget *parent, ActionQueue &settings); + +private slots: + void StartStopClicked(); + void ClearClicked(); + void UpdateLabels(); + +private: + QLabel *_queueRunStatus; + QPushButton *_startStopToggle; + QLabel *_queueSize; + QPushButton *_clear; + QCheckBox *_runOnStartup; + + ActionQueue &_queue; +}; + +class ActionQueueSelection : public ItemSelection { + Q_OBJECT + +public: + ActionQueueSelection(QWidget *parent = 0); + void SetActionQueue(const std::weak_ptr &); +}; + +class ActionQueueSignalManager : public QObject { + Q_OBJECT +public: + ActionQueueSignalManager(QObject *parent = nullptr); + static ActionQueueSignalManager *Instance(); + +private slots: + void StartNewQueue(const QString &); + +signals: + void Rename(const QString &, const QString &); + void Add(const QString &); + void Remove(const QString &); +}; + +void SetupActionQueues(); +void SaveActionQueues(obs_data_t *); +void LoadActionQueues(obs_data_t *); +std::weak_ptr GetWeakActionQueueByName(const std::string &name); +std::weak_ptr GetWeakActionQueueByQString(const QString &name); +std::string GetActionQueueName(const std::weak_ptr &); +void AutoStartActionQueues(); +void StopAndClearAllActionQueues(); + +} // namespace advss diff --git a/src/macro-core/macro-action-queue.cpp b/src/macro-core/macro-action-queue.cpp new file mode 100644 index 00000000..8f7ab0b7 --- /dev/null +++ b/src/macro-core/macro-action-queue.cpp @@ -0,0 +1,218 @@ +#include "macro-action-queue.hpp" +#include "macro-helpers.hpp" +#include "utility.hpp" + +namespace advss { + +const std::string MacroActionQueue::id = "queue"; + +bool MacroActionQueue::_registered = MacroActionFactory::Register( + MacroActionQueue::id, + {MacroActionQueue::Create, MacroActionQueueEdit::Create, + "AdvSceneSwitcher.action.queue"}); + +const static std::map actionTypes = { + {MacroActionQueue::Action::ADD_TO_QUEUE, + "AdvSceneSwitcher.action.queue.type.add"}, + {MacroActionQueue::Action::START_QUEUE, + "AdvSceneSwitcher.action.queue.type.start"}, + {MacroActionQueue::Action::STOP_QUEUE, + "AdvSceneSwitcher.action.queue.type.stop"}, + {MacroActionQueue::Action::CLEAR_QUEUE, + "AdvSceneSwitcher.action.queue.type.clear"}, +}; + +void MacroActionQueue::AddActions(ActionQueue *queue) +{ + auto macro = _macro.GetMacro(); + if (!macro) { + return; + } + + auto actions = *GetMacroActions(macro.get()); + for (const auto &action : actions) { + queue->Add(action); + } +} + +bool MacroActionQueue::PerformAction() +{ + auto queue = _queue.lock(); + if (!queue) { + return true; + } + + switch (_action) { + case Action::ADD_TO_QUEUE: + AddActions(queue.get()); + break; + case Action::CLEAR_QUEUE: + queue->Clear(); + break; + case Action::START_QUEUE: + queue->Start(); + break; + case Action::STOP_QUEUE: + queue->Stop(); + break; + default: + break; + } + return true; +} + +void MacroActionQueue::LogAction() const +{ + auto macro = _macro.GetMacro(); + if (!macro) { + return; + } + switch (_action) { + case Action::ADD_TO_QUEUE: + vblog(LOG_INFO, "queued actions of \"%s\" to \"%s\"", + GetMacroName(macro.get()).c_str(), + GetActionQueueName(_queue).c_str()); + break; + case Action::START_QUEUE: + vblog(LOG_INFO, "start queue \"%s\"", + GetActionQueueName(_queue).c_str()); + break; + case Action::STOP_QUEUE: + vblog(LOG_INFO, "stop queue \"%s\"", + GetActionQueueName(_queue).c_str()); + break; + case Action::CLEAR_QUEUE: + vblog(LOG_INFO, "cleared queue \"%s\"", + GetActionQueueName(_queue).c_str()); + break; + default: + break; + } +} + +bool MacroActionQueue::Save(obs_data_t *obj) const +{ + MacroAction::Save(obj); + _macro.Save(obj); + obs_data_set_int(obj, "action", static_cast(_action)); + obs_data_set_string(obj, "queue", GetActionQueueName(_queue).c_str()); + return true; +} + +bool MacroActionQueue::Load(obs_data_t *obj) +{ + MacroAction::Load(obj); + _macro.Load(obj); + _action = static_cast( + obs_data_get_int(obj, "action")); + _queue = GetWeakActionQueueByName(obs_data_get_string(obj, "queue")); + return true; +} + +std::string MacroActionQueue::GetShortDesc() const +{ + return GetActionQueueName(_queue); +} + +static inline void populateActionSelection(QComboBox *list) +{ + for (const auto &[_, name] : actionTypes) { + list->addItem(obs_module_text(name.c_str())); + } +} + +MacroActionQueueEdit::MacroActionQueueEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent), + _macros(new MacroSelection(parent)), + _queues(new ActionQueueSelection()), + _actions(new QComboBox()), + _layout(new QHBoxLayout()) +{ + populateActionSelection(_actions); + + QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)), + this, SLOT(MacroChanged(const QString &))); + QWidget::connect(_queues, SIGNAL(SelectionChanged(const QString &)), + this, SLOT(QueueChanged(const QString &))); + QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ActionChanged(int))); + + setLayout(_layout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroActionQueueEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + _actions->setCurrentIndex(static_cast(_entryData->_action)); + _macros->SetCurrentMacro(_entryData->_macro); + _queues->SetActionQueue(_entryData->_queue); + SetWidgetVisibility(); +} + +void MacroActionQueueEdit::MacroChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_macro = text; + emit HeaderInfoChanged( + QString::fromStdString(_entryData->GetShortDesc())); +} + +void MacroActionQueueEdit::QueueChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_queue = GetWeakActionQueueByQString(text); +} + +void MacroActionQueueEdit::ActionChanged(int value) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_action = static_cast(value); + SetWidgetVisibility(); + emit HeaderInfoChanged( + QString::fromStdString(_entryData->GetShortDesc())); +} + +void MacroActionQueueEdit::SetWidgetVisibility() +{ + _layout->removeWidget(_actions); + _layout->removeWidget(_queues); + _layout->removeWidget(_macros); + + ClearLayout(_layout); + + PlaceWidgets( + obs_module_text( + _entryData->_action == + MacroActionQueue::Action::ADD_TO_QUEUE + ? "AdvSceneSwitcher.action.queue.entry.add" + : "AdvSceneSwitcher.action.queue.entry.other"), + _layout, + {{"{{actions}}", _actions}, + {"{{queues}}", _queues}, + {"{{macros}}", _macros}}); + + _macros->setVisible(_entryData->_action == + MacroActionQueue::Action::ADD_TO_QUEUE); + _macros->HideSelectedMacro(); +} + +} // namespace advss diff --git a/src/macro-core/macro-action-queue.hpp b/src/macro-core/macro-action-queue.hpp new file mode 100644 index 00000000..3d60ee5d --- /dev/null +++ b/src/macro-core/macro-action-queue.hpp @@ -0,0 +1,75 @@ +#pragma once +#include "macro-action-edit.hpp" +#include "macro-selection.hpp" +#include "action-queue.hpp" + +#include + +namespace advss { + +class MacroActionQueue : public MacroRefAction { +public: + MacroActionQueue(Macro *m) : MacroAction(m), MacroRefAction(m) {} + bool PerformAction(); + void LogAction() const; + bool Save(obs_data_t *obj) const; + bool Load(obs_data_t *obj); + std::string GetShortDesc() const; + std::string GetId() const { return id; }; + static std::shared_ptr Create(Macro *m) + { + return std::make_shared(m); + } + + enum class Action { + ADD_TO_QUEUE, + START_QUEUE, + STOP_QUEUE, + CLEAR_QUEUE, + }; + Action _action = Action::ADD_TO_QUEUE; + std::weak_ptr _queue; + +private: + void AddActions(ActionQueue *); + + static bool _registered; + static const std::string id; +}; + +class MacroActionQueueEdit : public QWidget { + Q_OBJECT + +public: + MacroActionQueueEdit( + QWidget *parent, + std::shared_ptr entryData = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr action) + { + return new MacroActionQueueEdit( + parent, + std::dynamic_pointer_cast(action)); + } + +private slots: + void MacroChanged(const QString &text); + void QueueChanged(const QString &); + void ActionChanged(int value); +signals: + void HeaderInfoChanged(const QString &); + +private: + void SetWidgetVisibility(); + + MacroSelection *_macros; + ActionQueueSelection *_queues; + QComboBox *_actions; + QHBoxLayout *_layout; + + std::shared_ptr _entryData; + bool _loading = true; +}; + +} // namespace advss diff --git a/src/macro-core/macro-condition-queue.cpp b/src/macro-core/macro-condition-queue.cpp new file mode 100644 index 00000000..201ade61 --- /dev/null +++ b/src/macro-core/macro-condition-queue.cpp @@ -0,0 +1,163 @@ +#include "macro-condition-queue.hpp" +#include "utility.hpp" + +namespace advss { + +const std::string MacroConditionQueue::id = "queue"; + +bool MacroConditionQueue::_registered = MacroConditionFactory::Register( + MacroConditionQueue::id, + {MacroConditionQueue::Create, MacroConditionQueueEdit::Create, + "AdvSceneSwitcher.condition.queue"}); + +const static std::map + conditionTypes = { + {MacroConditionQueue::Condition::STARTED, + "AdvSceneSwitcher.condition.queue.type.started"}, + {MacroConditionQueue::Condition::STOPPED, + "AdvSceneSwitcher.condition.queue.type.stopped"}, + {MacroConditionQueue::Condition::SIZE, + "AdvSceneSwitcher.condition.queue.type.size"}, +}; + +bool MacroConditionQueue::CheckCondition() +{ + auto queue = _queue.lock(); + if (!queue) { + return false; + } + + switch (_condition) { + case Condition::STARTED: + return queue->IsRunning(); + case Condition::STOPPED: + return !queue->IsRunning(); + case Condition::SIZE: + return queue->Size() < _size; + default: + break; + } + return false; +} + +bool MacroConditionQueue::Save(obs_data_t *obj) const +{ + MacroCondition::Save(obj); + obs_data_set_int(obj, "condition", static_cast(_condition)); + obs_data_set_string(obj, "queue", GetActionQueueName(_queue).c_str()); + _size.Save(obj, "size"); + return true; +} + +bool MacroConditionQueue::Load(obs_data_t *obj) +{ + MacroCondition::Load(obj); + _condition = static_cast(obs_data_get_int(obj, "condition")); + _queue = GetWeakActionQueueByName(obs_data_get_string(obj, "queue")); + _size.Load(obj, "size"); + return true; +} + +static inline void populateQueueTypeSelection(QComboBox *list) +{ + for (const auto &[_, name] : conditionTypes) { + list->addItem(obs_module_text(name.c_str())); + } +} + +MacroConditionQueueEdit::MacroConditionQueueEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent), + _conditions(new QComboBox()), + _queues(new ActionQueueSelection()), + _size(new VariableSpinBox()), + _layout(new QHBoxLayout()) +{ + populateQueueTypeSelection(_conditions); + + _size->setMinimum(1); + + QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this, + SLOT(ConditionChanged(int))); + QWidget::connect(_queues, SIGNAL(SelectionChanged(const QString &)), + this, SLOT(QueueChanged(const QString &))); + QWidget::connect( + _size, + SIGNAL(NumberVariableChanged(const NumberVariable &)), + this, SLOT(SizeChanged(const NumberVariable &))); + + setLayout(_layout); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroConditionQueueEdit::ConditionChanged(int condition) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_condition = + static_cast(condition); + SetWidgetVisibility(); +} + +void MacroConditionQueueEdit::QueueChanged(const QString &text) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_queue = GetWeakActionQueueByQString(text); + emit HeaderInfoChanged( + QString::fromStdString(_entryData->GetShortDesc())); +} + +void MacroConditionQueueEdit::SizeChanged(const NumberVariable &value) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_size = value; +} + +void MacroConditionQueueEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + _conditions->setCurrentIndex(static_cast(_entryData->_condition)); + _queues->SetActionQueue(_entryData->_queue); + SetWidgetVisibility(); +} + +void MacroConditionQueueEdit::SetWidgetVisibility() +{ + _layout->removeWidget(_conditions); + _layout->removeWidget(_queues); + _layout->removeWidget(_size); + + ClearLayout(_layout); + + PlaceWidgets( + obs_module_text( + _entryData->_condition == + MacroConditionQueue::Condition::SIZE + ? "AdvSceneSwitcher.condition.queue.entry.size" + : "AdvSceneSwitcher.condition.queue.entry.startStop"), + _layout, + {{"{{conditions}}", _conditions}, + {"{{queues}}", _queues}, + {"{{size}}", _size}}); + + _size->setVisible(_entryData->_condition == + MacroConditionQueue::Condition::SIZE); +} + +} // namespace advss diff --git a/src/macro-core/macro-condition-queue.hpp b/src/macro-core/macro-condition-queue.hpp new file mode 100644 index 00000000..92d03033 --- /dev/null +++ b/src/macro-core/macro-condition-queue.hpp @@ -0,0 +1,74 @@ +#pragma once +#include "macro-condition-edit.hpp" +#include "action-queue.hpp" + +#include +#include +#include +#include + +namespace advss { + +class MacroConditionQueue : public MacroCondition { +public: + MacroConditionQueue(Macro *m) : MacroCondition(m, true) {} + bool CheckCondition(); + bool Save(obs_data_t *obj) const; + bool Load(obs_data_t *obj); + std::string GetId() const { return id; }; + static std::shared_ptr Create(Macro *m) + { + return std::make_shared(m); + } + + enum class Condition { + STARTED, + STOPPED, + SIZE, + }; + Condition _condition = Condition::STARTED; + std::weak_ptr _queue; + IntVariable _size = 1; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroConditionQueueEdit : public QWidget { + Q_OBJECT + +public: + MacroConditionQueueEdit( + QWidget *parent, + std::shared_ptr cond = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr cond) + { + return new MacroConditionQueueEdit( + parent, + std::dynamic_pointer_cast(cond)); + } + +private slots: + void ConditionChanged(int); + void QueueChanged(const QString &); + void SizeChanged(const NumberVariable &); + +signals: + void HeaderInfoChanged(const QString &); + +private: + void SetWidgetVisibility(); + + QComboBox *_conditions; + ActionQueueSelection *_queues; + VariableSpinBox *_size; + QHBoxLayout *_layout; + + std::shared_ptr _entryData; + bool _loading = true; +}; + +} // namespace advss