From ba1bb8b877dab0ba5b6d5c275fa1fa3e4fc743dc Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 14 Jul 2014 17:03:27 +0100 Subject: [PATCH] Refactored more sorting code. --- src/backend/game.cpp | 14 ++++++ src/backend/game.h | 6 +-- src/backend/graph.cpp | 5 +- src/backend/graph.h | 2 +- src/backend/sort.cpp | 101 +++++++++++++++++++++++++++++++++++++-- src/gui/main.cpp | 107 ++++-------------------------------------- 6 files changed, 128 insertions(+), 107 deletions(-) diff --git a/src/backend/game.cpp b/src/backend/game.cpp index 5887ce3b..18ed5ef9 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -29,6 +29,7 @@ #include "metadata.h" #include "parsers.h" #include "streams.h" +#include "generators.h" #include #include @@ -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 //---------------------------- diff --git a/src/backend/game.h b/src/backend/game.h index 99fdaedf..c15f13a6 100644 --- a/src/backend/game.h +++ b/src/backend/game.h @@ -56,11 +56,10 @@ namespace loot { class MetadataList { public: void Load(boost::filesystem::path& filepath); + void Save(boost::filesystem::path& filepath); std::list plugins; std::list messages; - std::unordered_map conditionCache; //Holds lowercased strings. - std::unordered_map 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& messages, std::function callback); + void SortPrep(const unsigned int language, std::list& messages, std::function progressCallback); + std::list Sort(const unsigned int language, std::list& messages, 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/graph.cpp index 08627e48..fd5bb6c8 100644 --- a/src/backend/graph.cpp +++ b/src/backend/graph.cpp @@ -90,7 +90,7 @@ namespace loot { return false; } - void Sort(const PluginGraph& graph, std::list& plugins) { + 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. @@ -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 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. diff --git a/src/backend/graph.h b/src/backend/graph.h index 2133c1db..366b7d2a 100644 --- a/src/backend/graph.h +++ b/src/backend/graph.h @@ -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& plugins); + std::list Sort(const PluginGraph& graph); void CheckForCycles(const PluginGraph& graph); diff --git a/src/backend/sort.cpp b/src/backend/sort.cpp index 2ad231af..178528e7 100644 --- a/src/backend/sort.cpp +++ b/src/backend/sort.cpp @@ -24,6 +24,7 @@ along with LOOT. If not, see #include "game.h" #include "helpers.h" +#include "graph.h" #include #include @@ -39,7 +40,7 @@ namespace fs = boost::filesystem; namespace loot { - void Game::SortPlugins(const unsigned int language, std::list& messages, std::function callback) { + void Game::SortPrep(const unsigned int language, std::list& messages, std::function 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 tempMasterlistPlugins; + for (const auto &plugin : this->plugins) { + list::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 Game::Sort(const unsigned int language, std::list& messages, 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(); + 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 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/main.cpp b/src/gui/main.cpp index d8584ab3..bc595d80 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -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 #include @@ -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 progressCallback([progDia](const std::string& message) { + progDia->Pulse(FromUTF8(message)); + }); + //Set language. if (_settings["Language"]) lang = Language(_settings["Language"].as()).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 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::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::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 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. }