mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Differentiate between load after and requirement metadata sources
When reporting a cyclic interaction error.
This commit is contained in:
@@ -38,8 +38,10 @@ enum struct EdgeType : unsigned int {
|
||||
Hardcoded,
|
||||
MasterFlag,
|
||||
Master,
|
||||
Requirement,
|
||||
LoadAfter,
|
||||
MasterlistRequirement,
|
||||
UserRequirement,
|
||||
MasterlistLoadAfter,
|
||||
UserLoadAfter,
|
||||
Group,
|
||||
Overlap,
|
||||
TieBreak,
|
||||
@@ -62,7 +64,7 @@ public:
|
||||
/**
|
||||
* @brief Get the name of the plugin or group.
|
||||
* @return The name of the plugin or group.
|
||||
*/
|
||||
*/
|
||||
std::string GetName() const;
|
||||
|
||||
/**
|
||||
@@ -70,8 +72,9 @@ public:
|
||||
* @details Each edge goes from the vertex that loads earlier to the vertex
|
||||
* that loads later.
|
||||
* @return The edge type.
|
||||
*/
|
||||
*/
|
||||
EdgeType GetTypeOfEdgeToNextVertex() const;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
EdgeType outEdgeType_;
|
||||
|
||||
@@ -24,45 +24,47 @@
|
||||
#include "loot/exception/cyclic_interaction_error.h"
|
||||
|
||||
namespace loot {
|
||||
Vertex::Vertex(std::string name, EdgeType outEdgeType) : name_(name), outEdgeType_(outEdgeType) {}
|
||||
Vertex::Vertex(std::string name, EdgeType outEdgeType) :
|
||||
name_(name),
|
||||
outEdgeType_(outEdgeType) {}
|
||||
|
||||
std::string Vertex::GetName() const {
|
||||
return name_;
|
||||
}
|
||||
std::string Vertex::GetName() const { return name_; }
|
||||
|
||||
EdgeType Vertex::GetTypeOfEdgeToNextVertex() const {
|
||||
return outEdgeType_;
|
||||
}
|
||||
EdgeType Vertex::GetTypeOfEdgeToNextVertex() const { return outEdgeType_; }
|
||||
|
||||
std::string describe(EdgeType edgeType) {
|
||||
switch (edgeType) {
|
||||
case EdgeType::Hardcoded:
|
||||
return "Hardcoded";
|
||||
case EdgeType::MasterFlag:
|
||||
return "Master Flag";
|
||||
case EdgeType::Master:
|
||||
return "Master";
|
||||
case EdgeType::Requirement:
|
||||
return "Requirement";
|
||||
case EdgeType::LoadAfter:
|
||||
return "Load After";
|
||||
case EdgeType::Group:
|
||||
return "Group";
|
||||
case EdgeType::Overlap:
|
||||
return "Overlap";
|
||||
case EdgeType::TieBreak:
|
||||
return "Tie Break";
|
||||
default:
|
||||
return "Unknown";
|
||||
case EdgeType::Hardcoded:
|
||||
return "Hardcoded";
|
||||
case EdgeType::MasterFlag:
|
||||
return "Master Flag";
|
||||
case EdgeType::Master:
|
||||
return "Master";
|
||||
case EdgeType::MasterlistRequirement:
|
||||
return "Masterlist Requirement";
|
||||
case EdgeType::UserRequirement:
|
||||
return "User Requirement";
|
||||
case EdgeType::MasterlistLoadAfter:
|
||||
return "Masterlist Load After";
|
||||
case EdgeType::UserLoadAfter:
|
||||
return "User Load After";
|
||||
case EdgeType::Group:
|
||||
return "Group";
|
||||
case EdgeType::Overlap:
|
||||
return "Overlap";
|
||||
case EdgeType::TieBreak:
|
||||
return "Tie Break";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// A.esp --[Master Flag]-> B.esp --Group->
|
||||
std::string describeCycle(const std::vector<Vertex>& cycle) {
|
||||
std::string text;
|
||||
for (const auto& vertex : cycle) {
|
||||
text += vertex.GetName() + " --[" + describe(vertex.GetTypeOfEdgeToNextVertex()) + "]-> ";
|
||||
text += vertex.GetName() + " --[" +
|
||||
describe(vertex.GetTypeOfEdgeToNextVertex()) + "]-> ";
|
||||
}
|
||||
if (!cycle.empty()) {
|
||||
text += cycle[0].GetName();
|
||||
|
||||
@@ -25,19 +25,49 @@
|
||||
#include "group_sort.h"
|
||||
|
||||
#include <boost/graph/adjacency_list.hpp>
|
||||
#include <boost/graph/graph_traits.hpp>
|
||||
#include <boost/graph/depth_first_search.hpp>
|
||||
#include <boost/graph/graph_traits.hpp>
|
||||
|
||||
#include "api/helpers/logging.h"
|
||||
#include "loot/exception/cyclic_interaction_error.h"
|
||||
#include "loot/exception/undefined_group_error.h"
|
||||
#include "api/helpers/logging.h"
|
||||
|
||||
namespace loot {
|
||||
class GroupSortingData {
|
||||
public:
|
||||
GroupSortingData() {}
|
||||
GroupSortingData(std::string name) : name_(name) {}
|
||||
|
||||
std::string GetName() const { return name_; }
|
||||
|
||||
std::unordered_set<std::string> GetMasterlistAfterGroups() const {
|
||||
return masterlistAfterGroups_;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> GetUserAfterGroups() const {
|
||||
return userAfterGroups_;
|
||||
}
|
||||
|
||||
void SetMasterlistAfterGroups(std::unordered_set<std::string> groups) {
|
||||
masterlistAfterGroups_ = groups;
|
||||
}
|
||||
|
||||
void SetUserAfterGroups(std::unordered_set<std::string> groups) {
|
||||
userAfterGroups_ = groups;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::unordered_set<std::string> masterlistAfterGroups_;
|
||||
std::unordered_set<std::string> userAfterGroups_;
|
||||
};
|
||||
|
||||
typedef boost::adjacency_list<boost::vecS,
|
||||
boost::vecS,
|
||||
boost::directedS,
|
||||
std::string,
|
||||
EdgeType> GroupGraph;
|
||||
GroupSortingData,
|
||||
EdgeType>
|
||||
GroupGraph;
|
||||
typedef boost::graph_traits<GroupGraph>::vertex_descriptor vertex_t;
|
||||
typedef boost::graph_traits<GroupGraph>::edge_descriptor edge_t;
|
||||
|
||||
@@ -46,11 +76,11 @@ public:
|
||||
void tree_edge(edge_t edge, const GroupGraph& graph) {
|
||||
auto source = boost::source(edge, graph);
|
||||
|
||||
auto vertex = Vertex(graph[source], graph[edge]);
|
||||
auto vertex = Vertex(graph[source].GetName(), graph[edge]);
|
||||
|
||||
// Check if the plugin already exists in the recorded trail.
|
||||
auto it = find_if(begin(trail), end(trail), [&](const Vertex& v) {
|
||||
return v.GetName() == graph[source];
|
||||
return v.GetName() == graph[source].GetName();
|
||||
});
|
||||
|
||||
if (it != end(trail)) {
|
||||
@@ -66,11 +96,11 @@ public:
|
||||
auto source = boost::source(edge, graph);
|
||||
auto target = boost::target(edge, graph);
|
||||
|
||||
auto vertex = Vertex(graph[source], graph[edge]);
|
||||
auto vertex = Vertex(graph[source].GetName(), graph[edge]);
|
||||
trail.push_back(vertex);
|
||||
|
||||
auto it = find_if(begin(trail), end(trail), [&](const Vertex& v) {
|
||||
return v.GetName() == graph[target];
|
||||
return v.GetName() == graph[target].GetName();
|
||||
});
|
||||
|
||||
if (it != trail.end()) {
|
||||
@@ -84,20 +114,22 @@ private:
|
||||
|
||||
class AfterGroupsVisitor : public boost::dfs_visitor<> {
|
||||
public:
|
||||
AfterGroupsVisitor(std::unordered_set<std::string>& visitedGroups) : visitedGroups_(visitedGroups) {}
|
||||
AfterGroupsVisitor(std::unordered_set<std::string>& visitedGroups) :
|
||||
visitedGroups_(visitedGroups) {}
|
||||
void tree_edge(edge_t edge, const GroupGraph& graph) {
|
||||
auto target = boost::target(edge, graph);
|
||||
visitedGroups_.insert(graph[target]);
|
||||
visitedGroups_.insert(graph[target].GetName());
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> get_visited_groups() const {
|
||||
return visitedGroups_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_set<std::string>& visitedGroups_;
|
||||
};
|
||||
|
||||
std::string join(const std::unordered_set<std::string> set) {
|
||||
std::string join(const std::unordered_set<std::string>& set) {
|
||||
std::string output;
|
||||
for (const auto& element : set) {
|
||||
output += element + ", ";
|
||||
@@ -106,7 +138,9 @@ std::string join(const std::unordered_set<std::string> set) {
|
||||
return output.substr(0, output.length() - 2);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> GetTransitiveAfterGroups(const std::unordered_set<Group> groups) {
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>>
|
||||
GetTransitiveAfterGroups(const std::unordered_set<Group>& masterlistGroups,
|
||||
const std::unordered_set<Group>& userGroups) {
|
||||
GroupGraph graph;
|
||||
|
||||
auto logger = getLogger();
|
||||
@@ -115,24 +149,59 @@ std::unordered_map<std::string, std::unordered_set<std::string>> GetTransitiveAf
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, vertex_t> groupVertices;
|
||||
for (const auto& group : groups) {
|
||||
auto vertex = boost::add_vertex(group.GetName(), graph);
|
||||
for (const auto& group : masterlistGroups) {
|
||||
auto groupSortingData = GroupSortingData(group.GetName());
|
||||
groupSortingData.SetMasterlistAfterGroups(group.GetAfterGroups());
|
||||
|
||||
auto vertex = boost::add_vertex(groupSortingData, graph);
|
||||
groupVertices.emplace(group.GetName(), vertex);
|
||||
}
|
||||
for (const auto& group : userGroups) {
|
||||
auto it = groupVertices.find(group.GetName());
|
||||
if (it != groupVertices.end()) {
|
||||
graph[it->second].SetUserAfterGroups(group.GetAfterGroups());
|
||||
} else {
|
||||
auto groupSortingData = GroupSortingData(group.GetName());
|
||||
groupSortingData.SetUserAfterGroups(group.GetAfterGroups());
|
||||
|
||||
auto vertex = boost::add_vertex(groupSortingData, graph);
|
||||
groupVertices.emplace(group.GetName(), vertex);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& group : groups) {
|
||||
for (const vertex_t& vertex :
|
||||
boost::make_iterator_range(boost::vertices(graph))) {
|
||||
auto group = graph[vertex];
|
||||
|
||||
if (logger) {
|
||||
logger->trace("Group \"{}\" directly loads after groups \"{}\"",
|
||||
group.GetName(), join(group.GetAfterGroups()));
|
||||
logger->trace(
|
||||
"Group \"{}\" directly loads after masterlist groups \"{}\" and user "
|
||||
"groups \"{}\"",
|
||||
group.GetName(),
|
||||
join(group.GetMasterlistAfterGroups()),
|
||||
join(group.GetUserAfterGroups()));
|
||||
}
|
||||
for (const auto& otherGroupName : group.GetAfterGroups()) {
|
||||
|
||||
for (const auto& otherGroupName : group.GetMasterlistAfterGroups()) {
|
||||
auto otherVertex = groupVertices.find(otherGroupName);
|
||||
if (otherVertex == groupVertices.end()) {
|
||||
throw UndefinedGroupError(otherGroupName);
|
||||
}
|
||||
|
||||
auto vertex = groupVertices[group.GetName()];
|
||||
boost::add_edge(vertex, otherVertex->second, EdgeType::LoadAfter, graph);
|
||||
boost::add_edge(
|
||||
vertex, otherVertex->second, EdgeType::MasterlistLoadAfter, graph);
|
||||
}
|
||||
|
||||
for (const auto& otherGroupName : group.GetUserAfterGroups()) {
|
||||
auto otherVertex = groupVertices.find(otherGroupName);
|
||||
if (otherVertex == groupVertices.end()) {
|
||||
throw UndefinedGroupError(otherGroupName);
|
||||
}
|
||||
|
||||
auto vertex = groupVertices[group.GetName()];
|
||||
boost::add_edge(
|
||||
vertex, otherVertex->second, EdgeType::UserLoadAfter, graph);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,23 +211,25 @@ std::unordered_map<std::string, std::unordered_set<std::string>> GetTransitiveAf
|
||||
}
|
||||
boost::depth_first_search(graph, boost::visitor(CycleDetector()));
|
||||
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> transitiveAfterGroups;
|
||||
for (const vertex_t& vertex : boost::make_iterator_range(boost::vertices(graph))) {
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>>
|
||||
transitiveAfterGroups;
|
||||
for (const vertex_t& vertex :
|
||||
boost::make_iterator_range(boost::vertices(graph))) {
|
||||
std::unordered_set<std::string> visitedGroups;
|
||||
AfterGroupsVisitor afterGroupsVisitor(visitedGroups);
|
||||
|
||||
// Create a color map.
|
||||
std::vector<boost::default_color_type> colorVec(boost::num_vertices(graph));
|
||||
auto colorMap = boost::make_iterator_property_map(colorVec.begin(),
|
||||
boost::get(boost::vertex_index, graph),
|
||||
colorVec[0]);
|
||||
auto colorMap = boost::make_iterator_property_map(
|
||||
colorVec.begin(), boost::get(boost::vertex_index, graph), colorVec[0]);
|
||||
|
||||
boost::depth_first_visit(graph, vertex, afterGroupsVisitor, colorMap);
|
||||
transitiveAfterGroups[graph[vertex]] = visitedGroups;
|
||||
transitiveAfterGroups[graph[vertex].GetName()] = visitedGroups;
|
||||
|
||||
if (logger) {
|
||||
logger->trace("Group \"{}\" transitively loads after groups \"{}\"",
|
||||
graph[vertex], join(visitedGroups));
|
||||
graph[vertex].GetName(),
|
||||
join(visitedGroups));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
|
||||
namespace loot {
|
||||
// Map entries are a group name and names of transitive load after groups.
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> GetTransitiveAfterGroups(const std::unordered_set<Group> groups);
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>>
|
||||
GetTransitiveAfterGroups(const std::unordered_set<Group>& masterlistGroups,
|
||||
const std::unordered_set<Group>& userGroups);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -208,15 +208,22 @@ void PluginSorter::AddPluginVertices(Game& game) {
|
||||
|
||||
for (const auto& plugin : game.GetCache()->GetPlugins()) {
|
||||
if (logger_) {
|
||||
logger_->trace("Getting and evaluating metadata for plugin {}",
|
||||
logger_->trace("Getting and evaluating metadata for plugin \"{}\"",
|
||||
plugin->GetName());
|
||||
}
|
||||
|
||||
auto metadata = game.GetDatabase()
|
||||
->GetPluginMetadata(plugin->GetName(), true, true)
|
||||
.value_or(PluginMetadata(plugin->GetName()));
|
||||
auto masterlistMetadata =
|
||||
game.GetDatabase()
|
||||
->GetPluginMetadata(plugin->GetName(), false, true)
|
||||
.value_or(PluginMetadata(plugin->GetName()));
|
||||
auto userMetadata = game.GetDatabase()
|
||||
->GetPluginUserMetadata(plugin->GetName(), true)
|
||||
.value_or(PluginMetadata(plugin->GetName()));
|
||||
|
||||
auto groupName = metadata.GetGroup().value_or(Group().GetName());
|
||||
auto pluginSortingData =
|
||||
PluginSortingData(*plugin, masterlistMetadata, userMetadata);
|
||||
|
||||
auto groupName = pluginSortingData.GetGroup();
|
||||
auto groupIt = groupPlugins.find(groupName);
|
||||
if (groupIt == groupPlugins.end()) {
|
||||
groupPlugins.emplace(groupName,
|
||||
@@ -225,18 +232,15 @@ void PluginSorter::AddPluginVertices(Game& game) {
|
||||
groupIt->second.push_back(plugin->GetName());
|
||||
}
|
||||
|
||||
if (logger_) {
|
||||
logger_->trace("Getting and evaluating metadata for plugin \"{}\"",
|
||||
plugin->GetName());
|
||||
}
|
||||
|
||||
boost::add_vertex(PluginSortingData(*plugin, std::move(metadata)), graph_);
|
||||
boost::add_vertex(pluginSortingData, graph_);
|
||||
}
|
||||
|
||||
// Map sets of transitive group dependencies to sets of transitive plugin
|
||||
// dependencies.
|
||||
groups_ = game.GetDatabase()->GetGroups();
|
||||
auto groups = GetTransitiveAfterGroups(groups_);
|
||||
|
||||
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) {
|
||||
@@ -465,17 +469,25 @@ void PluginSorter::AddSpecificEdges() {
|
||||
if (logger_) {
|
||||
logger_->trace("Adding in-edges for requirements.");
|
||||
}
|
||||
for (const auto& file : graph_[*vit].GetRequirements()) {
|
||||
for (const auto& file : graph_[*vit].GetMasterlistRequirements()) {
|
||||
if (GetVertexByName(file.GetName(), parentVertex))
|
||||
AddEdge(parentVertex, *vit, EdgeType::Requirement);
|
||||
AddEdge(parentVertex, *vit, EdgeType::MasterlistRequirement);
|
||||
}
|
||||
for (const auto& file : graph_[*vit].GetUserRequirements()) {
|
||||
if (GetVertexByName(file.GetName(), parentVertex))
|
||||
AddEdge(parentVertex, *vit, EdgeType::UserRequirement);
|
||||
}
|
||||
|
||||
if (logger_) {
|
||||
logger_->trace("Adding in-edges for 'load after's.");
|
||||
}
|
||||
for (const auto& file : graph_[*vit].GetLoadAfterFiles()) {
|
||||
for (const auto& file : graph_[*vit].GetMasterlistLoadAfterFiles()) {
|
||||
if (GetVertexByName(file.GetName(), parentVertex))
|
||||
AddEdge(parentVertex, *vit, EdgeType::LoadAfter);
|
||||
AddEdge(parentVertex, *vit, EdgeType::MasterlistLoadAfter);
|
||||
}
|
||||
for (const auto& file : graph_[*vit].GetUserLoadAfterFiles()) {
|
||||
if (GetVertexByName(file.GetName(), parentVertex))
|
||||
AddEdge(parentVertex, *vit, EdgeType::UserLoadAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,18 +25,34 @@
|
||||
#include "plugin_sorting_data.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/locale.hpp>
|
||||
|
||||
#include <loot/metadata/group.h>
|
||||
|
||||
namespace loot {
|
||||
PluginSortingData::PluginSortingData(const Plugin& plugin,
|
||||
const PluginMetadata&& metadata) :
|
||||
const PluginMetadata& masterlistMetadata,
|
||||
const PluginMetadata& userMetadata) :
|
||||
plugin_(plugin),
|
||||
PluginMetadata(metadata),
|
||||
group_(metadata.GetGroup().value_or(Group().GetName())) {}
|
||||
masterlistLoadAfter_(masterlistMetadata.GetLoadAfterFiles()),
|
||||
userLoadAfter_(userMetadata.GetLoadAfterFiles()),
|
||||
masterlistReq_(masterlistMetadata.GetRequirements()),
|
||||
userReq_(userMetadata.GetRequirements()) {
|
||||
if (userMetadata.GetGroup()) {
|
||||
group_ = userMetadata.GetGroup().value();
|
||||
} else if (masterlistMetadata.GetGroup()) {
|
||||
group_ = masterlistMetadata.GetGroup().value();
|
||||
} else {
|
||||
group_ = Group().GetName();
|
||||
}
|
||||
}
|
||||
|
||||
std::string PluginSortingData::GetName() const { return plugin_.GetName(); }
|
||||
|
||||
std::string PluginSortingData::GetLowercasedName() const {
|
||||
return boost::locale::to_lower(plugin_.GetName());
|
||||
}
|
||||
|
||||
bool PluginSortingData::IsMaster() const {
|
||||
return plugin_.IsMaster() || (plugin_.IsLightMaster() &&
|
||||
!boost::iends_with(plugin_.GetName(), ".esp"));
|
||||
@@ -59,11 +75,29 @@ bool PluginSortingData::DoFormIDsOverlap(
|
||||
|
||||
std::string PluginSortingData::GetGroup() const { return group_; }
|
||||
|
||||
std::unordered_set<std::string> PluginSortingData::GetAfterGroupPlugins() const {
|
||||
std::unordered_set<std::string> PluginSortingData::GetAfterGroupPlugins()
|
||||
const {
|
||||
return afterGroupPlugins_;
|
||||
}
|
||||
|
||||
void PluginSortingData::SetAfterGroupPlugins(std::unordered_set<std::string> plugins) {
|
||||
void PluginSortingData::SetAfterGroupPlugins(
|
||||
std::unordered_set<std::string> plugins) {
|
||||
afterGroupPlugins_ = plugins;
|
||||
}
|
||||
|
||||
const std::set<File>& PluginSortingData::GetMasterlistLoadAfterFiles() const {
|
||||
return masterlistLoadAfter_;
|
||||
}
|
||||
|
||||
const std::set<File>& PluginSortingData::GetUserLoadAfterFiles() const {
|
||||
return userLoadAfter_;
|
||||
}
|
||||
|
||||
const std::set<File>& PluginSortingData::GetMasterlistRequirements() const {
|
||||
return masterlistReq_;
|
||||
}
|
||||
|
||||
const std::set<File>& PluginSortingData::GetUserRequirements() const {
|
||||
return userReq_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,14 @@
|
||||
#include "loot/metadata/plugin_metadata.h"
|
||||
|
||||
namespace loot {
|
||||
class PluginSortingData : private PluginMetadata {
|
||||
class PluginSortingData {
|
||||
public:
|
||||
PluginSortingData(const Plugin& plugin, const PluginMetadata&& metadata);
|
||||
PluginSortingData(const Plugin& plugin,
|
||||
const PluginMetadata& masterlistMetadata,
|
||||
const PluginMetadata& userMetadata);
|
||||
|
||||
std::string GetName() const;
|
||||
std::string GetLowercasedName() const;
|
||||
bool IsMaster() const;
|
||||
bool LoadsArchive() const;
|
||||
std::vector<std::string> GetMasters() const;
|
||||
@@ -44,15 +47,21 @@ public:
|
||||
|
||||
std::unordered_set<std::string> GetAfterGroupPlugins() const;
|
||||
void SetAfterGroupPlugins(std::unordered_set<std::string> plugins);
|
||||
|
||||
using PluginMetadata::GetLowercasedName;
|
||||
using PluginMetadata::GetLoadAfterFiles;
|
||||
using PluginMetadata::GetRequirements;
|
||||
|
||||
const std::set<File>& GetMasterlistLoadAfterFiles() const;
|
||||
const std::set<File>& GetUserLoadAfterFiles() const;
|
||||
const std::set<File>& GetMasterlistRequirements() const;
|
||||
const std::set<File>& GetUserRequirements() const;
|
||||
|
||||
private:
|
||||
const Plugin& plugin_;
|
||||
std::string group_;
|
||||
std::unordered_set<std::string> afterGroupPlugins_;
|
||||
|
||||
std::set<File> masterlistLoadAfter_;
|
||||
std::set<File> userLoadAfter_;
|
||||
std::set<File> masterlistReq_;
|
||||
std::set<File> userReq_;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,55 +35,73 @@ along with LOOT. If not, see
|
||||
namespace loot {
|
||||
namespace test {
|
||||
TEST(GetTransitiveAfterGroups, shouldMapGroupsToTheirTransitiveAfterGroups) {
|
||||
std::unordered_set<Group> groups({
|
||||
Group("a"),
|
||||
Group("b", std::unordered_set<std::string>({ "a" })),
|
||||
Group("c", std::unordered_set<std::string>({"b"}))
|
||||
});
|
||||
std::unordered_set<Group> groups(
|
||||
{Group("a"),
|
||||
Group("b", std::unordered_set<std::string>({"a"})),
|
||||
Group("c", std::unordered_set<std::string>({"b"}))});
|
||||
|
||||
auto mapped = GetTransitiveAfterGroups(groups);
|
||||
auto mapped = GetTransitiveAfterGroups(groups, {});
|
||||
|
||||
EXPECT_TRUE(mapped["a"].empty());
|
||||
EXPECT_EQ(std::unordered_set<std::string>({ "a" }), mapped["b"]);
|
||||
EXPECT_EQ(std::unordered_set<std::string>({ "a", "b" }), mapped["c"]);
|
||||
EXPECT_EQ(std::unordered_set<std::string>({"a"}), mapped["b"]);
|
||||
EXPECT_EQ(std::unordered_set<std::string>({"a", "b"}), mapped["c"]);
|
||||
}
|
||||
|
||||
TEST(GetTransitiveAfterGroups, shouldThrowIfAnAfterGroupDoesNotExist) {
|
||||
std::unordered_set<Group> groups({
|
||||
Group("b", std::unordered_set<std::string>({ "a" }))
|
||||
});
|
||||
std::unordered_set<Group> groups(
|
||||
{Group("b", std::unordered_set<std::string>({"a"}))});
|
||||
|
||||
EXPECT_THROW(GetTransitiveAfterGroups(groups), UndefinedGroupError);
|
||||
EXPECT_THROW(GetTransitiveAfterGroups(groups, {}), UndefinedGroupError);
|
||||
}
|
||||
|
||||
TEST(GetTransitiveAfterGroups, shouldThrowIfAfterGroupsAreCyclic) {
|
||||
std::unordered_set<Group> groups({
|
||||
Group("a", std::unordered_set<std::string>({ "c" })),
|
||||
Group("b", std::unordered_set<std::string>({ "a" })),
|
||||
Group("c", std::unordered_set<std::string>({ "b" }))
|
||||
});
|
||||
std::unordered_set<Group> groups(
|
||||
{Group("a", std::unordered_set<std::string>({"c"})),
|
||||
Group("b", std::unordered_set<std::string>({"a"}))});
|
||||
std::unordered_set<Group> userGroups(
|
||||
{Group("c", std::unordered_set<std::string>({"b"}))});
|
||||
|
||||
try {
|
||||
GetTransitiveAfterGroups(groups);
|
||||
GetTransitiveAfterGroups(groups, userGroups);
|
||||
FAIL();
|
||||
}
|
||||
catch (CyclicInteractionError &e) {
|
||||
} catch (CyclicInteractionError &e) {
|
||||
ASSERT_EQ(3, e.GetCycle().size());
|
||||
EXPECT_EQ(EdgeType::LoadAfter, e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(EdgeType::LoadAfter, e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ(EdgeType::LoadAfter, e.GetCycle()[2].GetTypeOfEdgeToNextVertex());
|
||||
|
||||
// Vertices can be added in any order, so which group is first is undefined.
|
||||
if (e.GetCycle()[0].GetName() == "a") {
|
||||
EXPECT_EQ(EdgeType::MasterlistLoadAfter,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
|
||||
EXPECT_EQ("c", e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::UserLoadAfter,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
|
||||
EXPECT_EQ("b", e.GetCycle()[2].GetName());
|
||||
EXPECT_EQ(EdgeType::MasterlistLoadAfter,
|
||||
e.GetCycle()[2].GetTypeOfEdgeToNextVertex());
|
||||
} else if (e.GetCycle()[0].GetName() == "b") {
|
||||
EXPECT_EQ(EdgeType::MasterlistLoadAfter,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
|
||||
EXPECT_EQ("a", e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::MasterlistLoadAfter,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
|
||||
EXPECT_EQ("c", e.GetCycle()[2].GetName());
|
||||
EXPECT_EQ(EdgeType::UserLoadAfter,
|
||||
e.GetCycle()[2].GetTypeOfEdgeToNextVertex());
|
||||
} else {
|
||||
EXPECT_EQ("c", e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::UserLoadAfter,
|
||||
e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
|
||||
EXPECT_EQ("b", e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::MasterlistLoadAfter,
|
||||
e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
|
||||
EXPECT_EQ("a", e.GetCycle()[2].GetName());
|
||||
EXPECT_EQ(EdgeType::MasterlistLoadAfter,
|
||||
e.GetCycle()[2].GetTypeOfEdgeToNextVertex());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,9 +98,9 @@ protected:
|
||||
std::string getCCCFilename() {
|
||||
if (GetParam() == GameType::fo4) {
|
||||
return "Fallout4.ccc";
|
||||
}
|
||||
else {
|
||||
// Not every game has a .ccc file, but Skyrim SE does, so just assume that.
|
||||
} else {
|
||||
// Not every game has a .ccc file, but Skyrim SE does, so just assume
|
||||
// that.
|
||||
return "Skyrim.ccc";
|
||||
}
|
||||
}
|
||||
@@ -147,23 +147,28 @@ TEST_P(PluginSorterTest,
|
||||
|
||||
auto esp = PluginSortingData(
|
||||
*dynamic_cast<const Plugin *>(game_.GetPlugin(blankEsp).value().get()),
|
||||
PluginMetadata(),
|
||||
PluginMetadata());
|
||||
EXPECT_FALSE(esp.IsMaster());
|
||||
|
||||
auto master = PluginSortingData(
|
||||
*dynamic_cast<const Plugin *>(game_.GetPlugin(blankEsm).value().get()),
|
||||
PluginMetadata(),
|
||||
PluginMetadata());
|
||||
EXPECT_TRUE(master.IsMaster());
|
||||
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) {
|
||||
auto lightMaster = PluginSortingData(
|
||||
*dynamic_cast<const Plugin *>(game_.GetPlugin(blankEsl).value().get()),
|
||||
PluginMetadata(),
|
||||
PluginMetadata());
|
||||
EXPECT_TRUE(lightMaster.IsMaster());
|
||||
|
||||
auto lightMasterEsp = PluginSortingData(*dynamic_cast<const Plugin *>(
|
||||
auto lightMasterEsp =
|
||||
PluginSortingData(*dynamic_cast<const Plugin *>(
|
||||
game_.GetPlugin(blankEslEsp).value().get()),
|
||||
PluginMetadata());
|
||||
PluginMetadata(),
|
||||
PluginMetadata());
|
||||
EXPECT_FALSE(lightMasterEsp.IsMaster());
|
||||
}
|
||||
}
|
||||
@@ -379,7 +384,8 @@ TEST_P(
|
||||
FAIL();
|
||||
} catch (CyclicInteractionError &e) {
|
||||
ASSERT_EQ(3, e.GetCycle().size());
|
||||
EXPECT_EQ("Blank - Different Master Dependent.esm", e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ("Blank - Different Master Dependent.esm",
|
||||
e.GetCycle()[0].GetName());
|
||||
EXPECT_EQ(EdgeType::Group, e.GetCycle()[0].GetTypeOfEdgeToNextVertex());
|
||||
EXPECT_EQ("Blank.esm", e.GetCycle()[1].GetName());
|
||||
EXPECT_EQ(EdgeType::Master, e.GetCycle()[1].GetTypeOfEdgeToNextVertex());
|
||||
|
||||
Reference in New Issue
Block a user