Skip to content

Commit

Permalink
Added type inference to regular entries.
Browse files Browse the repository at this point in the history
e.g. users can now simply write "Form = ..." instead of "Spell = ..."
  • Loading branch information
adya committed Mar 29, 2024
1 parent 719ef95 commit 78dbffb
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 70 deletions.
124 changes: 76 additions & 48 deletions SPID/include/FormData.h
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,8 @@ namespace Forms
RECORD::TYPE type;
DataVec<Form> forms{};
DataVec<Form> formsWithLevels{};

void LookupForm(RE::TESDataHandler* a_dataHandler, INI::Data& rawForm);
};

inline Distributables<RE::SpellItem> spells{ RECORD::kSpell };
Expand Down Expand Up @@ -461,6 +463,17 @@ namespace Forms
a_func(packages, std::forward<Args>(args)...);
a_func(skins, std::forward<Args>(args)...);
}

/// <summary>
/// Performs lookup for a single entry.
/// It's up to the callee to add the form to the appropriate distributable container.
/// </summary>
/// <typeparam name="Form">Type of the form to lookup. This type can be defaulted to accept any TESForm.</typeparam>
/// <param name="dataHandler"></param>
/// <param name="rawForm">A raw form entry that needs to be looked up.</param>
/// <param name="callback">A callback to be called with validated data after successful lookup.</param>
template <class Form = RE::TESForm*>
void LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function<void(Form*, IndexOrCount&, FilterData&, std::string& path)> callback);
}

template <class Form>
Expand Down Expand Up @@ -511,6 +524,14 @@ Forms::DataVec<Form>& Forms::Distributables<Form>::GetForms(bool a_onlyLevelEntr
return forms;
}

template<class Form>
void Forms::Distributables<Form>::LookupForm(RE::TESDataHandler* a_dataHandler, INI::Data& rawForm)
{
Forms::LookupGenericForm<Form>(a_dataHandler, rawForm, [&](Form* form, auto& idxOrCount, auto& filters, std::string& path) {
forms.emplace_back(forms.size(), form, idxOrCount, filters, path);
});
}

template <class Form>
void Forms::Distributables<Form>::LookupForms(RE::TESDataHandler* a_dataHandler, std::string_view a_type, INI::DataVec& a_INIDataVec)
{
Expand All @@ -521,55 +542,9 @@ void Forms::Distributables<Form>::LookupForms(RE::TESDataHandler* a_dataHandler,
logger::info("{}", a_type);

forms.reserve(a_INIDataVec.size());
std::uint32_t index = 0;

for (auto& [formOrEditorID, strings, filterIDs, level, traits, idxOrCount, chance, path] : a_INIDataVec) {
try {
if (auto form = detail::get_form<Form>(a_dataHandler, formOrEditorID, path); form) {
FormFilters filterForms{};

bool validEntry = detail::formID_to_form(a_dataHandler, filterIDs.ALL, filterForms.ALL, path, true);
if (validEntry) {
validEntry = detail::formID_to_form(a_dataHandler, filterIDs.NOT, filterForms.NOT, path);
}
if (validEntry) {
validEntry = detail::formID_to_form(a_dataHandler, filterIDs.MATCH, filterForms.MATCH, path);
}

if (validEntry) {
forms.emplace_back(index, form, idxOrCount, FilterData(strings, filterForms, level, traits, chance), path);
index++;
}
}
} catch (const Lookup::UnknownFormIDException& e) {
buffered_logger::error("\t[{}] [0x{:X}] ({}) FAIL - formID doesn't exist", e.path, e.formID, e.modName.value_or(""));
} catch (const Lookup::InvalidKeywordException& e) {
buffered_logger::error("\t[{}] [0x{:X}] ({}) FAIL - keyword does not have a valid editorID", e.path, e.formID, e.modName.value_or(""));
} catch (const Lookup::KeywordNotFoundException& e) {
if (e.isDynamic) {
buffered_logger::critical("\t[{}] {} FAIL - couldn't create keyword", e.path, e.editorID);
} else {
buffered_logger::critical("\t[{}] {} FAIL - couldn't get existing keyword", e.path, e.editorID);
}
} catch (const Lookup::UnknownEditorIDException& e) {
buffered_logger::error("\t[{}] ({}) FAIL - editorID doesn't exist", e.path, e.editorID);
} catch (const Lookup::MalformedEditorIDException& e) {
buffered_logger::error("\t[{}] FAIL - editorID can't be empty", e.path);
} catch (const Lookup::MismatchingFormTypeException& e) {
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));
},
[&](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));
} },
e.formOrEditorID);
} catch (const Lookup::InvalidFormTypeException& e) {
// Whitelisting is disabled, so this should not occur
} catch (const Lookup::UnknownPluginException& e) {
// Likewise, we don't expect plugin names in distributable forms.
}
for (auto& rawForm : a_INIDataVec) {
LookupForm(a_dataHandler, rawForm);
}
}

