From a5b8a1c89af206ed4721e365fb5e1390202c571e Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Sun, 13 Jul 2014 21:02:30 +0100 Subject: [PATCH 01/12] Refactored masterlist updating code. Also refactored plugin loading code, but that's still WIP. --- src/backend/game.cpp | 58 +++++++++++++++++ src/backend/game.h | 87 +++++++++++++++++++++++-- src/backend/metadata.cpp | 18 ----- src/backend/metadata.h | 8 --- src/backend/network.cpp | 54 +++++++-------- src/backend/network.h | 40 ------------ src/gui/main.cpp | 137 ++++----------------------------------- 7 files changed, 178 insertions(+), 224 deletions(-) delete mode 100644 src/backend/network.h diff --git a/src/backend/game.cpp b/src/backend/game.cpp index 32bc7774..46ba275c 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -28,6 +28,7 @@ #include "error.h" #include "metadata.h" #include "parsers.h" +#include "streams.h" #include @@ -59,6 +60,63 @@ namespace loot { return games; } + // MetadataList member functions + //------------------------------ + + void MetadataList::Load(boost::filesystem::path& filepath) { + plugins.clear(); + messages.clear(); + + BOOST_LOG_TRIVIAL(debug) << "Loading file: " << filepath; + + loot::ifstream in(filepath); + YAML::Node metadataList = YAML::Load(in); + in.close(); + + if (metadataList["plugins"]) + plugins = metadataList["plugins"].as< list >(); + if (metadataList["globals"]) + messages = metadataList["globals"].as< list >(); + + BOOST_LOG_TRIVIAL(debug) << "File loaded successfully."; + } + + // Masterlist member functions + //---------------------------- + + void Masterlist::Load(Game& game, const unsigned int language) { + try { + Update(game, language); + } + catch (error& e) { + if (e.code() != error::ok) { + // Error wasn't a parsing error. Need to try parsing masterlist if it exists. + try { + MetadataList::Load(game.MasterlistPath()); + } + catch (...) {} + } + throw e; + } + } + + std::string Masterlist::GetRevision(boost::filesystem::path& path) { + if (revision.empty()) + GetGitInfo(path); + + return revision; + } + + std::string Masterlist::GetDate(boost::filesystem::path& path) { + if (date.empty()) + GetGitInfo(path); + + return date; + } + + // Game member functions + //---------------------- + Game::Game() : id(Game::autodetect) {} Game::Game(const unsigned int gameCode, const std::string& folder) : id(gameCode) { diff --git a/src/backend/game.h b/src/backend/game.h index b0a63aed..73384df0 100644 --- a/src/backend/game.h +++ b/src/backend/game.h @@ -40,6 +40,8 @@ #include namespace loot { + + class Game; /* Each Game object should store the config details specific to that game. It should also store the plugin and masterlist data for that game. @@ -51,6 +53,82 @@ namespace loot { data. Plugin data should be loaded as header-only and as full data. */ + class MetadataList { + public: + void Load(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 { + public: + + void Load(Game& game, const unsigned int language); //Handles update with load fallback. + void Update(Game& game, const unsigned int language); + + std::string GetRevision(boost::filesystem::path& path); + std::string GetDate(boost::filesystem::path& path); + + private: + void GetGitInfo(boost::filesystem::path& path); + + std::string revision; + std::string date; + }; + /* + class PluginCache { + + + bool IsActive(const std::string& plugin) const; + + void GetLoadOrder(std::list& loadOrder) const; + void SetLoadOrder(const std::list& loadOrder) const; //Modifies game load order, even though const. + + void RefreshActivePluginsList(); + void RedatePlugins(); //Change timestamps to match load order (Skyrim only). + void LoadPlugins(bool headersOnly); //Loads all installed plugins. + std::unordered_map plugins; //Map so that plugin data can be edited. + + std::unordered_set activePlugins; //Holds lowercased strings. + }; + */ + + // A couple of plugin loader classes for handling plugin loading in separate threads. + class PluginLoader { + public: + PluginLoader(Plugin& plugin, Game& game) : _plugin(plugin), _game(game) { + } + + void operator () () { + _plugin = Plugin(_game, _plugin.Name(), false); + } + + Plugin& _plugin; + Game& _game; + std::string _filename; + bool _b; + }; + + class PluginsLoader { + public: + PluginsLoader(std::list& plugins, Game& game) : _plugins(plugins), _game(game) {} + + void operator () () { + for (auto &plugin : _plugins) { + if (skipPlugins.find(plugin.Name()) == skipPlugins.end()) { + plugin = Plugin(_game, plugin.Name(), false); + } + } + } + + std::list& _plugins; + Game& _game; + std::set skipPlugins; + }; + class Game { public: //Game functions. @@ -81,8 +159,8 @@ namespace loot { boost::filesystem::path UserlistPath() const; boost::filesystem::path ReportDataPath() const; +//TO BE REMOVED //Game plugin functions. - bool IsActive(const std::string& plugin) const; void GetLoadOrder(std::list& loadOrder) const; @@ -95,11 +173,12 @@ namespace loot { //Caches for condition results, active plugins and CRCs. std::unordered_map conditionCache; //Holds lowercased strings. std::unordered_map crcCache; //Holds lowercased strings. +//END TO BE REMOVED //Plugin data and metadata lists. - MetadataList masterlist; + Masterlist masterlist; MetadataList userlist; - std::unordered_map plugins; //Map so that plugin data can be edited. + std::unordered_map plugins; //Map so that plugin data can be edited. TO BE REMOVED espm::Settings espm_settings; @@ -121,7 +200,7 @@ namespace loot { boost::filesystem::path gamePath; //Path to the game's folder. - std::unordered_set activePlugins; //Holds lowercased strings. + std::unordered_set activePlugins; //Holds lowercased strings. TO BE REMOVED //Creates directory in LOOT folder for LOOT's game-specific files. void CreateLOOTGameFolder(); diff --git a/src/backend/metadata.cpp b/src/backend/metadata.cpp index 94d22e60..c6538d2d 100644 --- a/src/backend/metadata.cpp +++ b/src/backend/metadata.cpp @@ -763,24 +763,6 @@ namespace loot { return boost::filesystem::exists(game.DataPath() / (name.substr(0, name.length() - 3) + "bsa")); } - void MetadataList::Load(boost::filesystem::path& filepath) { - plugins.clear(); - messages.clear(); - - BOOST_LOG_TRIVIAL(debug) << "Loading file: " << filepath; - - loot::ifstream in(filepath); - YAML::Node metadataList = YAML::Load(in); - in.close(); - - if (metadataList["plugins"]) - plugins = metadataList["plugins"].as< list >(); - if (metadataList["globals"]) - messages = metadataList["globals"].as< list >(); - - BOOST_LOG_TRIVIAL(debug) << "File loaded successfully."; - } - size_t plugin_hash::operator () (const Plugin& p) const { size_t seed = 0; boost::hash_combine(seed, p.Name()); diff --git a/src/backend/metadata.h b/src/backend/metadata.h index f7d76591..9633c40d 100644 --- a/src/backend/metadata.h +++ b/src/backend/metadata.h @@ -243,14 +243,6 @@ namespace loot { size_t numOverrideRecords; }; - class MetadataList { - public: - void Load(boost::filesystem::path& filepath); - - std::list plugins; - std::list messages; - }; - struct plugin_hash : std::unary_function { size_t operator () (const Plugin& p) const; }; diff --git a/src/backend/network.cpp b/src/backend/network.cpp index f3d33d1e..e28b0ff3 100644 --- a/src/backend/network.cpp +++ b/src/backend/network.cpp @@ -22,11 +22,11 @@ . */ -#include "network.h" #include "error.h" #include "parsers.h" #include "streams.h" #include "helpers.h" +#include "game.h" #include #include @@ -92,11 +92,6 @@ namespace loot { std::string ui_message; }; - int progress_cb(const char *str, int len, void *data) { - BOOST_LOG_TRIVIAL(info) << string(str, len); - return 0; - } - bool are_files_equal(const void * buf1, size_t buf1_size, const void * buf2, size_t buf2_size) { if (buf1_size != buf2_size) return false; @@ -114,12 +109,17 @@ namespace loot { return git_repository_open_ext(NULL, path.string().c_str(), GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) == 0; } - std::pair GetMasterlistRevision(const Game& game) { - if (!fs::exists(game.MasterlistPath().parent_path() / ".git")) { - return pair("Unknown: Git repository missing", "Unknown: Git repository missing"); + void Masterlist::GetGitInfo(boost::filesystem::path& path) { + if (!fs::exists(path.parent_path() / ".git")) { + revision = "Unknown: Git repository missing"; + date = "Unknown: Git repository missing"; + return; + } + else if (!fs::exists(path)) { + revision = "N/A: No masterlist present"; + date = "N/A: No masterlist present"; + return; } - else if (!fs::exists(game.MasterlistPath())) - return pair("N/A: No masterlist present", "N/A: No masterlist present"); else { /* Compares HEAD to the working dir. 1. Get an object for the masterlist in HEAD. @@ -128,9 +128,9 @@ namespace loot { 4. Compare the file and blob buffers. */ git_handler git; - git.ui_message = "An error occurred while trying to read the local masterlist's version. If this error happens again, try deleting the \".git\" folder in \"%LOCALAPPDATA%\\LOOT\\" + game.FolderName() + "\"."; + git.ui_message = "An error occurred while trying to read the local masterlist's version. If this error happens again, try deleting the \".git\" folder in " + path.parent_path().string() + "."; BOOST_LOG_TRIVIAL(debug) << "Existing repository found, attempting to open it."; - git.call(git_repository_open(&git.repo, game.MasterlistPath().parent_path().string().c_str())); + git.call(git_repository_open(&git.repo, path.parent_path().string().c_str())); BOOST_LOG_TRIVIAL(trace) << "Getting HEAD masterlist object."; git.call(git_revparse_single(&git.obj, git.repo, "HEAD:masterlist.yaml")); @@ -140,7 +140,7 @@ namespace loot { BOOST_LOG_TRIVIAL(debug) << "Opening masterlist in working directory."; std::string mlist; - loot::ifstream ifile(game.MasterlistPath().string().c_str(), ios::binary); + loot::ifstream ifile(path, ios::binary); if (ifile.fail()) throw error(error::path_read_fail, "Couldn't open masterlist."); ifile.unsetf(ios::skipws); // No white space skipping! @@ -153,7 +153,6 @@ namespace loot { BOOST_LOG_TRIVIAL(debug) << "Comparing files."; if (are_files_equal(git_blob_rawcontent(git.blob), git_blob_rawsize(git.blob), mlist.data(), mlist.length())) { - string revision, date; //Need to get the HEAD object, because the individual file has a different SHA. git_object_free(git.obj); git.obj = nullptr; //Just to be safe. @@ -176,15 +175,17 @@ namespace loot { out << boost::locale::as::ftime("%Y-%m-%d") << dateTime; date = out.str(); - return pair(revision, date); + return; } else { - return pair("Unknown: Masterlist edited", "Unknown: Masterlist edited"); + revision = "Unknown: Masterlist edited"; + date = "Unknown: Masterlist edited"; + return; } } } - std::pair UpdateMasterlist(Game& game, std::list& parsingErrors, std::list& plugins, std::list& messages, const unsigned int language) { + void Masterlist::Update(Game& game, const unsigned int language) { git_handler git; fs::path repo_path = game.MasterlistPath().parent_path(); string repo_branch = game.RepoBranch(); @@ -387,7 +388,7 @@ namespace loot { // and try again. bool parsingFailed = false; - string revision, date; + std::string parsingError; git.ui_message = "An error occurred while trying to read information on the updated masterlist. If this error happens again, try deleting the \".git\" folder in \"%LOCALAPPDATA%\\LOOT\\" + game.FolderName() + "\"."; do { // Get some descriptive info about what was checked out. @@ -428,14 +429,7 @@ namespace loot { //Now try parsing the masterlist. BOOST_LOG_TRIVIAL(debug) << "Testing masterlist parsing."; try { - loot::ifstream in(game.MasterlistPath()); - YAML::Node mlist = YAML::Load(in); - in.close(); - - if (mlist["globals"]) - messages = mlist["globals"].as< list >(); - if (mlist["plugins"]) - plugins = mlist["plugins"].as< list >(); + this->MetadataList::Load(game.MasterlistPath()); for (auto &plugin: plugins) { plugin.EvalAllConditions(game, language); @@ -466,10 +460,12 @@ namespace loot { BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD."; git.call(git_checkout_head(git.repo, &checkout_opts)); - parsingErrors.push_back(loot::Message(loot::Message::error, boost::locale::translate("Masterlist revision").str() + " " + string(revision) + ": " + e.what() + " " + boost::locale::translate("Rolled back to the previous revision.").str())); + if (parsingError.empty()) + parsingError = boost::locale::translate("Masterlist revision").str() + " " + string(revision) + ": " + e.what() + " " + boost::locale::translate("Rolled back to the previous revision.").str(); } } while (parsingFailed); - return pair(revision, date); + if (!parsingError.empty()) + throw error(error::ok, parsingError); //Throw an OK because the process still completed in a successful state. } } diff --git a/src/backend/network.h b/src/backend/network.h deleted file mode 100644 index 51922343..00000000 --- a/src/backend/network.h +++ /dev/null @@ -1,40 +0,0 @@ -/* LOOT - - A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and - Fallout: New Vegas. - - Copyright (C) 2012-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 - . -*/ - -#ifndef __LOOT_NETWORK__ -#define __LOOT_NETWORK__ - -#include -#include - -#include "game.h" -#include "metadata.h" - -namespace loot { - - std::pair UpdateMasterlist(Game& game, std::list& parsingErrors, std::list& plugins, std::list& messages, const unsigned int language); - - std::pair GetMasterlistRevision(const Game& game); -} -#endif diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 859a2e7e..316f5c48 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -33,7 +33,6 @@ #include "../backend/error.h" #include "../backend/helpers.h" #include "../backend/generators.h" -#include "../backend/network.h" #include "../backend/streams.h" #include "../backend/graph.h" @@ -71,123 +70,6 @@ using boost::format; namespace fs = boost::filesystem; namespace loc = boost::locale; - -struct plugin_loader { - plugin_loader(loot::Plugin& plugin, loot::Game& game) : _plugin(plugin), _game(game) { - } - - void operator () () { - _plugin = loot::Plugin(_game, _plugin.Name(), false); - } - - loot::Plugin& _plugin; - loot::Game& _game; - string _filename; - bool _b; -}; - -struct plugin_list_loader { - plugin_list_loader(list& plugins, loot::Game& game) : _plugins(plugins), _game(game) {} - - void operator () () { - for (auto &plugin : _plugins) { - if (skipPlugins.find(plugin.Name()) == skipPlugins.end()) { - plugin = loot::Plugin(_game, plugin.Name(), false); - } - } - } - - list& _plugins; - loot::Game& _game; - set skipPlugins; -}; - -struct masterlist_updater_parser { - masterlist_updater_parser(bool doUpdate, loot::Game& game, list& errors, list& plugins, list& messages, string& revision, string& date, const unsigned int language) : _doUpdate(doUpdate), _game(game), _errors(errors), _plugins(plugins), _messages(messages), _revision(revision), _date(date), _language(language) {} - - void operator () () { - - if (_doUpdate) { - BOOST_LOG_TRIVIAL(debug) << "Updating masterlist"; - try { - pair ret = UpdateMasterlist(_game, _errors, _plugins, _messages, _language); - _revision = ret.first; - _date = ret.second; - } catch (std::exception& e) { - _plugins.clear(); - _messages.clear(); - BOOST_LOG_TRIVIAL(error) << "Masterlist update failed. Details: " << e.what(); - _errors.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist update failed. Details: %1%")) % e.what()).str())); - //Try getting masterlist revision anyway. - try { - pair ret = GetMasterlistRevision(_game); - _revision = ret.first; - _date = ret.second; - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Masterlist revision check failed. Details: " << e.what(); - _errors.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist revision check failed. Details: %1%")) % e.what()).str())); - } - } - } - else { - BOOST_LOG_TRIVIAL(debug) << "Getting masterlist revision"; - try { - pair ret = GetMasterlistRevision(_game); - _revision = ret.first; - _date = ret.second; - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Masterlist revision check failed. Details: " << e.what(); - _errors.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist revision check failed. Details: %1%")) % e.what()).str())); - } - } - - if (_plugins.empty() && _messages.empty() && fs::exists(_game.MasterlistPath())) { - - BOOST_LOG_TRIVIAL(debug) << "Parsing masterlist..."; - try { - loot::ifstream in(_game.MasterlistPath()); - YAML::Node mlist = YAML::Load(in); - in.close(); - - if (mlist["globals"]) - _messages = mlist["globals"].as< list >(); - if (mlist["plugins"]) - _plugins = mlist["plugins"].as< list >(); - } catch (YAML::Exception& e) { - BOOST_LOG_TRIVIAL(error) << "Masterlist parsing failed. Details: " << e.what(); - _errors.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist parsing failed. Details: %1%")) % e.what()).str())); - } - BOOST_LOG_TRIVIAL(debug) << "Finished parsing masterlist."; - - } - - if (_revision.empty()) { - if (fs::exists(_game.MasterlistPath())) - _revision = loc::translate("Unknown"); - else - _revision = loc::translate("No masterlist"); - } - - if (_date.empty()) { - if (fs::exists(_game.MasterlistPath())) - _date = loc::translate("Unknown"); - else - _date = loc::translate("No masterlist"); - } - } - - bool _doUpdate; - loot::Game& _game; - list& _errors; - list& _plugins; - list& _messages; - string& _revision; - string& _date; - unsigned int _language; -}; - bool LOOT::OnInit() { //Check if GUI is already running. @@ -729,7 +611,6 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { list mlist_plugins, ulist_plugins; list plugins; boost::thread_group group; - string revision, date; wxProgressDialog *progDia = new wxProgressDialog(translate("LOOT: Working..."),translate("LOOT working..."), 1000, this, wxPD_APP_MODAL|wxPD_AUTO_HIDE|wxPD_ELAPSED_TIME); @@ -747,8 +628,14 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(lang).Name(); bool doUpdate = _settings["Update Masterlist"] && _settings["Update Masterlist"].as(); - masterlist_updater_parser mup(doUpdate, *_game, messages, mlist_plugins, mlist_messages, revision, date, lang); - group.create_thread(mup); + group.create_thread([this, lang, &messages]() { + try { + this->_game->masterlist.Load(*this->_game, lang); + } + catch (exception &e) { + messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist parsing failed. Details: %1%")) % e.what()).str())); + } + }); //First calculate the mean plugin size. Store it temporarily in a map to reduce filesystem lookups and file size recalculation. size_t meanFileSize = 0; @@ -765,7 +652,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { meanFileSize /= tempMap.size(); //Now load plugins. - plugin_list_loader pll(plugins, *_game); + PluginsLoader pll(plugins, *_game); for (const auto &pluginPair: tempMap) { BOOST_LOG_TRIVIAL(info) << "Found plugin: " << pluginPair.first; @@ -774,7 +661,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { if (pluginPair.second > meanFileSize) { pll.skipPlugins.insert(pluginPair.first); - plugin_loader pl(plugins.back(), *_game); + PluginLoader pl(plugins.back(), *_game); group.create_thread(pl); } @@ -1058,8 +945,8 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { GenerateReportData(*_game, messages, plugins, - revision, - date, + _game->masterlist.GetRevision(_game->MasterlistPath()), + _game->masterlist.GetDate(_game->MasterlistPath()), doUpdate); } catch (std::exception& e) { wxMessageBox( From 155a87321ad4e67258bf10c4bbb076dd611cffec Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Sun, 13 Jul 2014 21:13:27 +0100 Subject: [PATCH 02/12] Sorting and editor init now use game lists. Rather than temporary metadata list containers. --- src/gui/main.cpp | 104 +++++++++++++++-------------------------------- 1 file changed, 33 insertions(+), 71 deletions(-) diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 316f5c48..3f20668d 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -606,11 +606,10 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { BOOST_LOG_TRIVIAL(debug) << "Beginning sorting process."; - YAML::Node mlist, ulist; - list messages, mlist_messages, ulist_messages; - list mlist_plugins, ulist_plugins; + list messages; list plugins; boost::thread_group group; + unsigned int lang; wxProgressDialog *progDia = new wxProgressDialog(translate("LOOT: Working..."),translate("LOOT working..."), 1000, this, wxPD_APP_MODAL|wxPD_AUTO_HIDE|wxPD_ELAPSED_TIME); @@ -619,7 +618,6 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { /////////////////////////////////////////////////////// //Set language. - unsigned int lang; if (_settings["Language"]) lang = Language(_settings["Language"].as()).Code(); else @@ -627,7 +625,6 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(lang).Name(); - bool doUpdate = _settings["Update Masterlist"] && _settings["Update Masterlist"].as(); group.create_thread([this, lang, &messages]() { try { this->_game->masterlist.Load(*this->_game, lang); @@ -675,18 +672,11 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { BOOST_LOG_TRIVIAL(debug) << "Parsing userlist at: " << _game->UserlistPath(); try { - loot::ifstream in(_game->UserlistPath()); - YAML::Node ulist = YAML::Load(in); - in.close(); - - if (ulist["plugins"]) - ulist_plugins = ulist["plugins"].as< list >(); - } catch (YAML::ParserException& e) { + _game->userlist.Load(_game->UserlistPath()); + } catch (exception& e) { BOOST_LOG_TRIVIAL(error) << "Userlist parsing failed. Details: " << e.what(); messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("Userlist parsing failed. Details: %1%")) % e.what()).str())); } - if (ulist["plugins"]) - ulist_plugins = ulist["plugins"].as< list >(); } progDia->Pulse(); @@ -700,10 +690,10 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { //Merge all global message lists. BOOST_LOG_TRIVIAL(debug) << "Merging all global message lists."; - if (!mlist_messages.empty()) - messages.insert(messages.end(), mlist_messages.begin(), mlist_messages.end()); - if (!ulist_messages.empty()) - messages.insert(messages.end(), ulist_messages.begin(), ulist_messages.end()); + if (!_game->masterlist.messages.empty()) + messages.insert(messages.end(), _game->masterlist.messages.begin(), _game->masterlist.messages.end()); + if (!_game->userlist.messages.empty()) + messages.insert(messages.end(), _game->userlist.messages.begin(), _game->userlist.messages.end()); //Evaluate any conditions in the global messages. BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions."; @@ -727,9 +717,9 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { 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(), plugin); + list::iterator pos = std::find(_game->masterlist.plugins.begin(), _game->masterlist.plugins.end(), plugin); - if (pos != mlist_plugins.end()) { + if (pos != _game->masterlist.plugins.end()) { BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; plugin.MergeMetadata(*pos); } @@ -771,9 +761,9 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { 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]); + list::iterator pos = std::find(_game->userlist.plugins.begin(), _game->userlist.plugins.end(), graph[*vit]); - if (pos != ulist_plugins.end() && pos->Enabled()) { + if (pos != _game->userlist.plugins.end() && pos->Enabled()) { BOOST_LOG_TRIVIAL(trace) << "Merging userlist data down to plugin list data."; graph[*vit].MergeMetadata(*pos); } @@ -839,7 +829,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { GetWindowSizePos(_settings["windows"]["editor"], pos, size); } - MiniEditor editor(this, translate("LOOT: Calculated Load Order"), pos, size, plugins, ulist_plugins, *_game); + MiniEditor editor(this, translate("LOOT: Calculated Load Order"), pos, size, plugins, _game->userlist.plugins, *_game); long ret = editor.ShowModal(); const std::list& newUserlist = editor.GetNewUserlist(); @@ -855,14 +845,14 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { //Need to determine if any new edits have been made. bool haveNewEdits = false; - if (newUserlist.size() != ulist_plugins.size()) { + if (newUserlist.size() != _game->userlist.plugins.size()) { BOOST_LOG_TRIVIAL(info) << "Metadata edited for some plugin, new and old userlists differ in 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()) { + const auto it = std::find(_game->userlist.plugins.begin(), _game->userlist.plugins.end(), newEdit); + if (it == _game->userlist.plugins.end()) { BOOST_LOG_TRIVIAL(info) << "Metadata added for plugin: " << it->Name(); haveNewEdits = true; break; @@ -889,14 +879,14 @@ 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. - ulist_plugins = newUserlist; + _game->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 << ulist_plugins + << YAML::Key << "plugins" << YAML::Value << _game->userlist.plugins << YAML::EndMap; loot::ofstream uout(_game->UserlistPath()); @@ -947,7 +937,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { plugins, _game->masterlist.GetRevision(_game->MasterlistPath()), _game->masterlist.GetDate(_game->MasterlistPath()), - doUpdate); + true); } catch (std::exception& e) { wxMessageBox( FromUTF8(format(loc::translate("Error: %1%")) % e.what()), @@ -980,9 +970,16 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { void Launcher::OnEditMetadata(wxCommandEvent& event) { //Should probably check for masterlist updates before opening metadata editor. - list installed, mlist_plugins, ulist_plugins; + list installed; + unsigned int lang; - wxProgressDialog *progDia = new wxProgressDialog(translate("LOOT: Working..."),translate("LOOT working..."), 1000, this, wxPD_APP_MODAL|wxPD_AUTO_HIDE|wxPD_ELAPSED_TIME); + wxProgressDialog *progDia = new wxProgressDialog(translate("LOOT: Working..."), translate("LOOT working..."), 1000, this, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME); + + //Set language. + if (_settings["Language"]) + lang = Language(_settings["Language"].as()).Code(); + else + lang = loot::Language::any; //Scan for installed plugins. BOOST_LOG_TRIVIAL(debug) << "Reading installed plugins' headers."; @@ -1000,21 +997,7 @@ void Launcher::OnEditMetadata(wxCommandEvent& event) { //Parse masterlist. if (fs::exists(_game->MasterlistPath())) { BOOST_LOG_TRIVIAL(debug) << "Parsing masterlist."; - YAML::Node mlist; - try { - loot::ifstream in(_game->MasterlistPath()); - mlist = YAML::Load(in); - in.close(); - } catch (YAML::ParserException& e) { - BOOST_LOG_TRIVIAL(error) << "Masterlist parsing failed. " << e.what(); - wxMessageBox( - FromUTF8(format(loc::translate("Error: Masterlist parsing failed. %1%")) % e.what()), - translate("LOOT: Error"), - wxOK | wxICON_ERROR, - this); - } - if (mlist["plugins"]) - mlist_plugins = mlist["plugins"].as< list >(); + _game->masterlist.Load(*_game, lang); } progDia->Pulse(); @@ -1022,28 +1005,14 @@ void Launcher::OnEditMetadata(wxCommandEvent& event) { //Parse userlist. if (fs::exists(_game->UserlistPath())) { BOOST_LOG_TRIVIAL(debug) << "Parsing userlist."; - YAML::Node ulist; - try { - loot::ifstream in(_game->UserlistPath()); - ulist = YAML::Load(in); - in.close(); - } catch (YAML::ParserException& e) { - BOOST_LOG_TRIVIAL(error) << "Userlist parsing failed. " << e.what(); - wxMessageBox( - FromUTF8(format(loc::translate("Error: Userlist parsing failed. %1%")) % e.what()), - translate("LOOT: Error"), - wxOK | wxICON_ERROR, - this); - } - if (ulist["plugins"]) - ulist_plugins = ulist["plugins"].as< list >(); + _game->userlist.Load(_game->UserlistPath()); } progDia->Pulse(); //Merge the masterlist down into the installed mods list. BOOST_LOG_TRIVIAL(debug) << "Merging the masterlist down into the installed mods list."; - for (const auto &plugin: mlist_plugins) { + for (const auto &plugin: _game->masterlist.plugins) { auto pos = find(installed.begin(), installed.end(), plugin); if (pos != installed.end()) @@ -1054,20 +1023,13 @@ void Launcher::OnEditMetadata(wxCommandEvent& event) { //Add empty entries for any userlist entries that aren't installed. BOOST_LOG_TRIVIAL(debug) << "Padding the installed mods list to match the plugins in the userlist."; - for (const auto &plugin : ulist_plugins) { + for (const auto &plugin : _game->userlist.plugins) { if (find(installed.begin(), installed.end(), plugin) == installed.end()) installed.push_back(loot::Plugin(plugin.Name())); } progDia->Pulse(); - //Set language. - unsigned int lang; - if (_settings["Language"]) - lang = Language(_settings["Language"].as()).Code(); - else - lang = loot::Language::any; - //Load window size/pos settings. wxSize size = wxDefaultSize; wxPoint pos = wxDefaultPosition; @@ -1077,7 +1039,7 @@ void Launcher::OnEditMetadata(wxCommandEvent& event) { //Create editor window. BOOST_LOG_TRIVIAL(debug) << "Opening editor window."; - FullEditor *editor = new FullEditor(this, translate("LOOT: Metadata Editor"), pos, size, _game->UserlistPath().string(), installed, ulist_plugins, lang, *_game, _settings); + FullEditor *editor = new FullEditor(this, translate("LOOT: Metadata Editor"), pos, size, _game->UserlistPath().string(), installed, _game->userlist.plugins, lang, *_game, _settings); progDia->Destroy(); From 2b90fc6c8b54af8bb556f43fc2be64f38e1cedde Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Sun, 13 Jul 2014 22:17:19 +0100 Subject: [PATCH 03/12] Re-implemented plugin loading. Using a multi-threaded loader member function from the game class. This currently produces invalid cyclic errors for me though, so I need to fix that. --- src/backend/game.cpp | 40 +++++++++++++--- src/backend/game.h | 56 +--------------------- src/gui/main.cpp | 108 ++++++++++++++----------------------------- 3 files changed, 70 insertions(+), 134 deletions(-) diff --git a/src/backend/game.cpp b/src/backend/game.cpp index 46ba275c..56f34e07 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -31,6 +31,7 @@ #include "streams.h" #include +#include using namespace std; @@ -572,19 +573,44 @@ namespace loot { } void Game::LoadPlugins(bool headersOnly) { - //Add all plugins in data folder not already in the hashset to the hashset, and load them. - for (fs::directory_iterator it(DataPath()); it != fs::directory_iterator(); ++it) { + boost::thread_group group; + size_t meanFileSize = 0; + unordered_map tempMap; + std::set skipPlugins; + //First calculate the mean plugin size. Store it temporarily in a map to reduce filesystem lookups and file size recalculation. + for (fs::directory_iterator it(this->DataPath()); it != fs::directory_iterator(); ++it) { if (fs::is_regular_file(it->status()) && IsPlugin(it->path().string())) { - const string filename = it->path().filename().string(); - if (plugins.find(filename) == plugins.end()) - plugins.insert(std::pair(filename, Plugin(filename))); + size_t fileSize = fs::file_size(it->path()); + meanFileSize += fileSize; + + tempMap.emplace(it->path().filename().string(), fileSize); } } + meanFileSize /= tempMap.size(); - for (auto &pluginPair: plugins) { - pluginPair.second = Plugin(*this, pluginPair.second.Name(), headersOnly); + //Now load plugins. + for (const auto &pluginPair : tempMap) { + + BOOST_LOG_TRIVIAL(info) << "Found plugin: " << pluginPair.first; + + auto plugin = plugins.emplace(pluginPair.first, Plugin(pluginPair.first)); + + if (pluginPair.second > meanFileSize) { + skipPlugins.insert(pluginPair.first); + group.create_thread([this, &plugin, headersOnly]() { + plugin.first->second = Plugin(*this, plugin.first->first, headersOnly); + }); + } } + group.create_thread([this, &skipPlugins, headersOnly]() { + for (auto &pluginPair : this->plugins) { + if (skipPlugins.find(pluginPair.first) == skipPlugins.end()) { + pluginPair.second = Plugin(*this, pluginPair.first, headersOnly); + } + } + }); + group.join_all(); } void Game::CreateLOOTGameFolder() { diff --git a/src/backend/game.h b/src/backend/game.h index 73384df0..e67e9b47 100644 --- a/src/backend/game.h +++ b/src/backend/game.h @@ -78,56 +78,6 @@ namespace loot { std::string revision; std::string date; }; - /* - class PluginCache { - - - bool IsActive(const std::string& plugin) const; - - void GetLoadOrder(std::list& loadOrder) const; - void SetLoadOrder(const std::list& loadOrder) const; //Modifies game load order, even though const. - - void RefreshActivePluginsList(); - void RedatePlugins(); //Change timestamps to match load order (Skyrim only). - void LoadPlugins(bool headersOnly); //Loads all installed plugins. - std::unordered_map plugins; //Map so that plugin data can be edited. - - std::unordered_set activePlugins; //Holds lowercased strings. - }; - */ - - // A couple of plugin loader classes for handling plugin loading in separate threads. - class PluginLoader { - public: - PluginLoader(Plugin& plugin, Game& game) : _plugin(plugin), _game(game) { - } - - void operator () () { - _plugin = Plugin(_game, _plugin.Name(), false); - } - - Plugin& _plugin; - Game& _game; - std::string _filename; - bool _b; - }; - - class PluginsLoader { - public: - PluginsLoader(std::list& plugins, Game& game) : _plugins(plugins), _game(game) {} - - void operator () () { - for (auto &plugin : _plugins) { - if (skipPlugins.find(plugin.Name()) == skipPlugins.end()) { - plugin = Plugin(_game, plugin.Name(), false); - } - } - } - - std::list& _plugins; - Game& _game; - std::set skipPlugins; - }; class Game { public: @@ -159,7 +109,6 @@ namespace loot { boost::filesystem::path UserlistPath() const; boost::filesystem::path ReportDataPath() const; -//TO BE REMOVED //Game plugin functions. bool IsActive(const std::string& plugin) const; @@ -173,12 +122,11 @@ namespace loot { //Caches for condition results, active plugins and CRCs. std::unordered_map conditionCache; //Holds lowercased strings. std::unordered_map crcCache; //Holds lowercased strings. -//END TO BE REMOVED //Plugin data and metadata lists. Masterlist masterlist; MetadataList userlist; - std::unordered_map plugins; //Map so that plugin data can be edited. TO BE REMOVED + std::unordered_map plugins; //Map so that plugin data can be edited. espm::Settings espm_settings; @@ -200,7 +148,7 @@ namespace loot { boost::filesystem::path gamePath; //Path to the game's folder. - std::unordered_set activePlugins; //Holds lowercased strings. TO BE REMOVED + std::unordered_set activePlugins; //Holds lowercased strings. //Creates directory in LOOT folder for LOOT's game-specific files. void CreateLOOTGameFolder(); diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 3f20668d..da23ae3e 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -607,7 +607,6 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { BOOST_LOG_TRIVIAL(debug) << "Beginning sorting process."; list messages; - list plugins; boost::thread_group group; unsigned int lang; @@ -633,38 +632,9 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist parsing failed. Details: %1%")) % e.what()).str())); } }); - - //First calculate the mean plugin size. Store it temporarily in a map to reduce filesystem lookups and file size recalculation. - size_t meanFileSize = 0; - boost::unordered_map tempMap; - for (fs::directory_iterator it(_game->DataPath()); it != fs::directory_iterator(); ++it) { - if (fs::is_regular_file(it->status()) && IsPlugin(it->path().string())) { - - size_t fileSize = fs::file_size(it->path()); - meanFileSize += fileSize; - - tempMap.emplace(it->path().filename().string(), fileSize); - } - } - meanFileSize /= tempMap.size(); - - //Now load plugins. - PluginsLoader pll(plugins, *_game); - for (const auto &pluginPair: tempMap) { - - BOOST_LOG_TRIVIAL(info) << "Found plugin: " << pluginPair.first; - - plugins.push_back(loot::Plugin(pluginPair.first)); - - if (pluginPair.second > meanFileSize) { - pll.skipPlugins.insert(pluginPair.first); - PluginLoader pl(plugins.back(), *_game); - group.create_thread(pl); - } - - progDia->Pulse(); - } - group.create_thread(pll); + group.create_thread([this]() { + this->_game->LoadPlugins(false); + }); group.join_all(); //Now load userlist. @@ -685,48 +655,28 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { // Merge & Check Metadata /////////////////////////////////////////////////////// - if (fs::exists(_game->MasterlistPath()) || fs::exists(_game->UserlistPath())) { + //Merge all global message lists. + BOOST_LOG_TRIVIAL(debug) << "Merging all global message lists."; + if (!_game->masterlist.messages.empty()) + messages.insert(messages.end(), _game->masterlist.messages.begin(), _game->masterlist.messages.end()); + if (!_game->userlist.messages.empty()) + messages.insert(messages.end(), _game->userlist.messages.begin(), _game->userlist.messages.end()); - - //Merge all global message lists. - BOOST_LOG_TRIVIAL(debug) << "Merging all global message lists."; - if (!_game->masterlist.messages.empty()) - messages.insert(messages.end(), _game->masterlist.messages.begin(), _game->masterlist.messages.end()); - if (!_game->userlist.messages.empty()) - messages.insert(messages.end(), _game->userlist.messages.begin(), _game->userlist.messages.end()); - - //Evaluate any conditions in the global messages. - BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions."; - try { - list::iterator it=messages.begin(); - while (it != messages.end()) { - if (!it->EvalCondition(*_game, lang)) - it = messages.erase(it); - else - ++it; - } - } - catch (std::exception& e) { - 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())); - } - - //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(_game->masterlist.plugins.begin(), _game->masterlist.plugins.end(), plugin); - - if (pos != _game->masterlist.plugins.end()) { - BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; - plugin.MergeMetadata(*pos); - } - - progDia->Pulse(); + //Evaluate any conditions in the global messages. + BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions."; + try { + list::iterator it=messages.begin(); + while (it != messages.end()) { + if (!it->EvalCondition(*_game, lang)) + it = messages.erase(it); + else + ++it; } } + catch (std::exception& e) { + 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())); + } progDia->Update(800, translate("Building plugin graph...")); @@ -745,6 +695,10 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { */ //Check for back-edges, then perform a topological sort. + list plugins; + for (auto &plugin : _game->plugins) { + plugins.push_back(plugin.second); + } try { bool applyLoadOrder = false; @@ -760,8 +714,16 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { 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 masterlist. This will also find matching regex entries. + list::iterator pos = std::find(_game->masterlist.plugins.begin(), _game->masterlist.plugins.end(), graph[*vit]); + + if (pos != _game->masterlist.plugins.end()) { + BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; + graph[*vit].MergeMetadata(*pos); + } + //Check if there is a plugin entry in the userlist. This will also find matching regex entries. - list::iterator pos = std::find(_game->userlist.plugins.begin(), _game->userlist.plugins.end(), graph[*vit]); + pos = std::find(_game->userlist.plugins.begin(), _game->userlist.plugins.end(), graph[*vit]); if (pos != _game->userlist.plugins.end() && pos->Enabled()) { BOOST_LOG_TRIVIAL(trace) << "Merging userlist data down to plugin list data."; From 5aa4646ce6c2a140f78cbfb52445e510d09c262d Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 14 Jul 2014 10:09:50 +0100 Subject: [PATCH 04/12] Fixed broken plugin loading. Some plugins were being repeated or overwritten. Masterlist messages are currently not being displayed for plugins in the report though, I think I know why. --- src/backend/game.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/backend/game.cpp b/src/backend/game.cpp index 56f34e07..5887ce3b 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -576,7 +576,7 @@ namespace loot { boost::thread_group group; size_t meanFileSize = 0; unordered_map tempMap; - std::set skipPlugins; + std::vector groupPlugins; //First calculate the mean plugin size. Store it temporarily in a map to reduce filesystem lookups and file size recalculation. for (fs::directory_iterator it(this->DataPath()); it != fs::directory_iterator(); ++it) { if (fs::is_regular_file(it->status()) && IsPlugin(it->path().string())) { @@ -597,19 +597,24 @@ namespace loot { auto plugin = plugins.emplace(pluginPair.first, Plugin(pluginPair.first)); if (pluginPair.second > meanFileSize) { - skipPlugins.insert(pluginPair.first); - group.create_thread([this, &plugin, headersOnly]() { + BOOST_LOG_TRIVIAL(trace) << "Creating individual loading thread for: " << pluginPair.first; + group.create_thread([this, plugin, headersOnly]() { + BOOST_LOG_TRIVIAL(trace) << "Loading " << plugin.first->second.Name() << " individually."; plugin.first->second = Plugin(*this, plugin.first->first, headersOnly); }); } + else { + groupPlugins.push_back(&plugin.first->second); + } } - group.create_thread([this, &skipPlugins, headersOnly]() { - for (auto &pluginPair : this->plugins) { - if (skipPlugins.find(pluginPair.first) == skipPlugins.end()) { - pluginPair.second = Plugin(*this, pluginPair.first, headersOnly); - } + group.create_thread([this, &groupPlugins, headersOnly]() { + for (auto plugin : groupPlugins) { + const std::string name = plugin->Name(); + BOOST_LOG_TRIVIAL(trace) << "Loading " << plugin->Name() << " as part of a group."; + *plugin = Plugin(*this, name, headersOnly); } }); + group.join_all(); } From 98073c355e9406ff3a2730da7dd25cffad71f965 Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 14 Jul 2014 10:30:33 +0100 Subject: [PATCH 05/12] Fixed masterlist and userlist messages not shown. It's a bit messy, ideally I'd like to have the graph store pointers or references to the temporary plugin lists's contents. --- src/backend/graph.cpp | 10 +++++++--- src/gui/main.cpp | 22 +++++++++++++--------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/backend/graph.cpp b/src/backend/graph.cpp index bd9f49ec..08627e48 100644 --- a/src/backend/graph.cpp +++ b/src/backend/graph.cpp @@ -105,15 +105,18 @@ namespace loot { std::list 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 tempPlugins; + plugins.clear(); for (const auto &vertex: sortedVertices) { BOOST_LOG_TRIVIAL(info) << '\t' << graph[vertex].Name(); - tempPlugins.push_back(graph[vertex].Name()); + plugins.push_back(graph[vertex]); } + //Now sort exist plugins list according to order in tempPlugins. - plugins.sort([tempPlugins](const Plugin& first, const Plugin& second){ + /*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); @@ -123,6 +126,7 @@ namespace loot { return distance(tempPlugins.begin(), fIt) < distance(tempPlugins.begin(), sIt); }); + */ } void CheckForCycles(const PluginGraph& graph) { diff --git a/src/gui/main.cpp b/src/gui/main.cpp index da23ae3e..1b4d6f1d 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -698,6 +698,18 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { list plugins; for (auto &plugin : _game->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(_game->masterlist.plugins.begin(), _game->masterlist.plugins.end(), plugins.back()); + + if (pos != _game->masterlist.plugins.end()) { + BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; + plugins.back().MergeMetadata(*pos); + } } try { bool applyLoadOrder = false; @@ -714,16 +726,8 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { 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 masterlist. This will also find matching regex entries. - list::iterator pos = std::find(_game->masterlist.plugins.begin(), _game->masterlist.plugins.end(), graph[*vit]); - - if (pos != _game->masterlist.plugins.end()) { - BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; - graph[*vit].MergeMetadata(*pos); - } - //Check if there is a plugin entry in the userlist. This will also find matching regex entries. - pos = std::find(_game->userlist.plugins.begin(), _game->userlist.plugins.end(), graph[*vit]); + list::iterator pos = std::find(_game->userlist.plugins.begin(), _game->userlist.plugins.end(), graph[*vit]); if (pos != _game->userlist.plugins.end() && pos->Enabled()) { BOOST_LOG_TRIVIAL(trace) << "Merging userlist data down to plugin list data."; From db60a03e9e9088b21311c5be247d1b64ebaa2e35 Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 14 Jul 2014 10:42:10 +0100 Subject: [PATCH 06/12] Renamed network.cpp to git.cpp Gives a better indication of its content. --- src/backend/{network.cpp => git.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/backend/{network.cpp => git.cpp} (100%) diff --git a/src/backend/network.cpp b/src/backend/git.cpp similarity index 100% rename from src/backend/network.cpp rename to src/backend/git.cpp From f270f07c86185984680999924d459215ca0fd980 Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 14 Jul 2014 10:42:20 +0100 Subject: [PATCH 07/12] Fixed API compilation. --- CMakeLists.txt | 2 +- src/api/api.cpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7209b8cf..42e92acc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ set (LOOT_SRC "${CMAKE_SOURCE_DIR}/src/backend/metadata.cpp" set (LOOT_GUI_SRC ${LOOT_SRC} # Code the API doesn't need. "${CMAKE_SOURCE_DIR}/src/backend/graph.cpp" - "${CMAKE_SOURCE_DIR}/src/backend/network.cpp" + "${CMAKE_SOURCE_DIR}/src/backend/git.cpp" # Actual GUI code. "${CMAKE_SOURCE_DIR}/src/gui/ids.cpp" "${CMAKE_SOURCE_DIR}/src/gui/main.cpp" diff --git a/src/api/api.cpp b/src/api/api.cpp index 192cb183..5325af0d 100644 --- a/src/api/api.cpp +++ b/src/api/api.cpp @@ -158,6 +158,21 @@ unsigned int c_error(const unsigned int code, const std::string& what) { return c_error(loot::error(code, what.c_str())); } +//////////////////////////////////// +// Dummy Masterlist member functions +//////////////////////////////////// + +// The API doesn't depend on libgit2, by not compiling ".git.cpp", so the member functions are defined +// below as dummies. + +namespace loot { + void Masterlist::GetGitInfo(boost::filesystem::path& path) {} + + void Masterlist::Update(Game& game, const unsigned int language) { + this->MetadataList::Load(game.MasterlistPath()); + } +} + ////////////////////////////// // Error Handling Functions From 3a3361b9197bb926b879e87e630783d854faf163 Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 14 Jul 2014 10:58:54 +0100 Subject: [PATCH 08/12] Removed use of unnecessary current game reference. --- src/gui/main.cpp | 112 +++++++++++++++++++++++------------------------ src/gui/main.h | 4 +- 2 files changed, 57 insertions(+), 59 deletions(-) diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 1b4d6f1d..24fd2c13 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -294,14 +294,12 @@ bool LOOT::OnInit() { return false; } } - loot::Game & _game(_games[gameIndex]); - BOOST_LOG_TRIVIAL(debug) << "Game selected is " << _game.Name(); + BOOST_LOG_TRIVIAL(debug) << "Game selected is " << _games[gameIndex].Name(); //Now that game is selected, initialise it. BOOST_LOG_TRIVIAL(debug) << "Initialising game-specific settings."; try { - _game.Init(); - *find(_games.begin(), _games.end(), _game) = _game; //Sync changes. + _games[gameIndex].Init(); } catch (std::exception& e) { BOOST_LOG_TRIVIAL(error) << "Game-specific settings could not be initialised. " << e.what(); wxMessageBox( @@ -339,7 +337,7 @@ bool LOOT::OnInit() { //Create launcher window. BOOST_LOG_TRIVIAL(debug) << "Opening the main LOOT window."; - Launcher * launcher = new Launcher(wxT("LOOT"), _settings, &_game, _games, pos, size); + Launcher * launcher = new Launcher(wxT("LOOT"), _settings, _games, gameIndex, pos, size); launcher->SetIcon(wxIconLocation("LOOT.exe")); launcher->Show(); @@ -348,7 +346,7 @@ bool LOOT::OnInit() { return true; } -Launcher::Launcher(const wxChar *title, YAML::Node& settings, Game * game, vector& games, wxPoint pos, wxSize size) : wxFrame(nullptr, wxID_ANY, title, pos, size), _game(game), _settings(settings), _games(games) { +Launcher::Launcher(const wxChar *title, YAML::Node& settings, vector& games, size_t currentGame, wxPoint pos, wxSize size) : wxFrame(nullptr, wxID_ANY, title, pos, size), _settings(settings), _games(games), _currentGame(currentGame) { //Initialise menu items. wxMenuBar * MenuBar = new wxMenuBar(); @@ -378,7 +376,7 @@ Launcher::Launcher(const wxChar *title, YAML::Node& settings, Game * game, vecto //Game menu - set up initial item states here too. for (size_t i=0,max=_games.size(); i < max; ++i) { wxMenuItem * item = GameMenu->AppendRadioItem(MENU_LowestDynamicGameID + i, FromUTF8(_games[i].Name())); - if (*_game == _games[i]) + if (_games[_currentGame] == _games[i]) item->Check(); if (_games[i].IsInstalled()) @@ -425,13 +423,13 @@ Launcher::Launcher(const wxChar *title, YAML::Node& settings, Game * game, vecto if (!fs::exists(g_path_report)) ViewButton->Enable(false); - if (_game->Id() == loot::Game::tes5) + if (_games[_currentGame].Id() == loot::Game::tes5) RedatePluginsItem->Enable(true); else RedatePluginsItem->Enable(false); //Set title bar text. - SetTitle(FromUTF8("LOOT - " + _game->Name())); + SetTitle(FromUTF8("LOOT - " + _games[_currentGame].Name())); //Now set the layout and sizes. SetMenuBar(MenuBar); @@ -465,7 +463,7 @@ void Launcher::OnClose(wxCloseEvent& event) { _settings["windows"]["main"] = main; //Record game settings. - _settings["Last Game"] = _game->FolderName(); + _settings["Last Game"] = _games[_currentGame].FolderName(); _settings["Games"] = _games; //Save settings. @@ -495,7 +493,7 @@ void Launcher::OnViewLastReport(wxCommandEvent& event) { } //Create viewer window. BOOST_LOG_TRIVIAL(debug) << "Opening viewer window..."; - Viewer *viewer = new Viewer(this, translate("LOOT: Report Viewer"), FromUTF8(ToFileURL(g_path_report.string() + "?data=" + _game->ReportDataPath().string())), pos, size, _settings); + Viewer *viewer = new Viewer(this, translate("LOOT: Report Viewer"), FromUTF8(ToFileURL(g_path_report.string() + "?data=" + _games[_currentGame].ReportDataPath().string())), pos, size, _settings); viewer->Show(); BOOST_LOG_TRIVIAL(debug) << "Report displayed."; } @@ -526,10 +524,10 @@ void Launcher::OnOpenSettings(wxCommandEvent& event) { void Launcher::OnGameChange(wxCommandEvent& event) { BOOST_LOG_TRIVIAL(debug) << "Changing current game..."; - _game = &_games[event.GetId() - MENU_LowestDynamicGameID]; + _currentGame = event.GetId() - MENU_LowestDynamicGameID; try { - _game->Init(); //In case it hasn't already been done. - BOOST_LOG_TRIVIAL(debug) << "New game is " << _game->Name(); + _games[_currentGame].Init(); //In case it hasn't already been done. + BOOST_LOG_TRIVIAL(debug) << "New game is " << _games[_currentGame].Name(); } catch (std::exception& e) { BOOST_LOG_TRIVIAL(error) << "Game-specific settings could not be initialised." << e.what(); @@ -539,8 +537,8 @@ void Launcher::OnGameChange(wxCommandEvent& event) { wxOK | wxICON_ERROR, nullptr); } - SetTitle(FromUTF8("LOOT - " + _game->Name())); - if (_game->Id() == loot::Game::tes5) + SetTitle(FromUTF8("LOOT - " + _games[_currentGame].Name())); + if (_games[_currentGame].Id() == loot::Game::tes5) RedatePluginsItem->Enable(true); else RedatePluginsItem->Enable(false); @@ -626,23 +624,23 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { group.create_thread([this, lang, &messages]() { try { - this->_game->masterlist.Load(*this->_game, lang); + this->_games[_currentGame].masterlist.Load(this->_games[_currentGame], lang); } catch (exception &e) { messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist parsing failed. Details: %1%")) % e.what()).str())); } }); group.create_thread([this]() { - this->_game->LoadPlugins(false); + this->_games[_currentGame].LoadPlugins(false); }); group.join_all(); //Now load userlist. - if (fs::exists(_game->UserlistPath())) { - BOOST_LOG_TRIVIAL(debug) << "Parsing userlist at: " << _game->UserlistPath(); + if (fs::exists(_games[_currentGame].UserlistPath())) { + BOOST_LOG_TRIVIAL(debug) << "Parsing userlist at: " << _games[_currentGame].UserlistPath(); try { - _game->userlist.Load(_game->UserlistPath()); + _games[_currentGame].userlist.Load(_games[_currentGame].UserlistPath()); } catch (exception& e) { BOOST_LOG_TRIVIAL(error) << "Userlist parsing failed. Details: " << e.what(); messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("Userlist parsing failed. Details: %1%")) % e.what()).str())); @@ -657,17 +655,17 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { //Merge all global message lists. BOOST_LOG_TRIVIAL(debug) << "Merging all global message lists."; - if (!_game->masterlist.messages.empty()) - messages.insert(messages.end(), _game->masterlist.messages.begin(), _game->masterlist.messages.end()); - if (!_game->userlist.messages.empty()) - messages.insert(messages.end(), _game->userlist.messages.begin(), _game->userlist.messages.end()); + if (!_games[_currentGame].masterlist.messages.empty()) + messages.insert(messages.end(), _games[_currentGame].masterlist.messages.begin(), _games[_currentGame].masterlist.messages.end()); + if (!_games[_currentGame].userlist.messages.empty()) + messages.insert(messages.end(), _games[_currentGame].userlist.messages.begin(), _games[_currentGame].userlist.messages.end()); //Evaluate any conditions in the global messages. BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions."; try { list::iterator it=messages.begin(); while (it != messages.end()) { - if (!it->EvalCondition(*_game, lang)) + if (!it->EvalCondition(_games[_currentGame], lang)) it = messages.erase(it); else ++it; @@ -696,7 +694,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { //Check for back-edges, then perform a topological sort. list plugins; - for (auto &plugin : _game->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 @@ -704,9 +702,9 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { 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(_game->masterlist.plugins.begin(), _game->masterlist.plugins.end(), plugins.back()); + list::iterator pos = std::find(_games[_currentGame].masterlist.plugins.begin(), _games[_currentGame].masterlist.plugins.end(), plugins.back()); - if (pos != _game->masterlist.plugins.end()) { + if (pos != _games[_currentGame].masterlist.plugins.end()) { BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; plugins.back().MergeMetadata(*pos); } @@ -727,9 +725,9 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { 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(_game->userlist.plugins.begin(), _game->userlist.plugins.end(), graph[*vit]); + list::iterator pos = std::find(_games[_currentGame].userlist.plugins.begin(), _games[_currentGame].userlist.plugins.end(), graph[*vit]); - if (pos != _game->userlist.plugins.end() && pos->Enabled()) { + 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); } @@ -739,7 +737,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { //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); + 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(); @@ -750,7 +748,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { //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); + graph[*vit].CheckInstallValidity(_games[_currentGame]); progDia->Pulse(); } @@ -795,7 +793,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { GetWindowSizePos(_settings["windows"]["editor"], pos, size); } - MiniEditor editor(this, translate("LOOT: Calculated Load Order"), pos, size, plugins, _game->userlist.plugins, *_game); + MiniEditor editor(this, translate("LOOT: Calculated Load Order"), pos, size, plugins, _games[_currentGame].userlist.plugins, _games[_currentGame]); long ret = editor.ShowModal(); const std::list& newUserlist = editor.GetNewUserlist(); @@ -811,14 +809,14 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { //Need to determine if any new edits have been made. bool haveNewEdits = false; - if (newUserlist.size() != _game->userlist.plugins.size()) { + if (newUserlist.size() != _games[_currentGame].userlist.plugins.size()) { BOOST_LOG_TRIVIAL(info) << "Metadata edited for some plugin, new and old userlists differ in size."; haveNewEdits = true; } else { for (const auto& newEdit : newUserlist) { - const auto it = std::find(_game->userlist.plugins.begin(), _game->userlist.plugins.end(), newEdit); - if (it == _game->userlist.plugins.end()) { + const auto it = std::find(_games[_currentGame].userlist.plugins.begin(), _games[_currentGame].userlist.plugins.end(), newEdit); + if (it == _games[_currentGame].userlist.plugins.end()) { BOOST_LOG_TRIVIAL(info) << "Metadata added for plugin: " << it->Name(); haveNewEdits = true; break; @@ -845,17 +843,17 @@ 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. - _game->userlist.plugins = newUserlist; + _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 << _game->userlist.plugins + << YAML::Key << "plugins" << YAML::Value << _games[_currentGame].userlist.plugins << YAML::EndMap; - loot::ofstream uout(_game->UserlistPath()); + loot::ofstream uout(_games[_currentGame].UserlistPath()); uout << yout.c_str(); uout.close(); @@ -867,7 +865,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { //Applying the load order. BOOST_LOG_TRIVIAL(debug) << "Setting load order."; try { - _game->SetLoadOrder(plugins); + _games[_currentGame].SetLoadOrder(plugins); } catch (std::exception& e) { BOOST_LOG_TRIVIAL(error) << "Failed to set the load order. Details: " << e.what(); @@ -898,11 +896,11 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { BOOST_LOG_TRIVIAL(debug) << "Generating report..."; try { - GenerateReportData(*_game, + GenerateReportData(_games[_currentGame], messages, plugins, - _game->masterlist.GetRevision(_game->MasterlistPath()), - _game->masterlist.GetDate(_game->MasterlistPath()), + _games[_currentGame].masterlist.GetRevision(_games[_currentGame].MasterlistPath()), + _games[_currentGame].masterlist.GetDate(_games[_currentGame].MasterlistPath()), true); } catch (std::exception& e) { wxMessageBox( @@ -927,7 +925,7 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { } //Create viewer window. - Viewer *viewer = new Viewer(this, translate("LOOT: Report Viewer"), FromUTF8(ToFileURL(g_path_report.string() + "?data=" + _game->ReportDataPath().string())), pos, size, _settings); + Viewer *viewer = new Viewer(this, translate("LOOT: Report Viewer"), FromUTF8(ToFileURL(g_path_report.string() + "?data=" + _games[_currentGame].ReportDataPath().string())), pos, size, _settings); viewer->Show(); BOOST_LOG_TRIVIAL(debug) << "Report display successful. Sorting process complete."; @@ -949,36 +947,36 @@ void Launcher::OnEditMetadata(wxCommandEvent& event) { //Scan for installed plugins. BOOST_LOG_TRIVIAL(debug) << "Reading installed plugins' headers."; - _game->LoadPlugins(true); + _games[_currentGame].LoadPlugins(true); //Sort plugins into their load order. list loadOrder; - _game->GetLoadOrder(loadOrder); + _games[_currentGame].GetLoadOrder(loadOrder); for (const auto &pluginName: loadOrder) { - const auto pos = _game->plugins.find(pluginName); + const auto pos = _games[_currentGame].plugins.find(pluginName); - if (pos != _game->plugins.end()) + if (pos != _games[_currentGame].plugins.end()) installed.push_back(pos->second); } //Parse masterlist. - if (fs::exists(_game->MasterlistPath())) { + if (fs::exists(_games[_currentGame].MasterlistPath())) { BOOST_LOG_TRIVIAL(debug) << "Parsing masterlist."; - _game->masterlist.Load(*_game, lang); + _games[_currentGame].masterlist.Load(_games[_currentGame], lang); } progDia->Pulse(); //Parse userlist. - if (fs::exists(_game->UserlistPath())) { + if (fs::exists(_games[_currentGame].UserlistPath())) { BOOST_LOG_TRIVIAL(debug) << "Parsing userlist."; - _game->userlist.Load(_game->UserlistPath()); + _games[_currentGame].userlist.Load(_games[_currentGame].UserlistPath()); } progDia->Pulse(); //Merge the masterlist down into the installed mods list. BOOST_LOG_TRIVIAL(debug) << "Merging the masterlist down into the installed mods list."; - for (const auto &plugin: _game->masterlist.plugins) { + for (const auto &plugin: _games[_currentGame].masterlist.plugins) { auto pos = find(installed.begin(), installed.end(), plugin); if (pos != installed.end()) @@ -989,7 +987,7 @@ void Launcher::OnEditMetadata(wxCommandEvent& event) { //Add empty entries for any userlist entries that aren't installed. BOOST_LOG_TRIVIAL(debug) << "Padding the installed mods list to match the plugins in the userlist."; - for (const auto &plugin : _game->userlist.plugins) { + for (const auto &plugin : _games[_currentGame].userlist.plugins) { if (find(installed.begin(), installed.end(), plugin) == installed.end()) installed.push_back(loot::Plugin(plugin.Name())); } @@ -1005,7 +1003,7 @@ void Launcher::OnEditMetadata(wxCommandEvent& event) { //Create editor window. BOOST_LOG_TRIVIAL(debug) << "Opening editor window."; - FullEditor *editor = new FullEditor(this, translate("LOOT: Metadata Editor"), pos, size, _game->UserlistPath().string(), installed, _game->userlist.plugins, lang, *_game, _settings); + FullEditor *editor = new FullEditor(this, translate("LOOT: Metadata Editor"), pos, size, _games[_currentGame].UserlistPath().string(), installed, _games[_currentGame].userlist.plugins, lang, _games[_currentGame], _settings); progDia->Destroy(); @@ -1019,7 +1017,7 @@ void Launcher::OnRedatePlugins(wxCommandEvent& event) { if (dia->ShowModal() == wxID_YES) { BOOST_LOG_TRIVIAL(debug) << "Redating plugins."; try { - _game->RedatePlugins(); + _games[_currentGame].RedatePlugins(); } catch (std::exception& e) { BOOST_LOG_TRIVIAL(error) << "Failed to redate plugins. " << e.what(); wxMessageBox( diff --git a/src/gui/main.h b/src/gui/main.h index a854de19..ea646ca3 100644 --- a/src/gui/main.h +++ b/src/gui/main.h @@ -45,7 +45,7 @@ private: class Launcher : public wxFrame { public: - Launcher(const wxChar *title, YAML::Node& settings, loot::Game * inGame, std::vector& games, wxPoint pos, wxSize size); + Launcher(const wxChar *title, YAML::Node& settings, std::vector& games, size_t currentGame, wxPoint pos, wxSize size); void OnSortPlugins(wxCommandEvent& event); void OnEditMetadata(wxCommandEvent& event); @@ -65,9 +65,9 @@ private: wxMenuItem * RedatePluginsItem; wxButton * ViewButton; - loot::Game * _game; YAML::Node& _settings; //LOOT Settings. std::vector& _games; + size_t _currentGame; void GetWindowSizePos(const YAML::Node& node, wxPoint& pos, wxSize& size); }; From dc8f616bcb637f2fd4158aaf820b555a19eedf27 Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 14 Jul 2014 14:22:39 +0100 Subject: [PATCH 09/12] Started to refactor plugin sorting code. --- CMakeLists.txt | 3 +- src/backend/game.h | 2 + src/backend/sort.cpp | 108 +++++++++++++++++++++++++++++++++++++++++++ src/gui/main.cpp | 65 +++----------------------- 4 files changed, 119 insertions(+), 59 deletions(-) create mode 100644 src/backend/sort.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 42e92acc..a51f7768 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,8 @@ set (LOOT_SRC "${CMAKE_SOURCE_DIR}/src/backend/metadata.cpp" "${CMAKE_SOURCE_DIR}/src/backend/game.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/generators.cpp" + "${CMAKE_SOURCE_DIR}/src/backend/sort.cpp") set (LOOT_GUI_SRC ${LOOT_SRC} # Code the API doesn't need. diff --git a/src/backend/game.h b/src/backend/game.h index e67e9b47..99fdaedf 100644 --- a/src/backend/game.h +++ b/src/backend/game.h @@ -119,6 +119,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); + //Caches for condition results, active plugins and CRCs. std::unordered_map conditionCache; //Holds lowercased strings. std::unordered_map crcCache; //Holds lowercased strings. diff --git a/src/backend/sort.cpp b/src/backend/sort.cpp new file mode 100644 index 00000000..2ad231af --- /dev/null +++ b/src/backend/sort.cpp @@ -0,0 +1,108 @@ +/* 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 +. +*/ + +#include "game.h" +#include "helpers.h" + +#include +#include +#include +#include + +using namespace std; + +using boost::format; + +namespace loc = boost::locale; +namespace fs = boost::filesystem; + +namespace loot { + + 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(); + + /////////////////////////////////////////////////////// + // Load Plugins & Lists + /////////////////////////////////////////////////////// + + callback("Reading installed plugins..."); + + group.create_thread([this, language, &messages]() { + try { + this->masterlist.Load(*this, language); + } + catch (exception &e) { + messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist parsing failed. Details: %1%")) % e.what()).str())); + } + }); + group.create_thread([this]() { + this->LoadPlugins(false); + }); + group.join_all(); + + //Now load userlist. + if (fs::exists(this->UserlistPath())) { + BOOST_LOG_TRIVIAL(debug) << "Parsing userlist at: " << this->UserlistPath(); + + try { + this->userlist.Load(this->UserlistPath()); + } + catch (exception& e) { + BOOST_LOG_TRIVIAL(error) << "Userlist parsing failed. Details: " << e.what(); + messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("Userlist parsing failed. Details: %1%")) % e.what()).str())); + } + } + + /////////////////////////////////////////////////////// + // Evaluate Global Messages + /////////////////////////////////////////////////////// + + callback("Evaluating global messages..."); + + //Merge all global message lists. + BOOST_LOG_TRIVIAL(debug) << "Merging all global message lists."; + if (!this->masterlist.messages.empty()) + messages.insert(messages.end(), this->masterlist.messages.begin(), this->masterlist.messages.end()); + if (!this->userlist.messages.empty()) + messages.insert(messages.end(), this->userlist.messages.begin(), this->userlist.messages.end()); + + //Evaluate any conditions in the global messages. + BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions."; + try { + list::iterator it = messages.begin(); + while (it != messages.end()) { + if (!it->EvalCondition(*this, language)) + it = messages.erase(it); + else + ++it; + } + } + catch (std::exception& e) { + 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())); + } + } +} \ No newline at end of file diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 24fd2c13..d8584ab3 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -605,14 +605,9 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { BOOST_LOG_TRIVIAL(debug) << "Beginning sorting process."; list messages; - boost::thread_group group; unsigned int lang; - wxProgressDialog *progDia = new wxProgressDialog(translate("LOOT: Working..."),translate("LOOT working..."), 1000, this, wxPD_APP_MODAL|wxPD_AUTO_HIDE|wxPD_ELAPSED_TIME); - - /////////////////////////////////////////////////////// - // Load Plugins & Lists - /////////////////////////////////////////////////////// + wxProgressDialog *progDia = new wxProgressDialog(translate("LOOT: Working..."), translate("LOOT working..."), 1000, this, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME); //Set language. if (_settings["Language"]) @@ -622,61 +617,13 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(lang).Name(); - group.create_thread([this, lang, &messages]() { - try { - this->_games[_currentGame].masterlist.Load(this->_games[_currentGame], lang); - } - catch (exception &e) { - messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("Masterlist parsing failed. Details: %1%")) % e.what()).str())); - } - }); - group.create_thread([this]() { - this->_games[_currentGame].LoadPlugins(false); - }); - group.join_all(); - - //Now load userlist. - if (fs::exists(_games[_currentGame].UserlistPath())) { - BOOST_LOG_TRIVIAL(debug) << "Parsing userlist at: " << _games[_currentGame].UserlistPath(); - - try { - _games[_currentGame].userlist.Load(_games[_currentGame].UserlistPath()); - } catch (exception& e) { - BOOST_LOG_TRIVIAL(error) << "Userlist parsing failed. Details: " << e.what(); - messages.push_back(loot::Message(loot::Message::error, (format(loc::translate("Userlist parsing failed. Details: %1%")) % e.what()).str())); - } - } - - progDia->Pulse(); - /////////////////////////////////////////////////////// - // Merge & Check Metadata + // Load Plugins & Lists /////////////////////////////////////////////////////// - //Merge all global message lists. - BOOST_LOG_TRIVIAL(debug) << "Merging all global message lists."; - if (!_games[_currentGame].masterlist.messages.empty()) - messages.insert(messages.end(), _games[_currentGame].masterlist.messages.begin(), _games[_currentGame].masterlist.messages.end()); - if (!_games[_currentGame].userlist.messages.empty()) - messages.insert(messages.end(), _games[_currentGame].userlist.messages.begin(), _games[_currentGame].userlist.messages.end()); - - //Evaluate any conditions in the global messages. - BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions."; - try { - list::iterator it=messages.begin(); - while (it != messages.end()) { - if (!it->EvalCondition(_games[_currentGame], lang)) - it = messages.erase(it); - else - ++it; - } - } - catch (std::exception& e) { - 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())); - } - - progDia->Update(800, translate("Building plugin graph...")); + _games[_currentGame].SortPlugins(lang, messages, [progDia](const std::string& message) { + progDia->Pulse(FromUTF8(message)); + }); /////////////////////////////////////////////////////// // Build Graph Edges & Sort @@ -692,6 +639,8 @@ 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) { From ba1bb8b877dab0ba5b6d5c275fa1fa3e4fc743dc Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 14 Jul 2014 17:03:27 +0100 Subject: [PATCH 10/12] 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. } From bdfb13c82ba93cb01bce377d677ead2133ef7ba1 Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 14 Jul 2014 19:43:37 +0100 Subject: [PATCH 11/12] Refactored game selection code. --- src/backend/game.cpp | 28 ++++++++++++++++++++++---- src/backend/game.h | 2 ++ src/gui/main.cpp | 48 ++++++++++---------------------------------- 3 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/backend/game.cpp b/src/backend/game.cpp index 18ed5ef9..e29e08ac 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -62,6 +62,26 @@ namespace loot { return games; } + size_t SelectGame(const YAML::Node& settings, const std::vector& games, const std::string& cmdLineGame) { + string preferredGame(cmdLineGame); + if (preferredGame.empty()) { + // Get preferred game from settings. + if (settings["Game"] && settings["Game"].as() != "auto") + preferredGame = settings["Game"].as(); + else if (settings["Last Game"] && settings["Last Game"].as() != "auto") + preferredGame = settings["Last Game"].as(); + } + + // Get index of preferred game if there is one. + for (size_t i = 0; i < games.size(); ++i) { + if (preferredGame.empty() && games[i].IsInstalled()) + return i; + else if (!preferredGame.empty() && preferredGame == games[i].FolderName() && games[i].IsInstalled()) + return i; + } + throw error(error::no_game_detected, "None of the supported games were detected."); + } + // MetadataList member functions //------------------------------ @@ -588,20 +608,20 @@ namespace loot { void Game::LoadPlugins(bool headersOnly) { boost::thread_group group; - size_t meanFileSize = 0; - unordered_map tempMap; + uintmax_t meanFileSize = 0; + unordered_map tempMap; std::vector groupPlugins; //First calculate the mean plugin size. Store it temporarily in a map to reduce filesystem lookups and file size recalculation. for (fs::directory_iterator it(this->DataPath()); it != fs::directory_iterator(); ++it) { if (fs::is_regular_file(it->status()) && IsPlugin(it->path().string())) { - size_t fileSize = fs::file_size(it->path()); + uintmax_t fileSize = fs::file_size(it->path()); meanFileSize += fileSize; tempMap.emplace(it->path().filename().string(), fileSize); } } - meanFileSize /= tempMap.size(); + meanFileSize /= tempMap.size(); //Rounding error, but not important. //Now load plugins. for (const auto &pluginPair : tempMap) { diff --git a/src/backend/game.h b/src/backend/game.h index c15f13a6..540c53b1 100644 --- a/src/backend/game.h +++ b/src/backend/game.h @@ -157,6 +157,8 @@ namespace loot { }; std::vector GetGames(const YAML::Node& settings); + + size_t SelectGame(const YAML::Node& settings, const std::vector& games, const std::string& cmdLineGame); } #endif diff --git a/src/gui/main.cpp b/src/gui/main.cpp index bc595d80..15ea9edf 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -34,18 +34,12 @@ #include "../backend/helpers.h" #include "../backend/generators.h" #include "../backend/streams.h" -//#include "../backend/graph.h" -#include #include -#include -#include #include -#include #include #include -#include #include #include #include @@ -54,7 +48,6 @@ #include #include #include -#include #include #include @@ -237,7 +230,6 @@ bool LOOT::OnInit() { BOOST_LOG_TRIVIAL(debug) << "Selecting game."; string target; - int gameIndex = -1; wxCmdLineEntryDesc cmdLineDesc[2]; cmdLineDesc[0].kind = wxCMD_LINE_OPTION; @@ -263,36 +255,18 @@ bool LOOT::OnInit() { break; } - if (target.empty()) { - if (_settings["Game"] && _settings["Game"].as() != "auto") - target = _settings["Game"].as(); - else if (_settings["Last Game"] && _settings["Last Game"].as() != "auto") - target = _settings["Last Game"].as(); + size_t gameIndex(0); + try { + gameIndex = SelectGame(_settings, _games, target); } - - if (!target.empty()) { - for (size_t i=0, max=_games.size(); i < max; ++i) { - if (target == _games[i].FolderName() && _games[i].IsInstalled()) - gameIndex = i; - } - } - if (gameIndex < 0) { - //Set gameIndex to the first installed game. - for (size_t i=0, max=_games.size(); i < max; ++i) { - if (_games[i].IsInstalled()) { - gameIndex = i; - break; - } - } - if (gameIndex < 0) { - BOOST_LOG_TRIVIAL(error) << "None of the supported games were detected."; - wxMessageBox( - translate("Error: None of the supported games were detected."), - translate("LOOT: Error"), - wxOK | wxICON_ERROR, - nullptr); - return false; - } + catch (exception &e) { + BOOST_LOG_TRIVIAL(error) << "None of the supported games were detected."; + wxMessageBox( + translate("Error: None of the supported games were detected."), + translate("LOOT: Error"), + wxOK | wxICON_ERROR, + nullptr); + return false; } BOOST_LOG_TRIVIAL(debug) << "Game selected is " << _games[gameIndex].Name(); From 67fa220f1a8c18f203a254861473814c07b04801 Mon Sep 17 00:00:00 2001 From: WrinklyNinja Date: Mon, 14 Jul 2014 20:25:13 +0100 Subject: [PATCH 12/12] Refactored userlist comparison code. Also improved editor userlist output, and some const correctness improvements. --- src/backend/game.cpp | 40 ++++++++++++++++++++++++++++++++++++---- src/backend/game.h | 12 +++++++----- src/backend/git.cpp | 2 +- src/gui/editor.cpp | 23 ++++++++--------------- src/gui/editor.h | 10 ++++++---- src/gui/main.cpp | 34 ++++++---------------------------- 6 files changed, 64 insertions(+), 57 deletions(-) diff --git a/src/backend/game.cpp b/src/backend/game.cpp index e29e08ac..b40ffd56 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -85,7 +85,7 @@ namespace loot { // MetadataList member functions //------------------------------ - void MetadataList::Load(boost::filesystem::path& filepath) { + void MetadataList::Load(const boost::filesystem::path& filepath) { plugins.clear(); messages.clear(); @@ -103,7 +103,7 @@ namespace loot { BOOST_LOG_TRIVIAL(debug) << "File loaded successfully."; } - void MetadataList::Save(boost::filesystem::path& filepath) { + void MetadataList::Save(const boost::filesystem::path& filepath) { YAML::Emitter yout; yout.SetIndent(2); yout << YAML::BeginMap @@ -116,6 +116,38 @@ namespace loot { uout.close(); } + bool MetadataList::operator == (const MetadataList& rhs) const { + if (this->plugins.size() != rhs.plugins.size() || this->messages.size() != rhs.messages.size()) { + BOOST_LOG_TRIVIAL(info) << "Metadata edited for some plugin, new and old userlists differ in size."; + return false; + } + else { + for (const auto& rhsPlugin : rhs.plugins) { + const auto it = std::find(this->plugins.begin(), this->plugins.end(), rhsPlugin); + + if (it == this->plugins.end()) { + BOOST_LOG_TRIVIAL(info) << "Metadata added for plugin: " << it->Name(); + return false; + } + + if (!it->DiffMetadata(rhsPlugin).HasNameOnly()) { + BOOST_LOG_TRIVIAL(info) << "Metadata edited for plugin: " << it->Name(); + return false; + } + } + // Messages are compared exactly by the '==' operator, so there's no need to do a more + // fine-grained check. + for (const auto& rhsMessage : rhs.messages) { + const auto it = std::find(this->messages.begin(), this->messages.end(), rhsMessage); + + if (it == this->messages.end()) { + return false; + } + } + } + return true; + } + // Masterlist member functions //---------------------------- @@ -135,14 +167,14 @@ namespace loot { } } - std::string Masterlist::GetRevision(boost::filesystem::path& path) { + std::string Masterlist::GetRevision(const boost::filesystem::path& path) { if (revision.empty()) GetGitInfo(path); return revision; } - std::string Masterlist::GetDate(boost::filesystem::path& path) { + std::string Masterlist::GetDate(const boost::filesystem::path& path) { if (date.empty()) GetGitInfo(path); diff --git a/src/backend/game.h b/src/backend/game.h index 540c53b1..7da286de 100644 --- a/src/backend/game.h +++ b/src/backend/game.h @@ -55,8 +55,10 @@ namespace loot { class MetadataList { public: - void Load(boost::filesystem::path& filepath); - void Save(boost::filesystem::path& filepath); + void Load(const boost::filesystem::path& filepath); + void Save(const boost::filesystem::path& filepath); + + bool operator == (const MetadataList& rhs) const; //Compares content. std::list plugins; std::list messages; @@ -68,11 +70,11 @@ namespace loot { void Load(Game& game, const unsigned int language); //Handles update with load fallback. void Update(Game& game, const unsigned int language); - std::string GetRevision(boost::filesystem::path& path); - std::string GetDate(boost::filesystem::path& path); + std::string GetRevision(const boost::filesystem::path& path); + std::string GetDate(const boost::filesystem::path& path); private: - void GetGitInfo(boost::filesystem::path& path); + void GetGitInfo(const boost::filesystem::path& path); std::string revision; std::string date; diff --git a/src/backend/git.cpp b/src/backend/git.cpp index e28b0ff3..8202403e 100644 --- a/src/backend/git.cpp +++ b/src/backend/git.cpp @@ -109,7 +109,7 @@ namespace loot { return git_repository_open_ext(NULL, path.string().c_str(), GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) == 0; } - void Masterlist::GetGitInfo(boost::filesystem::path& path) { + void Masterlist::GetGitInfo(const boost::filesystem::path& path) { if (!fs::exists(path.parent_path() / ".git")) { revision = "Unknown: Git repository missing"; date = "Unknown: Git repository missing"; diff --git a/src/gui/editor.cpp b/src/gui/editor.cpp index 7b45d834..1d9d78c1 100644 --- a/src/gui/editor.cpp +++ b/src/gui/editor.cpp @@ -1041,8 +1041,10 @@ void EditorPanel::ApplyCurrentEdits() { ApplyEdits(currentPlugin); } -const std::list& EditorPanel::GetNewUserlist() const { - return _editedPlugins; +loot::MetadataList EditorPanel::GetNewUserlist() const { + loot::MetadataList newUserlist; + newUserlist.plugins = _editedPlugins; + return newUserlist; } loot::Plugin EditorPanel::GetMasterData(const wxString& plugin) const { @@ -1197,7 +1199,7 @@ void MiniEditor::OnResize(wxSizeEvent& event) { event.Skip(); } -const std::list& MiniEditor::GetNewUserlist() const { +loot::MetadataList MiniEditor::GetNewUserlist() const { return editorPanel->GetNewUserlist(); } @@ -1206,7 +1208,7 @@ const std::list& MiniEditor::GetNewUserlist() const { // Full Editor Class /////////////////////////////////// -FullEditor::FullEditor(wxWindow *parent, const wxString& title, wxPoint pos, wxSize size, const std::string userlistPath, const std::list& basePlugins, std::list& editedPlugins, const unsigned int language, const loot::Game& game, YAML::Node &settings) : wxFrame(parent, wxID_ANY, title, pos, size), _userlistPath(userlistPath), _settings(settings) { +FullEditor::FullEditor(wxWindow *parent, const wxString& title, wxPoint pos, wxSize size, const boost::filesystem::path& userlistPath, const std::list& basePlugins, std::list& editedPlugins, const unsigned int language, const loot::Game& game, YAML::Node &settings) : wxFrame(parent, wxID_ANY, title, pos, size), _userlistPath(userlistPath), _settings(settings) { //Set up content. editorPanel = new EditorPanel(this, basePlugins, editedPlugins, language, game); applyBtn = new wxButton(this, BUTTON_Apply, translate("Save Changes")); @@ -1245,17 +1247,8 @@ void FullEditor::OnQuit(wxCommandEvent& event) { BOOST_LOG_TRIVIAL(debug) << "Saving metadata edits to userlist."; - //Save edits to userlist. - YAML::Emitter yout; - yout.SetIndent(2); - yout << YAML::BeginMap - << YAML::Key << "plugins" << YAML::Value << editorPanel->GetNewUserlist() - << YAML::EndMap; - - boost::filesystem::path p(_userlistPath); - loot::ofstream out(p); - out << yout.c_str(); - out.close(); + loot::MetadataList userlist = editorPanel->GetNewUserlist(); + userlist.Save(_userlistPath); } Close(); } diff --git a/src/gui/editor.h b/src/gui/editor.h index 21e1971a..ef18a0cc 100644 --- a/src/gui/editor.h +++ b/src/gui/editor.h @@ -26,7 +26,9 @@ #include "ids.h" #include "misc.h" + #include "../backend/metadata.h" +#include "../backend/game.h" #include #include @@ -69,7 +71,7 @@ public: void SetSimpleView(bool on = true); void ApplyCurrentEdits(); - const std::list& GetNewUserlist() const; + loot::MetadataList GetNewUserlist() const; void OnPluginSelect(wxListEvent& event); void OnPluginListRightClick(wxListEvent& event); @@ -134,7 +136,7 @@ public: void OnApply(wxCommandEvent& event); void OnResize(wxSizeEvent& event); - const std::list& GetNewUserlist() const; + loot::MetadataList GetNewUserlist() const; private: EditorPanel * editorPanel; wxStaticText * descText; @@ -144,7 +146,7 @@ private: class FullEditor : public wxFrame { public: - FullEditor(wxWindow *parent, const wxString& title, wxPoint pos, wxSize size, const std::string userlistPath, const std::list& basePlugins, std::list& editedPlugins, const unsigned int language, const loot::Game& game, YAML::Node &settings); + FullEditor(wxWindow *parent, const wxString& title, wxPoint pos, wxSize size, const boost::filesystem::path& userlistPath, const std::list& basePlugins, std::list& editedPlugins, const unsigned int language, const loot::Game& game, YAML::Node &settings); void OnQuit(wxCommandEvent& event); void OnClose(wxCloseEvent &event); @@ -153,7 +155,7 @@ private: wxButton * applyBtn; wxButton * cancelBtn; - const std::string _userlistPath; + const boost::filesystem::path _userlistPath; YAML::Node& _settings; }; #endif diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 15ea9edf..0c559f40 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -259,7 +259,7 @@ bool LOOT::OnInit() { try { gameIndex = SelectGame(_settings, _games, target); } - catch (exception &e) { + catch (exception &) { BOOST_LOG_TRIVIAL(error) << "None of the supported games were detected."; wxMessageBox( translate("Error: None of the supported games were detected."), @@ -615,13 +615,13 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { */ - //Check for back-edges, then perform a topological sort. list plugins; try { bool applyLoadOrder = false; do { + // Perform sort. plugins = _games[_currentGame].Sort(lang, messages, progressCallback); progDia->Destroy(); @@ -636,10 +636,11 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { GetWindowSizePos(_settings["windows"]["editor"], pos, size); } + // Display mini editor. MiniEditor editor(this, translate("LOOT: Calculated Load Order"), pos, size, plugins, _games[_currentGame].userlist.plugins, _games[_currentGame]); long ret = editor.ShowModal(); - const std::list& newUserlist = editor.GetNewUserlist(); + MetadataList newUserlist = editor.GetNewUserlist(); //Record window settings. YAML::Node node; @@ -650,34 +651,11 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) { _settings["windows"]["editor"] = node; - //Need to determine if any new edits have been made. - bool haveNewEdits = false; - if (newUserlist.size() != _games[_currentGame].userlist.plugins.size()) { - BOOST_LOG_TRIVIAL(info) << "Metadata edited for some plugin, new and old userlists differ in size."; - haveNewEdits = true; - } - else { - for (const auto& newEdit : newUserlist) { - const auto it = std::find(_games[_currentGame].userlist.plugins.begin(), _games[_currentGame].userlist.plugins.end(), newEdit); - if (it == _games[_currentGame].userlist.plugins.end()) { - BOOST_LOG_TRIVIAL(info) << "Metadata added for plugin: " << it->Name(); - haveNewEdits = true; - break; - } - - if (!it->DiffMetadata(newEdit).HasNameOnly()) { - BOOST_LOG_TRIVIAL(info) << "Metadata edited for plugin: " << it->Name(); - haveNewEdits = true; - break; - } - } - } - if (ret != wxID_APPLY) { applyLoadOrder = false; break; } - else if (!haveNewEdits) { + else if (_games[_currentGame].userlist == newUserlist) { applyLoadOrder = true; break; } @@ -686,7 +664,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. - _games[_currentGame].userlist.plugins = newUserlist; + _games[_currentGame].userlist = newUserlist; //Save edits to userlist. _games[_currentGame].userlist.Save(_games[_currentGame].UserlistPath());