diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index b530e870c..59b575056 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -716,46 +716,68 @@ bool CModHandler::checkDependencies(const std::vector & input) const return true; } -std::vector CModHandler::resolveDependencies(std::vector modsToResolve) const +// Returned vector affects the resource loaders call order (see CFilesystemList::load). +// The loaders call order matters when dependent mod overrides resources in its dependencies. +std::vector CModHandler::validateAndSortDependencies(std::vector modsToResolve) const { - std::vector brokenMods; + // Topological sort algorithm. + // TODO: Investigate possible ways to improve performance. + boost::range::sort(modsToResolve); // Sort mods per name + std::vector sortedValidMods; // Vector keeps order of elements (LIFO) + sortedValidMods.reserve(modsToResolve.size()); // push_back calls won't cause memory reallocation + std::set resolvedModIDs; // Use a set for validation for performance reason, but set does not keep order of elements - auto looksValid = [&](const CModInfo & mod) -> bool + // Mod is resolved if it has not dependencies or all its dependencies are already resolved + auto isResolved = [&](const CModInfo & mod) -> CModInfo::EValidationStatus { - auto res = true; + if(mod.dependencies.size() > resolvedModIDs.size()) + return CModInfo::PENDING; + for(const TModID & dependency : mod.dependencies) { - if(!vstd::contains(modsToResolve, dependency)) - { - logMod->error("Mod '%s' will not work: it depends on mod '%s', which is not installed.", mod.name, dependency); - res = false; //continue iterations, since we should show all errors for the current mod. - } + if(!vstd::contains(resolvedModIDs, dependency)) + return CModInfo::PENDING; } - return res; + return CModInfo::PASSED; }; while(true) { - for(auto mod : modsToResolve) + std::set resolvedOnCurrentTreeLevel; + for(auto it = modsToResolve.begin(); it != modsToResolve.end();) // One iteration - one level of mods tree { - if(!looksValid(this->allMods.at(mod))) - brokenMods.push_back(mod); - } - if(!brokenMods.empty()) - { - vstd::erase_if(modsToResolve, [&](TModID mid) + if(isResolved(allMods.at(*it)) == CModInfo::PASSED) { - return brokenMods.end() != std::find(brokenMods.begin(), brokenMods.end(), mid); - }); - brokenMods.clear(); - continue; + resolvedOnCurrentTreeLevel.insert(*it); // Not to the resolvedModIDs, so current node childs will be resolved on the next iteration + sortedValidMods.push_back(*it); + it = modsToResolve.erase(it); + continue; + } + it++; } + if(resolvedOnCurrentTreeLevel.size()) + { + resolvedModIDs.insert(resolvedOnCurrentTreeLevel.begin(), resolvedOnCurrentTreeLevel.end()); + continue; + } + // If there're no valid mods on the current mods tree level, no more mod can be resolved, should be end. break; } - boost::range::sort(modsToResolve); - return modsToResolve; + + // Left mods have unresolved dependencies, output all to log. + for(const auto & brokenModID : modsToResolve) + { + const CModInfo & brokenMod = allMods.at(brokenModID); + for(const TModID & dependency : brokenMod.dependencies) + { + if(!vstd::contains(resolvedModIDs, dependency)) + logMod->error("Mod '%s' will not work: it depends on mod '%s', which is not installed.", brokenMod.name, dependency); + } + } + return sortedValidMods; } + std::vector CModHandler::getModList(std::string path) { std::string modDir = boost::to_upper_copy(path + "MODS/"); @@ -895,7 +917,7 @@ static ui32 calculateModChecksum(const std::string modName, ISimpleResourceLoade void CModHandler::loadModFilesystems() { - activeMods = resolveDependencies(activeMods); + activeMods = validateAndSortDependencies(activeMods); coreMod.updateChecksum(calculateModChecksum("core", CResourceHandler::get("core"))); diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 6be65e728..6bc7373db 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -242,9 +242,15 @@ class DLL_LINKAGE CModHandler // - circular dependencies bool checkDependencies(const std::vector & input) const; - // returns load order in which all dependencies are resolved, e.g. loaded after required mods - // function assumes that input list is valid (checkDependencies returned true) - std::vector resolveDependencies(std::vector input) const; + /** + * 1. Set apart mods with resolved dependencies from mods which have unresolved dependencies + * 2. Sort resolved mods using topological algorithm + * 3. Log all problem mods and their unresolved dependencies + * + * @param modsToResolve list of valid mod IDs (checkDependencies returned true - TODO: Clarify it.) + * @return a vector of the topologically sorted resolved mods: child nodes (dependent mods) have greater index than parents + */ + std::vector validateAndSortDependencies(std::vector modsToResolve) const; std::vector getModList(std::string path); void loadMods(std::string path, std::string parent, const JsonNode & modSettings, bool enableMods);