diff --git a/CMakeLists.txt b/CMakeLists.txt index 16be3087..fb3d989d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,6 +155,8 @@ target_sources( lib/queue/action-queue-tab.hpp lib/utils/backup.cpp lib/utils/backup.hpp + lib/utils/condition-logic.cpp + lib/utils/condition-logic.hpp lib/utils/curl-helper.cpp lib/utils/curl-helper.hpp lib/utils/cursor-shape-changer.cpp diff --git a/lib/advanced-scene-switcher.hpp b/lib/advanced-scene-switcher.hpp index e3819e78..b26c4428 100644 --- a/lib/advanced-scene-switcher.hpp +++ b/lib/advanced-scene-switcher.hpp @@ -1,5 +1,6 @@ #pragma once #include "macro-segment-list.hpp" +#include "condition-logic.hpp" #include "log-helper.hpp" #include @@ -12,7 +13,6 @@ class MacroActionEdit; class MacroConditionEdit; class Duration; class SequenceWidget; -enum class LogicType; struct SceneGroup; /******************************************************************************* @@ -174,7 +174,7 @@ public slots: void MacroConditionReorder(int to, int target); void AddMacroCondition(int idx); void AddMacroCondition(Macro *macro, int idx, const std::string &id, - obs_data_t *data, LogicType logic); + obs_data_t *data, Logic::Type logic); void RemoveMacroCondition(int idx); void MoveMacroConditionUp(int idx); void MoveMacroConditionDown(int idx); diff --git a/lib/macro/macro-condition-edit.cpp b/lib/macro/macro-condition-edit.cpp index a4867790..2775e019 100644 --- a/lib/macro/macro-condition-edit.cpp +++ b/lib/macro/macro-condition-edit.cpp @@ -10,26 +10,6 @@ namespace advss { -static inline void populateLogicSelection(QComboBox *list, bool root = false) -{ - if (root) { - for (const auto &entry : MacroCondition::logicTypes) { - if (static_cast(entry.first) < logic_root_offset) { - list->addItem(obs_module_text( - entry.second._name.c_str())); - } - } - } else { - for (const auto &entry : MacroCondition::logicTypes) { - if (static_cast(entry.first) >= - logic_root_offset) { - list->addItem(obs_module_text( - entry.second._name.c_str())); - } - } - } -} - static inline void populateConditionSelection(QComboBox *list) { for (const auto &[_, condition] : @@ -120,13 +100,13 @@ void DurationModifierEdit::Collapse(bool collapse) MacroConditionEdit::MacroConditionEdit( QWidget *parent, std::shared_ptr *entryData, - const std::string &id, bool root) + const std::string &id, bool isRootCondition) : MacroSegmentEdit(parent), _logicSelection(new QComboBox()), _conditionSelection(new FilterComboBox()), _dur(new DurationModifierEdit()), _entryData(entryData), - _isRoot(root) + _isRoot(isRootCondition) { QWidget::connect(_logicSelection, SIGNAL(currentIndexChanged(int)), this, SLOT(LogicSelectionChanged(int))); @@ -139,7 +119,7 @@ MacroConditionEdit::MacroConditionEdit( this, SLOT(DurationModifierChanged(DurationModifier::Type))); - populateLogicSelection(_logicSelection, root); + Logic::PopulateLogicTypeSelection(_logicSelection, isRootCondition); populateConditionSelection(_conditionSelection); _section->AddHeaderWidget(_logicSelection); @@ -168,15 +148,10 @@ void MacroConditionEdit::LogicSelectionChanged(int idx) return; } - LogicType type; - if (IsRootNode()) { - type = static_cast(idx); - } else { - type = static_cast(idx + logic_root_offset); - } - auto lock = LockContext(); - (*_entryData)->SetLogicType(type); + const auto logic = static_cast( + _logicSelection->itemData(idx).toInt()); + (*_entryData)->SetLogicType(logic); } bool MacroConditionEdit::IsRootNode() @@ -186,13 +161,9 @@ bool MacroConditionEdit::IsRootNode() void MacroConditionEdit::SetLogicSelection() { - auto logic = (*_entryData)->GetLogicType(); - if (IsRootNode()) { - _logicSelection->setCurrentIndex(static_cast(logic)); - } else { - _logicSelection->setCurrentIndex(static_cast(logic) - - logic_root_offset); - } + const auto logic = (*_entryData)->GetLogicType(); + _logicSelection->setCurrentIndex( + _logicSelection->findData(static_cast(logic))); } void MacroConditionEdit::SetRootNode(bool root) @@ -200,7 +171,7 @@ void MacroConditionEdit::SetRootNode(bool root) _isRoot = root; const QSignalBlocker blocker(_logicSelection); _logicSelection->clear(); - populateLogicSelection(_logicSelection, root); + Logic::PopulateLogicTypeSelection(_logicSelection, root); SetLogicSelection(); } @@ -301,17 +272,17 @@ void AdvSceneSwitcher::AddMacroCondition(int idx) } std::string id; - LogicType logic; + Logic::Type logic; if (idx >= 1) { id = macro->Conditions().at(idx - 1)->GetId(); if (idx == 1) { - logic = LogicType::OR; + logic = Logic::Type::OR; } else { logic = macro->Conditions().at(idx - 1)->GetLogicType(); } } else { id = MacroCondition::GetDefaultID(); - logic = LogicType::ROOT_NONE; + logic = Logic::Type::ROOT_NONE; } OBSDataAutoRelease data; @@ -324,7 +295,7 @@ void AdvSceneSwitcher::AddMacroCondition(int idx) void AdvSceneSwitcher::AddMacroCondition(Macro *macro, int idx, const std::string &id, - obs_data_t *data, LogicType logic) + obs_data_t *data, Logic::Type logic) { if (idx < 0 || idx > (int)macro->Conditions().size()) { assert(false); @@ -389,7 +360,7 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx) macro->UpdateConditionIndices(); if (idx == 0 && macro->Conditions().size() > 0) { auto newRoot = macro->Conditions().at(0); - newRoot->SetLogicType(LogicType::ROOT_NONE); + newRoot->SetLogicType(Logic::Type::ROOT_NONE); static_cast( ui->conditionsList->WidgetAt(0)) ->SetRootNode(true); @@ -540,22 +511,23 @@ void AdvSceneSwitcher::MacroConditionReorder(int to, int from) auto lock = LockContext(); auto condition = macro->Conditions().at(from); if (to == 0) { - condition->SetLogicType(LogicType::ROOT_NONE); + condition->SetLogicType(Logic::Type::ROOT_NONE); static_cast( ui->conditionsList->WidgetAt(from)) ->SetRootNode(true); - macro->Conditions().at(0)->SetLogicType(LogicType::AND); + macro->Conditions().at(0)->SetLogicType( + Logic::Type::AND); static_cast( ui->conditionsList->WidgetAt(0)) ->SetRootNode(false); } if (from == 0) { - condition->SetLogicType(LogicType::AND); + condition->SetLogicType(Logic::Type::AND); static_cast( ui->conditionsList->WidgetAt(from)) ->SetRootNode(false); macro->Conditions().at(1)->SetLogicType( - LogicType::ROOT_NONE); + Logic::Type::ROOT_NONE); static_cast( ui->conditionsList->WidgetAt(1)) ->SetRootNode(true); diff --git a/lib/macro/macro-condition.cpp b/lib/macro/macro-condition.cpp index f529bbc9..cf9c2cdc 100644 --- a/lib/macro/macro-condition.cpp +++ b/lib/macro/macro-condition.cpp @@ -2,16 +2,6 @@ namespace advss { -const std::map MacroCondition::logicTypes = { - {LogicType::NONE, {"AdvSceneSwitcher.logic.none"}}, - {LogicType::AND, {"AdvSceneSwitcher.logic.and"}}, - {LogicType::OR, {"AdvSceneSwitcher.logic.or"}}, - {LogicType::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}}, - {LogicType::OR_NOT, {"AdvSceneSwitcher.logic.orNot"}}, - {LogicType::ROOT_NONE, {"AdvSceneSwitcher.logic.rootNone"}}, - {LogicType::ROOT_NOT, {"AdvSceneSwitcher.logic.not"}}, -}; - void DurationModifier::Save(obs_data_t *obj, const char *condName, const char *duration) const { @@ -84,7 +74,7 @@ bool MacroCondition::Save(obs_data_t *obj) const { MacroSegment::Save(obj); obs_data_set_string(obj, "id", GetId().c_str()); - obs_data_set_int(obj, "logic", static_cast(_logic)); + _logic.Save(obj, "logic"); // To avoid conflicts with conditions which also use the Duration class // save the duration modifier in a separate obj @@ -99,7 +89,7 @@ bool MacroCondition::Save(obs_data_t *obj) const bool MacroCondition::Load(obs_data_t *obj) { MacroSegment::Load(obj); - _logic = static_cast(obs_data_get_int(obj, "logic")); + _logic.Load(obj, "logic"); if (obs_data_has_user_value(obj, "durationModifier")) { auto durObj = obs_data_get_obj(obj, "durationModifier"); _duration.Load(durObj); @@ -111,6 +101,27 @@ bool MacroCondition::Load(obs_data_t *obj) return true; } +void MacroCondition::ValidateLogicSelection(bool isRootCondition, + const char *context) +{ + if (_logic.IsValidSelection(isRootCondition)) { + return; + } + + if (_logic.IsRootType()) { + _logic.SetType(Logic::Type::ROOT_NONE); + blog(LOG_WARNING, + "setting invalid logic selection to 'if' for macro %s", + context); + return; + } + + _logic.SetType(Logic::Type::NONE); + blog(LOG_WARNING, + "setting invalid logic selection to 'ignore' for macro %s", + context); +} + void MacroCondition::ResetDuration() { _duration.Reset(); diff --git a/lib/macro/macro-condition.hpp b/lib/macro/macro-condition.hpp index 987036b9..9f5694a1 100644 --- a/lib/macro/macro-condition.hpp +++ b/lib/macro/macro-condition.hpp @@ -1,34 +1,11 @@ #pragma once #include "macro-segment.hpp" +#include "condition-logic.hpp" +#include "duration-control.hpp" #include "macro-ref.hpp" -#include namespace advss { -constexpr auto logic_root_offset = 100; - -enum class LogicType { - ROOT_NONE = 0, - ROOT_NOT, - ROOT_LAST, - // leave some space for potential expansion - NONE = 100, - AND, - OR, - AND_NOT, - OR_NOT, - LAST, -}; - -static inline bool isRootLogicType(LogicType l) -{ - return static_cast(l) < logic_root_offset; -} - -struct LogicTypeInfo { - std::string _name; -}; - class DurationModifier { public: enum class Type { @@ -65,9 +42,9 @@ public: virtual bool CheckCondition() = 0; virtual bool Save(obs_data_t *obj) const = 0; virtual bool Load(obs_data_t *obj) = 0; - LogicType GetLogicType() { return _logic; } - void SetLogicType(LogicType logic) { _logic = logic; } - static const std::map logicTypes; + Logic::Type GetLogicType() const { return _logic.GetType(); } + void SetLogicType(const Logic::Type &logic) { _logic.SetType(logic); } + void ValidateLogicSelection(bool isRootCondition, const char *context); void ResetDuration(); void CheckDurationModifier(bool &val); DurationModifier GetDurationModifier() { return _duration; } @@ -77,7 +54,7 @@ public: static std::string_view GetDefaultID(); private: - LogicType _logic = LogicType::ROOT_NONE; + Logic _logic = Logic(Logic::Type::ROOT_NONE); DurationModifier _duration; }; diff --git a/lib/macro/macro-segment-copy-paste.cpp b/lib/macro/macro-segment-copy-paste.cpp index 261c8dc0..37394811 100644 --- a/lib/macro/macro-segment-copy-paste.cpp +++ b/lib/macro/macro-segment-copy-paste.cpp @@ -61,13 +61,13 @@ void AdvSceneSwitcher::PasteMacroSegment() const auto condition = std::static_pointer_cast( copyInfo.segment); auto logic = condition->GetLogicType(); - if (logic > LogicType::ROOT_LAST && + if (logic > Logic::Type::ROOT_LAST && macro->Conditions().empty()) { - logic = LogicType::ROOT_NONE; + logic = Logic::Type::ROOT_NONE; } - if (logic < LogicType::ROOT_LAST && + if (logic < Logic::Type::ROOT_LAST && !macro->Conditions().empty()) { - logic = LogicType::OR; + logic = Logic::Type::OR; } AddMacroCondition(macro.get(), macro->Conditions().size(), copyInfo.segment->GetId(), data.Get(), logic); diff --git a/lib/macro/macro.cpp b/lib/macro/macro.cpp index 868ee7b8..cb81ea45 100644 --- a/lib/macro/macro.cpp +++ b/lib/macro/macro.cpp @@ -17,7 +17,6 @@ namespace advss { -static constexpr int perfLogThreshold = 300; static std::deque> macros; Macro::Macro(const std::string &name, const bool addHotkey) @@ -101,6 +100,30 @@ void Macro::PrepareMoveToGroup(std::shared_ptr group, } } +static bool checkCondition(const std::shared_ptr &condition) +{ + using namespace std::chrono_literals; + static constexpr auto perfLogThreshold = 300ms; + + const auto startTime = std::chrono::high_resolution_clock::now(); + const bool conditionMatched = condition->CheckCondition(); + const auto endTime = std::chrono::high_resolution_clock::now(); + const auto timeSpent = endTime - startTime; + + if (timeSpent >= perfLogThreshold) { + const long int ms = + std::chrono::duration_cast( + timeSpent) + .count(); + blog(LOG_WARNING, + "spent %ld ms in %s condition check of macro '%s'!", ms, + condition->GetId().c_str(), + condition->GetMacro()->Name().c_str()); + } + + return conditionMatched; +} + bool Macro::CeckMatch(bool ignorePause) { if (_isGroup) { @@ -108,77 +131,35 @@ bool Macro::CeckMatch(bool ignorePause) } _matched = false; - for (auto &c : _conditions) { + for (auto &condition : _conditions) { if (_paused && !ignorePause) { vblog(LOG_INFO, "Macro %s is paused", _name.c_str()); return false; } - auto startTime = std::chrono::high_resolution_clock::now(); - bool cond = c->CheckCondition(); - auto endTime = std::chrono::high_resolution_clock::now(); - auto ms = std::chrono::duration_cast( - endTime - startTime); - if (ms.count() >= perfLogThreshold) { - blog(LOG_WARNING, - "spent %ld ms in %s condition check of macro '%s'!", - (long int)ms.count(), c->GetId().c_str(), - Name().c_str()); - } + bool conditionMatched = checkCondition(condition); + condition->CheckDurationModifier(conditionMatched); - c->CheckDurationModifier(cond); - - switch (c->GetLogicType()) { - case LogicType::NONE: - vblog(LOG_INFO, - "ignoring condition check 'none' for '%s'", - _name.c_str()); + const auto logicType = condition->GetLogicType(); + if (logicType == Logic::Type::NONE) { + vblog(LOG_INFO, "ignoring condition '%s' for '%s'", + condition->GetId().c_str(), _name.c_str()); continue; - case LogicType::AND: - _matched = _matched && cond; - if (cond) { - c->EnableHighlight(); - } - break; - case LogicType::OR: - _matched = _matched || cond; - if (cond) { - c->EnableHighlight(); - } - break; - case LogicType::AND_NOT: - _matched = _matched && !cond; - if (!cond) { - c->EnableHighlight(); - } - break; - case LogicType::OR_NOT: - _matched = _matched || !cond; - if (!cond) { - c->EnableHighlight(); - } - break; - case LogicType::ROOT_NONE: - _matched = cond; - if (cond) { - c->EnableHighlight(); - } - break; - case LogicType::ROOT_NOT: - _matched = !cond; - if (!cond) { - c->EnableHighlight(); - } - break; - default: - blog(LOG_WARNING, - "ignoring unknown condition check for '%s'", - _name.c_str()); - break; } - vblog(LOG_INFO, "condition %s returned %d", c->GetId().c_str(), - cond); + vblog(LOG_INFO, "condition %s returned %d", + condition->GetId().c_str(), conditionMatched); + + const bool isNegativeLogicType = + Logic::IsNegationType(logicType); + if ((conditionMatched && !isNegativeLogicType) || + (!conditionMatched && isNegativeLogicType)) { + condition->EnableHighlight(); + } + + _matched = Logic::ApplyConditionLogic( + logicType, _matched, conditionMatched, _name.c_str()); } + vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched); _conditionSateChanged = _lastMatched != _matched; @@ -631,40 +612,6 @@ bool Macro::Save(obs_data_t *obj) const return true; } -bool isValidLogic(LogicType t, bool root) -{ - bool isRoot = isRootLogicType(t); - if (!isRoot == root) { - return false; - } - if (isRoot) { - if (t >= LogicType::ROOT_LAST) { - return false; - } - } else if (t >= LogicType::LAST) { - return false; - } - return true; -} - -void setValidLogic(MacroCondition *c, bool root, std::string name) -{ - if (isValidLogic(c->GetLogicType(), root)) { - return; - } - if (root) { - c->SetLogicType(LogicType::ROOT_NONE); - blog(LOG_WARNING, - "setting invalid logic selection to 'if' for macro %s", - name.c_str()); - } else { - c->SetLogicType(LogicType::NONE); - blog(LOG_WARNING, - "setting invalid logic selection to 'ignore' for macro %s", - name.c_str()); - } -} - bool Macro::Load(obs_data_t *obj) { _name = obs_data_get_string(obj, "name"); @@ -720,7 +667,7 @@ bool Macro::Load(obs_data_t *obj) _conditions.emplace_back(newEntry); auto c = _conditions.back().get(); c->Load(arrayObj); - setValidLogic(c, root, _name); + c->ValidateLogicSelection(root, Name().c_str()); } else { blog(LOG_WARNING, "discarding condition entry with unknown id (%s) for macro %s", diff --git a/lib/utils/condition-logic.cpp b/lib/utils/condition-logic.cpp new file mode 100644 index 00000000..ebe6c88e --- /dev/null +++ b/lib/utils/condition-logic.cpp @@ -0,0 +1,109 @@ +#include "condition-logic.hpp" +#include "obs-module-helper.hpp" +#include "log-helper.hpp" + +#include +#include + +namespace advss { + +const std::map Logic::localeMap = { + {Logic::Type::NONE, {"AdvSceneSwitcher.logic.none"}}, + {Logic::Type::AND, {"AdvSceneSwitcher.logic.and"}}, + {Logic::Type::OR, {"AdvSceneSwitcher.logic.or"}}, + {Logic::Type::AND_NOT, {"AdvSceneSwitcher.logic.andNot"}}, + {Logic::Type::OR_NOT, {"AdvSceneSwitcher.logic.orNot"}}, + {Logic::Type::ROOT_NONE, {"AdvSceneSwitcher.logic.rootNone"}}, + {Logic::Type::ROOT_NOT, {"AdvSceneSwitcher.logic.not"}}, +}; + +bool Logic::ApplyConditionLogic(Type type, bool currentMatchResult, + bool conditionMatched, const char *context) +{ + if (!context) { + context = ""; + } + + switch (type) { + case Type::ROOT_NONE: + return conditionMatched; + case Type::ROOT_NOT: + return !conditionMatched; + case Type::ROOT_LAST: + break; + case Type::NONE: + vblog(LOG_INFO, "skipping condition check for '%s'", context); + return currentMatchResult; + case Type::AND: + return currentMatchResult && conditionMatched; + case Type::OR: + return currentMatchResult || conditionMatched; + case Type::AND_NOT: + return currentMatchResult && !conditionMatched; + case Type::OR_NOT: + return currentMatchResult || !conditionMatched; + case Type::LAST: + default: + blog(LOG_WARNING, "ignoring invalid logic check (%s)", context); + return currentMatchResult; + } + + return currentMatchResult; +} + +void Logic::PopulateLogicTypeSelection(QComboBox *list, bool isRootCondition) +{ + auto compare = isRootCondition + ? std::function{[](int typeValue) { + return typeValue < rootOffset; + }} + : std::function{[](int typeValue) { + return typeValue >= rootOffset; + }}; + for (const auto &[type, name] : localeMap) { + const int typeValue = static_cast(type); + if (compare(typeValue)) { + list->addItem(obs_module_text(name), typeValue); + } + } +} + +void Logic::Save(obs_data_t *obj, const char *name) const +{ + obs_data_set_int(obj, name, static_cast(_type)); +} + +void Logic::Load(obs_data_t *obj, const char *name) +{ + _type = static_cast(obs_data_get_int(obj, name)); +} + +bool Logic::IsRootType() const +{ + return _type < static_cast(rootOffset); +} + +bool Logic::IsNegationType(Logic::Type type) +{ + return type == Type::ROOT_NOT || type == Type::AND_NOT || + type == Type::OR_NOT; + ; +} + +bool Logic::IsValidSelection(bool isRootCondition) const +{ + if (!IsRootType() == isRootCondition) { + return false; + } + if (IsRootType() && + (_type < Type::ROOT_NONE || _type >= Type::ROOT_LAST)) { + return false; + } + if (!IsRootType() && + (_type <= Type::ROOT_LAST || _type >= Type::LAST)) { + return false; + } + return true; +} + +} // namespace advss diff --git a/lib/utils/condition-logic.hpp b/lib/utils/condition-logic.hpp new file mode 100644 index 00000000..4fc8a733 --- /dev/null +++ b/lib/utils/condition-logic.hpp @@ -0,0 +1,51 @@ +#pragma once +#include +#include +#include + +class QComboBox; + +namespace advss { + +class Logic { +public: + static constexpr auto rootOffset = 100; + + enum class Type { + ROOT_NONE = 0, + ROOT_NOT, + ROOT_LAST, + + // leave some space for potential expansion + NONE = rootOffset, + AND, + OR, + AND_NOT, + OR_NOT, + LAST, + }; + + Logic(Type type) { _type = type; } + + void Save(obs_data_t *obj, const char *name) const; + void Load(obs_data_t *obj, const char *name); + + Type GetType() const { return _type; } + void SetType(const Type &type) { _type = type; } + bool IsRootType() const; + static bool IsNegationType(Logic::Type); + bool IsValidSelection(bool isRootCondition) const; + + static bool ApplyConditionLogic(Type, bool currentMatchResult, + bool conditionMatched, + const char *context); + + static void PopulateLogicTypeSelection(QComboBox *list, + bool isRootCondition); + +private: + Type _type = Type::NONE; + static const std::map localeMap; +}; + +} // namespace advss