From ddf818ff51f5b60825bce167391bcb3c40578c56 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sat, 31 Dec 2022 19:14:05 +0000 Subject: [PATCH] Refactor adding plugin vertices to graph --- src/api/sorting/plugin_graph.cpp | 163 --------------------------- src/api/sorting/plugin_graph.h | 2 - src/api/sorting/plugin_sort.cpp | 183 +++++++++++++++++++++++++++++-- 3 files changed, 176 insertions(+), 172 deletions(-) diff --git a/src/api/sorting/plugin_graph.cpp b/src/api/sorting/plugin_graph.cpp index 22de01ff..67a89fd6 100644 --- a/src/api/sorting/plugin_graph.cpp +++ b/src/api/sorting/plugin_graph.cpp @@ -195,48 +195,6 @@ std::unordered_set FindGroupsInAllPaths( return groupsInPaths; } -int ComparePlugins(const PluginSortingData& plugin1, - const PluginSortingData& plugin2) { - if (plugin1.GetLoadOrderIndex().has_value() && - !plugin2.GetLoadOrderIndex().has_value()) { - return -1; - } - - if (!plugin1.GetLoadOrderIndex().has_value() && - plugin2.GetLoadOrderIndex().has_value()) { - return 1; - } - - if (plugin1.GetLoadOrderIndex().has_value() && - plugin2.GetLoadOrderIndex().has_value()) { - if (plugin1.GetLoadOrderIndex().value() < - plugin2.GetLoadOrderIndex().value()) { - return -1; - } else { - return 1; - } - } - - // Neither plugin has a load order position. Compare plugin basenames to - // get an ordering. - const auto name1 = plugin1.GetName(); - const auto name2 = plugin2.GetName(); - const auto basename1 = name1.substr(0, name1.length() - 4); - const auto basename2 = name2.substr(0, name2.length() - 4); - - const int result = CompareFilenames(basename1, basename2); - - if (result != 0) { - return result; - } else { - // Could be a .esp and .esm plugin with the same basename, - // compare their extensions. - const auto ext1 = name1.substr(name1.length() - 4); - const auto ext2 = name2.substr(name2.length() - 4); - return CompareFilenames(ext1, ext2); - } -} - std::string describeEdgeType(EdgeType edgeType) { switch (edgeType) { case EdgeType::hardcoded: @@ -613,127 +571,6 @@ void PluginGraph::AddVertex(const PluginSortingData& plugin) { pluginNameVertexMap.emplace(plugin.GetName(), vertex); } -void PluginGraph::AddPluginVertices(const Game& game, - const std::vector& loadOrder) { - auto loadedPlugins = game.GetCache().GetPlugins(); - - // Sort plugins by their names. This is necessary to ensure that - // plugin precedessor group plugins are listed in a consistent - // order, which is important because that is the order in which - // group edges are added and differences could cause different - // sorting results. - std::sort(loadedPlugins.begin(), - loadedPlugins.end(), - [](const auto& lhs, const auto& rhs) { - if (!lhs) { - return false; - } - - if (!rhs) { - return true; - } - - return lhs->GetName() < rhs->GetName(); - }); - - std::vector loadedPluginInterfaces; - std::transform(loadedPlugins.begin(), - loadedPlugins.end(), - std::back_inserter(loadedPluginInterfaces), - [](const Plugin* plugin) { return plugin; }); - - std::vector pluginsSortingData; - pluginsSortingData.reserve(loadedPlugins.size()); - - for (const auto& plugin : loadedPlugins) { - if (!plugin) { - continue; - } - - const auto masterlistMetadata = - game.GetDatabase() - .GetPluginMetadata(plugin->GetName(), false, true) - .value_or(PluginMetadata(plugin->GetName())); - const auto userMetadata = - game.GetDatabase() - .GetPluginUserMetadata(plugin->GetName(), true) - .value_or(PluginMetadata(plugin->GetName())); - - const auto pluginSortingData = PluginSortingData(plugin, - masterlistMetadata, - userMetadata, - loadOrder, - game.Type(), - loadedPluginInterfaces); - - pluginsSortingData.push_back(pluginSortingData); - } - - std::unordered_map> groupPlugins; - for (const auto& plugin : pluginsSortingData) { - const auto groupName = plugin.GetGroup(); - const auto groupIt = groupPlugins.find(groupName); - if (groupIt == groupPlugins.end()) { - groupPlugins.emplace(groupName, - std::vector({plugin.GetName()})); - } else { - groupIt->second.push_back(plugin.GetName()); - } - } - - // Map sets of transitive group dependencies to sets of transitive plugin - // dependencies. - auto groups = GetTransitiveAfterGroups(game.GetDatabase().GetGroups(false), - game.GetDatabase().GetUserGroups()); - for (auto& group : groups) { - std::unordered_set transitivePlugins; - for (const auto& afterGroup : group.second) { - const 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. - const auto logger = getLogger(); - for (auto& plugin : pluginsSortingData) { - if (logger) { - logger->trace( - "Plugin \"{}\" belongs to group \"{}\", setting after group plugins", - plugin.GetName(), - plugin.GetGroup()); - } - - const auto groupsIt = groups.find(plugin.GetGroup()); - if (groupsIt == groups.end()) { - throw UndefinedGroupError(plugin.GetGroup()); - } else { - plugin.SetAfterGroupPlugins(groupsIt->second); - } - } - - // Sort the plugins according to into their existing load order, or - // lexicographical ordering for pairs of plugins without load order positions. - // This ensures a consistent iteration order for vertices given the same input - // data. The vertex iteration order can affect what edges get added and so - // the final sorting result, so consistency is important. - // Load order is used because this simplifies the logic when adding tie-break - // edges. - std::sort(pluginsSortingData.begin(), - pluginsSortingData.end(), - [](const auto& lhs, const auto& rhs) { - return ComparePlugins(lhs, rhs) < 0; - }); - - for (const auto& plugin : pluginsSortingData) { - AddVertex(plugin); - } -} - void PluginGraph::AddSpecificEdges() { const auto logger = getLogger(); if (logger) { diff --git a/src/api/sorting/plugin_graph.h b/src/api/sorting/plugin_graph.h index 2f104e1f..7fd3da27 100644 --- a/src/api/sorting/plugin_graph.h +++ b/src/api/sorting/plugin_graph.h @@ -90,8 +90,6 @@ public: EdgeType edgeType); void AddVertex(const PluginSortingData& plugin); - void AddPluginVertices(const Game& game, - const std::vector& loadOrder); void AddSpecificEdges(); void AddHardcodedPluginEdges(const Game& game); void AddGroupEdges(const std::unordered_map& groups); diff --git a/src/api/sorting/plugin_sort.cpp b/src/api/sorting/plugin_sort.cpp index 60b288be..f1b800c5 100644 --- a/src/api/sorting/plugin_sort.cpp +++ b/src/api/sorting/plugin_sort.cpp @@ -25,22 +25,185 @@ #include "plugin_sort.h" #include "api/helpers/logging.h" +#include "api/sorting/group_sort.h" #include "api/sorting/plugin_graph.h" +#include "loot/exception/undefined_group_error.h" namespace loot { +int ComparePlugins(const PluginSortingData& plugin1, + const PluginSortingData& plugin2) { + if (plugin1.GetLoadOrderIndex().has_value() && + !plugin2.GetLoadOrderIndex().has_value()) { + return -1; + } + + if (!plugin1.GetLoadOrderIndex().has_value() && + plugin2.GetLoadOrderIndex().has_value()) { + return 1; + } + + if (plugin1.GetLoadOrderIndex().has_value() && + plugin2.GetLoadOrderIndex().has_value()) { + if (plugin1.GetLoadOrderIndex().value() < + plugin2.GetLoadOrderIndex().value()) { + return -1; + } else { + return 1; + } + } + + // Neither plugin has a load order position. Compare plugin basenames to + // get an ordering. + const auto name1 = plugin1.GetName(); + const auto name2 = plugin2.GetName(); + const auto basename1 = name1.substr(0, name1.length() - 4); + const auto basename2 = name2.substr(0, name2.length() - 4); + + const int result = CompareFilenames(basename1, basename2); + + if (result != 0) { + return result; + } else { + // Could be a .esp and .esm plugin with the same basename, + // compare their extensions. + const auto ext1 = name1.substr(name1.length() - 4); + const auto ext2 = name2.substr(name2.length() - 4); + return CompareFilenames(ext1, ext2); + } +} + +std::vector GetPluginsSortingData( + const Game& game, + const std::vector& loadOrder) { + auto loadedPlugins = game.GetCache().GetPlugins(); + + // Sort plugins by their names. This is necessary to ensure that + // plugin precedessor group plugins are listed in a consistent + // order, which is important because that is the order in which + // group edges are added and differences could cause different + // sorting results. + std::sort(loadedPlugins.begin(), + loadedPlugins.end(), + [](const auto& lhs, const auto& rhs) { + if (!lhs) { + return false; + } + + if (!rhs) { + return true; + } + + return lhs->GetName() < rhs->GetName(); + }); + + std::vector loadedPluginInterfaces; + std::transform(loadedPlugins.begin(), + loadedPlugins.end(), + std::back_inserter(loadedPluginInterfaces), + [](const Plugin* plugin) { return plugin; }); + + std::vector pluginsSortingData; + pluginsSortingData.reserve(loadedPlugins.size()); + + for (const auto& plugin : loadedPlugins) { + if (!plugin) { + continue; + } + + const auto masterlistMetadata = + game.GetDatabase() + .GetPluginMetadata(plugin->GetName(), false, true) + .value_or(PluginMetadata(plugin->GetName())); + const auto userMetadata = + game.GetDatabase() + .GetPluginUserMetadata(plugin->GetName(), true) + .value_or(PluginMetadata(plugin->GetName())); + + const auto pluginSortingData = PluginSortingData(plugin, + masterlistMetadata, + userMetadata, + loadOrder, + game.Type(), + loadedPluginInterfaces); + + pluginsSortingData.push_back(pluginSortingData); + } + + std::unordered_map> groupPlugins; + for (const auto& plugin : pluginsSortingData) { + const auto groupName = plugin.GetGroup(); + const auto groupIt = groupPlugins.find(groupName); + if (groupIt == groupPlugins.end()) { + groupPlugins.emplace(groupName, + std::vector({plugin.GetName()})); + } else { + groupIt->second.push_back(plugin.GetName()); + } + } + + // Map sets of transitive group dependencies to sets of transitive plugin + // dependencies. + auto groups = GetTransitiveAfterGroups(game.GetDatabase().GetGroups(false), + game.GetDatabase().GetUserGroups()); + for (auto& group : groups) { + std::unordered_set transitivePlugins; + for (const auto& afterGroup : group.second) { + const 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. + const auto logger = getLogger(); + for (auto& plugin : pluginsSortingData) { + if (logger) { + logger->trace( + "Plugin \"{}\" belongs to group \"{}\", setting after group plugins", + plugin.GetName(), + plugin.GetGroup()); + } + + const auto groupsIt = groups.find(plugin.GetGroup()); + if (groupsIt == groups.end()) { + throw UndefinedGroupError(plugin.GetGroup()); + } else { + plugin.SetAfterGroupPlugins(groupsIt->second); + } + } + + // Sort the plugins according to into their existing load order, or + // lexicographical ordering for pairs of plugins without load order positions. + // This ensures a consistent iteration order for vertices given the same input + // data. The vertex iteration order can affect what edges get added and so + // the final sorting result, so consistency is important. + // Load order is used because this simplifies the logic when adding tie-break + // edges. + std::sort(pluginsSortingData.begin(), + pluginsSortingData.end(), + [](const auto& lhs, const auto& rhs) { + return ComparePlugins(lhs, rhs) < 0; + }); + + return pluginsSortingData; +} + std::vector SortPlugins( const Game& game, const std::vector& loadOrder) { - PluginGraph graph; + const auto pluginsSortingData = GetPluginsSortingData(game, loadOrder); - graph.AddPluginVertices(game, loadOrder); - - // If there aren't any vertices, exit early, because sorting assumes + // If there aren't any plugins, exit early, because sorting assumes // there is at least one plugin. - if (graph.CountVertices() == 0) - return std::vector(); + if (pluginsSortingData.empty()) { + return {}; + } - auto logger = getLogger(); + const auto logger = getLogger(); if (logger) { logger->debug("Current load order:"); for (const auto& plugin : loadOrder) { @@ -48,6 +211,12 @@ std::vector SortPlugins( } } + PluginGraph graph; + + for (const auto& plugin : pluginsSortingData) { + graph.AddVertex(plugin); + } + // Now add the interactions between plugins to the graph as edges. graph.AddSpecificEdges(); graph.AddHardcodedPluginEdges(game);