diff --git a/src/Data/Config/Property.cpp b/src/Data/Config/Property.cpp index 6bf61f4..a473ae5 100644 --- a/src/Data/Config/Property.cpp +++ b/src/Data/Config/Property.cpp @@ -2,6 +2,8 @@ #include "Data/Defines/Armor.h" +#include + namespace Data { bool AnyOfProperty::Match(const RE::GFxValue& a_value) const @@ -21,6 +23,25 @@ namespace Data _subProperties.push_back(a_property); } + void AnyOfProperty::RemoveZeroNumberMatches() + { + _subProperties.erase( + std::remove_if( + _subProperties.begin(), + _subProperties.end(), + [](const std::shared_ptr& a_property) + { + auto matchProperty = std::dynamic_pointer_cast(a_property); + return matchProperty && matchProperty->IsZeroNumber(); + }), + _subProperties.end()); + } + + bool AnyOfProperty::Empty() const + { + return _subProperties.empty(); + } + bool MatchProperty::Match(const RE::GFxValue& a_value) const { if (a_value.IsArray()) { @@ -39,6 +60,11 @@ namespace Data return a_value == _value; } + bool MatchProperty::IsZeroNumber() const + { + return _value.IsNumber() && _value.GetNumber() == 0; + } + bool RangeProperty::Match(const RE::GFxValue& a_value) const { if (!a_value.IsNumber()) { diff --git a/src/Data/Config/Property.h b/src/Data/Config/Property.h index 3143333..9e76ae0 100644 --- a/src/Data/Config/Property.h +++ b/src/Data/Config/Property.h @@ -23,6 +23,10 @@ namespace Data void AddProperty(const std::string& a_name, std::shared_ptr a_property) override; + void RemoveZeroNumberMatches(); + + [[nodiscard]] bool Empty() const; + private: std::vector> _subProperties; }; @@ -44,6 +48,8 @@ namespace Data bool Match(const RE::GFxValue& a_value) const override; + [[nodiscard]] bool IsZeroNumber() const; + private: std::string _cachedString; RE::GFxValue _value; diff --git a/src/Data/Config/Rule.cpp b/src/Data/Config/Rule.cpp index 23a804d..69a5935 100644 --- a/src/Data/Config/Rule.cpp +++ b/src/Data/Config/Rule.cpp @@ -30,9 +30,55 @@ namespace Data _customData.insert({ "iconSource"s, nullptr }); } + if (auto i = _properties.find("formId"s); i != _properties.end()) { + if (auto matchProperty = std::dynamic_pointer_cast(i->second); + matchProperty && matchProperty->IsZeroNumber()) { + return false; + } + + if (auto anyOfProperty = std::dynamic_pointer_cast(i->second)) { + anyOfProperty->RemoveZeroNumberMatches(); + if (anyOfProperty->Empty()) { + return false; + } + } + } + return !_properties.empty() && !_customData.empty(); } + void Rule::Classify( + const std::unordered_set& a_nonIconAssignKeys, + const std::unordered_set& a_allMatchKeys) + { + _passBehavior = PassBehavior::SkipIfUnmatched; + _trivialAssign = true; + + for (const auto& property : _properties) { + const auto& name = property.first; + if (name == "iconLabel" || name == "iconColor") { + _passBehavior = PassBehavior::AlwaysReeval; + break; + } + + if (a_nonIconAssignKeys.contains(name)) { + _passBehavior = PassBehavior::ReevalIfModified; + } + } + + for (const auto& data : _customData) { + const auto& name = data.first; + if (name.rfind("icon", 0) == 0) { + continue; + } + + if (a_allMatchKeys.contains(name)) { + _trivialAssign = false; + break; + } + } + } + bool Rule::ValidateIconSource(std::string a_iconSource) { if (auto i = _validatedSources.find(a_iconSource); i != _validatedSources.end()) { @@ -48,12 +94,17 @@ namespace Data return valid; } - void Rule::SetInfo(RE::GFxValue* a_entryObject, bool& a_needsIconUpdate) const + bool Rule::SetInfo( + RE::GFxValue* a_entryObject, + bool& a_needsIconUpdate, + const RE::GFxValue& a_formIdCache, + const RE::GFxValue& a_formTypeCache, + const RE::GFxValue& a_keywordsCache) const { assert(a_entryObject); - if (!Match(a_entryObject)) { - return; + if (!Match(a_entryObject, a_formIdCache, a_formTypeCache, a_keywordsCache)) { + return false; } a_needsIconUpdate |= HasInfo(); @@ -65,13 +116,19 @@ namespace Data a_entryObject->SetMember(name.c_str(), value); } + + return true; } - void Rule::SetIcon(RE::GFxValue* a_entryObject) const + void Rule::SetIcon( + RE::GFxValue* a_entryObject, + const RE::GFxValue& a_formIdCache, + const RE::GFxValue& a_formTypeCache, + const RE::GFxValue& a_keywordsCache) const { assert(a_entryObject); - if (!Match(a_entryObject)) { + if (!Match(a_entryObject, a_formIdCache, a_formTypeCache, a_keywordsCache)) { return; } @@ -84,11 +141,56 @@ namespace Data } } - bool Rule::Match(const RE::GFxValue* a_entryObject) const + Rule::PassBehavior Rule::GetPassBehavior() const + { + return _passBehavior; + } + + bool Rule::IsTrivialAssign() const + { + return _trivialAssign; + } + + const std::map>& Rule::GetProperties() const + { + return _properties; + } + + const std::map& Rule::GetCustomData() const + { + return _customData; + } + + bool Rule::Match( + const RE::GFxValue* a_entryObject, + const RE::GFxValue& a_formIdCache, + const RE::GFxValue& a_formTypeCache, + const RE::GFxValue& a_keywordsCache) const { assert(a_entryObject); for (const auto& [name, prop] : _properties) { + if (name == "formId") { + if (!prop->Match(a_formIdCache)) { + return false; + } + continue; + } + + if (name == "formType") { + if (!prop->Match(a_formTypeCache)) { + return false; + } + continue; + } + + if (name == "keywords") { + if (!prop->Match(a_keywordsCache)) { + return false; + } + continue; + } + RE::GFxValue value; a_entryObject->GetMember(name.c_str(), &value); diff --git a/src/Data/Config/Rule.h b/src/Data/Config/Rule.h index d71037b..ff1a27b 100644 --- a/src/Data/Config/Rule.h +++ b/src/Data/Config/Rule.h @@ -3,23 +3,57 @@ #include "CustomData.h" #include "Property.h" +#include + namespace Data { class Rule final : public IPropertyContainer, public ICustomDataContainer { public: + enum class PassBehavior + { + SkipIfUnmatched, + ReevalIfModified, + AlwaysReeval + }; + void AddProperty(const std::string& a_name, std::shared_ptr a_property) override; void AddCustomData(const std::string& a_name, const CustomData& a_data) override; bool Validate(); - void SetInfo(RE::GFxValue* a_entryObject, bool& a_needsIconUpdate) const; + void Classify( + const std::unordered_set& a_nonIconAssignKeys, + const std::unordered_set& a_allMatchKeys); + + bool SetInfo( + RE::GFxValue* a_entryObject, + bool& a_needsIconUpdate, + const RE::GFxValue& a_formIdCache, + const RE::GFxValue& a_formTypeCache, + const RE::GFxValue& a_keywordsCache) const; + + void SetIcon( + RE::GFxValue* a_entryObject, + const RE::GFxValue& a_formIdCache, + const RE::GFxValue& a_formTypeCache, + const RE::GFxValue& a_keywordsCache) const; + + [[nodiscard]] PassBehavior GetPassBehavior() const; + + [[nodiscard]] bool IsTrivialAssign() const; + + [[nodiscard]] const std::map>& GetProperties() const; - void SetIcon(RE::GFxValue* a_entryObject) const; + [[nodiscard]] const std::map& GetCustomData() const; private: - bool Match(const RE::GFxValue* a_entryObject) const; + bool Match( + const RE::GFxValue* a_entryObject, + const RE::GFxValue& a_formIdCache, + const RE::GFxValue& a_formTypeCache, + const RE::GFxValue& a_keywordsCache) const; bool HasInfo() const; @@ -29,5 +63,7 @@ namespace Data std::map> _properties; std::map _customData; + PassBehavior _passBehavior = PassBehavior::SkipIfUnmatched; + bool _trivialAssign = true; }; } diff --git a/src/Data/CustomDataManager.cpp b/src/Data/CustomDataManager.cpp index 1beae38..b152982 100644 --- a/src/Data/CustomDataManager.cpp +++ b/src/Data/CustomDataManager.cpp @@ -3,6 +3,7 @@ #include "Data/Config/RuleParser.h" #include +#include namespace Data { @@ -53,6 +54,55 @@ namespace Data LoadConfig(configFile); } + + std::unordered_set nonIconAssignKeys; + std::unordered_set allMatchKeys; + + for (const auto& rule : _rules) { + for (const auto& property : rule.GetProperties()) { + allMatchKeys.insert(property.first); + } + + for (const auto& data : rule.GetCustomData()) { + const auto& name = data.first; + if (name.rfind("icon", 0) != 0) { + nonIconAssignKeys.insert(name); + } + } + } + + std::uint32_t numSkip = 0; + std::uint32_t numReeval = 0; + std::uint32_t numAlways = 0; + std::uint32_t numTrivialAssign = 0; + + for (auto& rule : _rules) { + rule.Classify(nonIconAssignKeys, allMatchKeys); + + switch (rule.GetPassBehavior()) { + case Rule::PassBehavior::SkipIfUnmatched: + numSkip++; + break; + case Rule::PassBehavior::ReevalIfModified: + numReeval++; + break; + case Rule::PassBehavior::AlwaysReeval: + numAlways++; + break; + } + + if (rule.IsTrivialAssign()) { + numTrivialAssign++; + } + } + + logger::info( + "Classified {} rules: {} SkipIfUnmatched, {} ReevalIfModified, {} AlwaysReeval, {} trivial assignments", + _rules.size(), + numSkip, + numReeval, + numAlways, + numTrivialAssign); } void CustomDataManager::LoadConfig(const std::filesystem::path& a_path) @@ -74,28 +124,76 @@ namespace Data return; Json::Value rules = root["rules"]; - std::uint32_t numRules = 0; + std::uint32_t numRulesTotal = 0; + std::uint32_t numRulesLoaded = 0; if (rules.isArray()) { for (auto& rule : rules) { + numRulesTotal++; auto parsed = RuleParser::ParseRule(rule); if (parsed.Validate()) { _rules.push_back(std::move(parsed)); - numRules++; + numRulesLoaded++; } } } - logger::info("Read {} rules from {}", numRules, a_path.filename().string()); + logger::info( + "Read {} rules, {} loaded from {}", + numRulesTotal, + numRulesLoaded, + a_path.filename().string()); } void CustomDataManager::ProcessEntry( RE::GFxValue* a_entryObject, std::function a_processIconCallback) const { + if (a_entryObject->HasMember("IIIIDone")) { + RE::GFxValue iconSource; + if (a_entryObject->GetMember("IIIIRestoreIconSource", &iconSource)) { + a_entryObject->SetMember("iconSource", iconSource); + } + + RE::GFxValue iconLabel; + if (a_entryObject->GetMember("IIIIRestoreIconLabel", &iconLabel)) { + a_entryObject->SetMember("iconLabel", iconLabel); + } + + RE::GFxValue iconColor; + if (a_entryObject->GetMember("IIIIRestoreIconColor", &iconColor)) { + a_entryObject->SetMember("iconColor", iconColor); + } + + return; + } + bool needsIconUpdate = false; + + bool hadNonTrivialAssign = false; + std::vector ruleMatches; + ruleMatches.reserve(_rules.size()); + + RE::GFxValue formIdCache; + RE::GFxValue formTypeCache; + RE::GFxValue keywordsCache; + a_entryObject->GetMember("formId", &formIdCache); + a_entryObject->GetMember("formType", &formTypeCache); + a_entryObject->GetMember("keywords", &keywordsCache); + for (const auto& rule : _rules) { - rule.SetInfo(a_entryObject, needsIconUpdate); + const auto matched = rule.SetInfo( + a_entryObject, + needsIconUpdate, + formIdCache, + formTypeCache, + keywordsCache); + + ruleMatches.push_back(matched); + + if (matched && !rule.IsTrivialAssign()) { + hadNonTrivialAssign = true; + } } if (needsIconUpdate && a_processIconCallback) { @@ -104,8 +202,43 @@ namespace Data a_processIconCallback(a_entryObject); } - for (const auto& rule : _rules) { - rule.SetIcon(a_entryObject); + for (std::size_t i = 0; i < _rules.size(); i++) { + const auto& rule = _rules[i]; + const bool matched = ruleMatches[i]; + + switch (rule.GetPassBehavior()) { + case Rule::PassBehavior::AlwaysReeval: + break; + case Rule::PassBehavior::ReevalIfModified: + if (!matched && !hadNonTrivialAssign) { + continue; + } + break; + case Rule::PassBehavior::SkipIfUnmatched: + if (!matched) { + continue; + } + break; + } + + rule.SetIcon(a_entryObject, formIdCache, formTypeCache, keywordsCache); + } + + a_entryObject->SetMember("IIIIDone", RE::GFxValue(true)); + + RE::GFxValue iconSource; + if (a_entryObject->GetMember("iconSource", &iconSource)) { + a_entryObject->SetMember("IIIIRestoreIconSource", iconSource); + } + + RE::GFxValue iconLabel; + if (a_entryObject->GetMember("iconLabel", &iconLabel)) { + a_entryObject->SetMember("IIIIRestoreIconLabel", iconLabel); + } + + RE::GFxValue iconColor; + if (a_entryObject->GetMember("iconColor", &iconColor)) { + a_entryObject->SetMember("IIIIRestoreIconColor", iconColor); } } }