diff --git a/src/backend/graph.cpp b/src/backend/graph.cpp index d8d80399..96a19650 100644 --- a/src/backend/graph.cpp +++ b/src/backend/graph.cpp @@ -95,12 +95,23 @@ namespace loot { boost::topological_sort(graph, std::front_inserter(sortedVertices), boost::vertex_index_map(v_index_map)); BOOST_LOG_TRIVIAL(info) << "Calculated order: "; - list tempPlugins; + list tempPlugins; for (const auto &vertex: sortedVertices) { BOOST_LOG_TRIVIAL(info) << '\t' << graph[vertex].Name(); - tempPlugins.push_back(graph[vertex]); + tempPlugins.push_back(graph[vertex].Name()); } - plugins.swap(tempPlugins); + + //Now sort exist plugins list according to order in tempPlugins. + plugins.sort([tempPlugins](const Plugin& first, const Plugin& second){ + //Find both plugins, and compare distances from beginning. + auto fIt = find(tempPlugins.begin(), tempPlugins.end(), first); + auto sIt = find(tempPlugins.begin(), tempPlugins.end(), second); + + if (fIt == tempPlugins.end() || sIt == tempPlugins.end()) + return false; + + return distance(tempPlugins.begin(), fIt) < distance(tempPlugins.begin(), sIt); + }); } void CheckForCycles(const PluginGraph& graph) { diff --git a/src/gui/editor.cpp b/src/gui/editor.cpp index 61363b80..758e6214 100644 --- a/src/gui/editor.cpp +++ b/src/gui/editor.cpp @@ -931,7 +931,8 @@ void EditorPanel::OnFilterToggle(wxCommandEvent& event) { list plugins(_basePlugins); for (const auto &plugin : _editedPlugins) { list::iterator pos = std::find(plugins.begin(), plugins.end(), plugin); - pos->MergeMetadata(plugin); + if (pos != plugins.end()) + pos->MergeMetadata(plugin); } //Disable list selection. @@ -1040,7 +1041,7 @@ void EditorPanel::ApplyCurrentEdits() { ApplyEdits(currentPlugin); } -const std::list& EditorPanel::GetEditedPlugins() const { +const std::list& EditorPanel::GetNewUserlist() const { return _editedPlugins; } @@ -1153,7 +1154,9 @@ MiniEditor::MiniEditor(wxWindow *parent, const wxString& title, const std::list< //Initialise content. editorPanel = new EditorPanel(this, basePlugins, editedPlugins, loot::Language::any, game); editorPanel->SetSimpleView(true); + feedbackText = translate("If LOOT has gotten something wrong, please let the team know. See the Contributing To LOOT section of the readme for details."); + descText = new wxStaticText(this, wxID_ANY, feedbackText); //Set up event handling. Bind(wxEVT_BUTTON, &MiniEditor::OnApply, this, wxID_APPLY); @@ -1167,7 +1170,7 @@ MiniEditor::MiniEditor(wxWindow *parent, const wxString& title, const std::list< //Need to add 'Yes' and 'No' buttons. wxSizer * sizer = CreateSeparatedButtonSizer(wxAPPLY | wxCANCEL); - + //Now add buttons to window sizer. if (sizer != nullptr) bigBox->Add(sizer, 0, wxEXPAND | wxLEFT | wxBOTTOM | wxRIGHT, 15); @@ -1191,8 +1194,8 @@ void MiniEditor::OnResize(wxSizeEvent& event) { event.Skip(); } -const std::list& MiniEditor::GetEditedPlugins() const { - return editorPanel->GetEditedPlugins(); +const std::list& MiniEditor::GetNewUserlist() const { + return editorPanel->GetNewUserlist(); } @@ -1240,7 +1243,7 @@ void FullEditor::OnQuit(wxCommandEvent& event) { YAML::Emitter yout; yout.SetIndent(2); yout << YAML::BeginMap - << YAML::Key << "plugins" << YAML::Value << editorPanel->GetEditedPlugins() + << YAML::Key << "plugins" << YAML::Value << editorPanel->GetNewUserlist() << YAML::EndMap; boost::filesystem::path p(_userlistPath); diff --git a/src/gui/editor.h b/src/gui/editor.h index d49eb9a6..75edd682 100644 --- a/src/gui/editor.h +++ b/src/gui/editor.h @@ -68,7 +68,7 @@ public: void SetSimpleView(bool on = true); void ApplyCurrentEdits(); - const std::list& GetEditedPlugins() const; + const std::list& GetNewUserlist() const; void OnPluginSelect(wxListEvent& event); void OnPluginListRightClick(wxListEvent& event); @@ -133,7 +133,7 @@ public: void OnApply(wxCommandEvent& event); void OnResize(wxSizeEvent& event); - const std::list& GetEditedPlugins() const; + const std::list& GetNewUserlist() const; private: EditorPanel * editorPanel; wxStaticText * descText; diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 4b249fd1..7ca1cd3c 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -87,18 +87,17 @@ struct plugin_loader { }; struct plugin_list_loader { - plugin_list_loader(PluginGraph& graph, loot::Game& game) : _graph(graph), _game(game) {} + plugin_list_loader(list& plugins, loot::Game& game) : _plugins(plugins), _game(game) {} void operator () () { - loot::vertex_it vit, vitend; - for (boost::tie(vit, vitend) = boost::vertices(_graph); vit != vitend; ++vit) { - if (skipPlugins.find(_graph[*vit].Name()) == skipPlugins.end()) { - _graph[*vit] = loot::Plugin(_game, _graph[*vit].Name(), false); + for (auto &plugin : _plugins) { + if (skipPlugins.find(plugin.Name()) == skipPlugins.end()) { + plugin = loot::Plugin(_game, plugin.Name(), false); } } } - PluginGraph& _graph; + list& _plugins; loot::Game& _game; set skipPlugins; }; @@ -681,7 +680,6 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { list mlist_plugins, ulist_plugins; list plugins; boost::thread_group group; - loot::PluginGraph graph; string revision, date; wxProgressDialog *progDia = new wxProgressDialog(translate("LOOT: Working..."),translate("LOOT working..."), 1000, this, wxPD_APP_MODAL|wxPD_AUTO_HIDE|wxPD_ELAPSED_TIME); @@ -704,22 +702,21 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { meanFileSize += fileSize; tempMap.emplace(it->path().filename().string(), fileSize); - plugins.push_back(loot::Plugin(it->path().filename().string())); //Just in case there's an error with the graph. } } meanFileSize /= tempMap.size(); //Now load plugins. - plugin_list_loader pll(graph, *_game); + plugin_list_loader pll(plugins, *_game); for (const auto &pluginPair: tempMap) { BOOST_LOG_TRIVIAL(info) << "Found plugin: " << pluginPair.first; - vertex_t v = boost::add_vertex(loot::Plugin(pluginPair.first), graph); + plugins.push_back(loot::Plugin(pluginPair.first)); if (pluginPair.second > meanFileSize) { pll.skipPlugins.insert(pluginPair.first); - plugin_loader pl(graph[v], *_game); + plugin_loader pl(plugins.back(), *_game); group.create_thread(pl); } @@ -753,14 +750,14 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { // Merge & Check Metadata /////////////////////////////////////////////////////// - if (fs::exists(_game->MasterlistPath()) || fs::exists(_game->UserlistPath())) { + //Set language. + unsigned int lang; + if (_settings["Language"]) + lang = Language(_settings["Language"].as()).Code(); + else + lang = loot::Language::any; - //Set language. - unsigned int lang; - if (_settings["Language"]) - lang = Language(_settings["Language"].as()).Code(); - else - lang = loot::Language::any; + if (fs::exists(_game->MasterlistPath()) || fs::exists(_game->UserlistPath())) { //Merge all global message lists. @@ -786,46 +783,19 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { 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())); } - //Merge plugin list, masterlist and userlist plugin data. - BOOST_LOG_TRIVIAL(debug) << "Merging plugin list, masterlist and userlist data, 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() << "\""; + //Merge plugin list and masterlist. + BOOST_LOG_TRIVIAL(debug) << "Merging plugin list and masterlist data."; + for (auto &plugin : plugins) { + BOOST_LOG_TRIVIAL(trace) << "Merging for plugin \"" << plugin.Name() << "\""; //Check if there is a plugin entry in the masterlist. This will also find matching regex entries. - list::iterator pos = std::find(mlist_plugins.begin(), mlist_plugins.end(), graph[*vit]); + list::iterator pos = std::find(mlist_plugins.begin(), mlist_plugins.end(), plugin); if (pos != mlist_plugins.end()) { BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; - graph[*vit].MergeMetadata(*pos); + plugin.MergeMetadata(*pos); } - //Check if there is a plugin entry in the userlist. This will also find matching regex entries. - pos = std::find(ulist_plugins.begin(), ulist_plugins.end(), graph[*vit]); - - if (pos != ulist_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(*_game, 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(*_game); - progDia->Pulse(); } } @@ -849,19 +819,60 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { //Check for back-edges, then perform a topological sort. 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(ulist_plugins.begin(), ulist_plugins.end(), graph[*vit]); + + if (pos != ulist_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(*_game, 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(*_game); + + 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."; - AddSpecificEdges(graph, overriddenPriorities); + loot::AddSpecificEdges(graph, overriddenPriorities); BOOST_LOG_TRIVIAL(debug) << "Adding priority edges."; - AddPriorityEdges(graph); + loot::AddPriorityEdges(graph); BOOST_LOG_TRIVIAL(debug) << "Adding overlap edges."; - AddOverlapEdges(graph); + loot::AddOverlapEdges(graph); BOOST_LOG_TRIVIAL(info) << "Checking to see if the graph is cyclic."; loot::CheckForCycles(graph); @@ -885,13 +896,32 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { MiniEditor editor(this, translate("LOOT: Calculated Load Order"), plugins, ulist_plugins, *_game); long ret = editor.ShowModal(); - const std::list edits = editor.GetEditedPlugins(); + const std::list& newUserlist = editor.GetNewUserlist(); + + //Need to determine if any new edits have been made. + bool haveNewEdits = false; + if (newUserlist.size() != ulist_plugins.size()) + haveNewEdits = true; + else { + for (const auto& newEdit : newUserlist) { + const auto it = std::find(ulist_plugins.begin(), ulist_plugins.end(), newEdit); + if (it == ulist_plugins.end()) { + haveNewEdits = true; + break; + } + + if (!it->DiffMetadata(newEdit).HasNameOnly()) { + haveNewEdits = true; + break; + } + } + } if (ret != wxID_APPLY) { applyLoadOrder = false; break; } - else if (edits.empty()) { + else if (!haveNewEdits) { applyLoadOrder = true; break; } @@ -900,35 +930,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { progDia = new wxProgressDialog(translate("LOOT: Working..."), translate("Recalculating load order..."), 1000, this, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME); //User accepted edits, now apply them, then loop. - //Apply edits to the graph vertices. - loot::vertex_it vit, vitend; - for (boost::tie(vit, vitend) = boost::vertices(graph); vit != vitend; ++vit) { - std::list::const_iterator pos = std::find(edits.begin(), edits.end(), graph[*vit]); - - if (pos != edits.end()) { - BOOST_LOG_TRIVIAL(trace) << "Merging edits down to plugin list data."; - graph[*vit].MergeMetadata(*pos); - } - } - - //Clear all existing edges from the graph. - BOOST_LOG_TRIVIAL(debug) << "Clearing all existing edges from the plugin graph."; - for (boost::tie(vit, vitend) = boost::vertices(graph); vit != vitend; ++vit) { - boost::clear_vertex(*vit, graph); - } - - //Merge edits down to the userlist entries. - BOOST_LOG_TRIVIAL(debug) << "Merging down edits to the userlist."; - for (const auto &plugin: edits) { - auto it = find(ulist_plugins.begin(), ulist_plugins.end(), plugin); - - if (it != ulist_plugins.end()) { - it->MergeMetadata(plugin); - } - else { - ulist_plugins.push_back(plugin); - } - } + ulist_plugins = newUserlist; //Save edits to userlist. BOOST_LOG_TRIVIAL(info) << "Saving edited userlist."; @@ -947,7 +949,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { } while (true); if (applyLoadOrder) { - //User doesn't want to make any changes. Just go straight to applying the load order. + //Applying the load order. BOOST_LOG_TRIVIAL(debug) << "Setting load order."; try { _game->SetLoadOrder(plugins);