Expand All @@ -595,3 +570,56 @@ void Forms::Distributables<Form>::FinishLookupForms()
std::back_inserter(formsWithLevels),
[](const auto& formData) { return formData.filters.HasLevelFilters(); });
}

template <class Form>
void Forms::LookupGenericForm(RE::TESDataHandler* const dataHandler, INI::Data& rawForm, std::function<void(Form*, IndexOrCount&, FilterData&, std::string& path)> callback)
{
auto& [formOrEditorID, strings, filterIDs, level, traits, idxOrCount, chance, path] = rawForm;

try {
if (auto form = detail::get_form<Form>(dataHandler, formOrEditorID, path); form) {
FormFilters filterForms{};

bool validEntry = detail::formID_to_form(dataHandler, filterIDs.ALL, filterForms.ALL, path, true);
if (validEntry) {
validEntry = detail::formID_to_form(dataHandler, filterIDs.NOT, filterForms.NOT, path);
}
if (validEntry) {
validEntry = detail::formID_to_form(dataHandler, filterIDs.MATCH, filterForms.MATCH, path);
}

if (validEntry) {
FilterData filters{ strings, filterForms, level, traits, chance };
callback(form, idxOrCount, filters, path);
}
}
} catch (const Lookup::UnknownFormIDException& e) {
buffered_logger::error("\t[{}] [0x{:X}] ({}) FAIL - formID doesn't exist", e.path, e.formID, e.modName.value_or(""));
} catch (const Lookup::InvalidKeywordException& e) {
buffered_logger::error("\t[{}] [0x{:X}] ({}) FAIL - keyword does not have a valid editorID", e.path, e.formID, e.modName.value_or(""));
} catch (const Lookup::KeywordNotFoundException& e) {
if (e.isDynamic) {
buffered_logger::critical("\t[{}] {} FAIL - couldn't create keyword", e.path, e.editorID);
} else {
buffered_logger::critical("\t[{}] {} FAIL - couldn't get existing keyword", e.path, e.editorID);
}
} catch (const Lookup::UnknownEditorIDException& e) {
buffered_logger::error("\t[{}] ({}) FAIL - editorID doesn't exist", e.path, e.editorID);
} catch (const Lookup::MalformedEditorIDException& e) {
buffered_logger::error("\t[{}] FAIL - editorID can't be empty", e.path);
} catch (const Lookup::MismatchingFormTypeException& e) {
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));
},
[&](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));
} },
e.formOrEditorID);
} catch (const Lookup::InvalidFormTypeException& e) {
// Whitelisting is disabled, so this should not occur
} catch (const Lookup::UnknownPluginException& e) {
// Likewise, we don't expect plugin names in distributable forms.
}
}
6 changes: 3 additions & 3 deletions SPID/include/LinkedDistribution.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ namespace LinkedDistribution
bool TryParse(const std::string& a_key, const std::string& a_value, const std::string& a_path);
}

using namespace Forms;

class Manager;

