mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Add group evaluation to plugin sorting
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
#include "api/game/game.h"
|
||||
#include "api/helpers/logging.h"
|
||||
#include "api/metadata/condition_evaluator.h"
|
||||
#include "api/sorting/group_sort.h"
|
||||
#include "loot/exception/cyclic_interaction_error.h"
|
||||
|
||||
using std::list;
|
||||
@@ -216,6 +217,8 @@ void PluginSorter::AddPluginVertices(Game& game) {
|
||||
// Using a set of plugin names followed by finding the matching key
|
||||
// in the unordered map, as it's probably faster than copying the
|
||||
// full plugin objects then sorting them.
|
||||
std::map<std::string, std::vector<std::string>> groupPlugins;
|
||||
|
||||
for (const auto& plugin : game.GetCache()->GetPlugins()) {
|
||||
if (logger_) {
|
||||
logger_->trace("Getting and evaluating metadata for plugin {}",
|
||||
@@ -225,13 +228,52 @@ void PluginSorter::AddPluginVertices(Game& game) {
|
||||
auto metadata =
|
||||
game.GetDatabase()->GetPluginMetadata(plugin->GetName(), true, true);
|
||||
|
||||
auto groupIt = groupPlugins.find(metadata.GetGroup());
|
||||
if (groupIt == groupPlugins.end()) {
|
||||
groupPlugins.emplace(metadata.GetGroup(), std::vector<std::string>({ plugin->GetName() }));
|
||||
}
|
||||
else {
|
||||
groupIt->second.push_back(plugin->GetName());
|
||||
}
|
||||
|
||||
if (logger_) {
|
||||
logger_->trace("Getting and evaluating metadata for plugin \"{}\"",
|
||||
plugin->GetName());
|
||||
}
|
||||
|
||||
vertex_t v = boost::add_vertex(
|
||||
PluginSortingData(*plugin, std::move(metadata)), graph_);
|
||||
boost::add_vertex(PluginSortingData(*plugin, std::move(metadata)), graph_);
|
||||
}
|
||||
|
||||
// Map sets of transitive group dependencies to sets of transitive plugin
|
||||
// dependencies.
|
||||
auto groups = GetTransitiveAfterGroups(game.GetDatabase()->GetGroups());
|
||||
for (auto& group : groups) {
|
||||
std::unordered_set<std::string> transitivePlugins;
|
||||
for (const auto& afterGroup : group.second) {
|
||||
auto pluginsIt = groupPlugins.find(afterGroup);
|
||||
if (pluginsIt != groupPlugins.end()) {
|
||||
transitivePlugins.insert(pluginsIt->second.begin(), pluginsIt->second.end());
|
||||
}
|
||||
}
|
||||
group.second = transitivePlugins;
|
||||
}
|
||||
|
||||
// Add all transitive plugin dependencies for a group to the plugin's load
|
||||
// after metadata.
|
||||
for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph_))) {
|
||||
PluginSortingData& plugin = graph_[vertex];
|
||||
auto groupsIt = groups.find(plugin.GetGroup());
|
||||
if (groupsIt == groups.end()) {
|
||||
throw std::invalid_argument("The group \"" + plugin.GetGroup() +
|
||||
"\" set for plugin \"" + plugin.GetName() + "\" does not exist.");
|
||||
}
|
||||
else {
|
||||
auto loadAfter = plugin.GetLoadAfterFiles();
|
||||
for (const auto& afterPlugin : groupsIt->second) {
|
||||
loadAfter.insert(File(afterPlugin));
|
||||
}
|
||||
plugin.SetLoadAfterFiles(loadAfter);
|
||||
}
|
||||
}
|
||||
|
||||
// Prebuild an index map, which std::list-based VertexList graphs don't have.
|
||||
|
||||
@@ -41,15 +41,17 @@ public:
|
||||
bool DoFormIDsOverlap(const PluginSortingData& plugin) const;
|
||||
|
||||
using PluginMetadata::GetGlobalPriority;
|
||||
using PluginMetadata::GetGroup;
|
||||
using PluginMetadata::GetLoadAfterFiles;
|
||||
using PluginMetadata::GetLocalPriority;
|
||||
using PluginMetadata::GetRequirements;
|
||||
using PluginMetadata::SetGlobalPriority;
|
||||
using PluginMetadata::SetLocalPriority;
|
||||
using PluginMetadata::SetLoadAfterFiles;
|
||||
|
||||
private:
|
||||
const Plugin& plugin_;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -36,6 +36,7 @@ class PluginSorterTest : public CommonGameTestFixture {
|
||||
protected:
|
||||
PluginSorterTest() :
|
||||
game_(GetParam(), dataPath.parent_path(), localPath),
|
||||
masterlistPath_("./userlist.yaml"),
|
||||
blankEslEsp("Blank.esl.esp") {}
|
||||
|
||||
void TearDown() {
|
||||
@@ -44,6 +45,8 @@ protected:
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) {
|
||||
boost::filesystem::remove(dataPath / blankEslEsp);
|
||||
}
|
||||
|
||||
boost::filesystem::remove(masterlistPath_);
|
||||
}
|
||||
|
||||
void loadInstalledPlugins(Game &game_, bool headersOnly) {
|
||||
@@ -73,8 +76,25 @@ protected:
|
||||
game_.LoadPlugins(plugins, headersOnly);
|
||||
}
|
||||
|
||||
void GenerateMasterlist() {
|
||||
using std::endl;
|
||||
|
||||
boost::filesystem::ofstream masterlist(masterlistPath_);
|
||||
masterlist << "groups:" << endl
|
||||
<< " - name: group1" << endl
|
||||
<< " - name: group2" << endl
|
||||
<< " after:" << endl
|
||||
<< " - group1" << endl
|
||||
<< " - name: group3" << endl
|
||||
<< " after:" << endl
|
||||
<< " - group2" << endl;
|
||||
|
||||
masterlist.close();
|
||||
}
|
||||
|
||||
Game game_;
|
||||
const std::string blankEslEsp;
|
||||
const boost::filesystem::path masterlistPath_;
|
||||
};
|
||||
|
||||
// Pass an empty first argument, as it's a prefix for the test instantation,
|
||||
@@ -139,6 +159,73 @@ TEST_P(PluginSorterTest,
|
||||
std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder)));
|
||||
}
|
||||
|
||||
TEST_P(PluginSorterTest, sortingShouldResolveGroupsAsTransitiveLoadAfterSets) {
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
GenerateMasterlist();
|
||||
game_.GetDatabase()->LoadLists(masterlistPath_.string());
|
||||
|
||||
PluginMetadata plugin(blankDifferentEsm);
|
||||
plugin.SetGroup("group1");
|
||||
game_.GetDatabase()->SetPluginUserMetadata(plugin);
|
||||
|
||||
plugin = PluginMetadata(blankEsm);
|
||||
plugin.SetGroup("group3");
|
||||
game_.GetDatabase()->SetPluginUserMetadata(plugin);
|
||||
|
||||
PluginSorter ps;
|
||||
std::vector<std::string> expectedSortedOrder({
|
||||
masterFile,
|
||||
blankDifferentEsm,
|
||||
blankEsm,
|
||||
blankMasterDependentEsm,
|
||||
blankDifferentMasterDependentEsm,
|
||||
blankEsp,
|
||||
blankDifferentEsp,
|
||||
blankMasterDependentEsp,
|
||||
blankDifferentMasterDependentEsp,
|
||||
blankPluginDependentEsp,
|
||||
blankDifferentPluginDependentEsp,
|
||||
});
|
||||
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) {
|
||||
expectedSortedOrder.insert(expectedSortedOrder.begin() + 5, blankEsl);
|
||||
}
|
||||
|
||||
std::vector<std::string> sorted = ps.Sort(game_);
|
||||
EXPECT_EQ(expectedSortedOrder, sorted);
|
||||
}
|
||||
|
||||
TEST_P(PluginSorterTest, sortingShouldThrowIfAPluginHasAGroupThatDoesNotExist) {
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
PluginMetadata plugin(blankDifferentEsm);
|
||||
plugin.SetGroup("group1");
|
||||
game_.GetDatabase()->SetPluginUserMetadata(plugin);
|
||||
|
||||
PluginSorter ps;
|
||||
EXPECT_THROW(ps.Sort(game_), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST_P(PluginSorterTest, sortingShouldThrowIfAGroupIntroducesACycle) {
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
GenerateMasterlist();
|
||||
game_.GetDatabase()->LoadLists(masterlistPath_.string());
|
||||
|
||||
PluginMetadata plugin(blankDifferentEsm);
|
||||
plugin.SetGroup("group1");
|
||||
plugin.SetLoadAfterFiles({ File(blankEsm) });
|
||||
game_.GetDatabase()->SetPluginUserMetadata(plugin);
|
||||
|
||||
plugin = PluginMetadata(blankEsm);
|
||||
plugin.SetGroup("group3");
|
||||
game_.GetDatabase()->SetPluginUserMetadata(plugin);
|
||||
|
||||
PluginSorter ps;
|
||||
EXPECT_THROW(ps.Sort(game_), CyclicInteractionError);
|
||||
}
|
||||
|
||||
TEST_P(PluginSorterTest, sortingShouldEvaluateRelativeGlobalPriorities) {
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
PluginMetadata plugin(blankDifferentMasterDependentEsp);
|
||||
|
||||
Reference in New Issue
Block a user