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