mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Add function to get groups' transitive after groups
This will be used to map to transitive plugin sets during sorting.
This commit is contained in:
@@ -182,6 +182,7 @@ set (LOOT_API_SRC "${CMAKE_BINARY_DIR}/generated/loot_version.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/metadata_list.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/masterlist.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/plugin.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/sorting/group_sort.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/sorting/plugin_sorter.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/sorting/plugin_sorting_data.cpp"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/helpers/crc.cpp"
|
||||
@@ -233,6 +234,7 @@ set (LOOT_API_HEADERS "${CMAKE_SOURCE_DIR}/include/loot/api.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/metadata_list.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/masterlist.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/plugin.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/sorting/group_sort.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/sorting/plugin_sorter.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/sorting/plugin_sorting_data.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/api/helpers/git_helper.h"
|
||||
@@ -263,6 +265,7 @@ set (LOOT_TESTS_HEADERS "${CMAKE_SOURCE_DIR}/src/tests/api/internals/game/game_t
|
||||
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/priority_test.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata/tag_test.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/plugin_test.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/sorting/group_sort_test.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/sorting/plugin_sorter_test.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/masterlist_test.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/tests/api/internals/metadata_list_test.h"
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/* LOOT
|
||||
|
||||
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
|
||||
Fallout: New Vegas.
|
||||
|
||||
Copyright (C) 2018 WrinklyNinja
|
||||
|
||||
This file is part of LOOT.
|
||||
|
||||
LOOT 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.
|
||||
|
||||
LOOT 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 LOOT. If not, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "group_sort.h"
|
||||
|
||||
#include <boost/graph/adjacency_list.hpp>
|
||||
#include <boost/graph/graph_traits.hpp>
|
||||
#include <boost/graph/depth_first_search.hpp>
|
||||
|
||||
#include "loot/exception/cyclic_interaction_error.h"
|
||||
|
||||
namespace loot {
|
||||
typedef boost::adjacency_list<boost::vecS,
|
||||
boost::vecS,
|
||||
boost::directedS,
|
||||
std::string> GroupGraph;
|
||||
typedef boost::graph_traits<GroupGraph>::vertex_descriptor vertex_t;
|
||||
typedef boost::graph_traits<GroupGraph>::edge_descriptor edge_t;
|
||||
|
||||
class GroupCycleDetector : public boost::dfs_visitor<> {
|
||||
public:
|
||||
void tree_edge(edge_t edge, const GroupGraph& graph) {
|
||||
auto source = boost::source(edge, graph);
|
||||
auto name = graph[source];
|
||||
|
||||
// Check if the plugin already exists in the recorded trail.
|
||||
auto it = find(begin(trail), end(trail), name);
|
||||
|
||||
if (it != end(trail)) {
|
||||
// Erase everything from this position onwards, as it doesn't
|
||||
// contribute to a forward-cycle.
|
||||
trail.erase(it, end(trail));
|
||||
}
|
||||
|
||||
trail.push_back(name);
|
||||
}
|
||||
|
||||
void back_edge(edge_t edge, const GroupGraph& graph) {
|
||||
auto source = boost::source(edge, graph);
|
||||
auto target = boost::target(edge, graph);
|
||||
|
||||
trail.push_back(graph[source]);
|
||||
std::string backCycle;
|
||||
auto it = find(begin(trail), end(trail), graph[target]);
|
||||
for (it; it != end(trail); ++it) {
|
||||
backCycle += *it + ", ";
|
||||
}
|
||||
backCycle.erase(backCycle.length() - 2);
|
||||
|
||||
throw CyclicInteractionError(
|
||||
graph[source], graph[target], backCycle);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> trail;
|
||||
};
|
||||
|
||||
class AfterGroupsVisitor : public boost::dfs_visitor<> {
|
||||
public:
|
||||
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]);
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> get_visited_groups() const {
|
||||
return visitedGroups_;
|
||||
}
|
||||
private:
|
||||
std::unordered_set<std::string>& visitedGroups_;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> GetTransitiveAfterGroups(const std::unordered_set<Group> groups) {
|
||||
GroupGraph graph;
|
||||
|
||||
std::unordered_map<std::string, vertex_t> groupVertices;
|
||||
for (const auto& group : groups) {
|
||||
auto vertex = boost::add_vertex(group.GetName(), graph);
|
||||
groupVertices.emplace(group.GetName(), vertex);
|
||||
}
|
||||
|
||||
for (const auto& group : groups) {
|
||||
for (const auto& otherGroupName : group.GetAfterGroups()) {
|
||||
auto otherVertex = groupVertices.find(otherGroupName);
|
||||
if (otherVertex == groupVertices.end()) {
|
||||
throw std::invalid_argument("The group \"" + otherGroupName + "\" does not exist");
|
||||
}
|
||||
|
||||
auto vertex = groupVertices[group.GetName()];
|
||||
boost::add_edge(vertex, otherVertex->second, graph);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for cycles.
|
||||
boost::depth_first_search(graph, visitor(GroupCycleDetector()));
|
||||
|
||||
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]);
|
||||
|
||||
boost::depth_first_visit(graph, vertex, afterGroupsVisitor, colorMap);
|
||||
transitiveAfterGroups[graph[vertex]] = visitedGroups;
|
||||
}
|
||||
|
||||
return transitiveAfterGroups;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/* LOOT
|
||||
|
||||
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
|
||||
Fallout: New Vegas.
|
||||
|
||||
Copyright (C) 2018 WrinklyNinja
|
||||
|
||||
This file is part of LOOT.
|
||||
|
||||
LOOT 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.
|
||||
|
||||
LOOT 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 LOOT. If not, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LOOT_API_SORTING_GROUP_SORT
|
||||
#define LOOT_API_SORTING_GROUP_SORT
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "loot/metadata/group.h"
|
||||
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "tests/api/internals/metadata/priority_test.h"
|
||||
#include "tests/api/internals/metadata/tag_test.h"
|
||||
#include "tests/api/internals/metadata_list_test.h"
|
||||
#include "tests/api/internals/sorting/group_sort_test.h"
|
||||
#include "tests/api/internals/sorting/plugin_sorter_test.h"
|
||||
#include "tests/api/internals/plugin_test.h"
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/* LOOT
|
||||
|
||||
A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and
|
||||
Fallout: New Vegas.
|
||||
|
||||
Copyright (C) 2018 WrinklyNinja
|
||||
|
||||
This file is part of LOOT.
|
||||
|
||||
LOOT 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.
|
||||
|
||||
LOOT 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 LOOT. If not, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LOOT_TESTS_API_INTERNALS_SORTING_GROUP_SORT_TEST
|
||||
#define LOOT_TESTS_API_INTERNALS_SORTING_GROUP_SORT_TEST
|
||||
|
||||
#include "api/sorting/group_sort.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "loot/exception/cyclic_interaction_error.h"
|
||||
|
||||
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" }))
|
||||
});
|
||||
|
||||
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"]);
|
||||
}
|
||||
|
||||
TEST(GetTransitiveAfterGroups, shouldThrowIfAnAfterGroupDoesNotExist) {
|
||||
std::unordered_set<Group> groups({
|
||||
Group("b", std::unordered_set<std::string>({ "a" }))
|
||||
});
|
||||
|
||||
EXPECT_THROW(GetTransitiveAfterGroups(groups), std::invalid_argument);
|
||||
}
|
||||
|
||||
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" }))
|
||||
});
|
||||
|
||||
EXPECT_THROW(GetTransitiveAfterGroups(groups), CyclicInteractionError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user