Some code refactoring.

This commit is contained in:
WrinklyNinja
2014-08-19 12:16:07 +01:00
parent fed515f038
commit 1d29628bde
6 changed files with 132 additions and 211 deletions
-1
View File
@@ -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}
+52
View File
@@ -30,9 +30,12 @@
#include "parsers.h"
#include "streams.h"
#include "generators.h"
#include "graph.h"
#include <boost/algorithm/string.hpp>
#include <boost/thread.hpp>
#include <boost/locale.hpp>
#include <boost/log/trivial.hpp>
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<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("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<loot::Plugin>::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<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.
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);
}
}
+79 -67
View File
@@ -22,6 +22,7 @@
<http://www.gnu.org/licenses/>.
*/
#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<std::string> 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<string>::iterator it = find(trail.begin(), trail.end(), g[vTarget].Name());
string backCycle;
for (list<string>::iterator endIt = trail.end(); it != endIt; ++it) {
backCycle += *it + ", ";
}
backCycle.erase(backCycle.length() - 2);
trail.push_back(g[vSource].Name());
list<string>::iterator it = find(trail.begin(), trail.end(), g[vTarget].Name());
string backCycle;
for (list<string>::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<Plugin> 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<vertex_t, size_t> index_map;
boost::associative_property_map< map<vertex_t, size_t> > 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<vertex_t> 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<Plugin> 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<vertex_t, size_t> 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<std::string, int>& 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<vertex_t, size_t> index_map;
boost::associative_property_map< map<vertex_t, size_t> > v_index_map(index_map);
size_t i = 0;
BGL_FORALL_VERTICES(v, graph, PluginGraph)
put(v_index_map, v, i++);
map<vertex_t, vertex_t> predecessor_map;
boost::associative_property_map< map<vertex_t, vertex_t> > 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<string, int>(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<Plugin> 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<vertex_t, size_t> index_map;
boost::associative_property_map< map<vertex_t, size_t> > v_index_map(index_map);
size_t i=0;
BGL_FORALL_VERTICES(v, graph, PluginGraph)
put(v_index_map, v, i++);
map<vertex_t, vertex_t> predecessor_map;
boost::associative_property_map< map<vertex_t, vertex_t> > v_predecessor_map(predecessor_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));
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<Plugin> plugins;
for (const auto &vertex: sortedVertices) {
BOOST_LOG_TRIVIAL(info) << '\t' << graph[vertex].Name();
plugins.push_back(graph[vertex]);
}
return plugins;
}
}
+1 -25
View File
@@ -26,9 +26,6 @@
#define __LOOT_GRAPH__
#include "metadata.h"
#include "error.h"
#include <unordered_map>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
@@ -43,28 +40,7 @@ namespace loot {
typedef boost::graph_traits<PluginGraph>::edge_descriptor edge_t;
typedef boost::graph_traits<PluginGraph>::edge_iterator edge_it;
struct cycle_detector : public boost::dfs_visitor<> {
cycle_detector();
std::list<std::string> 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<Plugin> Sort(const PluginGraph& graph);
void CheckForCycles(const PluginGraph& graph);
void AddSpecificEdges(PluginGraph& graph, std::map<std::string, int>& overriddenPriorities);
void AddPriorityEdges(PluginGraph& graph);
void AddOverlapEdges(PluginGraph& graph);
bool EdgeCreatesCycle(PluginGraph& graph, vertex_t u, vertex_t v);
std::list<Plugin> Sort(PluginGraph& graph);
}
#endif
-116
View File
@@ -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
<http://www.gnu.org/licenses/>.
*/
#include "game.h"
#include "helpers.h"
#include "graph.h"
#include <boost/log/trivial.hpp>
#include <boost/thread.hpp>
#include <boost/format.hpp>
#include <boost/locale.hpp>
using namespace std;
using boost::format;
namespace loc = boost::locale;
namespace fs = boost::filesystem;
namespace loot {
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("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<loot::Plugin>::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<Message> 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<std::string, int> 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);
}
}
-2
View File
@@ -188,8 +188,6 @@ namespace loot {
//Sort plugins into their load order.
list<Plugin> plugins = g_app_state.CurrentGame().Sort(language, [](const string& message){});
map<string, uint32_t> crcs;
list<string> loadOrder;
YAML::Node node;
for (const auto &plugin : plugins) {
node["loadOrder"].push_back(plugin.Name());