Issue #39 work. Started to implement graph sort.

Also moved graph-related stuff into a new h/cpp file pair, and fixed the
boss::master_sort function. For some unknown reason, an exception gets
thrown after roughly 180 operations on the graph object, ie. near the
end of the out-edges addition for the first plugin in the list.
This commit is contained in:
WrinklyNinja
2013-08-07 20:03:53 +01:00
parent 94d1a3f2b6
commit 2d307267a2
6 changed files with 242 additions and 129 deletions
+4 -4
View File
@@ -11,9 +11,9 @@
cmake_minimum_required (VERSION 2.8.9)
project (boss)
set (BOSS_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/network.cpp" "${CMAKE_SOURCE_DIR}/src/backend/globals.cpp" "${PROJECT_LIBS_DIR}/pugixml/src/pugixml.cpp")
set (BOSS_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/network.cpp" "${CMAKE_SOURCE_DIR}/src/backend/globals.cpp" "${CMAKE_SOURCE_DIR}/src/backend/graph.cpp")
set (BOSS_SRC ${BOSS_SRC} "${PROJECT_LIBS_DIR}/boost/libs/iostreams/src/file_descriptor.cpp")
set (BOSS_SRC ${BOSS_SRC} "${PROJECT_LIBS_DIR}/pugixml/src/pugixml.cpp" "${PROJECT_LIBS_DIR}/boost/libs/iostreams/src/file_descriptor.cpp")
# Include source and library directories.
include_directories ("${PROJECT_LIBS_DIR}/alphanum" "${PROJECT_LIBS_DIR}/boost" "${PROJECT_LIBS_DIR}/yaml-cpp/include" "${CMAKE_SOURCE_DIR}/src" "${PROJECT_LIBS_DIR}/libloadorder/src" "${PROJECT_LIBS_DIR}/libespm" "${PROJECT_LIBS_DIR}/zlib" "${PROJECT_LIBS_DIR}/pugixml/src" "${PROJECT_LIBS_DIR}/wxWidgets/include" "${PROJECT_LIBS_DIR}/libgit2/include")
@@ -49,8 +49,8 @@ link_directories ("${PROJECT_LIBS_DIR}/wxWidgets/lib")
# Settings when compiling and cross-compiling on Linux.
IF (CMAKE_HOST_SYSTEM_NAME MATCHES "Linux")
set (CMAKE_C_FLAGS "-m${PROJECT_ARCH}")
set (CMAKE_CXX_FLAGS "-m${PROJECT_ARCH}")
set (CMAKE_C_FLAGS "-m${PROJECT_ARCH} -O3")
set (CMAKE_CXX_FLAGS "-m${PROJECT_ARCH} -O3")
set (CMAKE_EXE_LINKER_FLAGS "-static-libstdc++ -static-libgcc")
set (CMAKE_SHARED_LINKER_FLAGS "-static-libstdc++ -static-libgcc")
set (CMAKE_MODULE_LINKER_FLAGS "-static-libstdc++ -static-libgcc")
+87
View File
@@ -0,0 +1,87 @@
/* BOSS
A plugin load order optimiser for games that use the esp/esm plugin system.
Copyright (C) 2012 WrinklyNinja
This file is part of BOSS.
BOSS 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.
BOSS 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 BOSS. If not, see
<http://www.gnu.org/licenses/>.
*/
#include "graph.h"
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
using namespace std;
namespace boss {
vertex_t GetPluginVertex(PluginGraph& graph, const Plugin& plugin, boost::unordered_map<std::string, vertex_t>& pluginVertexMap) {
vertex_t vertex;
string name = boost::to_lower_copy(plugin.Name());
boost::unordered_map<string, vertex_t>::iterator vertexMapIt = pluginVertexMap.find(name);
if (vertexMapIt == pluginVertexMap.end()) {
BOOST_LOG_TRIVIAL(trace) << "Vertex for \"" << name << "\" doesn't exist, creating one.";
Plugin p;
// vertex = boost::add_vertex(p, graph);
BOOST_LOG_TRIVIAL(trace) << "Adding vertex to map.";
pluginVertexMap.emplace(name, vertex);
} else
vertex = vertexMapIt->second;
return vertex;
}
//The map maps each plugin name to a vector of names of plugins that overlap with it and should load before it.
void CalcPluginOverlaps(const std::list<Plugin>& plugins, boost::unordered_map< std::string, std::vector<list<Plugin>::const_iterator> >& overlapMap) {
for (list<Plugin>::const_iterator it=plugins.begin(),
endit=plugins.end();
it != endit;
++it) {
list<Plugin>::const_iterator jt = it;
++jt;
for (jt, endit; jt != endit; ++jt) {
BOOST_LOG_TRIVIAL(trace) << "Checking for FormID overlap between \"" << it->Name() << "\" and \"" << jt->Name() << "\".";
if (it->DoFormIDsOverlap(*jt)) {
std::string key;
list<Plugin>::const_iterator value;
//Priority values should override the number of override records as the deciding factor if they differ.
if (it->Priority() < jt->Priority()) {
key = jt->Name();
value = it;
} else if (jt->Priority() < it->Priority()) {
key = it->Name();
value = jt;
} else if (it->NumOverrideFormIDs() >= jt->NumOverrideFormIDs()) {
key = jt->Name();
value = it;
} else {
key = it->Name();
value = jt;
}
boost::unordered_map< string, vector<list<Plugin>::const_iterator> >::iterator mapIt = overlapMap.find(key);
if (mapIt == overlapMap.end()) {
overlapMap.insert(pair<string, vector<list<Plugin>::const_iterator> >(key, vector<list<Plugin>::const_iterator>(1, value)));
} else {
mapIt->second.push_back(value);
}
}
}
}
}
}
+47
View File
@@ -0,0 +1,47 @@
/* BOSS
A plugin load order optimiser for games that use the esp/esm plugin system.
Copyright (C) 2012 WrinklyNinja
This file is part of BOSS.
BOSS 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.
BOSS 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 BOSS. If not, see
<http://www.gnu.org/licenses/>.
*/
#ifndef __BOSS_GRAPH__
#define __BOSS_GRAPH__
#include "metadata.h"
#include <boost/unordered_map.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/topological_sort.hpp>
namespace boss {
typedef boost::adjacency_list<boost::listS, boost::listS, boost::bidirectionalS, std::list<boss::Plugin>::const_iterator> PluginGraph;
typedef boost::graph_traits<PluginGraph>::vertex_descriptor vertex_t;
//Gets the vertex for the plugin if it exists, or creates one if it doesn't.
vertex_t GetPluginVertex(PluginGraph& graph, const Plugin& plugin, boost::unordered_map<std::string, vertex_t>& pluginVertexMap);
//The map maps each plugin name to a vector of names of plugins that overlap with it and should load before it.
void CalcPluginOverlaps(const std::list<Plugin>& plugins, boost::unordered_map< std::string, std::vector<std::list<Plugin>::const_iterator> >& overlapMap);
}
#endif
+1 -62
View File
@@ -706,7 +706,7 @@ namespace boss {
}
bool master_sort(const Plugin& lhs, const Plugin& rhs) {
if ((lhs.IsMaster() && !rhs.IsMaster()) || (!lhs.IsMaster() && rhs.IsMaster()))
if (lhs.IsMaster() && !rhs.IsMaster())
return true;
else
return false;
@@ -719,65 +719,4 @@ namespace boss {
else
return false;
}
//The map maps each plugin name to a vector of names of plugins that overlap with it and should load before it.
void CalcPluginOverlaps(const std::list<Plugin>& plugins, boost::unordered_map< std::string, std::vector<std::string> >& overlapMap) {
for (list<Plugin>::const_iterator it=plugins.begin(),
endit=plugins.end();
it != endit;
++it) {
list<Plugin>::const_iterator jt = it;
++jt;
for (jt, endit; jt != endit; ++jt) {
BOOST_LOG_TRIVIAL(trace) << "Checking for FormID overlap between \"" << it->Name() << "\" and \"" << jt->Name() << "\".";
if (it->DoFormIDsOverlap(*jt)) {
std::string key;
std::string value;
//Priority values should override the number of override records as the deciding factor if they differ.
if (it->Priority() < jt->Priority()) {
key = jt->Name();
value = it->Name();
} else if (jt->Priority() < it->Priority()) {
key = it->Name();
value = jt->Name();
} else if (it->NumOverrideFormIDs() >= jt->NumOverrideFormIDs()) {
key = jt->Name();
value = it->Name();
} else {
key = it->Name();
value = jt->Name();
}
boost::unordered_map< string, vector<string> >::iterator mapIt = overlapMap.find(key);
if (mapIt == overlapMap.end()) {
overlapMap.insert(pair<string, vector<string> >(key, vector<string>(1, value)));
} else {
mapIt->second.push_back(value);
}
}
}
}
}
void GetPluginInEdges(const Plugin& plugin, const boost::unordered_map< std::string, std::vector<std::string> >& overlapMap, std::set<std::string>& inVertices) {
//In-vertices are named in the plugin's entry in the overlap map, and in the plugin's requirements, masters and loadAfter members.
vector<string> strVec = plugin.Masters();
inVertices.insert(strVec.begin(), strVec.end());
boost::unordered_map< std::string, std::vector<std::string> >::const_iterator it = overlapMap.find(plugin.Name());
if (it != overlapMap.end())
inVertices.insert(it->second.begin(), it->second.end());
set<File> fileset = plugin.Reqs();
for (set<File>::const_iterator it=fileset.begin(), endIt=fileset.end(); it != endIt; ++it) {
inVertices.insert(it->Name());
}
fileset = plugin.LoadAfter();
for (set<File>::const_iterator it=fileset.begin(), endIt=fileset.end(); it != endIt; ++it) {
inVertices.insert(it->Name());
}
}
}
-7
View File
@@ -32,8 +32,6 @@
#include <list>
#include <set>
#include <boost/unordered_map.hpp>
namespace boss {
@@ -216,11 +214,6 @@ namespace boss {
bool master_sort(const Plugin& lhs, const Plugin& rhs);
bool IsPlugin(const std::string& file);
//The map maps each plugin name to a vector of names of plugins that overlap with it and should load before it.
void CalcPluginOverlaps(const std::list<Plugin>& plugins, boost::unordered_map< std::string, std::vector<std::string> >& overlapMap);
void GetPluginInEdges(const Plugin& plugin, const boost::unordered_map< std::string, std::vector<std::string> >& overlapMap, std::set<std::string>& inVertices);
}
#endif
+103 -56
View File
@@ -33,6 +33,7 @@
#include "../backend/generators.h"
#include "../backend/network.h"
#include "../backend/streams.h"
#include "../backend/graph.h"
#include <ostream>
#include <algorithm>
@@ -56,6 +57,7 @@
#include <boost/log/support/date_time.hpp>
#include <boost/thread/thread.hpp>
#include <wx/snglinst.h>
#include <wx/aboutdlg.h>
#include <wx/progdlg.h>
@@ -475,7 +477,6 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
progDia->Pulse();
bool cyclicDependenciesExist = false;
if (fs::exists(_game.MasterlistPath()) || fs::exists(_game.UserlistPath())) {
BOOST_LOG_TRIVIAL(trace) << "Merging plugin lists, evaluating conditions and and checking for install validity...";
@@ -529,16 +530,9 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
progDia->Pulse();
//Check that the metadata is self-consistent.
BOOST_LOG_TRIVIAL(trace) << "Checking that plugin data is self-consistent.";
set<string> dependencies, incompatibilities, reqs;
map<string, bool> issues;
issues = it->CheckSelfConsistency(plugins, dependencies, incompatibilities, reqs);
consistencyIssues.insert(issues.begin(), issues.end());
//Also check install validity.
BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to this plugin's data.";
issues = it->CheckInstallValidity(_game);
map<string, bool> issues = it->CheckInstallValidity(_game);
list<boss::Message> pluginMessages = it->Messages();
for (map<string,bool>::const_iterator jt=issues.begin(), endJt=issues.end(); jt != endJt; ++jt) {
if (jt->second) {
@@ -554,66 +548,119 @@ void Launcher::OnSortPlugins(wxCommandEvent& event) {
progDia->Pulse();
}
for (map<string,bool>::const_iterator jt=consistencyIssues.begin(), endJt=consistencyIssues.end(); jt != endJt; ++jt) {
if (jt->second) {
BOOST_LOG_TRIVIAL(error) << "\"" << jt->first << "\" is a circular dependency. Plugins will not be sorted.";
messages.push_back(boss::Message(boss::g_message_error, (format(loc::translate("\"%1%\" is a circular dependency. Plugins will not be sorted.")) % jt->first).str()));
cyclicDependenciesExist = true;
} else {
BOOST_LOG_TRIVIAL(error) << "\"" << jt->first << "\" is given as a dependency and an incompatibility.";
messages.push_back(boss::Message(boss::g_message_error, (format(loc::translate("\"%1%\" is given as a dependency and an incompatibility.")) % jt->first).str()));
}
}
}
progDia->Pulse();
if (!cyclicDependenciesExist) {
BOOST_LOG_TRIVIAL(trace) << "Sorting plugins...";
//First sort the plugins list so that masters come before non-masters.
BOOST_LOG_TRIVIAL(trace) << "Moving masters before non-masters.";
plugins.sort(boss::master_sort);
//std::sort doesn't compare each plugin to every other plugin, it seems to compare plugins until no movement is necessary, because it assumes that each plugin will be comparable on all tested data, which isn't the case when dealing with masters, etc.
list<boss::Plugin>::iterator it=plugins.begin();
while (it != plugins.end()) {
list<boss::Plugin>::iterator jt=it;
++jt;
list<boss::Plugin>::const_iterator firstNonMaster;
for (list<boss::Plugin>::const_iterator it=plugins.begin(), endIt=plugins.end(); it != endIt; ++it) {
if (!it->IsMaster()) {
firstNonMaster = it;
break;
}
}
BOOST_LOG_TRIVIAL(trace) << "Sorting for: " << it->Name();
//Now build overlap map.
BOOST_LOG_TRIVIAL(trace) << "Building plugin overlap map.";
boost::unordered_map< string, vector<list<Plugin>::const_iterator> > overlapMap;
CalcPluginOverlaps(plugins, overlapMap);
/* vector<string> masters = it->Masters();
if (!masters.empty()) {
out << "\t" << "Masters:" << endl;
for (size_t i=0, max=masters.size(); i < max; ++i)
out << "\t\t" << masters[i] << endl;
}*/
BOOST_LOG_TRIVIAL(trace) << "Building the plugin dependency graph...";
bool cyclicDependenciesExist = false;
list<boss::Plugin> moved;
while (jt != plugins.end()) {
/* if (it->MustLoadAfter(*jt)
|| jt->Priority() < it->Priority()) {
|| (!jt->OverlapFormIDs(*it).empty() && jt->FormIDs().size() != it->FormIDs().size() && jt->FormIDs().size() > it->FormIDs().size())) {
*/
// if (load_order_sort(*jt, *it)) {
if (it->MustLoadAfter(*jt)) {
BOOST_LOG_TRIVIAL(trace) << jt->Name() << " should load before " << it->Name();
moved.push_back(*jt);
jt = plugins.erase(jt);
} else
++jt;
//Use an adjacency list (don't know yet if list or matrix is the better choice), and use "listS" as the VertexList type. We need a possible multi-graph to catch some forms of cyclic dependency (a working graph would not be a multi-graph though), so use "listS". Want a directed graph where we can access in-edges, so use "bidirectionalS". Also provide the boss::Plugin class as the vertex property type.
progDia->Pulse();
boss::PluginGraph graph;
//Now add the plugins in order to the graph as vertices.
for (list<boss::Plugin>::const_iterator it=plugins.begin(), endIt=plugins.end(); it != endIt; ++it) {
BOOST_LOG_TRIVIAL(trace) << "Creating vertex for \"" << it->Name() << "\".";
boost::add_vertex(it, graph);
}
//Vertices are numbered from 0 to (N - 1), where N is the number of vertices in the graph. A plugin's vertex number is therefore the same as its position in the list, and this can be used to create edges between plugins.
list<boss::Plugin>::const_iterator beginIt = plugins.begin();
for (list<boss::Plugin>::const_iterator it=plugins.begin(), endIt=plugins.end(); it != endIt; ++it) {
BOOST_LOG_TRIVIAL(trace) << "Editing vertex for \"" << it->Name() << "\".";
//Check if this plugin already has a vertex, and if not, create one.
boss::vertex_t vertex = boost::vertex( std::distance(beginIt, it), graph);
//Now add edges for everything.
list<boss::Plugin>::const_iterator result;
set<File> fileset;
vector<string> strVec = it->Masters();
if (it->IsMaster()) {
BOOST_LOG_TRIVIAL(trace) << "Adding out-edges for non-master plugins.";
//Need to add out-edges to all non-master plugins.
for (list<boss::Plugin>::const_iterator jt=firstNonMaster, endIt; jt != endIt; ++jt) {
BOOST_LOG_TRIVIAL(trace) << "Getting vertex for \"" << jt->Name() << "\".";
boss::vertex_t childVertex = boost::vertex( std::distance(beginIt, jt), graph );
//Now that we have the non-master plugin's vertex, create an edge between the two.
BOOST_LOG_TRIVIAL(trace) << "Adding edge from \"" << it->Name() << "\" to \"" << jt->Name() << "\".";
boost::add_edge(vertex, childVertex, graph);
}
if (!moved.empty()) {
plugins.insert(it, moved.begin(), moved.end());
advance(it, -1*(int)moved.size());
} else
++it;
progDia->Pulse();
}
plugins.sort(boss::load_order_sort);
//Now add masters.
BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for masters.";
for (vector<string>::const_iterator jt=strVec.begin(), endjt=strVec.end(); jt != endjt; ++jt) {
//Find the other plugin.
result = std::find(plugins.begin(), plugins.end(), boss::Plugin(*jt));
//Add the vertex (again assuming that duplicates won't be created).
boss::vertex_t parentVertex = boost::vertex( std::distance(beginIt, result), graph);
boost::add_edge(parentVertex, vertex, graph);
}
//Now add requirements.
BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for requirements.";
fileset = it->Reqs();
for (set<File>::const_iterator jt=fileset.begin(), endjt=fileset.end(); jt != endjt; ++jt) {
if (boss::IsPlugin(jt->Name())) {
//Find the other plugin.
result = std::find(plugins.begin(), plugins.end(), *jt);
//Add the vertex (again assuming that duplicates won't be created).
boss::vertex_t parentVertex = boost::vertex( std::distance(beginIt, result), graph);
boost::add_edge(parentVertex, vertex, graph);
}
}
//Now add "load after" plugins.
BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for 'load after's.";
fileset = it->LoadAfter();
for (set<File>::const_iterator jt=fileset.begin(), endjt=fileset.end(); jt != endjt; ++jt) {
if (boss::IsPlugin(jt->Name())) {
//Find the other plugin.
result = std::find(plugins.begin(), plugins.end(), *jt);
//Add the vertex (again assuming that duplicates won't be created).
boss::vertex_t parentVertex = boost::vertex( std::distance(beginIt, result), graph);
boost::add_edge(parentVertex, vertex, graph);
}
}
//Now add any overlaps, except where an edge already exists between the two plugins, going the other way, since overlap-based edges have the lowest priority and are not a candidate for causing cyclic loop errors.
BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for overlaps.";
boost::unordered_map< std::string, std::vector<list<Plugin>::const_iterator> >::const_iterator overlapIt = overlapMap.find(it->Name());
if (overlapIt != overlapMap.end()) {
for (vector<list<Plugin>::const_iterator>::const_iterator jt=overlapIt->second.begin(), endjt=overlapIt->second.end(); jt != endjt; ++jt) {
boss::vertex_t parentVertex = boost::vertex( std::distance(beginIt, *jt), graph);
if (!boost::edge(vertex, parentVertex, graph).second) {
//No edge going the other way, OK to add this edge.
boost::add_edge(parentVertex, vertex, graph);
}
}
}
}
if (!cyclicDependenciesExist) {
progDia->Pulse();
BOOST_LOG_TRIVIAL(debug) << "Displaying load order preview.";