Skip to content

Commit

Permalink
Sort blueprint plugins after all others
Browse files Browse the repository at this point in the history
Also validate that no non-blueprint masters or non-masters are expected to load after any blueprint masters.
  • Loading branch information
Ortham committed Aug 22, 2024
1 parent 93af258 commit 52c17e3
Show file tree
Hide file tree
Showing 7 changed files with 581 additions and 25 deletions.
1 change: 1 addition & 0 deletions include/loot/enum/edge_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ enum struct EdgeType : unsigned int {
recordOverlap,
assetOverlap,
tieBreak,
blueprintMaster,
};
}

Expand Down
141 changes: 122 additions & 19 deletions src/api/sorting/plugin_sort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,25 +78,40 @@ std::unordered_map<std::string, Group> GetGroupsMap(
return groupsMap;
}

bool IsInRange(const std::vector<PluginSortingData>::const_iterator& begin,
const std::vector<PluginSortingData>::const_iterator& end,
const std::string& name) {
return std::any_of(begin, end, [&](const PluginSortingData& plugin) {
return CompareFilenames(plugin.GetName(), name) == 0;
});
}

void ValidateSpecificAndHardcodedEdges(
const std::vector<PluginSortingData>::const_iterator& begin,
const std::vector<PluginSortingData>::const_iterator& firstBlueprintMaster,
const std::vector<PluginSortingData>::const_iterator& firstNonMaster,
const std::vector<PluginSortingData>::const_iterator& end,
const std::vector<std::string>& hardcodedPlugins) {
const auto isNonMaster = [&](const std::string& name) {
return std::any_of(
firstNonMaster, end, [&](const PluginSortingData& plugin) {
return CompareFilenames(plugin.GetName(), name) == 0;
});
return IsInRange(firstNonMaster, end, name);
};
const auto isBlueprintMaster = [&](const std::string& name) {
return IsInRange(firstBlueprintMaster, firstNonMaster, name);
};

for (auto it = begin; it != firstNonMaster; ++it) {
for (auto it = begin; it != firstBlueprintMaster; ++it) {
for (const auto& master : it->GetMasters()) {
if (isNonMaster(master)) {
throw CyclicInteractionError(
std::vector<Vertex>{Vertex(master, EdgeType::master),
Vertex(it->GetName(), EdgeType::masterFlag)});
}

if (isBlueprintMaster(master)) {
throw CyclicInteractionError(std::vector<Vertex>{
Vertex(master, EdgeType::master),
Vertex(it->GetName(), EdgeType::blueprintMaster)});
}
}

for (const auto& file : it->GetMasterlistRequirements()) {
Expand All @@ -106,6 +121,12 @@ void ValidateSpecificAndHardcodedEdges(
std::vector<Vertex>{Vertex(name, EdgeType::masterlistRequirement),
Vertex(it->GetName(), EdgeType::masterFlag)});
}

if (isBlueprintMaster(name)) {
throw CyclicInteractionError(std::vector<Vertex>{
Vertex(name, EdgeType::masterlistRequirement),
Vertex(it->GetName(), EdgeType::blueprintMaster)});
}
}

for (const auto& file : it->GetUserRequirements()) {
Expand All @@ -115,6 +136,12 @@ void ValidateSpecificAndHardcodedEdges(
std::vector<Vertex>{Vertex(name, EdgeType::userRequirement),
Vertex(it->GetName(), EdgeType::masterFlag)});
}

if (isBlueprintMaster(name)) {
throw CyclicInteractionError(std::vector<Vertex>{
Vertex(name, EdgeType::userRequirement),
Vertex(it->GetName(), EdgeType::blueprintMaster)});
}
}

for (const auto& file : it->GetMasterlistLoadAfterFiles()) {
Expand All @@ -124,6 +151,12 @@ void ValidateSpecificAndHardcodedEdges(
std::vector<Vertex>{Vertex(name, EdgeType::masterlistLoadAfter),
Vertex(it->GetName(), EdgeType::masterFlag)});
}

if (isBlueprintMaster(name)) {
throw CyclicInteractionError(std::vector<Vertex>{
Vertex(name, EdgeType::masterlistLoadAfter),
Vertex(it->GetName(), EdgeType::blueprintMaster)});
}
}

