mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Refactor adding plugin vertices to graph
This commit is contained in:
@@ -195,48 +195,6 @@ std::unordered_set<std::string> FindGroupsInAllPaths(
|
||||
return groupsInPaths;
|
||||
}
|
||||
|
||||
int ComparePlugins(const PluginSortingData& plugin1,
|
||||
const PluginSortingData& plugin2) {
|
||||
if (plugin1.GetLoadOrderIndex().has_value() &&
|
||||
!plugin2.GetLoadOrderIndex().has_value()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!plugin1.GetLoadOrderIndex().has_value() &&
|
||||
plugin2.GetLoadOrderIndex().has_value()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (plugin1.GetLoadOrderIndex().has_value() &&
|
||||
plugin2.GetLoadOrderIndex().has_value()) {
|
||||
if (plugin1.GetLoadOrderIndex().value() <
|
||||
plugin2.GetLoadOrderIndex().value()) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Neither plugin has a load order position. Compare plugin basenames to
|
||||
// get an ordering.
|
||||
const auto name1 = plugin1.GetName();
|
||||
const auto name2 = plugin2.GetName();
|
||||
const auto basename1 = name1.substr(0, name1.length() - 4);
|
||||
const auto basename2 = name2.substr(0, name2.length() - 4);
|
||||
|
||||
const int result = CompareFilenames(basename1, basename2);
|
||||
|
||||
if (result != 0) {
|
||||
return result;
|
||||
} else {
|
||||
// Could be a .esp and .esm plugin with the same basename,
|
||||
// compare their extensions.
|
||||
const auto ext1 = name1.substr(name1.length() - 4);
|
||||
const auto ext2 = name2.substr(name2.length() - 4);
|
||||
return CompareFilenames(ext1, ext2);
|
||||
}
|
||||
}
|
||||
|
||||
std::string describeEdgeType(EdgeType edgeType) {
|
||||
switch (edgeType) {
|
||||
case EdgeType::hardcoded:
|
||||
@@ -613,127 +571,6 @@ void PluginGraph::AddVertex(const PluginSortingData& plugin) {
|
||||
pluginNameVertexMap.emplace(plugin.GetName(), vertex);
|
||||
}
|
||||
|
||||
void PluginGraph::AddPluginVertices(const Game& game,
|
||||
const std::vector<std::string>& loadOrder) {
|
||||
auto loadedPlugins = game.GetCache().GetPlugins();
|
||||
|
||||
// Sort plugins by their names. This is necessary to ensure that
|
||||
// plugin precedessor group plugins are listed in a consistent
|
||||
// order, which is important because that is the order in which
|
||||
// group edges are added and differences could cause different
|
||||
// sorting results.
|
||||
std::sort(loadedPlugins.begin(),
|
||||
loadedPlugins.end(),
|
||||
[](const auto& lhs, const auto& rhs) {
|
||||
if (!lhs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rhs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return lhs->GetName() < rhs->GetName();
|
||||
});
|
||||
|
||||
std::vector<const PluginInterface*> loadedPluginInterfaces;
|
||||
std::transform(loadedPlugins.begin(),
|
||||
loadedPlugins.end(),
|
||||
std::back_inserter(loadedPluginInterfaces),
|
||||
[](const Plugin* plugin) { return plugin; });
|
||||
|
||||
std::vector<PluginSortingData> pluginsSortingData;
|
||||
pluginsSortingData.reserve(loadedPlugins.size());
|
||||
|
||||
for (const auto& plugin : loadedPlugins) {
|
||||
if (!plugin) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto masterlistMetadata =
|
||||
game.GetDatabase()
|
||||
.GetPluginMetadata(plugin->GetName(), false, true)
|
||||
.value_or(PluginMetadata(plugin->GetName()));
|
||||
const auto userMetadata =
|
||||
game.GetDatabase()
|
||||
.GetPluginUserMetadata(plugin->GetName(), true)
|
||||
.value_or(PluginMetadata(plugin->GetName()));
|
||||
|
||||
const auto pluginSortingData = PluginSortingData(plugin,
|
||||
masterlistMetadata,
|
||||
userMetadata,
|
||||
loadOrder,
|
||||
game.Type(),
|
||||
loadedPluginInterfaces);
|
||||
|
||||
pluginsSortingData.push_back(pluginSortingData);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::vector<std::string>> groupPlugins;
|
||||
for (const auto& plugin : pluginsSortingData) {
|
||||
const auto groupName = plugin.GetGroup();
|
||||
const auto groupIt = groupPlugins.find(groupName);
|
||||
if (groupIt == groupPlugins.end()) {
|
||||
groupPlugins.emplace(groupName,
|
||||
std::vector<std::string>({plugin.GetName()}));
|
||||
} else {
|
||||
groupIt->second.push_back(plugin.GetName());
|
||||
}
|
||||
}
|
||||
|
||||
// Map sets of transitive group dependencies to sets of transitive plugin
|
||||
// dependencies.
|
||||
auto groups = GetTransitiveAfterGroups(game.GetDatabase().GetGroups(false),
|
||||
game.GetDatabase().GetUserGroups());
|
||||
for (auto& group : groups) {
|
||||
std::unordered_set<std::string> transitivePlugins;
|
||||
for (const auto& afterGroup : group.second) {
|
||||
const auto pluginsIt = groupPlugins.find(afterGroup);
|
||||
if (pluginsIt != groupPlugins.end()) {
|
||||
transitivePlugins.insert(pluginsIt->second.begin(),
|
||||
pluginsIt->second.end());
|
||||
}
|
||||
}
|
||||
group.second = transitivePlugins;
|
||||
}
|
||||
|
||||
// Add all transitive plugin dependencies for a group to the plugin's load
|
||||
// after metadata.
|
||||
const auto logger = getLogger();
|
||||
for (auto& plugin : pluginsSortingData) {
|
||||
if (logger) {
|
||||
logger->trace(
|
||||
"Plugin \"{}\" belongs to group \"{}\", setting after group plugins",
|
||||
plugin.GetName(),
|
||||
plugin.GetGroup());
|
||||
}
|
||||
|
||||
const auto groupsIt = groups.find(plugin.GetGroup());
|
||||
if (groupsIt == groups.end()) {
|
||||
throw UndefinedGroupError(plugin.GetGroup());
|
||||
} else {
|
||||
plugin.SetAfterGroupPlugins(groupsIt->second);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the plugins according to into their existing load order, or
|
||||
// lexicographical ordering for pairs of plugins without load order positions.
|
||||
// This ensures a consistent iteration order for vertices given the same input
|
||||
// data. The vertex iteration order can affect what edges get added and so
|
||||
// the final sorting result, so consistency is important.
|
||||
// Load order is used because this simplifies the logic when adding tie-break
|
||||
// edges.
|
||||
std::sort(pluginsSortingData.begin(),
|
||||
pluginsSortingData.end(),
|
||||
[](const auto& lhs, const auto& rhs) {
|
||||
return ComparePlugins(lhs, rhs) < 0;
|
||||
});
|
||||
|
||||
for (const auto& plugin : pluginsSortingData) {
|
||||
AddVertex(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
void PluginGraph::AddSpecificEdges() {
|
||||
const auto logger = getLogger();
|
||||
if (logger) {
|
||||
|
||||
@@ -90,8 +90,6 @@ public:
|
||||
EdgeType edgeType);
|
||||
void AddVertex(const PluginSortingData& plugin);
|
||||
|
||||
void AddPluginVertices(const Game& game,
|
||||
const std::vector<std::string>& loadOrder);
|
||||
void AddSpecificEdges();
|
||||
void AddHardcodedPluginEdges(const Game& game);
|
||||
void AddGroupEdges(const std::unordered_map<std::string, Group>& groups);
|
||||
|
||||
@@ -25,22 +25,185 @@
|
||||
#include "plugin_sort.h"
|
||||
|
||||
#include "api/helpers/logging.h"
|
||||
#include "api/sorting/group_sort.h"
|
||||
#include "api/sorting/plugin_graph.h"
|
||||
#include "loot/exception/undefined_group_error.h"
|
||||
|
||||
namespace loot {
|
||||
int ComparePlugins(const PluginSortingData& plugin1,
|
||||
const PluginSortingData& plugin2) {
|
||||
if (plugin1.GetLoadOrderIndex().has_value() &&
|
||||
!plugin2.GetLoadOrderIndex().has_value()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!plugin1.GetLoadOrderIndex().has_value() &&
|
||||
plugin2.GetLoadOrderIndex().has_value()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (plugin1.GetLoadOrderIndex().has_value() &&
|
||||
plugin2.GetLoadOrderIndex().has_value()) {
|
||||
if (plugin1.GetLoadOrderIndex().value() <
|
||||
plugin2.GetLoadOrderIndex().value()) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Neither plugin has a load order position. Compare plugin basenames to
|
||||
// get an ordering.
|
||||
const auto name1 = plugin1.GetName();
|
||||
const auto name2 = plugin2.GetName();
|
||||
const auto basename1 = name1.substr(0, name1.length() - 4);
|
||||
const auto basename2 = name2.substr(0, name2.length() - 4);
|
||||
|
||||
const int result = CompareFilenames(basename1, basename2);
|
||||
|
||||
if (result != 0) {
|
||||
return result;
|
||||
} else {
|
||||
// Could be a .esp and .esm plugin with the same basename,
|
||||
// compare their extensions.
|
||||
const auto ext1 = name1.substr(name1.length() - 4);
|
||||
const auto ext2 = name2.substr(name2.length() - 4);
|
||||
return CompareFilenames(ext1, ext2);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<PluginSortingData> GetPluginsSortingData(
|
||||
const Game& game,
|
||||
const std::vector<std::string>& loadOrder) {
|
||||
auto loadedPlugins = game.GetCache().GetPlugins();
|
||||
|
||||
// Sort plugins by their names. This is necessary to ensure that
|
||||
// plugin precedessor group plugins are listed in a consistent
|
||||
// order, which is important because that is the order in which
|
||||
// group edges are added and differences could cause different
|
||||
// sorting results.
|
||||
std::sort(loadedPlugins.begin(),
|
||||
loadedPlugins.end(),
|
||||
[](const auto& lhs, const auto& rhs) {
|
||||
if (!lhs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rhs) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return lhs->GetName() < rhs->GetName();
|
||||
});
|
||||
|
||||
std::vector<const PluginInterface*> loadedPluginInterfaces;
|
||||
std::transform(loadedPlugins.begin(),
|
||||
loadedPlugins.end(),
|
||||
std::back_inserter(loadedPluginInterfaces),
|
||||
[](const Plugin* plugin) { return plugin; });
|
||||
|
||||
std::vector<PluginSortingData> pluginsSortingData;
|
||||
pluginsSortingData.reserve(loadedPlugins.size());
|
||||
|
||||
for (const auto& plugin : loadedPlugins) {
|
||||
if (!plugin) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto masterlistMetadata =
|
||||
game.GetDatabase()
|
||||
.GetPluginMetadata(plugin->GetName(), false, true)
|
||||
.value_or(PluginMetadata(plugin->GetName()));
|
||||
const auto userMetadata =
|
||||
game.GetDatabase()
|
||||
.GetPluginUserMetadata(plugin->GetName(), true)
|
||||
.value_or(PluginMetadata(plugin->GetName()));
|
||||
|
||||
const auto pluginSortingData = PluginSortingData(plugin,
|
||||
masterlistMetadata,
|
||||
userMetadata,
|
||||
loadOrder,
|
||||
game.Type(),
|
||||
loadedPluginInterfaces);
|
||||
|
||||
pluginsSortingData.push_back(pluginSortingData);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::vector<std::string>> groupPlugins;
|
||||
for (const auto& plugin : pluginsSortingData) {
|
||||
const auto groupName = plugin.GetGroup();
|
||||
const auto groupIt = groupPlugins.find(groupName);
|
||||
if (groupIt == groupPlugins.end()) {
|
||||
groupPlugins.emplace(groupName,
|
||||
std::vector<std::string>({plugin.GetName()}));
|
||||
} else {
|
||||
groupIt->second.push_back(plugin.GetName());
|
||||
}
|
||||
}
|
||||
|
||||
// Map sets of transitive group dependencies to sets of transitive plugin
|
||||
// dependencies.
|
||||
auto groups = GetTransitiveAfterGroups(game.GetDatabase().GetGroups(false),
|
||||
game.GetDatabase().GetUserGroups());
|
||||
for (auto& group : groups) {
|
||||
std::unordered_set<std::string> transitivePlugins;
|
||||
for (const auto& afterGroup : group.second) {
|
||||
const auto pluginsIt = groupPlugins.find(afterGroup);
|
||||
if (pluginsIt != groupPlugins.end()) {
|
||||
transitivePlugins.insert(pluginsIt->second.begin(),
|
||||
pluginsIt->second.end());
|
||||
}
|
||||
}
|
||||
group.second = transitivePlugins;
|
||||
}
|
||||
|
||||
// Add all transitive plugin dependencies for a group to the plugin's load
|
||||
// after metadata.
|
||||
const auto logger = getLogger();
|
||||
for (auto& plugin : pluginsSortingData) {
|
||||
if (logger) {
|
||||
logger->trace(
|
||||
"Plugin \"{}\" belongs to group \"{}\", setting after group plugins",
|
||||
plugin.GetName(),
|
||||
plugin.GetGroup());
|
||||
}
|
||||
|
||||
const auto groupsIt = groups.find(plugin.GetGroup());
|
||||
if (groupsIt == groups.end()) {
|
||||
throw UndefinedGroupError(plugin.GetGroup());
|
||||
} else {
|
||||
plugin.SetAfterGroupPlugins(groupsIt->second);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the plugins according to into their existing load order, or
|
||||
// lexicographical ordering for pairs of plugins without load order positions.
|
||||
// This ensures a consistent iteration order for vertices given the same input
|
||||
// data. The vertex iteration order can affect what edges get added and so
|
||||
// the final sorting result, so consistency is important.
|
||||
// Load order is used because this simplifies the logic when adding tie-break
|
||||
// edges.
|
||||
std::sort(pluginsSortingData.begin(),
|
||||
pluginsSortingData.end(),
|
||||
[](const auto& lhs, const auto& rhs) {
|
||||
return ComparePlugins(lhs, rhs) < 0;
|
||||
});
|
||||
|
||||
return pluginsSortingData;
|
||||
}
|
||||
|
||||
std::vector<std::string> SortPlugins(
|
||||
const Game& game,
|
||||
const std::vector<std::string>& loadOrder) {
|
||||
PluginGraph graph;
|
||||
const auto pluginsSortingData = GetPluginsSortingData(game, loadOrder);
|
||||
|
||||
graph.AddPluginVertices(game, loadOrder);
|
||||
|
||||
// If there aren't any vertices, exit early, because sorting assumes
|
||||
// If there aren't any plugins, exit early, because sorting assumes
|
||||
// there is at least one plugin.
|
||||
if (graph.CountVertices() == 0)
|
||||
return std::vector<std::string>();
|
||||
if (pluginsSortingData.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto logger = getLogger();
|
||||
const auto logger = getLogger();
|
||||
if (logger) {
|
||||
logger->debug("Current load order:");
|
||||
for (const auto& plugin : loadOrder) {
|
||||
@@ -48,6 +211,12 @@ std::vector<std::string> SortPlugins(
|
||||
}
|
||||
}
|
||||
|
||||
PluginGraph graph;
|
||||
|
||||
for (const auto& plugin : pluginsSortingData) {
|
||||
graph.AddVertex(plugin);
|
||||
}
|
||||
|
||||
// Now add the interactions between plugins to the graph as edges.
|
||||
graph.AddSpecificEdges();
|
||||
graph.AddHardcodedPluginEdges(game);
|
||||
|
||||
Reference in New Issue
Block a user