Refactored more sorting code.

This commit is contained in:
WrinklyNinja
2014-07-14 17:03:27 +01:00
parent dc8f616bcb
commit ba1bb8b877
6 changed files with 128 additions and 107 deletions
+14
View File
@@ -29,6 +29,7 @@
#include "metadata.h"
#include "parsers.h"
#include "streams.h"
#include "generators.h"
#include <boost/algorithm/string.hpp>
#include <boost/thread.hpp>
@@ -82,6 +83,19 @@ namespace loot {
BOOST_LOG_TRIVIAL(debug) << "File loaded successfully.";
}
void MetadataList::Save(boost::filesystem::path& filepath) {
YAML::Emitter yout;
yout.SetIndent(2);
yout << YAML::BeginMap
<< YAML::Key << "plugins" << YAML::Value << plugins
<< YAML::Key << "globals" << YAML::Value << messages
<< YAML::EndMap;
loot::ofstream uout(filepath);
uout << yout.c_str();
uout.close();
}
// Masterlist member functions
//----------------------------
+3 -3
View File
@@ -56,11 +56,10 @@ namespace loot {
class MetadataList {
public:
void Load(boost::filesystem::path& filepath);
void Save(boost::filesystem::path& filepath);
std::list<Plugin> plugins;
std::list<Message> messages;
std::unordered_map<std::string, bool> conditionCache; //Holds lowercased strings.
std::unordered_map<std::string, uint32_t> crcCache; //Holds lowercased strings.
};
class Masterlist : public MetadataList {
@@ -119,7 +118,8 @@ namespace loot {
void RedatePlugins(); //Change timestamps to match load order (Skyrim only).
void LoadPlugins(bool headersOnly); //Loads all installed plugins.
void SortPlugins(const unsigned int language, std::list<Message>& messages, std::function<void(const std::string&)> callback);
void SortPrep(const unsigned int language, std::list<Message>& messages, std::function<void(const std::string&)> progressCallback);
std::list<Plugin> Sort(const unsigned int language, std::list<Message>& messages, 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.
+3 -2
View File
@@ -90,7 +90,7 @@ namespace loot {
return false;
}
void Sort(const PluginGraph& graph, std::list<Plugin>& plugins) {
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.
@@ -108,11 +108,12 @@ namespace loot {
/* 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: ";
plugins.clear();
list<Plugin> plugins;
for (const auto &vertex: sortedVertices) {
BOOST_LOG_TRIVIAL(info) << '\t' << graph[vertex].Name();
plugins.push_back(graph[vertex]);
}
return plugins;
//Now sort exist plugins list according to order in tempPlugins.
+1 -1
View File
@@ -54,7 +54,7 @@ namespace loot {
bool GetVertexByName(const PluginGraph& graph, const std::string& name, vertex_t& vertex);
void Sort(const PluginGraph& graph, std::list<Plugin>& plugins);
std::list<Plugin> Sort(const PluginGraph& graph);
void CheckForCycles(const PluginGraph& graph);
+98 -3
View File
@@ -24,6 +24,7 @@ along with LOOT. If not, see
#include "game.h"
#include "helpers.h"
#include "graph.h"
#include <boost/log/trivial.hpp>
#include <boost/thread.hpp>
@@ -39,7 +40,7 @@ namespace fs = boost::filesystem;
namespace loot {
void Game::SortPlugins(const unsigned int language, std::list<Message>& messages, std::function<void(const std::string&)> callback) {
void Game::SortPrep(const unsigned int language, std::list<Message>& messages, std::function<void(const std::string&)> progressCallback) {
boost::thread_group group;
BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(language).Name();
@@ -48,7 +49,7 @@ namespace loot {
// Load Plugins & Lists
///////////////////////////////////////////////////////
callback("Reading installed plugins...");
progressCallback("Reading installed plugins...");
group.create_thread([this, language, &messages]() {
try {
@@ -80,7 +81,7 @@ namespace loot {
// Evaluate Global Messages
///////////////////////////////////////////////////////
callback("Evaluating global messages...");
progressCallback("Evaluating global messages...");
//Merge all global message lists.
BOOST_LOG_TRIVIAL(debug) << "Merging all global message lists.";
@@ -104,5 +105,99 @@ namespace loot {
BOOST_LOG_TRIVIAL(error) << "A global message contains a condition that could not be evaluated. Details: " << e.what();
messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("A global message contains a condition that could not be evaluated. Details: %1%")) % e.what()).str()));
}
////////////////////////////////////////////////////////
// Slim down masterlist
////////////////////////////////////////////////////////
//
// Userlist data gets replaced every time sorting is looped, so there's no point evaluating it
// outside the loop, but the masterlist can be slimmed down now.
progressCallback("Filtering masterlist...");
std::list<Plugin> tempMasterlistPlugins;
for (const auto &plugin : this->plugins) {
list<loot::Plugin>::iterator pos = std::find(this->masterlist.plugins.begin(), this->masterlist.plugins.end(), plugin.second);
if (pos != this->masterlist.plugins.end()) {
// The plugin exists in the masterlist, store a copy of its metadata.
tempMasterlistPlugins.push_back(*pos);
}
}
// Now replace the current full masterlist plugin metadata list with the install-specific one.
this->masterlist.plugins = tempMasterlistPlugins;
}
std::list<Plugin> Game::Sort(const unsigned int language, std::list<Message>& messages, 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();
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()));
}
//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);
}
}
+9 -98
View File
@@ -34,7 +34,7 @@
#include "../backend/helpers.h"
#include "../backend/generators.h"
#include "../backend/streams.h"
#include "../backend/graph.h"
//#include "../backend/graph.h"
#include <ostream>
#include <algorithm>
@@ -609,6 +609,10 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
wxProgressDialog *progDia = new wxProgressDialog(translate("LOOT: Working..."), translate("LOOT working..."), 1000, this, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME);
function<void(const std::string&)> progressCallback([progDia](const std::string& message) {
progDia->Pulse(FromUTF8(message));
});
//Set language.
if (_settings["Language"])
lang = Language(_settings["Language"].as<string>()).Code();
@@ -621,9 +625,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
// Load Plugins & Lists
///////////////////////////////////////////////////////
_games[_currentGame].SortPlugins(lang, messages, [progDia](const std::string& message) {
progDia->Pulse(FromUTF8(message));
});
_games[_currentGame].SortPrep(lang, messages, progressCallback);
///////////////////////////////////////////////////////
// Build Graph Edges & Sort
@@ -639,96 +641,14 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
*/
progDia->Update(800, translate("Building plugin graph..."));
//Check for back-edges, then perform a topological sort.
list<loot::Plugin> plugins;
for (auto &plugin : _games[_currentGame].plugins) {
plugins.push_back(plugin.second);
// Merge the masterlist data down into the plugins now, as userlist changes won't affect
// it.
BOOST_LOG_TRIVIAL(trace) << "Merging for plugin \"" << plugins.back().Name() << "\"";
//Check if there is a plugin entry in the masterlist. This will also find matching regex entries.
list<loot::Plugin>::iterator pos = std::find(_games[_currentGame].masterlist.plugins.begin(), _games[_currentGame].masterlist.plugins.end(), plugins.back());
if (pos != _games[_currentGame].masterlist.plugins.end()) {
BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data.";
plugins.back().MergeMetadata(*pos);
}
}
try {
bool applyLoadOrder = false;
do {
//Create a plugin graph containing the plugin and masterlist data.
loot::PluginGraph graph;
for (auto &plugin : plugins) {
vertex_t v = boost::add_vertex(plugin, graph);
}
BOOST_LOG_TRIVIAL(info) << "Merging userlist into plugin list/masterlist, evaluating conditions and checking for install validity.";
loot::vertex_it vit, vitend;
for (boost::tie(vit, vitend) = boost::vertices(graph); vit != vitend; ++vit) {
BOOST_LOG_TRIVIAL(trace) << "Merging for plugin \"" << graph[*vit].Name() << "\"";
//Check if there is a plugin entry in the userlist. This will also find matching regex entries.
list<loot::Plugin>::iterator pos = std::find(_games[_currentGame].userlist.plugins.begin(), _games[_currentGame].userlist.plugins.end(), graph[*vit]);
if (pos != _games[_currentGame].userlist.plugins.end() && pos->Enabled()) {
BOOST_LOG_TRIVIAL(trace) << "Merging userlist data down to plugin list data.";
graph[*vit].MergeMetadata(*pos);
}
progDia->Pulse();
//Now that items are merged, evaluate any conditions they have.
BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data.";
try {
graph[*vit].EvalAllConditions(_games[_currentGame], lang);
}
catch (std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "\"" << graph[*vit].Name() << "\" contains a condition that could not be evaluated. Details: " << e.what();
messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("\"%1%\" contains a condition that could not be evaluated. Details: %2%")) % graph[*vit].Name() % e.what()).str()));
}
progDia->Pulse();
//Also check install validity.
BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to this plugin's data.";
graph[*vit].CheckInstallValidity(_games[_currentGame]);
progDia->Pulse();
}
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);
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);
}
}
progDia->Pulse();
BOOST_LOG_TRIVIAL(info) << "Performing a topological sort.";
loot::Sort(graph, plugins);
plugins = _games[_currentGame].Sort(lang, messages, progressCallback);
progDia->Destroy();
progDia = nullptr;
@@ -795,16 +715,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
_games[_currentGame].userlist.plugins = newUserlist;
//Save edits to userlist.
BOOST_LOG_TRIVIAL(info) << "Saving edited userlist.";
YAML::Emitter yout;
yout.SetIndent(2);
yout << YAML::BeginMap
<< YAML::Key << "plugins" << YAML::Value << _games[_currentGame].userlist.plugins
<< YAML::EndMap;
loot::ofstream uout(_games[_currentGame].UserlistPath());
uout << yout.c_str();
uout.close();
_games[_currentGame].userlist.Save(_games[_currentGame].UserlistPath());
//Now loop.
}