for (const auto& file : it->GetUserLoadAfterFiles()) {
Expand All @@ -133,6 +166,58 @@ void ValidateSpecificAndHardcodedEdges(
std::vector<Vertex>{Vertex(name, EdgeType::userLoadAfter),
Vertex(it->GetName(), EdgeType::masterFlag)});
}

if (isBlueprintMaster(name)) {
throw CyclicInteractionError(std::vector<Vertex>{
Vertex(name, EdgeType::userLoadAfter),
Vertex(it->GetName(), EdgeType::blueprintMaster)});
}
}
}

for (auto it = firstNonMaster; it != end; ++it) {
for (const auto& master : it->GetMasters()) {
if (isBlueprintMaster(master)) {
throw CyclicInteractionError(std::vector<Vertex>{
Vertex(master, EdgeType::master),
Vertex(it->GetName(), EdgeType::blueprintMaster)});
}
}

for (const auto& file : it->GetMasterlistRequirements()) {
const auto name = std::string(file.GetName());
if (isBlueprintMaster(name)) {
throw CyclicInteractionError(std::vector<Vertex>{
Vertex(name, EdgeType::masterlistRequirement),
Vertex(it->GetName(), EdgeType::blueprintMaster)});
}
}

for (const auto& file : it->GetUserRequirements()) {
const auto name = std::string(file.GetName());
if (isBlueprintMaster(name)) {
throw CyclicInteractionError(std::vector<Vertex>{
Vertex(name, EdgeType::userRequirement),
Vertex(it->GetName(), EdgeType::blueprintMaster)});
}
}

for (const auto& file : it->GetMasterlistLoadAfterFiles()) {
const auto name = std::string(file.GetName());
if (isBlueprintMaster(name)) {
throw CyclicInteractionError(std::vector<Vertex>{
Vertex(name, EdgeType::masterlistLoadAfter),
Vertex(it->GetName(), EdgeType::blueprintMaster)});
}
}

for (const auto& file : it->GetUserLoadAfterFiles()) {
const auto name = std::string(file.GetName());
if (isBlueprintMaster(name)) {
throw CyclicInteractionError(std::vector<Vertex>{
Vertex(name, EdgeType::userLoadAfter),
Vertex(it->GetName(), EdgeType::blueprintMaster)});
}
}
}

Expand Down Expand Up @@ -231,38 +316,56 @@ std::vector<std::string> SortPlugins(
// two thirds of all edges added. The cost of each bidirectional search
// scales with the number of edges, so reducing edges makes searches
// faster.
// As such, sort plugins using two separate graphs for masters and
// non-masters. This means that any edges that go from a non-master to a
// master are effectively ignored, so won't cause cyclic interaction errors.
// Edges going the other way will also effectively be ignored, but that
// shouldn't have a noticeable impact.
// Similarly, blueprint plugins load after all others.
// As such, sort plugins using three separate graphs for masters,
// non-masters and blueprint plugins. This means that any edges that go from a
// non-master to a master are effectively ignored, so won't cause cyclic
// interaction errors. Edges going the other way will also effectively be
// ignored, but that shouldn't have a noticeable impact.
const auto firstNonMasterIt = std::stable_partition(
pluginsSortingData.begin(),
pluginsSortingData.end(),
[](const PluginSortingData& plugin) { return plugin.IsMaster(); });

const auto firstBlueprintPluginIt =
std::stable_partition(pluginsSortingData.begin(),
firstNonMasterIt,
[](const PluginSortingData& plugin) {
return !plugin.IsBlueprintMaster();
});

ValidateSpecificAndHardcodedEdges(pluginsSortingData.begin(),
firstBlueprintPluginIt,
firstNonMasterIt,
pluginsSortingData.end(),
earlyLoadingPlugins);

auto newLoadOrder = SortPlugins(pluginsSortingData.begin(),
firstNonMasterIt,
earlyLoadingPlugins,
groupsMap,
predecessorGroupsMap);
auto newMastersLoadOrder = SortPlugins(pluginsSortingData.begin(),
firstBlueprintPluginIt,
earlyLoadingPlugins,
groupsMap,
predecessorGroupsMap);

const auto newBlueprintMastersLoadOrder = SortPlugins(firstBlueprintPluginIt,
firstNonMasterIt,
earlyLoadingPlugins,
groupsMap,
predecessorGroupsMap);

const auto newNonMastersLoadOrder = SortPlugins(firstNonMasterIt,
pluginsSortingData.end(),
earlyLoadingPlugins,
groupsMap,
predecessorGroupsMap);

newLoadOrder.insert(newLoadOrder.end(),
newNonMastersLoadOrder.begin(),
newNonMastersLoadOrder.end());
newMastersLoadOrder.insert(newMastersLoadOrder.end(),
newNonMastersLoadOrder.begin(),
newNonMastersLoadOrder.end());
newMastersLoadOrder.insert(newMastersLoadOrder.end(),
newBlueprintMastersLoadOrder.begin(),
newBlueprintMastersLoadOrder.end());

return newLoadOrder;
return newMastersLoadOrder;
}

