From 0004a23861dd70d221ce7578010d6a9d1ddb8253 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 25 Feb 2018 14:24:03 +0000 Subject: [PATCH] Add group evaluation to plugin sorting --- src/api/sorting/plugin_sorter.cpp | 46 +++++++++- src/api/sorting/plugin_sorting_data.h | 4 +- .../internals/sorting/plugin_sorter_test.h | 87 +++++++++++++++++++ 3 files changed, 134 insertions(+), 3 deletions(-) diff --git a/src/api/sorting/plugin_sorter.cpp b/src/api/sorting/plugin_sorter.cpp index 3d459839..09066f3f 100644 --- a/src/api/sorting/plugin_sorter.cpp +++ b/src/api/sorting/plugin_sorter.cpp @@ -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> 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({ 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 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. diff --git a/src/api/sorting/plugin_sorting_data.h b/src/api/sorting/plugin_sorting_data.h index 85998b9b..81155877 100644 --- a/src/api/sorting/plugin_sorting_data.h +++ b/src/api/sorting/plugin_sorting_data.h @@ -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 \ No newline at end of file +#endif diff --git a/src/tests/api/internals/sorting/plugin_sorter_test.h b/src/tests/api/internals/sorting/plugin_sorter_test.h index 4fc66763..3fe46906 100644 --- a/src/tests/api/internals/sorting/plugin_sorter_test.h +++ b/src/tests/api/internals/sorting/plugin_sorter_test.h @@ -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 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 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);