diff --git a/CMakeLists.txt b/CMakeLists.txt index 13b6e32b..f9b81bee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,7 +50,6 @@ set (LOOT_SRC "${CMAKE_SOURCE_DIR}/src/backend/metadata.cpp" "${CMAKE_SOURCE_DIR}/src/backend/helpers.cpp" "${CMAKE_SOURCE_DIR}/src/backend/globals.cpp" "${CMAKE_SOURCE_DIR}/src/backend/generators.cpp" - "${CMAKE_SOURCE_DIR}/src/backend/sort.cpp" "${CMAKE_SOURCE_DIR}/src/backend/graph.cpp") set (LOOT_GUI_SRC ${LOOT_SRC} diff --git a/src/backend/game.cpp b/src/backend/game.cpp index df33dcf7..916a193e 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -30,9 +30,12 @@ #include "parsers.h" #include "streams.h" #include "generators.h" +#include "graph.h" #include #include +#include +#include using namespace std; @@ -714,4 +717,53 @@ namespace loot { throw error(error::path_write_fail, lc::translate("Could not create LOOT folder for game. Details:").str() + " " + e.what()); } } + + 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("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); + list::iterator pos; + 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. + pos = std::find(this->masterlist.plugins.begin(), this->masterlist.plugins.end(), graph[v]); + + if (pos != this->masterlist.plugins.end()) { + BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; + graph[v].MergeMetadata(*pos); + } + + //Check if there is a plugin entry in the userlist. This will also find matching regex entries. + pos = std::find(this->userlist.plugins.begin(), this->userlist.plugins.end(), graph[v]); + + if (pos != this->userlist.plugins.end() && pos->Enabled()) { + BOOST_LOG_TRIVIAL(trace) << "Merging userlist data down to plugin list data."; + graph[v].MergeMetadata(*pos); + } + + //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. + BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to this plugin's data."; + graph[v].CheckInstallValidity(*this); + } + + // Now add edges and sort. + progressCallback("Adding edges to plugin graph and performing topological sort..."); + return loot::Sort(graph); + } } diff --git a/src/backend/graph.cpp b/src/backend/graph.cpp index e21230cc..5f493377 100644 --- a/src/backend/graph.cpp +++ b/src/backend/graph.cpp @@ -22,6 +22,7 @@ . */ +#include "error.h" #include "graph.h" #include "streams.h" #include "helpers.h" @@ -36,45 +37,47 @@ using namespace std; -namespace lc = boost::locale; - namespace loot { - cycle_detector::cycle_detector() {} + struct cycle_detector : public boost::dfs_visitor<> { + cycle_detector() {} - void cycle_detector::tree_edge(edge_t e, const PluginGraph& g) { - vertex_t source = boost::source(e, g); + std::list trail; - string name = g[source].Name(); + inline void tree_edge(edge_t e, const PluginGraph& g) { + vertex_t source = boost::source(e, g); - // Check if the plugin already exists in the recorded trail. - auto it = find(trail.begin(), trail.end(), name); + string name = g[source].Name(); - if (it != trail.end()) { - // Erase everything from this position onwards, as it doesn't - // contribute to a forward-cycle. - trail.erase(it, trail.end()); + // Check if the plugin already exists in the recorded trail. + auto it = find(trail.begin(), trail.end(), name); + + if (it != trail.end()) { + // Erase everything from this position onwards, as it doesn't + // contribute to a forward-cycle. + trail.erase(it, trail.end()); + } + + trail.push_back(name); } - trail.push_back(name); - } + inline void back_edge(edge_t e, const PluginGraph& g) { + vertex_t vSource = boost::source(e, g); + vertex_t vTarget = boost::target(e, g); - void cycle_detector::back_edge(edge_t e, const PluginGraph& g) { - vertex_t vSource = boost::source(e, g); - vertex_t vTarget = boost::target(e, g); + trail.push_back(g[vSource].Name()); + list::iterator it = find(trail.begin(), trail.end(), g[vTarget].Name()); + string backCycle; + for (list::iterator endIt = trail.end(); it != endIt; ++it) { + backCycle += *it + ", "; + } + backCycle.erase(backCycle.length() - 2); - trail.push_back(g[vSource].Name()); - list::iterator it = find(trail.begin(), trail.end(), g[vTarget].Name()); - string backCycle; - for (list::iterator endIt = trail.end(); it != endIt; ++it) { - backCycle += *it + ", "; + BOOST_LOG_TRIVIAL(error) << "Cyclic interaction detected between plugins \"" << g[vSource].Name() << "\" and \"" << g[vTarget].Name() << "\". Back cycle: " << backCycle; + + throw loot::error(loot::error::sorting_error, (boost::format(boost::locale::translate("Cyclic interaction detected between plugins \"%1%\" and \"%2%\". Back cycle: %3%")) % g[vSource].Name() % g[vTarget].Name() % backCycle).str()); } - backCycle.erase(backCycle.length() - 2); - - BOOST_LOG_TRIVIAL(error) << "Cyclic interaction detected between plugins \"" << g[vSource].Name() << "\" and \"" << g[vTarget].Name() << "\". Back cycle: " << backCycle; - - throw loot::error(loot::error::sorting_error, (boost::format(lc::translate("Cyclic interaction detected between plugins \"%1%\" and \"%2%\". Back cycle: %3%")) % g[vSource].Name() % g[vTarget].Name() % backCycle).str()); - } + }; bool GetVertexByName(const PluginGraph& graph, const std::string& name, vertex_t& vertex) { vertex_it vit, vit_end; @@ -90,34 +93,7 @@ namespace loot { return false; } - std::list Sort(const PluginGraph& graph) { - - //Topological sort requires an index map, which std::list-based VertexList graphs don't have, so one needs to be built separately. - - map index_map; - boost::associative_property_map< map > v_index_map(index_map); - size_t i=0; - BGL_FORALL_VERTICES(v, graph, PluginGraph) - put(v_index_map, v, i++); - - //Now we can sort. - - std::list sortedVertices; - boost::topological_sort(graph, std::front_inserter(sortedVertices), boost::vertex_index_map(v_index_map)); - - /* Sorting now evaluates conditions inside the graph, so existing plugins list is missing - data present in the graph, so we need to swap the two lists. */ - 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 CheckForCycles(const PluginGraph& graph) { - //Depth-first search requires an index map, which std::list-based VertexList graphs don't have, so one needs to be built separately. map index_map; @@ -130,7 +106,26 @@ namespace loot { boost::depth_first_search(graph, visitor(vis).vertex_index_map(v_index_map)); } - void AddSpecificEdges(PluginGraph& graph, std::map& overriddenPriorities) { + bool EdgeCreatesCycle(PluginGraph& graph, vertex_t u, vertex_t v) { + //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. + + //Breadth-first search requires an index map, which std::list-based VertexList graphs don't have, so one needs to be built separately. + + map index_map; + boost::associative_property_map< map > v_index_map(index_map); + size_t i = 0; + BGL_FORALL_VERTICES(v, graph, PluginGraph) + put(v_index_map, v, i++); + + map predecessor_map; + boost::associative_property_map< map > v_predecessor_map(predecessor_map); + + boost::breadth_first_search(graph, v, visitor(boost::make_bfs_visitor(boost::record_predecessors(v_predecessor_map, boost::on_tree_edge()))).vertex_index_map(v_index_map)); + + return predecessor_map.find(u) != predecessor_map.end(); + } + + void AddSpecificEdges(PluginGraph& graph) { //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) { @@ -224,7 +219,6 @@ namespace loot { //Set the current plugin's priority to parentPlugin. if (parentPriority > 0 && graph[*vit].Priority() < parentPriority) { BOOST_LOG_TRIVIAL(trace) << "Overriding priority for " << graph[*vit].Name() << " from " << graph[*vit].Priority() << " to " << parentPriority; - overriddenPriorities.insert(pair(graph[*vit].Name(), graph[*vit].Priority())); graph[*vit].Priority(parentPriority); } @@ -236,7 +230,7 @@ namespace loot { for (boost::tie(vit, vitend) = boost::vertices(graph); vit != vitend; ++vit) { BOOST_LOG_TRIVIAL(trace) << "Adding priority difference edges to vertex for \"" << graph[*vit].Name() << "\"."; - //Priority differences should only be taken account between plugins that conflict. + //Priority differences should only be taken account between plugins that conflict. //However, an exception is made for plugins that contain only a header record, //as they are for loading BSAs, and in Skyrim that means the resources they load can //be affected by load order. @@ -244,7 +238,7 @@ namespace loot { loot::vertex_it vit2, vitend2; for (boost::tie(vit2, vitend2) = boost::vertices(graph); vit2 != vitend2; ++vit2) { - if (graph[*vit].Priority() == graph[*vit2].Priority() + if (graph[*vit].Priority() == graph[*vit2].Priority() || (abs(graph[*vit].Priority()) < max_priority && abs(graph[*vit2].Priority()) < max_priority && !graph[*vit].FormIDs().empty() && !graph[*vit2].FormIDs().empty() && !graph[*vit].DoFormIDsOverlap(graph[*vit2]) ) @@ -327,22 +321,40 @@ namespace loot { } } - bool EdgeCreatesCycle(PluginGraph& graph, vertex_t u, vertex_t v) { - //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. + std::list Sort(PluginGraph& graph) { - //Breadth-first search requires an index map, which std::list-based VertexList graphs don't have, so one needs to be built separately. + //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); + BOOST_LOG_TRIVIAL(debug) << "Adding priority edges."; + AddPriorityEdges(graph); + + BOOST_LOG_TRIVIAL(debug) << "Adding overlap edges."; + + BOOST_LOG_TRIVIAL(info) << "Checking to see if the graph is cyclic."; + CheckForCycles(graph); + + //Topological sort requires an index map, which std::list-based VertexList graphs don't have, so one needs to be built separately. map index_map; boost::associative_property_map< map > v_index_map(index_map); size_t i=0; BGL_FORALL_VERTICES(v, graph, PluginGraph) put(v_index_map, v, i++); - map predecessor_map; - boost::associative_property_map< map > v_predecessor_map(predecessor_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)); - boost::breadth_first_search(graph, v, visitor(boost::make_bfs_visitor(boost::record_predecessors(v_predecessor_map, boost::on_tree_edge()))).vertex_index_map(v_index_map)); - - return predecessor_map.find(u) != predecessor_map.end(); + // 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/graph.h index 366b7d2a..cbd0d340 100644 --- a/src/backend/graph.h +++ b/src/backend/graph.h @@ -26,9 +26,6 @@ #define __LOOT_GRAPH__ #include "metadata.h" -#include "error.h" - -#include #include #include @@ -43,28 +40,7 @@ namespace loot { typedef boost::graph_traits::edge_descriptor edge_t; typedef boost::graph_traits::edge_iterator edge_it; - struct cycle_detector : public boost::dfs_visitor<> { - cycle_detector(); - - std::list trail; - - void tree_edge(edge_t e, const PluginGraph& g); - void back_edge(edge_t e, const PluginGraph& g); - }; - - bool GetVertexByName(const PluginGraph& graph, const std::string& name, vertex_t& vertex); - - std::list Sort(const PluginGraph& graph); - - void CheckForCycles(const PluginGraph& graph); - - void AddSpecificEdges(PluginGraph& graph, std::map& overriddenPriorities); - - void AddPriorityEdges(PluginGraph& graph); - - void AddOverlapEdges(PluginGraph& graph); - - bool EdgeCreatesCycle(PluginGraph& graph, vertex_t u, vertex_t v); + std::list Sort(PluginGraph& graph); } #endif diff --git a/src/backend/sort.cpp b/src/backend/sort.cpp deleted file mode 100644 index 168db6e1..00000000 --- a/src/backend/sort.cpp +++ /dev/null @@ -1,116 +0,0 @@ -/* LOOT - -A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and -Fallout: New Vegas. - -Copyright (C) 2014 WrinklyNinja - -This file is part of LOOT. - -LOOT is free software: you can redistribute -it and/or modify it under the terms of the GNU General Public License -as published by the Free Software Foundation, either version 3 of -the License, or (at your option) any later version. - -LOOT is distributed in the hope that it will -be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with LOOT. If not, see -. -*/ - -#include "game.h" -#include "helpers.h" -#include "graph.h" - -#include -#include -#include -#include - -using namespace std; - -using boost::format; - -namespace loc = boost::locale; -namespace fs = boost::filesystem; - -namespace loot { - - 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("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); - list::iterator pos; - 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. - pos = std::find(this->masterlist.plugins.begin(), this->masterlist.plugins.end(), graph[v]); - - if (pos != this->masterlist.plugins.end()) { - BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; - graph[v].MergeMetadata(*pos); - } - - //Check if there is a plugin entry in the userlist. This will also find matching regex entries. - pos = std::find(this->userlist.plugins.begin(), this->userlist.plugins.end(), graph[v]); - - if (pos != this->userlist.plugins.end() && pos->Enabled()) { - BOOST_LOG_TRIVIAL(trace) << "Merging userlist data down to plugin list data."; - graph[v].MergeMetadata(*pos); - } - - //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, (format(loc::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. - BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to this plugin's data."; - graph[v].CheckInstallValidity(*this); - } - - BOOST_LOG_TRIVIAL(info) << "Building the plugin dependency graph..."; - - //Now add the interactions between plugins to the graph as edges. - std::map overriddenPriorities; - BOOST_LOG_TRIVIAL(debug) << "Adding non-overlap edges."; - loot::AddSpecificEdges(graph, overriddenPriorities); - - BOOST_LOG_TRIVIAL(debug) << "Adding priority edges."; - loot::AddPriorityEdges(graph); - - BOOST_LOG_TRIVIAL(debug) << "Adding overlap edges."; - loot::AddOverlapEdges(graph); - - progressCallback("Checking for graph cycles..."); - - BOOST_LOG_TRIVIAL(info) << "Checking to see if the graph is cyclic."; - loot::CheckForCycles(graph); - - for (const auto &overriddenPriority : overriddenPriorities) { - vertex_t vertex; - if (loot::GetVertexByName(graph, overriddenPriority.first, vertex)) { - graph[vertex].Priority(overriddenPriority.second); - } - } - - BOOST_LOG_TRIVIAL(info) << "Performing a topological sort."; - progressCallback("Performing topological sort..."); - return loot::Sort(graph); - } -} \ No newline at end of file diff --git a/src/gui/handler.cpp b/src/gui/handler.cpp index a8bb48a0..e4237d81 100644 --- a/src/gui/handler.cpp +++ b/src/gui/handler.cpp @@ -188,8 +188,6 @@ namespace loot { //Sort plugins into their load order. list plugins = g_app_state.CurrentGame().Sort(language, [](const string& message){}); - map crcs; - list loadOrder; YAML::Node node; for (const auto &plugin : plugins) { node["loadOrder"].push_back(plugin.Name());