From ef2a34aa23341bfe5256df430dcd78731d687622 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sun, 1 Jan 2023 23:32:11 +0000 Subject: [PATCH] Minor optimisations to adding hardcoded edges It only speeds up sorting by about 3% with my 1619 plugin load order, but I think the logic is simpler despite all the iterators involved. --- docs/api/sorting.rst | 7 +++ src/api/sorting/plugin_graph.cpp | 85 +++++++++++++++++++------------- src/api/sorting/plugin_graph.h | 3 +- src/api/sorting/plugin_sort.cpp | 43 ++++++++++++---- 4 files changed, 95 insertions(+), 43 deletions(-) diff --git a/docs/api/sorting.rst b/docs/api/sorting.rst index ae8d9f53..b25c8823 100644 --- a/docs/api/sorting.rst +++ b/docs/api/sorting.rst @@ -76,6 +76,13 @@ For each plugin: 4. Add edges coming from all the plugin's load after files that are installed plugins. +Hardcoded edges +--------------- + +Some games hardcode certain plugins to load in certain positions, and this +section adds edges in the correct order between those plugins, and between those +plugins and the rest of the plugins in the graph. + Group edges ----------- diff --git a/src/api/sorting/plugin_graph.cpp b/src/api/sorting/plugin_graph.cpp index 794147c5..ca4aef12 100644 --- a/src/api/sorting/plugin_graph.cpp +++ b/src/api/sorting/plugin_graph.cpp @@ -636,9 +636,8 @@ void PluginGraph::AddSpecificEdges() { } } -void PluginGraph::AddHardcodedPluginEdges(const Game& game) { - using std::filesystem::u8path; - +void PluginGraph::AddHardcodedPluginEdges( + const std::vector& hardcodedPlugins) { const auto logger = getLogger(); if (logger) { logger->trace( @@ -646,45 +645,65 @@ void PluginGraph::AddHardcodedPluginEdges(const Game& game) { "positions..."); } - const auto implicitlyActivePlugins = - game.GetLoadOrderHandler().GetImplicitlyActivePlugins(); + if (hardcodedPlugins.empty()) { + return; + } - std::set processedPluginPaths; - for (const auto& plugin : implicitlyActivePlugins) { - processedPluginPaths.insert(NormalizeFilename(plugin)); + std::map::const_iterator, vertex_t> + implicitlyActivePluginVertices; + std::vector otherPluginVertices; - if (game.Type() == GameType::tes5 && - loot::equivalent(game.DataPath() / u8path(plugin), - game.DataPath() / "update.esm")) { - if (logger) { - logger->debug( - "Skipping adding hardcoded plugin edges for Update.esm as it does " - "not have a hardcoded position for Skyrim."); - continue; + // Build the vertex map for implicitly active plugins and record the vertices + // for other plugins. + for (const auto& vertex : boost::make_iterator_range(GetVertices())) { + const auto pluginName = GetPlugin(vertex).GetName(); + const auto it = + std::find_if(hardcodedPlugins.begin(), + hardcodedPlugins.end(), + [&](const std::string& name) { + return CompareFilenames(name, pluginName) == 0; + }); + + if (it != hardcodedPlugins.end()) { + implicitlyActivePluginVertices.emplace(it, vertex); + } else { + otherPluginVertices.push_back(vertex); + } + } + + if (implicitlyActivePluginVertices.empty()) { + return; + } + + // Now add edges between consecutive implicitly active plugins. + auto lastImplicitlyActiveVertexIt = implicitlyActivePluginVertices.end(); + for (auto it = hardcodedPlugins.begin(); it != hardcodedPlugins.end();) { + const auto fromVertexIt = implicitlyActivePluginVertices.find(it); + + // Find the next valid implicitly active plugin and its vertex. + auto toVertexIt = implicitlyActivePluginVertices.end(); + for (it = std::next(it); it != hardcodedPlugins.end(); ++it) { + toVertexIt = implicitlyActivePluginVertices.find(it); + if (toVertexIt != implicitlyActivePluginVertices.end()) { + break; } } - const auto pluginVertex = GetVertexByName(plugin); + if (fromVertexIt != implicitlyActivePluginVertices.end()) { + lastImplicitlyActiveVertexIt = fromVertexIt; - if (!pluginVertex.has_value()) { - if (logger) { - logger->debug( - "Skipping adding hardcoded plugin edges for \"{}\" as it has not " - "been loaded.", - plugin); + if (toVertexIt != implicitlyActivePluginVertices.end()) { + AddEdge(fromVertexIt->second, toVertexIt->second, EdgeType::hardcoded); } - continue; } + } - for (const auto& vertex : boost::make_iterator_range(GetVertices())) { - if (vertex == pluginVertex.value()) { - continue; - } - - if (processedPluginPaths.count( - NormalizeFilename(GetPlugin(vertex).GetName())) == 0) { - AddEdge(pluginVertex.value(), vertex, EdgeType::hardcoded); - } + // Finally, add edges from the last implicitly active plugin to the other + // plugins. + if (lastImplicitlyActiveVertexIt != implicitlyActivePluginVertices.end()) { + for (const auto& vertex : otherPluginVertices) { + AddEdge( + lastImplicitlyActiveVertexIt->second, vertex, EdgeType::hardcoded); } } } diff --git a/src/api/sorting/plugin_graph.h b/src/api/sorting/plugin_graph.h index 7fd3da27..7bf8ea39 100644 --- a/src/api/sorting/plugin_graph.h +++ b/src/api/sorting/plugin_graph.h @@ -91,7 +91,8 @@ public: void AddVertex(const PluginSortingData& plugin); void AddSpecificEdges(); - void AddHardcodedPluginEdges(const Game& game); + void AddHardcodedPluginEdges( + const std::vector& hardcodedPlugins); void AddGroupEdges(const std::unordered_map& groups); void AddOverlapEdges(); void AddTieBreakEdges(); diff --git a/src/api/sorting/plugin_sort.cpp b/src/api/sorting/plugin_sort.cpp index 5d1c7262..3d73189c 100644 --- a/src/api/sorting/plugin_sort.cpp +++ b/src/api/sorting/plugin_sort.cpp @@ -24,6 +24,8 @@ #include "plugin_sort.h" +#include + #include "api/helpers/logging.h" #include "api/sorting/group_sort.h" #include "api/sorting/plugin_graph.h" @@ -192,16 +194,30 @@ std::vector GetPluginsSortingData( return pluginsSortingData; } -std::vector SortPluginGraph(PluginGraph& graph, const Game& game) { +std::vector GetPluginsWithHardcodedPositions(const Game& game) { + auto plugins = game.GetLoadOrderHandler().GetImplicitlyActivePlugins(); + + if (game.Type() == GameType::tes5) { + auto newEndIt = std::remove_if( + plugins.begin(), plugins.end(), [](const std::string& plugin) { + return boost::iequals(plugin, "update.esm"); + }); + + plugins.erase(newEndIt, plugins.end()); + } + + return plugins; +} + +std::vector SortPluginGraph( + PluginGraph& graph, + const std::vector& hardcodedPlugins, + const std::unordered_map& groupsMap) { // Now add the interactions between plugins to the graph as edges. graph.AddSpecificEdges(); - graph.AddHardcodedPluginEdges(game); + graph.AddHardcodedPluginEdges(hardcodedPlugins); - std::unordered_map groups; - for (const auto& group : game.GetDatabase().GetGroups()) { - groups.emplace(group.GetName(), group); - } - graph.AddGroupEdges(groups); + graph.AddGroupEdges(groupsMap); // Check for cycles now because from this point on edges are only added if // they don't cause cycles, and adding tie-break edges is by far the slowest @@ -275,8 +291,17 @@ std::vector SortPlugins( nonMastersGraph.AddVertex(*it); } - auto newLoadOrder = SortPluginGraph(mastersGraph, game); - const auto newNonMastersLoadOrder = SortPluginGraph(nonMastersGraph, game); + const auto hardcodedPlugins = GetPluginsWithHardcodedPositions(game); + + std::unordered_map groupsMap; + for (const auto& group : game.GetDatabase().GetGroups()) { + groupsMap.emplace(group.GetName(), group); + } + + auto newLoadOrder = + SortPluginGraph(mastersGraph, hardcodedPlugins, groupsMap); + const auto newNonMastersLoadOrder = + SortPluginGraph(nonMastersGraph, hardcodedPlugins, groupsMap); newLoadOrder.insert(newLoadOrder.end(), newNonMastersLoadOrder.begin(),