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:
Oliver Hamlet
2018-03-17 18:26:37 +00:00
parent 64871cd65e
commit 16a930a2ca
5 changed files with 247 additions and 0 deletions
+3
View File
@@ -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"
+135
View File
@@ -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;
}
}
+38
View File
@@ -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
+1
View File
@@ -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