Add group evaluation to plugin sorting

This commit is contained in:
Oliver Hamlet
2018-03-17 18:26:37 +00:00
parent 16a930a2ca
commit 0004a23861
3 changed files with 134 additions and 3 deletions
+44 -2
View File
@@ -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.
+3 -1
View File
@@ -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);