diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d2ea8a5..2e6e42bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,7 +97,7 @@ set (LOOT_SRC "${CMAKE_SOURCE_DIR}/src/backend/metadata/conditional_metadata.c "${CMAKE_SOURCE_DIR}/src/backend/helpers/helpers.cpp" "${CMAKE_SOURCE_DIR}/src/backend/helpers/language.cpp" "${CMAKE_SOURCE_DIR}/src/backend/helpers/version.cpp" - "${CMAKE_SOURCE_DIR}/src/backend/graph.cpp" + "${CMAKE_SOURCE_DIR}/src/backend/plugin_sorter.cpp" "${CMAKE_BINARY_DIR}/generated/globals.cpp") set (LOOT_HEADERS "${CMAKE_SOURCE_DIR}/src/backend/metadata/condition_grammar.h" @@ -124,7 +124,7 @@ set (LOOT_HEADERS "${CMAKE_SOURCE_DIR}/src/backend/metadata/condition_grammar.h" "${CMAKE_SOURCE_DIR}/src/backend/helpers/version.h" "${CMAKE_SOURCE_DIR}/src/backend/helpers/yaml_set_helpers.h" "${CMAKE_SOURCE_DIR}/src/backend/globals.h" - "${CMAKE_SOURCE_DIR}/src/backend/graph.h" + "${CMAKE_SOURCE_DIR}/src/backend/plugin_sorter.h" "${CMAKE_SOURCE_DIR}/src/backend/error.h") set (LOOT_GUI_SRC ${LOOT_SRC} diff --git a/src/api/api.cpp b/src/api/api.cpp index d79e7a08..c20c9523 100644 --- a/src/api/api.cpp +++ b/src/api/api.cpp @@ -27,6 +27,7 @@ #include "../backend/globals.h" #include "../backend/error.h" #include "../backend/helpers/streams.h" +#include "../backend/plugin_sorter.h" #include @@ -436,7 +437,8 @@ LOOT_API unsigned int loot_sort_plugins(loot_db db, db->LoadPlugins(false); //Sort plugins into their load order. - std::list plugins = db->Sort(loot_lang_any, [](const std::string& message) {}); + loot::PluginSorter sorter; + std::list plugins = sorter.Sort(*db, loot_lang_any, [](const std::string& message) {}); db->extStringArraySize = plugins.size(); db->extStringArray = new char*[db->extStringArraySize]; diff --git a/src/backend/game/game.cpp b/src/backend/game/game.cpp index 89743955..69cf284d 100644 --- a/src/backend/game/game.cpp +++ b/src/backend/game/game.cpp @@ -27,7 +27,6 @@ #include "../helpers/helpers.h" #include "../error.h" #include "../helpers/streams.h" -#include "../graph.h" #include #include @@ -196,55 +195,6 @@ namespace loot { return false; } - std::list Game::Sort(const unsigned int language, std::function progressCallback) { - //Create a plugin graph containing the plugin and masterlist data. - loot::PluginGraph graph; - - progressCallback(lc::translate("Building plugin graph...")); - BOOST_LOG_TRIVIAL(info) << "Merging masterlist, userlist into plugin list, evaluating conditions and checking for install validity."; - for (const auto &plugin : this->plugins) { - vertex_t v = boost::add_vertex(plugin.second, graph); - BOOST_LOG_TRIVIAL(trace) << "Merging for plugin \"" << graph[v].Name() << "\""; - - //Check if there is a plugin entry in the masterlist. This will also find matching regex entries. - BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; - graph[v].MergeMetadata(this->masterlist.FindPlugin(graph[v])); - - //Check if there is a plugin entry in the userlist. This will also find matching regex entries. - PluginMetadata ulistPlugin = this->userlist.FindPlugin(graph[v]); - - if (!ulistPlugin.HasNameOnly() && ulistPlugin.Enabled()) { - BOOST_LOG_TRIVIAL(trace) << "Merging userlist data down to plugin list data."; - graph[v].MergeMetadata(ulistPlugin); - } - - //Now that items are merged, evaluate any conditions they have. - BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data."; - try { - graph[v].EvalAllConditions(*this, language); - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "\"" << graph[v].Name() << "\" contains a condition that could not be evaluated. Details: " << e.what(); - list messages(graph[v].Messages()); - messages.push_back(loot::Message(loot::Message::error, (boost::format(lc::translate("\"%1%\" contains a condition that could not be evaluated. Details: %2%")) % graph[v].Name() % e.what()).str())); - graph[v].Messages(messages); - } - - //Also check install validity. - graph[v].CheckInstallValidity(*this); - } - - // Get the existing load order. - list loadorder = GetLoadOrder(); - BOOST_LOG_TRIVIAL(info) << "Fetched existing load order: "; - for (const auto &plugin : loadorder) - BOOST_LOG_TRIVIAL(info) << plugin; - - // Now add edges and sort. - progressCallback(lc::translate("Adding edges to plugin graph and performing topological sort...")); - return loot::Sort(graph, loadorder); - } - std::list ToGames(const std::list& settings) { return list(settings.begin(), settings.end()); } diff --git a/src/backend/game/game.h b/src/backend/game/game.h index 154a6b67..0888ae0e 100644 --- a/src/backend/game/game.h +++ b/src/backend/game/game.h @@ -63,7 +63,6 @@ namespace loot { void LoadPlugins(bool headersOnly); //Loads all installed plugins. bool HasBeenLoaded(); // Checks if the game's plugins have already been loaded. - std::list Sort(const unsigned int language, std::function progressCallback); //Caches for condition results, active plugins and CRCs. std::unordered_map conditionCache; //Holds lowercased strings. diff --git a/src/backend/graph.cpp b/src/backend/plugin_sorter.cpp similarity index 77% rename from src/backend/graph.cpp rename to src/backend/plugin_sorter.cpp index 0f7f42f0..914c56de 100644 --- a/src/backend/graph.cpp +++ b/src/backend/plugin_sorter.cpp @@ -22,8 +22,9 @@ . */ +#include "game/game.h" #include "error.h" -#include "graph.h" +#include "plugin_sorter.h" #include "helpers/streams.h" #include "helpers/helpers.h" @@ -32,6 +33,8 @@ #include #include #include +#include +#include #include #include @@ -41,7 +44,6 @@ namespace loot { typedef boost::graph_traits::vertex_iterator vertex_it; typedef boost::graph_traits::edge_descriptor edge_t; typedef boost::graph_traits::edge_iterator edge_it; - typedef boost::associative_property_map> vertex_map_t; struct cycle_detector : public boost::dfs_visitor < > { cycle_detector() {} @@ -92,7 +94,103 @@ namespace loot { } }; - bool GetVertexByName(const PluginGraph& graph, const std::string& name, vertex_t& vertex) { + std::list PluginSorter::Sort(Game& game, + const unsigned int language, + std::function progressCallback) { + progressCallback(boost::locale::translate("Building plugin graph...")); + BuildPluginGraph(game, language); + + // Get the existing load order. + oldLoadOrder = game.GetLoadOrder(); + BOOST_LOG_TRIVIAL(info) << "Fetched existing load order: "; + for (const auto &plugin : oldLoadOrder) + BOOST_LOG_TRIVIAL(info) << plugin; + + // Now add edges and sort. + progressCallback(boost::locale::translate("Adding edges to plugin graph and performing topological sort...")); + + //Now add the interactions between plugins to the graph as edges. + BOOST_LOG_TRIVIAL(info) << "Adding edges to plugin graph."; + BOOST_LOG_TRIVIAL(debug) << "Adding non-overlap edges."; + AddSpecificEdges(); + + BOOST_LOG_TRIVIAL(debug) << "Adding priority edges."; + AddPriorityEdges(); + + BOOST_LOG_TRIVIAL(debug) << "Adding overlap edges."; + AddOverlapEdges(); + + BOOST_LOG_TRIVIAL(debug) << "Adding tie-break edges."; + AddTieBreakEdges(); + + BOOST_LOG_TRIVIAL(info) << "Checking to see if the graph is cyclic."; + CheckForCycles(); + + //Now we can sort. + BOOST_LOG_TRIVIAL(info) << "Performing a topological sort."; + list sortedVertices; + boost::topological_sort(graph, std::front_inserter(sortedVertices), boost::vertex_index_map(vertexIndexMap)); + + // Check that the sorted path is Hamiltonian (ie. unique). + for (auto it = sortedVertices.begin(); it != sortedVertices.end(); ++it) { + if (next(it) != sortedVertices.end() && !boost::edge(*it, *next(it), graph).second) { + BOOST_LOG_TRIVIAL(error) << "The calculated load order is not unique. No edge exists between" + << graph[*it].Name() << " and " << graph[*next(it)].Name() << "."; + } + } + + // Output a plugin list using the sorted vertices. + BOOST_LOG_TRIVIAL(info) << "Calculated order: "; + list plugins; + for (const auto &vertex : sortedVertices) { + BOOST_LOG_TRIVIAL(info) << '\t' << graph[vertex].Name(); + plugins.push_back(graph[vertex]); + } + return plugins; + } + + void PluginSorter::BuildPluginGraph(Game& game, const unsigned int language) { + BOOST_LOG_TRIVIAL(info) << "Merging masterlist, userlist into plugin list, evaluating conditions and checking for install validity."; + for (const auto &plugin : game.plugins) { + vertex_t v = boost::add_vertex(plugin.second, graph); + BOOST_LOG_TRIVIAL(trace) << "Merging for plugin \"" << graph[v].Name() << "\""; + + //Check if there is a plugin entry in the masterlist. This will also find matching regex entries. + BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; + graph[v].MergeMetadata(game.masterlist.FindPlugin(graph[v])); + + //Check if there is a plugin entry in the userlist. This will also find matching regex entries. + PluginMetadata ulistPlugin = game.userlist.FindPlugin(graph[v]); + + if (!ulistPlugin.HasNameOnly() && ulistPlugin.Enabled()) { + BOOST_LOG_TRIVIAL(trace) << "Merging userlist data down to plugin list data."; + graph[v].MergeMetadata(ulistPlugin); + } + + //Now that items are merged, evaluate any conditions they have. + BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data."; + try { + graph[v].EvalAllConditions(game, language); + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "\"" << graph[v].Name() << "\" contains a condition that could not be evaluated. Details: " << e.what(); + list messages(graph[v].Messages()); + messages.push_back(loot::Message(loot::Message::error, (boost::format(boost::locale::translate("\"%1%\" contains a condition that could not be evaluated. Details: %2%")) % graph[v].Name() % e.what()).str())); + graph[v].Messages(messages); + } + + //Also check install validity. + graph[v].CheckInstallValidity(game); + } + + // Prebuild an index map, which std::list-based VertexList graphs don't have. + vertexIndexMap = vertex_map_t(indexMap); + size_t i = 0; + BGL_FORALL_VERTICES(v, graph, PluginGraph) + put(vertexIndexMap, v, i++); + } + + bool PluginSorter::GetVertexByName(const std::string& name, vertex_t& vertex) const { vertex_it vit, vit_end; boost::tie(vit, vit_end) = boost::vertices(graph); @@ -106,17 +204,17 @@ namespace loot { return false; } - void CheckForCycles(const PluginGraph& graph, const vertex_map_t& v_index_map) { + void PluginSorter::CheckForCycles() const { cycle_detector vis; - boost::depth_first_search(graph, visitor(vis).vertex_index_map(v_index_map)); + boost::depth_first_search(graph, visitor(vis).vertex_index_map(vertexIndexMap)); } - bool EdgeCreatesCycle(const vertex_t& u, const vertex_t& v, const PluginGraph& graph, const vertex_map_t& v_index_map) { + bool PluginSorter::EdgeCreatesCycle(const vertex_t& u, const vertex_t& v) const { //A cycle is created when adding the edge (u,v) if there already exists a path from v to u, so check for that using a breadth-first search. path_detector vis; vis.target = u; try { - boost::breadth_first_search(graph, v, visitor(vis).vertex_index_map(v_index_map)); + boost::breadth_first_search(graph, v, visitor(vis).vertex_index_map(vertexIndexMap)); } catch (error& e) { if (e.code() == error::ok) @@ -125,7 +223,7 @@ namespace loot { return false; } - void AddSpecificEdges(PluginGraph& graph, const vertex_map_t& v_index_map) { + void PluginSorter::AddSpecificEdges() { //Add edges for all relationships that aren't overlaps or priority differences. loot::vertex_it vit, vitend; for (boost::tie(vit, vitend) = boost::vertices(graph); vit != vitend; ++vit) { @@ -165,7 +263,7 @@ namespace loot { BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for masters."; vector strVec(graph[*vit].Masters()); for (const auto &master : strVec) { - if (loot::GetVertexByName(graph, master, parentVertex) && + if (GetVertexByName(master, parentVertex) && !boost::edge(parentVertex, *vit, graph).second) { BOOST_LOG_TRIVIAL(trace) << "Adding edge from \"" << graph[parentVertex].Name() << "\" to \"" << graph[*vit].Name() << "\"."; @@ -180,7 +278,7 @@ namespace loot { BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for requirements."; set fileset(graph[*vit].Reqs()); for (const auto &file : fileset) { - if (loot::GetVertexByName(graph, file.Name(), parentVertex) && + if (GetVertexByName(file.Name(), parentVertex) && !boost::edge(parentVertex, *vit, graph).second) { BOOST_LOG_TRIVIAL(trace) << "Adding edge from \"" << graph[parentVertex].Name() << "\" to \"" << graph[*vit].Name() << "\"."; @@ -196,7 +294,7 @@ namespace loot { BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for 'load after's."; fileset = graph[*vit].LoadAfter(); for (const auto &file : fileset) { - if (loot::GetVertexByName(graph, file.Name(), parentVertex) && + if (GetVertexByName(file.Name(), parentVertex) && !boost::edge(parentVertex, *vit, graph).second) { BOOST_LOG_TRIVIAL(trace) << "Adding edge from \"" << graph[parentVertex].Name() << "\" to \"" << graph[*vit].Name() << "\"."; @@ -218,7 +316,7 @@ namespace loot { } } - void AddPriorityEdges(PluginGraph& graph, const vertex_map_t& v_index_map) { + void PluginSorter::AddPriorityEdges() { loot::vertex_it vit, vitend; for (boost::tie(vit, vitend) = boost::vertices(graph); vit != vitend; ++vit) { @@ -254,7 +352,7 @@ namespace loot { } if (!boost::edge(parentVertex, vertex, graph).second && - !EdgeCreatesCycle(parentVertex, vertex, graph, v_index_map)) { //No edge going the other way, OK to add this edge. + !EdgeCreatesCycle(parentVertex, vertex)) { //No edge going the other way, OK to add this edge. BOOST_LOG_TRIVIAL(trace) << "Adding edge from \"" << graph[parentVertex].Name() << "\" to \"" << graph[vertex].Name() << "\"."; boost::add_edge(parentVertex, vertex, graph); @@ -263,7 +361,7 @@ namespace loot { } } - void AddOverlapEdges(PluginGraph& graph, const vertex_map_t& v_index_map) { + void PluginSorter::AddOverlapEdges() { loot::vertex_it vit, vitend; for (boost::tie(vit, vitend) = boost::vertices(graph); vit != vitend; ++vit) { @@ -297,7 +395,7 @@ namespace loot { } //BOOST_LOG_TRIVIAL(trace) << "Checking edge validity between \"" << graph[*vit].Name() << "\" and \"" << graph[*vit2].Name() << "\"."; - if (!EdgeCreatesCycle(parentVertex, vertex, graph, v_index_map)) { //No edge going the other way, OK to add this edge. + if (!EdgeCreatesCycle(parentVertex, vertex)) { //No edge going the other way, OK to add this edge. BOOST_LOG_TRIVIAL(trace) << "Adding edge from \"" << graph[parentVertex].Name() << "\" to \"" << graph[vertex].Name() << "\"."; boost::add_edge(parentVertex, vertex, graph); @@ -307,16 +405,16 @@ namespace loot { } } - int plugincmp(const list& loadorder, const std::string& plugin1, const std::string& plugin2) { - auto it1 = find(loadorder.begin(), loadorder.end(), plugin1); - auto it2 = find(loadorder.begin(), loadorder.end(), plugin2); + int PluginSorter::plugincmp(const std::string& plugin1, const std::string& plugin2) const { + auto it1 = find(oldLoadOrder.begin(), oldLoadOrder.end(), plugin1); + auto it2 = find(oldLoadOrder.begin(), oldLoadOrder.end(), plugin2); - if (it1 != loadorder.end() && it2 == loadorder.end()) + if (it1 != oldLoadOrder.end() && it2 == oldLoadOrder.end()) return -1; - else if (it1 == loadorder.end() && it2 != loadorder.end()) + else if (it1 == oldLoadOrder.end() && it2 != oldLoadOrder.end()) return 1; - else if (it1 != loadorder.end() && it2 != loadorder.end()) { - if (distance(loadorder.begin(), it1) < distance(loadorder.begin(), it2)) + else if (it1 != oldLoadOrder.end() && it2 != oldLoadOrder.end()) { + if (distance(oldLoadOrder.begin(), it1) < distance(oldLoadOrder.begin(), it2)) return -1; else return 1; @@ -347,7 +445,7 @@ namespace loot { return 0; } - void AddTieBreakEdges(PluginGraph& graph, const list& loadorder, const vertex_map_t& v_index_map) { + void PluginSorter::AddTieBreakEdges() { // In order for the sort to be performed stably, there must be only one possible result. // This can be enforced by adding edges between all vertices that aren't already linked. // Use existing load order to decide the direction of these edges. @@ -362,7 +460,7 @@ namespace loot { continue; vertex_t vertex, parentVertex; - if (plugincmp(loadorder, graph[*vit].Name(), graph[*vit2].Name()) < 0) { + if (plugincmp(graph[*vit].Name(), graph[*vit2].Name()) < 0) { parentVertex = *vit; vertex = *vit2; } @@ -372,7 +470,7 @@ namespace loot { } //BOOST_LOG_TRIVIAL(trace) << "Checking edge validity between \"" << graph[*vit].Name() << "\" and \"" << graph[*vit2].Name() << "\"."; - if (!EdgeCreatesCycle(parentVertex, vertex, graph, v_index_map)) { //No edge going the other way, OK to add this edge. + if (!EdgeCreatesCycle(parentVertex, vertex)) { //No edge going the other way, OK to add this edge. BOOST_LOG_TRIVIAL(trace) << "Adding edge from \"" << graph[parentVertex].Name() << "\" to \"" << graph[vertex].Name() << "\"."; boost::add_edge(parentVertex, vertex, graph); @@ -380,52 +478,4 @@ namespace loot { } } } - - std::list Sort(PluginGraph& graph, const list& loadorder) { - // Prebuild an index map, which std::list-based VertexList graphs don't have. - map index_map; - vertex_map_t v_index_map(index_map); - size_t i = 0; - BGL_FORALL_VERTICES(v, graph, PluginGraph) - put(v_index_map, v, i++); - - //Now add the interactions between plugins to the graph as edges. - BOOST_LOG_TRIVIAL(info) << "Adding edges to plugin graph."; - BOOST_LOG_TRIVIAL(debug) << "Adding non-overlap edges."; - AddSpecificEdges(graph, v_index_map); - - BOOST_LOG_TRIVIAL(debug) << "Adding priority edges."; - AddPriorityEdges(graph, v_index_map); - - BOOST_LOG_TRIVIAL(debug) << "Adding overlap edges."; - AddOverlapEdges(graph, v_index_map); - - BOOST_LOG_TRIVIAL(debug) << "Adding tie-break edges."; - AddTieBreakEdges(graph, loadorder, v_index_map); - - BOOST_LOG_TRIVIAL(info) << "Checking to see if the graph is cyclic."; - CheckForCycles(graph, v_index_map); - - //Now we can sort. - BOOST_LOG_TRIVIAL(info) << "Performing a topological sort."; - list sortedVertices; - boost::topological_sort(graph, std::front_inserter(sortedVertices), boost::vertex_index_map(v_index_map)); - - // Check that the sorted path is Hamiltonian (ie. unique). - for (auto it = sortedVertices.begin(); it != sortedVertices.end(); ++it) { - if (next(it) != sortedVertices.end() && !boost::edge(*it, *next(it), graph).second) { - BOOST_LOG_TRIVIAL(error) << "The calculated load order is not unique. No edge exists between" - << graph[*it].Name() << " and " << graph[*next(it)].Name() << "."; - } - } - - // Output a plugin list using the sorted vertices. - BOOST_LOG_TRIVIAL(info) << "Calculated order: "; - list plugins; - for (const auto &vertex : sortedVertices) { - BOOST_LOG_TRIVIAL(info) << '\t' << graph[vertex].Name(); - plugins.push_back(graph[vertex]); - } - return plugins; - } } diff --git a/src/backend/graph.h b/src/backend/plugin_sorter.h similarity index 54% rename from src/backend/graph.h rename to src/backend/plugin_sorter.h index 20623a68..e8bb6ebb 100644 --- a/src/backend/graph.h +++ b/src/backend/plugin_sorter.h @@ -27,16 +27,41 @@ #include "plugin/plugin.h" +#include + #include #include -#include -#include namespace loot { typedef boost::adjacency_list PluginGraph; typedef boost::graph_traits::vertex_descriptor vertex_t; + typedef boost::associative_property_map> vertex_map_t; - std::list Sort(PluginGraph& graph, const std::list& loadorder); + class Game; + + class PluginSorter { + public: + std::list Sort(Game& game, + const unsigned int language, + std::function progressCallback); + private: + PluginGraph graph; + std::map indexMap; + vertex_map_t vertexIndexMap; + std::list oldLoadOrder; + + bool GetVertexByName(const std::string& name, vertex_t& vertex) const; + void CheckForCycles() const; + bool EdgeCreatesCycle(const vertex_t& u, const vertex_t& v) const; + + int plugincmp(const std::string& plugin1, const std::string& plugin2) const; + + void BuildPluginGraph(Game& game, const unsigned int language); + void AddSpecificEdges(); + void AddPriorityEdges(); + void AddOverlapEdges(); + void AddTieBreakEdges(); + }; } #endif diff --git a/src/gui/handler.cpp b/src/gui/handler.cpp index d5688d7d..96acce73 100644 --- a/src/gui/handler.cpp +++ b/src/gui/handler.cpp @@ -29,6 +29,7 @@ #include "../backend/error.h" #include "../backend/globals.h" +#include "../backend/plugin_sorter.h" #include "../backend/helpers/helpers.h" #include "../backend/helpers/json.h" @@ -951,7 +952,8 @@ namespace loot { _lootState.CurrentGame().LoadPlugins(false); //Sort plugins into their load order. - list plugins = _lootState.CurrentGame().Sort(language, [this, frame](const string& message) { + PluginSorter sorter; + list plugins = sorter.Sort(_lootState.CurrentGame(), language, [this, frame](const string& message) { this->SendProgressUpdate(frame, message); });