diff --git a/src/backend/game.cpp b/src/backend/game.cpp index 67ff7653..6e123c35 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -29,7 +29,6 @@ #include "metadata.h" #include "parsers.h" #include "streams.h" -#include "generators.h" #include #include @@ -83,19 +82,6 @@ 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 44098b7e..37065812 100644 --- a/src/backend/game.h +++ b/src/backend/game.h @@ -56,10 +56,11 @@ 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,8 +120,7 @@ namespace loot { void RedatePlugins(); //Change timestamps to match load order (Skyrim only). void LoadPlugins(bool headersOnly); //Loads all installed plugins. - 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); + void SortPlugins(const unsigned int language, std::list& messages, std::function callback); //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 fd5bb6c8..08627e48 100644 --- a/src/backend/graph.cpp +++ b/src/backend/graph.cpp @@ -90,7 +90,7 @@ namespace loot { return false; } - std::list Sort(const PluginGraph& graph) { + void Sort(const PluginGraph& graph, std::list& plugins) { //Topological sort requires an index map, which std::list-based VertexList graphs don't have, so one needs to be built separately. @@ -108,12 +108,11 @@ 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: "; - list plugins; + plugins.clear(); 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 366b7d2a..2133c1db 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); - std::list Sort(const PluginGraph& graph); + void Sort(const PluginGraph& graph, std::list& plugins); void CheckForCycles(const PluginGraph& graph); diff --git a/src/backend/sort.cpp b/src/backend/sort.cpp index 178528e7..2ad231af 100644 --- a/src/backend/sort.cpp +++ b/src/backend/sort.cpp @@ -24,7 +24,6 @@ along with LOOT. If not, see #include "game.h" #include "helpers.h" -#include "graph.h" #include #include @@ -40,7 +39,7 @@ namespace fs = boost::filesystem; namespace loot { - void Game::SortPrep(const unsigned int language, std::list& messages, std::function progressCallback) { + void Game::SortPlugins(const unsigned int language, std::list& messages, std::function callback) { boost::thread_group group; BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(language).Name(); @@ -49,7 +48,7 @@ namespace loot { // Load Plugins & Lists /////////////////////////////////////////////////////// - progressCallback("Reading installed plugins..."); + callback("Reading installed plugins..."); group.create_thread([this, language, &messages]() { try { @@ -81,7 +80,7 @@ namespace loot { // Evaluate Global Messages /////////////////////////////////////////////////////// - progressCallback("Evaluating global messages..."); + callback("Evaluating global messages..."); //Merge all global message lists. BOOST_LOG_TRIVIAL(debug) << "Merging all global message lists."; @@ -105,99 +104,5 @@ 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 b2df1a9b..3e5d0c61 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 @@ -626,10 +626,6 @@ 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(); @@ -642,7 +638,9 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { // Load Plugins & Lists /////////////////////////////////////////////////////// - _games[_currentGame].SortPrep(lang, messages, progressCallback); + _games[_currentGame].SortPlugins(lang, messages, [progDia](const std::string& message) { + progDia->Pulse(FromUTF8(message)); + }); /////////////////////////////////////////////////////// // Build Graph Edges & Sort @@ -658,14 +656,96 @@ 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 { - - plugins = _games[_currentGame].Sort(lang, messages, progressCallback); + //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); progDia->Destroy(); progDia = nullptr; @@ -732,7 +812,16 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { _games[_currentGame].userlist.plugins = newUserlist; //Save edits to userlist. - _games[_currentGame].userlist.Save(_games[_currentGame].UserlistPath()); + 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(); //Now loop. }