diff --git a/CMakeLists.txt b/CMakeLists.txt index 894a774f..b2de8027 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/src/backend/graph.cpp b/src/backend/graph.cpp new file mode 100644 index 00000000..75129310 --- /dev/null +++ b/src/backend/graph.cpp @@ -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 + . +*/ + +#include "graph.h" + +#include +#include + +using namespace std; + +namespace boss { + vertex_t GetPluginVertex(PluginGraph& graph, const Plugin& plugin, boost::unordered_map& pluginVertexMap) { + + vertex_t vertex; + string name = boost::to_lower_copy(plugin.Name()); + + boost::unordered_map::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& plugins, boost::unordered_map< std::string, std::vector::const_iterator> >& overlapMap) { + for (list::const_iterator it=plugins.begin(), + endit=plugins.end(); + it != endit; + ++it) { + list::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::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::const_iterator> >::iterator mapIt = overlapMap.find(key); + if (mapIt == overlapMap.end()) { + overlapMap.insert(pair::const_iterator> >(key, vector::const_iterator>(1, value))); + } else { + mapIt->second.push_back(value); + } + } + } + } + } +} diff --git a/src/backend/graph.h b/src/backend/graph.h new file mode 100644 index 00000000..12000c5c --- /dev/null +++ b/src/backend/graph.h @@ -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 + . +*/ + +#ifndef __BOSS_GRAPH__ +#define __BOSS_GRAPH__ + +#include "metadata.h" + +#include +#include +#include +#include + +namespace boss { + + typedef boost::adjacency_list::const_iterator> PluginGraph; + typedef boost::graph_traits::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& 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& plugins, boost::unordered_map< std::string, std::vector::const_iterator> >& overlapMap); + +} + +#endif diff --git a/src/backend/metadata.cpp b/src/backend/metadata.cpp index 4d0a46be..a02df116 100644 --- a/src/backend/metadata.cpp +++ b/src/backend/metadata.cpp @@ -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& plugins, boost::unordered_map< std::string, std::vector >& overlapMap) { - for (list::const_iterator it=plugins.begin(), - endit=plugins.end(); - it != endit; - ++it) { - list::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 >::iterator mapIt = overlapMap.find(key); - if (mapIt == overlapMap.end()) { - overlapMap.insert(pair >(key, vector(1, value))); - } else { - mapIt->second.push_back(value); - } - } - } - } - } - - void GetPluginInEdges(const Plugin& plugin, const boost::unordered_map< std::string, std::vector >& overlapMap, std::set& 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 strVec = plugin.Masters(); - inVertices.insert(strVec.begin(), strVec.end()); - - boost::unordered_map< std::string, std::vector >::const_iterator it = overlapMap.find(plugin.Name()); - if (it != overlapMap.end()) - inVertices.insert(it->second.begin(), it->second.end()); - - set fileset = plugin.Reqs(); - for (set::const_iterator it=fileset.begin(), endIt=fileset.end(); it != endIt; ++it) { - inVertices.insert(it->Name()); - } - - fileset = plugin.LoadAfter(); - for (set::const_iterator it=fileset.begin(), endIt=fileset.end(); it != endIt; ++it) { - inVertices.insert(it->Name()); - } - - - } } diff --git a/src/backend/metadata.h b/src/backend/metadata.h index 7f1e28b8..55062db4 100644 --- a/src/backend/metadata.h +++ b/src/backend/metadata.h @@ -32,8 +32,6 @@ #include #include -#include - 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& plugins, boost::unordered_map< std::string, std::vector >& overlapMap); - - void GetPluginInEdges(const Plugin& plugin, const boost::unordered_map< std::string, std::vector >& overlapMap, std::set& inVertices); } #endif diff --git a/src/gui/main.cpp b/src/gui/main.cpp index a65e7940..a3ae0460 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -33,6 +33,7 @@ #include "../backend/generators.h" #include "../backend/network.h" #include "../backend/streams.h" +#include "../backend/graph.h" #include #include @@ -56,6 +57,7 @@ #include #include + #include #include #include @@ -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 dependencies, incompatibilities, reqs; - map 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 issues = it->CheckInstallValidity(_game); list pluginMessages = it->Messages(); for (map::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::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::iterator it=plugins.begin(); - while (it != plugins.end()) { - list::iterator jt=it; - ++jt; + list::const_iterator firstNonMaster; + for (list::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::const_iterator> > overlapMap; + CalcPluginOverlaps(plugins, overlapMap); - /* vector 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 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::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::const_iterator beginIt = plugins.begin(); + for (list::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::const_iterator result; + set fileset; + vector 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::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::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::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::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::const_iterator> >::const_iterator overlapIt = overlapMap.find(it->Name()); + if (overlapIt != overlapMap.end()) { + for (vector::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.";