Refactored plugin sorting code.

Into a new PluginSorter class.
This commit is contained in:
Oliver Hamlet
2015-06-14 22:38:04 +01:00
parent c02bade851
commit 9404474858
7 changed files with 159 additions and 131 deletions
+2 -2
View File
@@ -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}
+3 -1
View File
@@ -27,6 +27,7 @@
#include "../backend/globals.h"
#include "../backend/error.h"
#include "../backend/helpers/streams.h"
#include "../backend/plugin_sorter.h"
#include <yaml-cpp/yaml.h>
@@ -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<loot::Plugin> plugins = db->Sort(loot_lang_any, [](const std::string& message) {});
loot::PluginSorter sorter;
std::list<loot::Plugin> plugins = sorter.Sort(*db, loot_lang_any, [](const std::string& message) {});
db->extStringArraySize = plugins.size();
db->extStringArray = new char*[db->extStringArraySize];
-50
View File
@@ -27,7 +27,6 @@
#include "../helpers/helpers.h"
#include "../error.h"
#include "../helpers/streams.h"
#include "../graph.h"
#include <boost/algorithm/string.hpp>
#include <boost/thread.hpp>
@@ -196,55 +195,6 @@ namespace loot {
return false;
}
std::list<Plugin> Game::Sort(const unsigned int language, std::function<void(const std::string&)> 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<Message> 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<string> 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<Game> ToGames(const std::list<GameSettings>& settings) {
return list<Game>(settings.begin(), settings.end());
}
-1
View File
@@ -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<Plugin> Sort(const unsigned int language, std::function<void(const std::string&)> progressCallback);
//Caches for condition results, active plugins and CRCs.
std::unordered_map<std::string, bool> conditionCache; //Holds lowercased strings.
@@ -22,8 +22,9 @@
<http://www.gnu.org/licenses/>.
*/
#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 <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <boost/graph/breadth_first_search.hpp>
#include <boost/graph/iteration_macros.hpp>
#include <boost/graph/topological_sort.hpp>
#include <boost/locale.hpp>
#include <boost/format.hpp>
@@ -41,7 +44,6 @@ namespace loot {
typedef boost::graph_traits<PluginGraph>::vertex_iterator vertex_it;
typedef boost::graph_traits<PluginGraph>::edge_descriptor edge_t;
typedef boost::graph_traits<PluginGraph>::edge_iterator edge_it;
typedef boost::associative_property_map<map<vertex_t, size_t>> 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<Plugin> PluginSorter::Sort(Game& game,
const unsigned int language,
std::function<void(const std::string&)> 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<vertex_t> 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<Plugin> 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<Message> 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<string> 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<File> 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<string>& 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<string>& 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<Plugin> Sort(PluginGraph& graph, const list<string>& loadorder) {
// Prebuild an index map, which std::list-based VertexList graphs don't have.
map<vertex_t, size_t> 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<vertex_t> 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<Plugin> plugins;
for (const auto &vertex : sortedVertices) {
BOOST_LOG_TRIVIAL(info) << '\t' << graph[vertex].Name();
plugins.push_back(graph[vertex]);
}
return plugins;
}
}
@@ -27,16 +27,41 @@
#include "plugin/plugin.h"
#include <map>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/topological_sort.hpp>
#include <boost/graph/graphviz.hpp>
namespace loot {
typedef boost::adjacency_list<boost::listS, boost::listS, boost::directedS, loot::Plugin> PluginGraph;
typedef boost::graph_traits<PluginGraph>::vertex_descriptor vertex_t;
typedef boost::associative_property_map<std::map<vertex_t, size_t>> vertex_map_t;
std::list<Plugin> Sort(PluginGraph& graph, const std::list<std::string>& loadorder);
class Game;
class PluginSorter {
public:
std::list<Plugin> Sort(Game& game,
const unsigned int language,
std::function<void(const std::string&)> progressCallback);
private:
PluginGraph graph;
std::map<vertex_t, size_t> indexMap;
vertex_map_t vertexIndexMap;
std::list<std::string> 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
+3 -1
View File
@@ -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<Plugin> plugins = _lootState.CurrentGame().Sort(language, [this, frame](const string& message) {
PluginSorter sorter;
list<Plugin> plugins = sorter.Sort(_lootState.CurrentGame(), language, [this, frame](const string& message) {
this->SendProgressUpdate(frame, message);
});