std::vector<std::string> SortPlugins(
Expand Down
5 changes: 5 additions & 0 deletions src/api/sorting/plugin_sorting_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ bool PluginSortingData::IsMaster() const {
return plugin_ != nullptr && plugin_->IsMaster();
}

bool PluginSortingData::IsBlueprintMaster() const {
return plugin_ != nullptr && plugin_->IsMaster() &&
plugin_->IsBlueprintPlugin();
}

bool PluginSortingData::LoadsArchive() const {
return plugin_ != nullptr && plugin_->LoadsArchive();
}
Expand Down
10 changes: 5 additions & 5 deletions src/api/sorting/plugin_sorting_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ class PluginSortingData {
* PluginSortingData objects must not live longer than the Plugin objects
* that they are constructed from.
*/
explicit PluginSortingData(
const PluginSortingInterface* plugin,
const PluginMetadata& masterlistMetadata,
const PluginMetadata& userMetadata,
const std::vector<std::string>& loadOrder);
explicit PluginSortingData(const PluginSortingInterface* plugin,
const PluginMetadata& masterlistMetadata,
const PluginMetadata& userMetadata,
const std::vector<std::string>& loadOrder);

std::string GetName() const;
bool IsMaster() const;
bool IsBlueprintMaster() const;
bool LoadsArchive() const;
std::vector<std::string> GetMasters() const;
size_t GetOverrideRecordCount() const;
Expand Down
7 changes: 6 additions & 1 deletion src/tests/api/internals/sorting/plugin_graph_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class TestPlugin : public PluginSortingInterface {

bool IsUpdatePlugin() const override { return false; }

bool IsBlueprintPlugin() const override { return false; }
bool IsBlueprintPlugin() const override { return isBlueprintMaster_; }

bool IsValidAsLightPlugin() const override { return false; }

Expand Down Expand Up @@ -100,6 +100,10 @@ class TestPlugin : public PluginSortingInterface {

void SetIsMaster(bool isMaster) { isMaster_ = isMaster; }

void SetIsBlueprintMaster(bool isBlueprintMaster) {
isBlueprintMaster_ = isBlueprintMaster;
}

void AddOverlappingRecords(const PluginInterface& plugin) {
recordsOverlapWith.insert(&plugin);
}
Expand All @@ -122,6 +126,7 @@ class TestPlugin : public PluginSortingInterface {
size_t overrideRecordCount_{0};
size_t assetCount_{0};
bool isMaster_{false};
bool isBlueprintMaster_{false};
};
}

Expand Down
Loading

0 comments on commit 52c17e3

Please sign in to comment.