template <class Form>
Expand All @@ -42,9 +44,7 @@ namespace LinkedDistribution
template <class Form = RE::TESForm>
Form* LookupLinkedForm(RE::TESDataHandler* const dataHandler, INI::RawLinkedForm& rawForm);
}

using namespace Forms;


template <class Form>
struct LinkedForms
{
Expand Down
8 changes: 7 additions & 1 deletion SPID/include/LookupConfigs.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ namespace RECORD
using namespace detail;
return static_cast<TYPE>(std::distance(add.begin(), std::find(add.begin(), add.end(), aType)));
}

inline constexpr TYPE GetType(const char* aType)
{
using namespace detail;
return static_cast<TYPE>(std::distance(add.begin(), std::find(add.begin(), add.end(), aType)));
}
}

namespace INI
Expand Down Expand Up @@ -101,7 +107,7 @@ namespace INI
using DataVec = std::vector<Data>;
using ExclusiveGroupsVec = std::vector<RawExclusiveGroup>;

inline StringMap<DataVec> configs{};
inline Map<RECORD::TYPE, DataVec> configs{};

/// <summary>
/// A list of RawExclusiveGroups that will be processed along with configs.
Expand Down
6 changes: 3 additions & 3 deletions SPID/src/LinkedDistribution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ namespace LinkedDistribution
factions.Link(faction, parentForms, idxOrCount, chance, path);
} else if (const auto skin = form->As<RE::TESObjectARMO>(); skin) {
skins.Link(skin, parentForms, idxOrCount, chance, path);
} else if (const auto package = form->As<RE::TESForm>(); package) {
auto type = package->GetFormType();
} else {
auto type = form->GetFormType();
if (type == RE::FormType::Package || type == RE::FormType::FormList) {
// With generic Form entries we default to RandomCount, so we need to properly convert it to Index if it turned out to be a package.
Index packageIndex = 1;
Expand All @@ -142,7 +142,7 @@ namespace LinkedDistribution
} else {
packageIndex = std::get<Index>(idxOrCount);
}
packages.Link(package, parentForms, packageIndex, chance, path);
packages.Link(form, parentForms, packageIndex, chance, path);
}
}
}
Expand Down
12 changes: 7 additions & 5 deletions SPID/src/LookupConfigs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ namespace INI
return group;
}

std::pair<Data, std::optional<std::string>> parse_ini(const std::string& a_key, const std::string& a_value, const std::string& a_path)
std::pair<Data, std::optional<std::string>> parse_ini(const RECORD::TYPE& typeHint, const std::string& a_value, const std::string& a_path)
{
Data data{};

Expand Down Expand Up @@ -242,12 +242,12 @@ namespace INI
}

