From 7a3130244c97d48667ba9ec0bff4818fa1101044 Mon Sep 17 00:00:00 2001 From: Arkadii Hlushchevskyi Date: Sat, 30 Mar 2024 19:15:07 +0200 Subject: [PATCH 01/10] Improved logging of type errors during parsing and lookup. --- SPID/include/FormData.h | 21 +++++++++++---------- SPID/include/LinkedDistribution.h | 4 ++-- SPID/include/LookupConfigs.h | 21 +++++---------------- SPID/src/LinkedDistribution.cpp | 6 ++++-- SPID/src/LookupConfigs.cpp | 6 +++++- SPID/src/LookupForms.cpp | 4 +++- 6 files changed, 30 insertions(+), 32 deletions(-) diff --git a/SPID/include/FormData.h b/SPID/include/FormData.h index 3d46c05..aef1856 100644 --- a/SPID/include/FormData.h +++ b/SPID/include/FormData.h @@ -37,13 +37,13 @@ namespace Forms /// struct MismatchingFormTypeException : std::exception { - const RE::FormType expectedFformType; + const RE::FormType expectedFormType; const RE::FormType actualFormType; const FormOrEditorID formOrEditorID; const std::string path; - MismatchingFormTypeException(RE::FormType expectedFformType, RE::FormType actualFormType, const FormOrEditorID& formOrEditorID, const std::string& path) : - expectedFformType(expectedFformType), + MismatchingFormTypeException(RE::FormType expectedFormType, RE::FormType actualFormType, const FormOrEditorID& formOrEditorID, const std::string& path) : + expectedFormType(expectedFormType), actualFormType(actualFormType), formOrEditorID(formOrEditorID), path(path) @@ -216,8 +216,10 @@ namespace Forms } form = as_form(anyForm); - if (!form) { - throw MismatchingFormTypeException(anyForm->GetFormType(), Form::FORMTYPE, FormModPair{ *formID, modName }, path); + if (!form || anyForm->GetFormType() != Form::FORMTYPE) { + // Ideally, we'd want to throw separate exception for unsupported form type, + // so that attempting to distribute, for example, CELL would properly report such error. + throw MismatchingFormTypeException(Form::FORMTYPE, anyForm->GetFormType(), FormModPair{ *formID, modName }, path); } if constexpr (std::is_same_v) { @@ -232,7 +234,6 @@ namespace Forms if (editorID.empty()) { throw MalformedEditorIDException(path); } - // if constexpr (std::is_same_v) { form = find_or_create_keyword(editorID); } else { @@ -322,10 +323,10 @@ namespace Forms std::visit(overload{ [&](const FormModPair& formMod) { auto& [formID, modName] = formMod; - buffered_logger::error("\t\t[{}] Filter[0x{:X}] ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.expectedFformType), RE::FormTypeToString(e.actualFormType)); + buffered_logger::error("\t\t[{}] Filter[0x{:X}] ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.expectedFormType), RE::FormTypeToString(e.actualFormType)); }, [&](std::string editorID) { - buffered_logger::error("\t\t[{}] Filter ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, editorID, RE::FormTypeToString(e.expectedFformType), RE::FormTypeToString(e.actualFormType)); + buffered_logger::error("\t\t[{}] Filter ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, editorID, RE::FormTypeToString(e.expectedFormType), RE::FormTypeToString(e.actualFormType)); } }, e.formOrEditorID); } catch (const InvalidFormTypeException& e) { @@ -631,10 +632,10 @@ void Forms::LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& std::visit(overload{ [&](const FormModPair& formMod) { auto& [formID, modName] = formMod; - buffered_logger::error("\t\t[{}] [0x{:X}] ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.expectedFformType), RE::FormTypeToString(e.actualFormType)); + buffered_logger::error("\t\t[{}] [0x{:X}] ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.expectedFormType), RE::FormTypeToString(e.actualFormType)); }, [&](std::string editorID) { - buffered_logger::error("\t\t[{}] ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, editorID, RE::FormTypeToString(e.expectedFformType), RE::FormTypeToString(e.actualFormType)); + buffered_logger::error("\t\t[{}] ({}) FAIL - mismatching form type (expected: {}, actual: {})", e.path, editorID, RE::FormTypeToString(e.expectedFormType), RE::FormTypeToString(e.actualFormType)); } }, e.formOrEditorID); } catch (const Lookup::InvalidFormTypeException& e) { diff --git a/SPID/include/LinkedDistribution.h b/SPID/include/LinkedDistribution.h index 537b1fd..f85bf1a 100644 --- a/SPID/include/LinkedDistribution.h +++ b/SPID/include/LinkedDistribution.h @@ -153,10 +153,10 @@ namespace LinkedDistribution std::visit(overload{ [&](const FormModPair& formMod) { auto& [formID, modName] = formMod; - buffered_logger::error("\t\t[{}] LinkedForm [0x{:X}] ({}) SKIP - mismatching form type (expected: {}, actual: {})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.expectedFformType), RE::FormTypeToString(e.actualFormType)); + buffered_logger::error("\t\t[{}] LinkedForm [0x{:X}] ({}) SKIP - mismatching form type (expected: {}, actual: {})", e.path, *formID, modName.value_or(""), RE::FormTypeToString(e.expectedFormType), RE::FormTypeToString(e.actualFormType)); }, [&](std::string editorID) { - buffered_logger::error("\t\t[{}] LinkedForm ({}) SKIP - mismatching form type (expected: {}, actual: {})", e.path, editorID, RE::FormTypeToString(e.expectedFformType), RE::FormTypeToString(e.actualFormType)); + buffered_logger::error("\t\t[{}] LinkedForm ({}) SKIP - mismatching form type (expected: {}, actual: {})", e.path, editorID, RE::FormTypeToString(e.expectedFormType), RE::FormTypeToString(e.actualFormType)); } }, e.formOrEditorID); } catch (const InvalidFormTypeException& e) { diff --git a/SPID/include/LookupConfigs.h b/SPID/include/LookupConfigs.h index d4c10fe..79f994c 100644 --- a/SPID/include/LookupConfigs.h +++ b/SPID/include/LookupConfigs.h @@ -27,7 +27,7 @@ namespace RECORD namespace detail { - inline static constexpr std::array add{ + inline static constexpr std::array names{ "Form"sv, "Spell"sv, "Perk"sv, @@ -46,25 +46,14 @@ namespace RECORD inline constexpr std::string_view GetTypeName(const TYPE aType) { - return detail::add.at(aType); + return detail::names.at(aType); } - inline constexpr TYPE GetType(const std::string& aType) + template + constexpr TYPE GetType(const T& aType) { using namespace detail; - return static_cast(std::distance(add.begin(), std::find(add.begin(), add.end(), aType))); - } - - inline constexpr TYPE GetType(const std::string_view& aType) - { - using namespace detail; - return static_cast(std::distance(add.begin(), std::find(add.begin(), add.end(), aType))); - } - - inline constexpr TYPE GetType(const char* aType) - { - using namespace detail; - return static_cast(std::distance(add.begin(), std::find(add.begin(), add.end(), aType))); + return static_cast(std::distance(names.begin(), std::find(names.begin(), names.end(), aType))); } } diff --git a/SPID/src/LinkedDistribution.cpp b/SPID/src/LinkedDistribution.cpp index b0b7886..b5c849a 100644 --- a/SPID/src/LinkedDistribution.cpp +++ b/SPID/src/LinkedDistribution.cpp @@ -30,7 +30,7 @@ namespace LinkedDistribution std::string rawType = a_key.substr(6); auto type = RECORD::GetType(rawType); if (type == RECORD::kTotal) { - logger::warn("IGNORED: Invalid Linked Form type: {}"sv, rawType); + logger::warn("IGNORED: Unsupported Linked Form type: {}"sv, rawType); return true; } @@ -136,13 +136,15 @@ namespace LinkedDistribution if (std::holds_alternative(idxOrCount)) { auto& count = std::get(idxOrCount); if (!count.IsExact()) { - logger::warn("Inferred Form is a Package, but specifies a random count instead of index. Min value ({}) of the range will be used as an index.", count.min); + logger::warn("\t[{}] Inferred Form is a Package, but specifies a random count instead of index. Min value ({}) of the range will be used as an index.", path, count.min); } packageIndex = count.min; } else { packageIndex = std::get(idxOrCount); } packages.Link(form, parentForms, packageIndex, chance, path); + } else { + logger::warn("\t[{}] Unsupported Form type: {}", path, RE::FormTypeToString(type)); } } } diff --git a/SPID/src/LookupConfigs.cpp b/SPID/src/LookupConfigs.cpp index 8d4f869..88cbe95 100644 --- a/SPID/src/LookupConfigs.cpp +++ b/SPID/src/LookupConfigs.cpp @@ -327,6 +327,10 @@ namespace INI } auto type = RECORD::GetType(key.pItem); + if (type == RECORD::kTotal) { + logger::warn("\t\tUnsupported Form type: {}"sv, key.pItem); + continue; + } auto [data, sanitized_str] = detail::parse_ini(type, entry, truncatedPath); configs[type].emplace_back(data); @@ -335,7 +339,7 @@ namespace INI oldFormatMap.emplace(key, std::make_pair(entry, *sanitized_str)); } } catch (...) { - logger::warn("\t\tFailed to parse entry [{} = {}]", key.pItem, entry); + logger::warn("\t\tFailed to parse entry [{} = {}]"sv, key.pItem, entry); shouldLogErrors = true; } } diff --git a/SPID/src/LookupForms.cpp b/SPID/src/LookupForms.cpp index bf64d33..996e5e3 100644 --- a/SPID/src/LookupForms.cpp +++ b/SPID/src/LookupForms.cpp @@ -43,13 +43,15 @@ bool LookupDistributables(RE::TESDataHandler* const dataHandler) if (std::holds_alternative(idxOrCount)) { auto& count = std::get(idxOrCount); if (!count.IsExact()) { - logger::warn("Inferred Form is a Package, but specifies a random count instead of index. Min value ({}) of the range will be used as an index.", count.min); + logger::warn("\t[{}] Inferred Form is a Package, but specifies a random count instead of index. Min value ({}) of the range will be used as an index.", path, count.min); } packageIndex = count.min; } else { packageIndex = std::get(idxOrCount); } packages.EmplaceForm(isValid, form, packageIndex, filters, path); + } else { + logger::warn("\t[{}] Unsupported Form type: {}", path, RE::FormTypeToString(type)); } } }); From f54569066af64d14fdba2f529a144fed8bfe003c Mon Sep 17 00:00:00 2001 From: adya Date: Sat, 30 Mar 2024 22:07:26 +0000 Subject: [PATCH 02/10] maintenance --- SPID/include/FormData.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPID/include/FormData.h b/SPID/include/FormData.h index aef1856..5335eb1 100644 --- a/SPID/include/FormData.h +++ b/SPID/include/FormData.h @@ -217,7 +217,7 @@ namespace Forms form = as_form(anyForm); if (!form || anyForm->GetFormType() != Form::FORMTYPE) { - // Ideally, we'd want to throw separate exception for unsupported form type, + // Ideally, we'd want to throw separate exception for unsupported form type, // so that attempting to distribute, for example, CELL would properly report such error. throw MismatchingFormTypeException(Form::FORMTYPE, anyForm->GetFormType(), FormModPair{ *formID, modName }, path); } From bde29a047b4f99d88809f9613faa5e9410e65885 Mon Sep 17 00:00:00 2001 From: Arkadii Hlushchevskyi Date: Sun, 31 Mar 2024 00:23:36 +0200 Subject: [PATCH 03/10] Isolated Exclusive Groups feature's code in respective files. This bring Exclusive Groups in line with Linked Forms structure, where features are isolated as much as possible to their own header/source. --- SPID/include/ExclusiveGroups.h | 25 +++++- SPID/include/LookupConfigs.h | 17 +--- SPID/src/ExclusiveGroups.cpp | 138 +++++++++++++++++++++++---------- SPID/src/LookupConfigs.cpp | 41 +--------- SPID/src/LookupForms.cpp | 17 +--- 5 files changed, 127 insertions(+), 111 deletions(-) diff --git a/SPID/include/ExclusiveGroups.h b/SPID/include/ExclusiveGroups.h index 4ea6043..7fde062 100644 --- a/SPID/include/ExclusiveGroups.h +++ b/SPID/include/ExclusiveGroups.h @@ -3,6 +3,27 @@ namespace ExclusiveGroups { + namespace INI + { + struct RawExclusiveGroup + { + std::string name{}; + + /// Raw filters in RawExclusiveGroup only use NOT and MATCH, there is no meaning for ALL, so it's ignored. + Filters formIDs{}; + std::string path{}; + }; + + using ExclusiveGroupsVec = std::vector; + + /// + /// A list of RawExclusiveGroups that will be processed along with configs. + /// + inline ExclusiveGroupsVec exclusiveGroups{}; + + bool TryParse(const std::string& a_key, const std::string& a_value, const std::string& a_path); + } + using GroupName = std::string; using LinkedGroups = std::unordered_map>; using Groups = std::unordered_map>; @@ -17,7 +38,9 @@ namespace ExclusiveGroups /// /// A DataHandler that will perform the actual lookup. /// A raw exclusive group entries that should be processed. - void LookupExclusiveGroups(RE::TESDataHandler* const dataHandler, INI::ExclusiveGroupsVec& rawExclusiveGroups); + void LookupExclusiveGroups(RE::TESDataHandler* const dataHandler, INI::ExclusiveGroupsVec& rawExclusiveGroups = INI::exclusiveGroups); + + void LogExclusiveGroupsLookup(); /// /// Gets a set of all forms that are in the same exclusive group as the given form. diff --git a/SPID/include/LookupConfigs.h b/SPID/include/LookupConfigs.h index 79f994c..322331b 100644 --- a/SPID/include/LookupConfigs.h +++ b/SPID/include/LookupConfigs.h @@ -84,24 +84,9 @@ namespace INI std::string path{}; }; - struct RawExclusiveGroup - { - std::string name{}; - - /// Raw filters in RawExclusiveGroup only use NOT and MATCH, there is no meaning for ALL, so it's ignored. - Filters rawFormFilters{}; - std::string path{}; - }; - using DataVec = std::vector; - using ExclusiveGroupsVec = std::vector; - + inline Map configs{}; - /// - /// A list of RawExclusiveGroups that will be processed along with configs. - /// - inline ExclusiveGroupsVec exclusiveGroups{}; - std::pair GetConfigs(); } diff --git a/SPID/src/ExclusiveGroups.cpp b/SPID/src/ExclusiveGroups.cpp index 607c3c0..efe6b1b 100644 --- a/SPID/src/ExclusiveGroups.cpp +++ b/SPID/src/ExclusiveGroups.cpp @@ -1,59 +1,117 @@ #include "ExclusiveGroups.h" #include "FormData.h" -void ExclusiveGroups::Manager::LookupExclusiveGroups(RE::TESDataHandler* const dataHandler, INI::ExclusiveGroupsVec& exclusiveGroups) +namespace ExclusiveGroups { - groups.clear(); - linkedGroups.clear(); - - for (auto& [name, filterIDs, path] : exclusiveGroups) { - auto& forms = groups[name]; - FormVec match{}; - FormVec formsNot{}; - - if (Forms::detail::formID_to_form(dataHandler, filterIDs.MATCH, match, path, false, false) && - Forms::detail::formID_to_form(dataHandler, filterIDs.NOT, formsNot, path, false, false)) { - for (const auto& form : match) { - if (std::holds_alternative(form)) { - forms.insert(std::get(form)); - } + bool INI::TryParse(const std::string& a_key, const std::string& a_value, const std::string& a_path) + { + if (a_key != "ExclusiveGroup") { + return false; + } + + const auto sections = string::split(a_value, "|"); + const auto size = sections.size(); + + if (size < 2) { + logger::warn("IGNORED: ExclusiveGroup must have a name and at least one Form Filter: {} = {}"sv, a_key, a_value); + return true; + } + + auto split_IDs = distribution::split_entry(sections[1]); + + if (split_IDs.empty()) { + logger::warn("ExclusiveGroup must have at least one Form Filter : {} = {}"sv, a_key, a_value); + return true; + } + + RawExclusiveGroup group{}; + group.name = sections[0]; + group.path = a_path; + + for (auto& IDs : split_IDs) { + if (IDs.at(0) == '-') { + IDs.erase(0, 1); + group.formIDs.NOT.push_back(distribution::get_record(IDs)); + } else { + group.formIDs.MATCH.push_back(distribution::get_record(IDs)); } + } + + exclusiveGroups.emplace_back(group); + + return true; + } + + void Manager::LookupExclusiveGroups(RE::TESDataHandler* const dataHandler, INI::ExclusiveGroupsVec& exclusiveGroups) + { + groups.clear(); + linkedGroups.clear(); - for (auto& form : formsNot) { - if (std::holds_alternative(form)) { - forms.erase(std::get(form)); + for (auto& [name, filterIDs, path] : exclusiveGroups) { + auto& forms = groups[name]; + FormVec match{}; + FormVec formsNot{}; + + if (Forms::detail::formID_to_form(dataHandler, filterIDs.MATCH, match, path, false, false) && + Forms::detail::formID_to_form(dataHandler, filterIDs.NOT, formsNot, path, false, false)) { + for (const auto& form : match) { + if (std::holds_alternative(form)) { + forms.insert(std::get(form)); + } + } + + for (auto& form : formsNot) { + if (std::holds_alternative(form)) { + forms.erase(std::get(form)); + } } } } - } - // Remove empty groups - std::erase_if(groups, [](const auto& pair) { return pair.second.empty(); }); + // Remove empty groups + std::erase_if(groups, [](const auto& pair) { return pair.second.empty(); }); - for (auto& [name, forms] : groups) { - for (auto& form : forms) { - linkedGroups[form].insert(name); + for (auto& [name, forms] : groups) { + for (auto& form : forms) { + linkedGroups[form].insert(name); + } } } -} -std::unordered_set ExclusiveGroups::Manager::MutuallyExclusiveFormsForForm(RE::TESForm* form) const -{ - std::unordered_set forms{}; - if (auto it = linkedGroups.find(form); it != linkedGroups.end()) { - std::ranges::for_each(it->second, [&](const GroupName& name) { - const auto& group = groups.at(name); - forms.insert(group.begin(), group.end()); - }); + void Manager::LogExclusiveGroupsLookup() + { + if (groups.empty()) { + return; + } + + logger::info("{:*^50}", "EXCLUSIVE GROUPS"); + + for (const auto& [group, forms] : groups) { + logger::info("Adding '{}' exclusive group", group); + for (const auto& form : forms) { + logger::info(" {}", describe(form)); + } + } } - // Remove self from the list. - forms.erase(form); + std::unordered_set Manager::MutuallyExclusiveFormsForForm(RE::TESForm* form) const + { + std::unordered_set forms{}; + if (auto it = linkedGroups.find(form); it != linkedGroups.end()) { + std::ranges::for_each(it->second, [&](const GroupName& name) { + const auto& group = groups.at(name); + forms.insert(group.begin(), group.end()); + }); + } - return forms; -} + // Remove self from the list. + forms.erase(form); -const ExclusiveGroups::Groups& ExclusiveGroups::Manager::GetGroups() const -{ - return groups; + return forms; + } + + const Groups& ExclusiveGroups::Manager::GetGroups() const + { + return groups; + } } diff --git a/SPID/src/LookupConfigs.cpp b/SPID/src/LookupConfigs.cpp index 88cbe95..ffd28a9 100644 --- a/SPID/src/LookupConfigs.cpp +++ b/SPID/src/LookupConfigs.cpp @@ -1,5 +1,6 @@ #include "LookupConfigs.h" #include "LinkedDistribution.h" +#include "ExclusiveGroups.h" namespace INI { @@ -47,43 +48,6 @@ namespace INI return newValue; } - std::optional parse_exclusive_group(const std::string& a_key, const std::string& a_value, const std::string& a_path) - { - if (a_key != "ExclusiveGroup") { - return std::nullopt; - } - - const auto sections = string::split(a_value, "|"); - const auto size = sections.size(); - - if (size < 2) { - logger::warn("IGNORED: ExclusiveGroup must have a name and at least one Form Filter: {} = {}"sv, a_key, a_value); - return std::nullopt; - } - - auto split_IDs = distribution::split_entry(sections[1]); - - if (split_IDs.empty()) { - logger::warn("ExclusiveGroup must have at least one Form Filter : {} = {}"sv, a_key, a_value); - return std::nullopt; - } - - RawExclusiveGroup group{}; - group.name = sections[0]; - group.path = a_path; - - for (auto& IDs : split_IDs) { - if (IDs.at(0) == '-') { - IDs.erase(0, 1); - group.rawFormFilters.NOT.push_back(distribution::get_record(IDs)); - } else { - group.rawFormFilters.MATCH.push_back(distribution::get_record(IDs)); - } - } - - return group; - } - std::pair> parse_ini(const RECORD::TYPE& typeHint, const std::string& a_value, const std::string& a_path) { Data data{}; @@ -317,8 +281,7 @@ namespace INI for (auto& [key, entry] : *values) { try { - if (const auto group = detail::parse_exclusive_group(key.pItem, entry, truncatedPath); group) { - exclusiveGroups.emplace_back(*group); + if (ExclusiveGroups::INI::TryParse(key.pItem, entry, truncatedPath)) { continue; } diff --git a/SPID/src/LookupForms.cpp b/SPID/src/LookupForms.cpp index 996e5e3..86bdc49 100644 --- a/SPID/src/LookupForms.cpp +++ b/SPID/src/LookupForms.cpp @@ -101,25 +101,12 @@ void LogDistributablesLookup() // instead of quering data handler for the same raw FormOrEditorID. void LookupExclusiveGroups(RE::TESDataHandler* const dataHandler) { - ExclusiveGroups::Manager::GetSingleton()->LookupExclusiveGroups(dataHandler, INI::exclusiveGroups); + ExclusiveGroups::Manager::GetSingleton()->LookupExclusiveGroups(dataHandler); } void LogExclusiveGroupsLookup() { - if (const auto manager = ExclusiveGroups::Manager::GetSingleton(); manager) { - const auto& groups = manager->GetGroups(); - - if (!groups.empty()) { - logger::info("{:*^50}", "EXCLUSIVE GROUPS"); - - for (const auto& [group, forms] : groups) { - logger::info("Adding '{}' exclusive group", group); - for (const auto& form : forms) { - logger::info(" {}", describe(form)); - } - } - } - } + ExclusiveGroups::Manager::GetSingleton()->LogExclusiveGroupsLookup(); } void LookupLinkedForms(RE::TESDataHandler* const dataHandler) From d2f96881157b09a227ce5e03c5c7f3da7b38a0c2 Mon Sep 17 00:00:00 2001 From: Arkadii Hlushchevskyi Date: Sun, 31 Mar 2024 00:58:52 +0200 Subject: [PATCH 04/10] Introduced an alias for Path parameter. --- SPID/include/Defs.h | 2 ++ SPID/include/ExclusiveGroups.h | 4 +-- SPID/include/FormData.h | 54 +++++++++++++++---------------- SPID/include/LinkedDistribution.h | 8 ++--- SPID/src/ExclusiveGroups.cpp | 2 +- SPID/src/LinkedDistribution.cpp | 14 ++++---- SPID/src/LookupConfigs.cpp | 2 +- 7 files changed, 44 insertions(+), 42 deletions(-) diff --git a/SPID/include/Defs.h b/SPID/include/Defs.h index f379e20..fd2d450 100644 --- a/SPID/include/Defs.h +++ b/SPID/include/Defs.h @@ -93,6 +93,8 @@ struct Traits std::optional teammate{}; }; +using Path = std::string; + using Index = std::int32_t; using Count = std::int32_t; using RandomCount = Range; diff --git a/SPID/include/ExclusiveGroups.h b/SPID/include/ExclusiveGroups.h index 7fde062..0fb37ea 100644 --- a/SPID/include/ExclusiveGroups.h +++ b/SPID/include/ExclusiveGroups.h @@ -11,7 +11,7 @@ namespace ExclusiveGroups /// Raw filters in RawExclusiveGroup only use NOT and MATCH, there is no meaning for ALL, so it's ignored. Filters formIDs{}; - std::string path{}; + Path path{}; }; using ExclusiveGroupsVec = std::vector; @@ -21,7 +21,7 @@ namespace ExclusiveGroups /// inline ExclusiveGroupsVec exclusiveGroups{}; - bool TryParse(const std::string& a_key, const std::string& a_value, const std::string& a_path); + bool TryParse(const std::string& a_key, const std::string& a_value, const Path& a_path); } using GroupName = std::string; diff --git a/SPID/include/FormData.h b/SPID/include/FormData.h index 5335eb1..749dae1 100644 --- a/SPID/include/FormData.h +++ b/SPID/include/FormData.h @@ -10,9 +10,9 @@ namespace Forms struct UnknownPluginException : std::exception { const std::string modName; - const std::string path; + const Path path; - UnknownPluginException(const std::string& modName, const std::string& path) : + UnknownPluginException(const std::string& modName, const Path& path) : modName(modName), path(path) {} @@ -22,9 +22,9 @@ namespace Forms { const RE::FormID formID; const std::optional modName; - const std::string path; + const Path path; - UnknownFormIDException(RE::FormID formID, const std::string& path, std::optional modName = std::nullopt) : + UnknownFormIDException(RE::FormID formID, const Path& path, std::optional modName = std::nullopt) : formID(formID), path(path), modName(modName) @@ -40,9 +40,9 @@ namespace Forms const RE::FormType expectedFormType; const RE::FormType actualFormType; const FormOrEditorID formOrEditorID; - const std::string path; + const Path path; - MismatchingFormTypeException(RE::FormType expectedFormType, RE::FormType actualFormType, const FormOrEditorID& formOrEditorID, const std::string& path) : + MismatchingFormTypeException(RE::FormType expectedFormType, RE::FormType actualFormType, const FormOrEditorID& formOrEditorID, const Path& path) : expectedFormType(expectedFormType), actualFormType(actualFormType), formOrEditorID(formOrEditorID), @@ -54,9 +54,9 @@ namespace Forms { const RE::FormID formID; const std::optional modName; - const std::string path; + const Path path; - InvalidKeywordException(RE::FormID formID, const std::string& path, std::optional modName = std::nullopt) : + InvalidKeywordException(RE::FormID formID, const Path& path, std::optional modName = std::nullopt) : formID(formID), modName(modName), path(path) @@ -67,9 +67,9 @@ namespace Forms { const std::string editorID; const bool isDynamic; - const std::string path; + const Path path; - KeywordNotFoundException(const std::string& editorID, bool isDynamic, const std::string& path) : + KeywordNotFoundException(const std::string& editorID, bool isDynamic, const Path& path) : editorID(editorID), isDynamic(isDynamic), path(path) @@ -79,9 +79,9 @@ namespace Forms struct UnknownEditorIDException : std::exception { const std::string editorID; - const std::string path; + const Path path; - UnknownEditorIDException(const std::string& editorID, const std::string& path) : + UnknownEditorIDException(const std::string& editorID, const Path& path) : editorID(editorID), path(path) {} @@ -94,9 +94,9 @@ namespace Forms { const RE::FormType formType; const FormOrEditorID formOrEditorID; - const std::string path; + const Path path; - InvalidFormTypeException(RE::FormType formType, const FormOrEditorID& formOrEditorID, const std::string& path) : + InvalidFormTypeException(RE::FormType formType, const FormOrEditorID& formOrEditorID, const Path& path) : formType(formType), formOrEditorID(formOrEditorID), path(path) @@ -105,9 +105,9 @@ namespace Forms struct MalformedEditorIDException : std::exception { - const std::string path; + const Path path; - MalformedEditorIDException(const std::string& path) : + MalformedEditorIDException(const Path& path) : path(path) {} }; @@ -140,7 +140,7 @@ namespace Forms } template - std::variant get_form_or_mod(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const std::string& path, bool whitelistedOnly = false) + std::variant get_form_or_mod(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const Path& path, bool whitelistedOnly = false) { Form* form = nullptr; const RE::TESFile* mod = nullptr; @@ -269,7 +269,7 @@ namespace Forms return form; } - inline const RE::TESFile* get_file(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const std::string& path) + inline const RE::TESFile* get_file(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const Path& path) { auto formOrMod = get_form_or_mod(dataHandler, formOrEditorID, path); @@ -281,7 +281,7 @@ namespace Forms } template - Form* get_form(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const std::string& path, bool whitelistedOnly = false) + Form* get_form(RE::TESDataHandler* const dataHandler, const FormOrEditorID& formOrEditorID, const Path& path, bool whitelistedOnly = false) { auto formOrMod = get_form_or_mod
(dataHandler, formOrEditorID, path, whitelistedOnly); @@ -292,7 +292,7 @@ namespace Forms return nullptr; } - inline bool formID_to_form(RE::TESDataHandler* const a_dataHandler, RawFormVec& a_rawFormVec, FormVec& a_formVec, const std::string& a_path, bool a_all = false, bool whitelistedOnly = true) + inline bool formID_to_form(RE::TESDataHandler* const a_dataHandler, RawFormVec& a_rawFormVec, FormVec& a_formVec, const Path& a_path, bool a_all = false, bool whitelistedOnly = true) { if (a_rawFormVec.empty()) { return true; @@ -355,7 +355,7 @@ namespace Forms IndexOrCount idxOrCount{ RandomCount(1, 1) }; FilterData filters{}; - std::string path{}; + Path path{}; std::uint32_t npcCount{ 0 }; bool operator==(const Data& a_rhs) const; @@ -421,8 +421,8 @@ namespace Forms DataVec& GetForms(bool a_onlyLevelEntries); DataVec& GetForms(); - void LookupForms(RE::TESDataHandler* a_dataHandler, std::string_view a_type, INI::DataVec& a_INIDataVec); - void EmplaceForm(bool isValid, Form*, const IndexOrCount&, const FilterData&, const std::string& path); + void LookupForms(RE::TESDataHandler*, std::string_view a_type, INI::DataVec&); + void EmplaceForm(bool isValid, Form*, const IndexOrCount&, const FilterData&, const Path&); // Init formsWithLevels and formsNoLevels void FinishLookupForms(); @@ -436,7 +436,7 @@ namespace Forms /// This counter is used for logging purposes. std::size_t lookupCount{ 0 }; - void LookupForm(RE::TESDataHandler* a_dataHandler, INI::Data& rawForm); + void LookupForm(RE::TESDataHandler*, INI::Data&); }; inline Distributables spells{ RECORD::kSpell }; @@ -481,7 +481,7 @@ namespace Forms /// A raw form entry that needs to be looked up. /// A callback to be called with validated data after successful lookup. template - void LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function callback); + void LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function callback); } template @@ -563,7 +563,7 @@ void Forms::Distributables::LookupForms(RE::TESDataHandler* dataHandler, s } template -void Forms::Distributables::EmplaceForm(bool isValid, Form* form, const IndexOrCount& idxOrCount, const FilterData& filters, const std::string& path) +void Forms::Distributables::EmplaceForm(bool isValid, Form* form, const IndexOrCount& idxOrCount, const FilterData& filters, const Path& path) { if (isValid) { forms.emplace_back(forms.size(), form, idxOrCount, filters, path); @@ -595,7 +595,7 @@ void Forms::Distributables::FinishLookupForms() } template -void Forms::LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function callback) +void Forms::LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function callback) { auto& [formOrEditorID, strings, filterIDs, level, traits, idxOrCount, chance, path] = rawForm; diff --git a/SPID/include/LinkedDistribution.h b/SPID/include/LinkedDistribution.h index f85bf1a..a5eeb18 100644 --- a/SPID/include/LinkedDistribution.h +++ b/SPID/include/LinkedDistribution.h @@ -17,7 +17,7 @@ namespace LinkedDistribution IndexOrCount idxOrCount{ RandomCount(1, 1) }; PercentChance chance{ 100 }; - std::string path{}; + Path path{}; }; using LinkedFormsVec = std::vector; @@ -29,7 +29,7 @@ namespace LinkedDistribution /// Checks whether given entry is a linked form and attempts to parse it. /// /// true if given entry was a linked form. Note that returned value doesn't represent whether or parsing was successful. - bool TryParse(const std::string& a_key, const std::string& a_value, const std::string& a_path); + bool TryParse(const std::string& key, const std::string& value, const Path&); } using namespace Forms; @@ -66,7 +66,7 @@ namespace LinkedDistribution RECORD::TYPE type; FormsMap forms{}; - void Link(Form* form, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const PercentChance& chance, const std::string& path); + void Link(Form*, const FormVec& linkedForms, const IndexOrCount&, const PercentChance&, const Path&); }; class Manager : public ISingleton @@ -215,7 +215,7 @@ namespace LinkedDistribution } template - void LinkedForms::Link(Form* form, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const PercentChance& chance, const std::string& path) + void LinkedForms::Link(Form* form, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const PercentChance& chance, const Path& path) { for (const auto& linkedForm : linkedForms) { if (std::holds_alternative(linkedForm)) { diff --git a/SPID/src/ExclusiveGroups.cpp b/SPID/src/ExclusiveGroups.cpp index efe6b1b..277b46e 100644 --- a/SPID/src/ExclusiveGroups.cpp +++ b/SPID/src/ExclusiveGroups.cpp @@ -3,7 +3,7 @@ namespace ExclusiveGroups { - bool INI::TryParse(const std::string& a_key, const std::string& a_value, const std::string& a_path) + bool INI::TryParse(const std::string& a_key, const std::string& a_value, const Path& a_path) { if (a_key != "ExclusiveGroup") { return false; diff --git a/SPID/src/LinkedDistribution.cpp b/SPID/src/LinkedDistribution.cpp index b5c849a..00a6e38 100644 --- a/SPID/src/LinkedDistribution.cpp +++ b/SPID/src/LinkedDistribution.cpp @@ -21,37 +21,37 @@ namespace LinkedDistribution kRequired = kLinkedForms }; - bool TryParse(const std::string& a_key, const std::string& a_value, const std::string& a_path) + bool TryParse(const std::string& key, const std::string& value, const Path& path) { - if (!a_key.starts_with("Linked"sv)) { + if (!key.starts_with("Linked"sv)) { return false; } - std::string rawType = a_key.substr(6); + std::string rawType = key.substr(6); auto type = RECORD::GetType(rawType); if (type == RECORD::kTotal) { logger::warn("IGNORED: Unsupported Linked Form type: {}"sv, rawType); return true; } - const auto sections = string::split(a_value, "|"); + const auto sections = string::split(value, "|"); const auto size = sections.size(); if (size <= kRequired) { - logger::warn("IGNORED: LinkedItem must have a form and at least one Form Filter: {} = {}"sv, a_key, a_value); + logger::warn("IGNORED: LinkedItem must have a form and at least one Form Filter: {} = {}"sv, key, value); return true; } auto split_IDs = distribution::split_entry(sections[kLinkedForms]); if (split_IDs.empty()) { - logger::warn("IGNORED: LinkedItem must have at least one Form Filter : {} = {}"sv, a_key, a_value); + logger::warn("IGNORED: LinkedItem must have at least one Form Filter : {} = {}"sv, key, value); return true; } INI::RawLinkedForm item{}; item.formOrEditorID = distribution::get_record(sections[kForm]); - item.path = a_path; + item.path = path; for (auto& IDs : split_IDs) { item.formIDs.MATCH.push_back(distribution::get_record(IDs)); diff --git a/SPID/src/LookupConfigs.cpp b/SPID/src/LookupConfigs.cpp index ffd28a9..48d85af 100644 --- a/SPID/src/LookupConfigs.cpp +++ b/SPID/src/LookupConfigs.cpp @@ -48,7 +48,7 @@ namespace INI return newValue; } - std::pair> parse_ini(const RECORD::TYPE& typeHint, const std::string& a_value, const std::string& a_path) + std::pair> parse_ini(const RECORD::TYPE& typeHint, const std::string& a_value, const Path& a_path) { Data data{}; From bb018e231123d21939d5af5411710dcfaf5d67f6 Mon Sep 17 00:00:00 2001 From: Arkadii Hlushchevskyi Date: Sun, 31 Mar 2024 02:01:34 +0200 Subject: [PATCH 05/10] Introduced Scope for linked forms. --- SPID/include/LinkedDistribution.h | 28 ++++++++++++++++++++++---- SPID/src/LinkedDistribution.cpp | 33 ++++++++++++++++++++----------- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/SPID/include/LinkedDistribution.h b/SPID/include/LinkedDistribution.h index a5eeb18..4c3b544 100644 --- a/SPID/include/LinkedDistribution.h +++ b/SPID/include/LinkedDistribution.h @@ -5,12 +5,31 @@ namespace LinkedDistribution { + + /// + /// Scope of a linked form determines which distributions can trigger linked forms. + /// + enum Scope : std::uint8_t + { + /// + /// Local scope links forms only to distributions defined in the same configuration file. + /// + kLocal = 0, + + /// + /// Global scope links forms to all distributions in all loaded configuration files. + /// + kGlobal + }; + namespace INI { struct RawLinkedForm { FormOrEditorID formOrEditorID{}; + Scope scope{ kLocal }; + /// Raw filters in RawLinkedForm only use MATCH, there is no meaning for ALL or NOT, so they are ignored. Filters formIDs{}; @@ -66,7 +85,7 @@ namespace LinkedDistribution RECORD::TYPE type; FormsMap forms{}; - void Link(Form*, const FormVec& linkedForms, const IndexOrCount&, const PercentChance&, const Path&); + void Link(Form*, Scope, const FormVec& linkedForms, const IndexOrCount&, const PercentChance&, const Path&); }; class Manager : public ISingleton @@ -206,17 +225,18 @@ namespace LinkedDistribution { for (auto& rawForm : rawLinkedForms) { auto form = detail::LookupLinkedForm(dataHandler, rawForm); - auto& [formID, parentFormIDs, count, chance, path] = rawForm; + auto& [formID, scope, parentFormIDs, count, chance, path] = rawForm; FormVec parentForms{}; if (Forms::detail::formID_to_form(dataHandler, parentFormIDs.MATCH, parentForms, path, false, false)) { - Link(form, parentForms, count, chance, path); + Link(form, scope, parentForms, count, chance, path); } } } template - void LinkedForms::Link(Form* form, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const PercentChance& chance, const Path& path) + void LinkedForms::Link(Form* form, Scope scope, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const PercentChance& chance, const Path& path) { + // TODO: Handle scope for (const auto& linkedForm : linkedForms) { if (std::holds_alternative(linkedForm)) { auto& distributableForms = forms[std::get(linkedForm)]; diff --git a/SPID/src/LinkedDistribution.cpp b/SPID/src/LinkedDistribution.cpp index 00a6e38..1374aff 100644 --- a/SPID/src/LinkedDistribution.cpp +++ b/SPID/src/LinkedDistribution.cpp @@ -21,14 +21,24 @@ namespace LinkedDistribution kRequired = kLinkedForms }; - bool TryParse(const std::string& key, const std::string& value, const Path& path) + bool TryParse(const std::string& originalKey, const std::string& value, const Path& path) { + std::string key = originalKey; + + Scope scope = kLocal; + + if (key.starts_with("Global"sv)) { + scope = kGlobal; + key.erase(0, 6); + } + if (!key.starts_with("Linked"sv)) { return false; } std::string rawType = key.substr(6); auto type = RECORD::GetType(rawType); + if (type == RECORD::kTotal) { logger::warn("IGNORED: Unsupported Linked Form type: {}"sv, rawType); return true; @@ -51,6 +61,7 @@ namespace LinkedDistribution INI::RawLinkedForm item{}; item.formOrEditorID = distribution::get_record(sections[kForm]); + item.scope = scope; item.path = path; for (auto& IDs : split_IDs) { @@ -106,28 +117,28 @@ namespace LinkedDistribution auto& genericForms = rawLinkedForms[RECORD::kForm]; for (auto& rawForm : genericForms) { if (auto form = detail::LookupLinkedForm(dataHandler, rawForm); form) { - auto& [formID, parentFormIDs, idxOrCount, chance, path] = rawForm; + auto& [formID, scope, parentFormIDs, idxOrCount, chance, path] = rawForm; FormVec parentForms{}; if (!Forms::detail::formID_to_form(dataHandler, parentFormIDs.MATCH, parentForms, path, false, false)) { continue; } // Add to appropriate list. (Note that type inferring doesn't recognize SleepOutfit or DeathItems) if (const auto keyword = form->As(); keyword) { - keywords.Link(keyword, parentForms, idxOrCount, chance, path); + keywords.Link(keyword, scope, parentForms, idxOrCount, chance, path); } else if (const auto spell = form->As(); spell) { - spells.Link(spell, parentForms, idxOrCount, chance, path); + spells.Link(spell, scope, parentForms, idxOrCount, chance, path); } else if (const auto perk = form->As(); perk) { - perks.Link(perk, parentForms, idxOrCount, chance, path); + perks.Link(perk, scope, parentForms, idxOrCount, chance, path); } else if (const auto shout = form->As(); shout) { - shouts.Link(shout, parentForms, idxOrCount, chance, path); + shouts.Link(shout, scope, parentForms, idxOrCount, chance, path); } else if (const auto item = form->As(); item) { - items.Link(item, parentForms, idxOrCount, chance, path); + items.Link(item, scope, parentForms, idxOrCount, chance, path); } else if (const auto outfit = form->As(); outfit) { - outfits.Link(outfit, parentForms, idxOrCount, chance, path); + outfits.Link(outfit, scope, parentForms, idxOrCount, chance, path); } else if (const auto faction = form->As(); faction) { - factions.Link(faction, parentForms, idxOrCount, chance, path); + factions.Link(faction, scope, parentForms, idxOrCount, chance, path); } else if (const auto skin = form->As(); skin) { - skins.Link(skin, parentForms, idxOrCount, chance, path); + skins.Link(skin, scope, parentForms, idxOrCount, chance, path); } else { auto type = form->GetFormType(); if (type == RE::FormType::Package || type == RE::FormType::FormList) { @@ -142,7 +153,7 @@ namespace LinkedDistribution } else { packageIndex = std::get(idxOrCount); } - packages.Link(form, parentForms, packageIndex, chance, path); + packages.Link(form, scope, parentForms, packageIndex, chance, path); } else { logger::warn("\t[{}] Unsupported Form type: {}", path, RE::FormTypeToString(type)); } From b2e692c84a4a5eb59c27ef61b1c1b32a180949f0 Mon Sep 17 00:00:00 2001 From: adya Date: Sun, 31 Mar 2024 00:01:49 +0000 Subject: [PATCH 06/10] maintenance --- SPID/include/LookupConfigs.h | 2 +- SPID/src/LinkedDistribution.cpp | 2 +- SPID/src/LookupConfigs.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SPID/include/LookupConfigs.h b/SPID/include/LookupConfigs.h index 322331b..7431303 100644 --- a/SPID/include/LookupConfigs.h +++ b/SPID/include/LookupConfigs.h @@ -85,7 +85,7 @@ namespace INI }; using DataVec = std::vector; - + inline Map configs{}; std::pair GetConfigs(); diff --git a/SPID/src/LinkedDistribution.cpp b/SPID/src/LinkedDistribution.cpp index 1374aff..4771d1f 100644 --- a/SPID/src/LinkedDistribution.cpp +++ b/SPID/src/LinkedDistribution.cpp @@ -25,7 +25,7 @@ namespace LinkedDistribution { std::string key = originalKey; - Scope scope = kLocal; + Scope scope = kLocal; if (key.starts_with("Global"sv)) { scope = kGlobal; diff --git a/SPID/src/LookupConfigs.cpp b/SPID/src/LookupConfigs.cpp index 48d85af..11d30e6 100644 --- a/SPID/src/LookupConfigs.cpp +++ b/SPID/src/LookupConfigs.cpp @@ -1,6 +1,6 @@ #include "LookupConfigs.h" -#include "LinkedDistribution.h" #include "ExclusiveGroups.h" +#include "LinkedDistribution.h" namespace INI { From f54db3af7a7815e839a2d50e73586f7a6a94bfd7 Mon Sep 17 00:00:00 2001 From: Arkadii Hlushchevskyi Date: Sun, 31 Mar 2024 21:14:09 +0300 Subject: [PATCH 07/10] Implemented Local scope for linked forms. --- SPID/include/Distribute.h | 36 +++++++++++++++++++------------ SPID/include/FormData.h | 7 +++--- SPID/include/LinkedDistribution.h | 35 ++++++++++++++++-------------- SPID/src/Distribute.cpp | 6 +++--- SPID/src/LinkedDistribution.cpp | 28 +++++++++++++----------- 5 files changed, 64 insertions(+), 48 deletions(-) diff --git a/SPID/include/Distribute.h b/SPID/include/Distribute.h index f840a3e..f222af3 100644 --- a/SPID/include/Distribute.h +++ b/SPID/include/Distribute.h @@ -70,6 +70,8 @@ namespace Distribute void add_item(RE::Actor* a_actor, RE::TESBoundObject* a_item, std::uint32_t a_itemCount); } + using namespace Forms; + #pragma region Packages, Death Items // old method (distributing one by one) // for now, only packages/death items use this @@ -79,12 +81,12 @@ namespace Distribute Forms::DataVec& forms, const PCLevelMult::Input& a_input, std::function a_callback, - std::set* accumulatedForms = nullptr) + DistributedForms* accumulatedForms = nullptr) { for (auto& formData : forms) { if (!a_npcData.HasMutuallyExclusiveForm(formData.form) && detail::passed_filters(a_npcData, a_input, formData)) { if (accumulatedForms) { - accumulatedForms->insert(formData.form); + accumulatedForms->insert({ formData.form, formData.path }); } a_callback(formData.form, formData.idxOrCount); ++formData.npcCount; @@ -100,12 +102,12 @@ namespace Distribute Forms::DataVec& forms, const PCLevelMult::Input& a_input, std::function a_callback, - std::set* accumulatedForms = nullptr) + DistributedForms* accumulatedForms = nullptr) { for (auto& formData : forms) { // Vector is reversed in FinishLookupForms if (!a_npcData.HasMutuallyExclusiveForm(formData.form) && detail::passed_filters(a_npcData, a_input, formData) && a_callback(formData.form)) { if (accumulatedForms) { - accumulatedForms->insert(formData.form); + accumulatedForms->insert({ formData.form, formData.path }); } ++formData.npcCount; break; @@ -121,14 +123,14 @@ namespace Distribute const NPCData& a_npcData, Forms::Distributables& a_distributables, std::function a_callback, - std::set* accumulatedForms = nullptr) + DistributedForms* accumulatedForms = nullptr) { auto& vec = a_distributables.GetForms(false); for (auto& formData : vec) { // Vector is reversed in FinishLookupForms if (!a_npcData.HasMutuallyExclusiveForm(formData.form) && detail::passed_filters(a_npcData, formData) && a_callback(formData.form)) { if (accumulatedForms) { - accumulatedForms->insert(formData.form); + accumulatedForms->insert({ formData.form, formData.path }); } ++formData.npcCount; break; @@ -144,7 +146,7 @@ namespace Distribute Forms::DataVec& forms, const PCLevelMult::Input& a_input, std::function&)> a_callback, - std::set* accumulatedForms = nullptr) + DistributedForms* accumulatedForms = nullptr) { std::map collectedForms{}; @@ -159,18 +161,21 @@ namespace Distribute leveledItem->CalculateCurrentFormList(level, count, calcedObjects, 0, true); for (auto& calcObj : calcedObjects) { collectedForms[static_cast(calcObj.form)] += calcObj.count; + if (accumulatedForms) { + accumulatedForms->insert({ calcObj.form, formData.path }); + } } } else { collectedForms[formData.form] += count; + if (accumulatedForms) { + accumulatedForms->insert({ formData.form, formData.path }); + } } ++formData.npcCount; } } if (!collectedForms.empty()) { - if (accumulatedForms) { - std::ranges::copy(collectedForms | std::views::keys, std::inserter(*accumulatedForms, accumulatedForms->end())); - } a_callback(collectedForms); } } @@ -185,7 +190,7 @@ namespace Distribute Forms::DataVec& forms, const PCLevelMult::Input& a_input, std::function&)> a_callback, - std::set* accumulatedForms = nullptr) + DistributedForms* accumulatedForms = nullptr) { const auto npc = a_npcData.GetNPC(); @@ -210,6 +215,9 @@ namespace Distribute if (formData.filters.HasLevelFilters()) { collectedLeveledFormIDs.emplace(formID); } + if (accumulatedForms) { + accumulatedForms->insert({ form, formData.path }); + } ++formData.npcCount; } } else { @@ -218,15 +226,15 @@ namespace Distribute if (formData.filters.HasLevelFilters()) { collectedLeveledFormIDs.emplace(formID); } + if (accumulatedForms) { + accumulatedForms->insert({ form, formData.path }); + } ++formData.npcCount; } } } if (!collectedForms.empty()) { - if (accumulatedForms) { - accumulatedForms->insert(collectedForms.begin(), collectedForms.end()); - } a_callback(collectedForms); if (!collectedLeveledFormIDs.empty()) { PCLevelMult::Manager::GetSingleton()->InsertDistributedEntry(a_input, Form::FORMTYPE, collectedLeveledFormIDs); diff --git a/SPID/include/FormData.h b/SPID/include/FormData.h index 749dae1..ffe2813 100644 --- a/SPID/include/FormData.h +++ b/SPID/include/FormData.h @@ -216,9 +216,7 @@ namespace Forms } form = as_form(anyForm); - if (!form || anyForm->GetFormType() != Form::FORMTYPE) { - // Ideally, we'd want to throw separate exception for unsupported form type, - // so that attempting to distribute, for example, CELL would properly report such error. + if (!form) { throw MismatchingFormTypeException(Form::FORMTYPE, anyForm->GetFormType(), FormModPair{ *formID, modName }, path); } @@ -364,6 +362,9 @@ namespace Forms template using DataVec = std::vector>; + using DistributedForm = std::pair; + using DistributedForms = std::set; + /// /// A set of distributable forms that should be processed. /// diff --git a/SPID/include/LinkedDistribution.h b/SPID/include/LinkedDistribution.h index 4c3b544..e4a85a2 100644 --- a/SPID/include/LinkedDistribution.h +++ b/SPID/include/LinkedDistribution.h @@ -70,7 +70,7 @@ namespace LinkedDistribution friend Manager; // allow Manager to later modify forms directly. friend Form* detail::LookupLinkedForm(RE::TESDataHandler* const, INI::RawLinkedForm&); - using FormsMap = std::unordered_map>; + using FormsMap = std::unordered_map>>; LinkedForms(RECORD::TYPE type) : type(type) @@ -108,7 +108,7 @@ namespace LinkedDistribution /// A set of forms for which distribution sets should be calculated. /// This is typically distributed forms accumulated during first distribution pass. /// A callback to be called with each DistributionSet. This is supposed to do the actual distribution. - void ForEachLinkedDistributionSet(const std::set& linkedForms, std::function callback); + void ForEachLinkedDistributionSet(const DistributedForms& linkedForms, std::function callback); /// /// Calculates DistributionSet with only DeathItems for each linked form and calls a callback for each of them. @@ -117,11 +117,11 @@ namespace LinkedDistribution /// A set of forms for which distribution sets should be calculated. /// This is typically distributed forms accumulated during first distribution pass. /// A callback to be called with each DistributionSet. This is supposed to do the actual distribution. - void ForEachLinkedDeathDistributionSet(const std::set& linkedForms, std::function callback); + void ForEachLinkedDeathDistributionSet(const DistributedForms& linkedForms, std::function callback); private: template - DataVec& LinkedFormsForForm(RE::TESForm* form, LinkedForms& linkedForms) const; + DataVec& LinkedFormsForForm(const DistributedForm&, LinkedForms&) const; LinkedForms spells{ RECORD::kSpell }; LinkedForms perks{ RECORD::kPerk }; @@ -193,14 +193,15 @@ namespace LinkedDistribution } template - DataVec& Manager::LinkedFormsForForm(RE::TESForm* form, LinkedForms& linkedForms) const + DataVec& Manager::LinkedFormsForForm(const DistributedForm& form, LinkedForms& linkedForms) const { - if (auto it = linkedForms.forms.find(form); it != linkedForms.forms.end()) { - return it->second; - } else { - static DataVec empty{}; - return empty; + if (const auto formsIt = linkedForms.forms.find(form.second); formsIt != linkedForms.forms.end()) { + if (const auto linkedFormsIt = formsIt->second.find(form.first); linkedFormsIt != formsIt->second.end()) { + return linkedFormsIt->second; + } } + static DataVec empty{}; + return empty; } template @@ -224,11 +225,12 @@ namespace LinkedDistribution void LinkedForms::LookupForms(RE::TESDataHandler* const dataHandler, INI::LinkedFormsVec& rawLinkedForms) { for (auto& rawForm : rawLinkedForms) { - auto form = detail::LookupLinkedForm(dataHandler, rawForm); - auto& [formID, scope, parentFormIDs, count, chance, path] = rawForm; - FormVec parentForms{}; - if (Forms::detail::formID_to_form(dataHandler, parentFormIDs.MATCH, parentForms, path, false, false)) { - Link(form, scope, parentForms, count, chance, path); + if (auto form = detail::LookupLinkedForm(dataHandler, rawForm); form) { + auto& [formID, scope, parentFormIDs, count, chance, path] = rawForm; + FormVec parentForms{}; + if (Forms::detail::formID_to_form(dataHandler, parentFormIDs.MATCH, parentForms, path, false, false)) { + Link(form, scope, parentForms, count, chance, path); + } } } } @@ -239,7 +241,8 @@ namespace LinkedDistribution // TODO: Handle scope for (const auto& linkedForm : linkedForms) { if (std::holds_alternative(linkedForm)) { - auto& distributableForms = forms[std::get(linkedForm)]; + auto& distributableFormsAtPath = forms[path]; + auto& distributableForms = distributableFormsAtPath[std::get(linkedForm)]; // Note that we don't use Data.index here, as these linked forms don't have any leveled filters // and as such do not to track their index. distributableForms.emplace_back(0, form, idxOrCount, FilterData({}, {}, {}, {}, chance), path); diff --git a/SPID/src/Distribute.cpp b/SPID/src/Distribute.cpp index 366cde2..6a6c6eb 100644 --- a/SPID/src/Distribute.cpp +++ b/SPID/src/Distribute.cpp @@ -21,7 +21,7 @@ namespace Distribute /// Leveling information about NPC that is being processed. /// A set of forms that should be distributed to NPC. /// An optional pointer to a set that will accumulate all distributed forms. - void distribute(NPCData& npcData, const PCLevelMult::Input& input, Forms::DistributionSet& forms, std::set* accumulatedForms) + void distribute(NPCData& npcData, const PCLevelMult::Input& input, Forms::DistributionSet& forms, DistributedForms* accumulatedForms) { const auto npc = npcData.GetNPC(); @@ -191,7 +191,7 @@ namespace Distribute Forms::skins.GetForms(input.onlyPlayerLevelEntries) }; - std::set distributedForms{}; + DistributedForms distributedForms{}; detail::distribute(npcData, input, entries, &distributedForms); // TODO: We can now log per-NPC distributed forms. @@ -212,7 +212,7 @@ namespace Distribute void DistributeDeathItems(NPCData& npcData, const PCLevelMult::Input& input) { - std::set distributedForms{}; + DistributedForms distributedForms{}; Forms::DistributionSet entries{ Forms::DistributionSet::empty(), diff --git a/SPID/src/LinkedDistribution.cpp b/SPID/src/LinkedDistribution.cpp index 4771d1f..076f5d6 100644 --- a/SPID/src/LinkedDistribution.cpp +++ b/SPID/src/LinkedDistribution.cpp @@ -182,15 +182,19 @@ namespace LinkedDistribution return; } - std::unordered_map> map{}; + std::unordered_map> map{}; // Iterate through the original map for (const auto& pair : linkedForms.GetForms()) { - const auto key = pair.first; - const DataVec& values = pair.second; - - for (const auto& value : values) { - map[value.form].push_back(key); + const auto& path = pair.first; + const auto& formsMap = pair.second; + + for (const auto& pair : formsMap) { + const auto key = pair.first; + const auto& values = pair.second; + for (const auto& value : values) { + map[value.form].emplace_back(key, path); + } } } @@ -203,17 +207,17 @@ namespace LinkedDistribution const auto lastItemIndex = linkedForms.size() - 1; for (int i = 0; i < lastItemIndex; ++i) { const auto& linkedItem = linkedForms[i]; - logger::info("\t├─── {}", describe(linkedItem)); + logger::info("\t├─── {} @{}", describe(linkedItem.first), linkedItem.second); } const auto& lastLinkedItem = linkedForms[lastItemIndex]; - logger::info("\t└─── {}", describe(lastLinkedItem)); + logger::info("\t└─── {} @{}", describe(lastLinkedItem.first), lastLinkedItem.second); } }); } - void Manager::ForEachLinkedDistributionSet(const std::set& targetForms, std::function performDistribution) + void Manager::ForEachLinkedDistributionSet(const DistributedForms& targetForms, std::function performDistribution) { - for (const auto form : targetForms) { + for (const auto& form : targetForms) { auto& linkedSpells = LinkedFormsForForm(form, spells); auto& linkedPerks = LinkedFormsForForm(form, perks); auto& linkedItems = LinkedFormsForForm(form, items); @@ -249,9 +253,9 @@ namespace LinkedDistribution } } - void Manager::ForEachLinkedDeathDistributionSet(const std::set& targetForms, std::function performDistribution) + void Manager::ForEachLinkedDeathDistributionSet(const DistributedForms& targetForms, std::function performDistribution) { - for (const auto form : targetForms) { + for (const auto& form : targetForms) { auto& linkedDeathItems = LinkedFormsForForm(form, deathItems); DistributionSet linkedEntries{ From 6ff3edafdb03126a228862a09e2217a4b459047f Mon Sep 17 00:00:00 2001 From: adya Date: Sun, 31 Mar 2024 18:25:15 +0000 Subject: [PATCH 08/10] maintenance --- SPID/src/LinkedDistribution.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SPID/src/LinkedDistribution.cpp b/SPID/src/LinkedDistribution.cpp index 076f5d6..ef1cded 100644 --- a/SPID/src/LinkedDistribution.cpp +++ b/SPID/src/LinkedDistribution.cpp @@ -186,11 +186,11 @@ namespace LinkedDistribution // Iterate through the original map for (const auto& pair : linkedForms.GetForms()) { - const auto& path = pair.first; + const auto& path = pair.first; const auto& formsMap = pair.second; for (const auto& pair : formsMap) { - const auto key = pair.first; + const auto key = pair.first; const auto& values = pair.second; for (const auto& value : values) { map[value.form].emplace_back(key, path); From b36708cbedd2a2b1b4c9b8179263411420de905e Mon Sep 17 00:00:00 2001 From: Arkadii Hlushchevskyi Date: Sun, 31 Mar 2024 22:09:09 +0300 Subject: [PATCH 09/10] Implemented Global scope for linked forms. --- SPID/include/FormData.h | 11 ++++++++ SPID/include/LinkedDistribution.h | 21 ++++++++------- SPID/src/LinkedDistribution.cpp | 45 ++++++++++++++++++++----------- 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/SPID/include/FormData.h b/SPID/include/FormData.h index ffe2813..1591bb4 100644 --- a/SPID/include/FormData.h +++ b/SPID/include/FormData.h @@ -645,3 +645,14 @@ void Forms::LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& // Likewise, we don't expect plugin names in distributable forms. } } + +inline std::ostream& operator<<(std::ostream& os, Forms::DistributedForm form) +{ + os << form.first; + + if (!form.second.empty()) { + os << " @" << form.second; + } + + return os; +} diff --git a/SPID/include/LinkedDistribution.h b/SPID/include/LinkedDistribution.h index e4a85a2..867b9a1 100644 --- a/SPID/include/LinkedDistribution.h +++ b/SPID/include/LinkedDistribution.h @@ -107,8 +107,8 @@ namespace LinkedDistribution /// /// A set of forms for which distribution sets should be calculated. /// This is typically distributed forms accumulated during first distribution pass. - /// A callback to be called with each DistributionSet. This is supposed to do the actual distribution. - void ForEachLinkedDistributionSet(const DistributedForms& linkedForms, std::function callback); + /// A callback to be called with each DistributionSet. This is supposed to do the actual distribution. + void ForEachLinkedDistributionSet(const DistributedForms& linkedForms, std::function distribute); /// /// Calculates DistributionSet with only DeathItems for each linked form and calls a callback for each of them. @@ -116,12 +116,15 @@ namespace LinkedDistribution /// /// A set of forms for which distribution sets should be calculated. /// This is typically distributed forms accumulated during first distribution pass. - /// A callback to be called with each DistributionSet. This is supposed to do the actual distribution. - void ForEachLinkedDeathDistributionSet(const DistributedForms& linkedForms, std::function callback); + /// A callback to be called with each DistributionSet. This is supposed to do the actual distribution. + void ForEachLinkedDeathDistributionSet(const DistributedForms& linkedForms, std::function distribute); private: template - DataVec& LinkedFormsForForm(const DistributedForm&, LinkedForms&) const; + DataVec& LinkedFormsForForm(const DistributedForm&, Scope, LinkedForms&) const; + + void ForEachLinkedDistributionSet(const DistributedForms& linkedForms, Scope, std::function distribute); + void ForEachLinkedDeathDistributionSet(const DistributedForms& linkedForms, Scope, std::function distribute); LinkedForms spells{ RECORD::kSpell }; LinkedForms perks{ RECORD::kPerk }; @@ -193,13 +196,14 @@ namespace LinkedDistribution } template - DataVec& Manager::LinkedFormsForForm(const DistributedForm& form, LinkedForms& linkedForms) const + DataVec& Manager::LinkedFormsForForm(const DistributedForm& form, Scope scope, LinkedForms& linkedForms) const { - if (const auto formsIt = linkedForms.forms.find(form.second); formsIt != linkedForms.forms.end()) { + if (const auto formsIt = linkedForms.forms.find(scope == kLocal ? form.second : ""); formsIt != linkedForms.forms.end()) { if (const auto linkedFormsIt = formsIt->second.find(form.first); linkedFormsIt != formsIt->second.end()) { return linkedFormsIt->second; } } + static DataVec empty{}; return empty; } @@ -238,10 +242,9 @@ namespace LinkedDistribution template void LinkedForms::Link(Form* form, Scope scope, const FormVec& linkedForms, const IndexOrCount& idxOrCount, const PercentChance& chance, const Path& path) { - // TODO: Handle scope for (const auto& linkedForm : linkedForms) { if (std::holds_alternative(linkedForm)) { - auto& distributableFormsAtPath = forms[path]; + auto& distributableFormsAtPath = forms[scope == kLocal ? path : ""]; // If item is global, we put it in a common map with no information about the path. auto& distributableForms = distributableFormsAtPath[std::get(linkedForm)]; // Note that we don't use Data.index here, as these linked forms don't have any leveled filters // and as such do not to track their index. diff --git a/SPID/src/LinkedDistribution.cpp b/SPID/src/LinkedDistribution.cpp index ef1cded..df6414f 100644 --- a/SPID/src/LinkedDistribution.cpp +++ b/SPID/src/LinkedDistribution.cpp @@ -207,28 +207,30 @@ namespace LinkedDistribution const auto lastItemIndex = linkedForms.size() - 1; for (int i = 0; i < lastItemIndex; ++i) { const auto& linkedItem = linkedForms[i]; - logger::info("\t├─── {} @{}", describe(linkedItem.first), linkedItem.second); + logger::info("\t├─── {}", describe(linkedItem)); } const auto& lastLinkedItem = linkedForms[lastItemIndex]; - logger::info("\t└─── {} @{}", describe(lastLinkedItem.first), lastLinkedItem.second); + logger::info("\t└─── {}", describe(lastLinkedItem)); } }); } +#pragma endregion - void Manager::ForEachLinkedDistributionSet(const DistributedForms& targetForms, std::function performDistribution) +#pragma region Distribution + void Manager::ForEachLinkedDistributionSet(const DistributedForms& targetForms, Scope scope, std::function performDistribution) { for (const auto& form : targetForms) { - auto& linkedSpells = LinkedFormsForForm(form, spells); - auto& linkedPerks = LinkedFormsForForm(form, perks); - auto& linkedItems = LinkedFormsForForm(form, items); - auto& linkedShouts = LinkedFormsForForm(form, shouts); - auto& linkedLevSpells = LinkedFormsForForm(form, levSpells); - auto& linkedPackages = LinkedFormsForForm(form, packages); - auto& linkedOutfits = LinkedFormsForForm(form, outfits); - auto& linkedKeywords = LinkedFormsForForm(form, keywords); - auto& linkedFactions = LinkedFormsForForm(form, factions); - auto& linkedSleepOutfits = LinkedFormsForForm(form, sleepOutfits); - auto& linkedSkins = LinkedFormsForForm(form, skins); + auto& linkedSpells = LinkedFormsForForm(form, scope, spells); + auto& linkedPerks = LinkedFormsForForm(form, scope, perks); + auto& linkedItems = LinkedFormsForForm(form, scope, items); + auto& linkedShouts = LinkedFormsForForm(form, scope, shouts); + auto& linkedLevSpells = LinkedFormsForForm(form, scope, levSpells); + auto& linkedPackages = LinkedFormsForForm(form, scope, packages); + auto& linkedOutfits = LinkedFormsForForm(form, scope, outfits); + auto& linkedKeywords = LinkedFormsForForm(form, scope, keywords); + auto& linkedFactions = LinkedFormsForForm(form, scope, factions); + auto& linkedSleepOutfits = LinkedFormsForForm(form, scope, sleepOutfits); + auto& linkedSkins = LinkedFormsForForm(form, scope, skins); DistributionSet linkedEntries{ linkedSpells, @@ -253,10 +255,16 @@ namespace LinkedDistribution } } - void Manager::ForEachLinkedDeathDistributionSet(const DistributedForms& targetForms, std::function performDistribution) + void Manager::ForEachLinkedDistributionSet(const DistributedForms& targetForms, std::function performDistribution) + { + ForEachLinkedDistributionSet(targetForms, Scope::kLocal, performDistribution); + ForEachLinkedDistributionSet(targetForms, Scope::kGlobal, performDistribution); + } + + void Manager::ForEachLinkedDeathDistributionSet(const DistributedForms& targetForms, Scope scope, std::function performDistribution) { for (const auto& form : targetForms) { - auto& linkedDeathItems = LinkedFormsForForm(form, deathItems); + auto& linkedDeathItems = LinkedFormsForForm(form, scope, deathItems); DistributionSet linkedEntries{ DistributionSet::empty(), @@ -281,5 +289,10 @@ namespace LinkedDistribution } } + void Manager::ForEachLinkedDeathDistributionSet(const DistributedForms& targetForms, std::function performDistribution) + { + ForEachLinkedDeathDistributionSet(targetForms, Scope::kLocal, performDistribution); + ForEachLinkedDeathDistributionSet(targetForms, Scope::kGlobal, performDistribution); + } #pragma endregion } From f16ce039327873328aaf127abdd9fc1737bbaa29 Mon Sep 17 00:00:00 2001 From: adya Date: Sun, 31 Mar 2024 19:09:25 +0000 Subject: [PATCH 10/10] maintenance --- SPID/include/LinkedDistribution.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SPID/include/LinkedDistribution.h b/SPID/include/LinkedDistribution.h index 867b9a1..560e7e0 100644 --- a/SPID/include/LinkedDistribution.h +++ b/SPID/include/LinkedDistribution.h @@ -203,7 +203,7 @@ namespace LinkedDistribution return linkedFormsIt->second; } } - + static DataVec empty{}; return empty; } @@ -244,7 +244,7 @@ namespace LinkedDistribution { for (const auto& linkedForm : linkedForms) { if (std::holds_alternative(linkedForm)) { - auto& distributableFormsAtPath = forms[scope == kLocal ? path : ""]; // If item is global, we put it in a common map with no information about the path. + auto& distributableFormsAtPath = forms[scope == kLocal ? path : ""]; // If item is global, we put it in a common map with no information about the path. auto& distributableForms = distributableFormsAtPath[std::get(linkedForm)]; // Note that we don't use Data.index here, as these linked forms don't have any leveled filters // and as such do not to track their index.