//ITEMCOUNT/INDEX
if (a_key == "Package") { // reuse item count for package stack index
if (typeHint == RECORD::kPackage) { // reuse item count for package stack index
data.idxOrCount = 0;
}

if (kIdxOrCount < size) {
if (a_key == "Package") { // If it's a package, then we only expect a single number.
if (typeHint == RECORD::kPackage) { // If it's a package, then we only expect a single number.
if (const auto& str = sections[kIdxOrCount]; distribution::is_valid_entry(str)) {
data.idxOrCount = string::to_num<Index>(str);
}
Expand Down Expand Up @@ -326,8 +326,10 @@ namespace INI
continue;
}

auto [data, sanitized_str] = detail::parse_ini(key.pItem, entry, truncatedPath);
configs[key.pItem].emplace_back(data);
auto type = RECORD::GetType(key.pItem);
auto [data, sanitized_str] = detail::parse_ini(type, entry, truncatedPath);

configs[type].emplace_back(data);

if (sanitized_str) {
oldFormatMap.emplace(key, std::make_pair(entry, *sanitized_str));
Expand Down
63 changes: 53 additions & 10 deletions SPID/src/LookupForms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,64 @@
#include "KeywordDependencies.h"
#include "LinkedDistribution.h"


bool LookupDistributables(RE::TESDataHandler* const dataHandler)
{
using namespace Forms;

bool valid = false;

ForEachDistributable([&]<typename Form>(Distributables<Form>& a_distributable) {
const auto& recordName = RECORD::GetTypeName(a_distributable.GetType());

a_distributable.LookupForms(dataHandler, recordName, INI::configs[recordName]);
if constexpr (std::is_same_v<RE::BGSKeyword, Form>) {
Dependencies::ResolveKeywords();
}
a_distributable.FinishLookupForms();
a_distributable.LookupForms(dataHandler, recordName, INI::configs[a_distributable.GetType()]);
});

auto& genericForms = INI::configs[RECORD::kForm];

for (auto& rawForm : genericForms) {
// Add to appropriate list. (Note that type inferring doesn't recognize SleepOutfit or DeathItems)
LookupGenericForm<RE::TESForm>(dataHandler, rawForm, [&](auto form, auto& idxOrCount, auto& filters, std::string& path) {
if (const auto keyword = form->As<RE::BGSKeyword>(); keyword) {
keywords.GetForms().emplace_back(keywords.GetSize(), keyword, idxOrCount, filters, path);
} else if (const auto spell = form->As<RE::SpellItem>(); spell) {
spells.GetForms().emplace_back(spells.GetSize(), spell, idxOrCount, filters, path);
} else if (const auto perk = form->As<RE::BGSPerk>(); perk) {
perks.GetForms().emplace_back(perks.GetSize(), perk, idxOrCount, filters, path);
} else if (const auto shout = form->As<RE::TESShout>(); shout) {
shouts.GetForms().emplace_back(shouts.GetSize(), shout, idxOrCount, filters, path);
} else if (const auto item = form->As<RE::TESBoundObject>(); item) {
items.GetForms().emplace_back(items.GetSize(), item, idxOrCount, filters, path);
} else if (const auto outfit = form->As<RE::BGSOutfit>(); outfit) {
outfits.GetForms().emplace_back(outfits.GetSize(), outfit, idxOrCount, filters, path);
} else if (const auto faction = form->As<RE::TESFaction>(); faction) {
factions.GetForms().emplace_back(factions.GetSize(), faction, idxOrCount, filters, path);
} else if (const auto skin = form->As<RE::TESObjectARMO>(); skin) {
skins.GetForms().emplace_back(skins.GetSize(), skin, idxOrCount, filters, path);
} else {
auto type = form->GetFormType();
if (type == RE::FormType::Package || type == RE::FormType::FormList) {
// With generic Form entries we default to RandomCount, so we need to properly convert it to Index if it turned out to be a package.
Index packageIndex = 1;
if (std::holds_alternative<RandomCount>(idxOrCount)) {
auto& count = std::get<RandomCount>(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);
}
packageIndex = count.min;
} else {
packageIndex = std::get<Index>(idxOrCount);
}
packages.GetForms().emplace_back(packages.GetSize(), form, packageIndex, filters, path);
}
}
});
}

Dependencies::ResolveKeywords();

bool valid = false;

ForEachDistributable([&]<typename Form>(Distributables<Form>& a_distributable) {
a_distributable.FinishLookupForms();
if (a_distributable) {
valid = true;
}
Expand All @@ -33,15 +76,15 @@ void LogDistributablesLookup()

logger::info("{:*^50}", "PROCESSING");

ForEachDistributable([]<typename Form>(const Distributables<Form>& a_distributable) {
ForEachDistributable([]<typename Form>(Distributables<Form>& a_distributable) {
const auto& recordName = RECORD::GetTypeName(a_distributable.GetType());

const auto all = INI::configs[recordName].size();
const auto all = INI::configs[a_distributable.GetType()].size();
const auto added = a_distributable.GetSize();

// Only log entries that are actually present in INIs.
if (all > 0) {
logger::info("Adding {}/{} {}s", added, all, recordName);
logger::info("Registered {}/{} {}s:", added, all, recordName);
}
});

Expand Down

0 comments on commit 78dbffb

Please sign in to comment.