Skip to content

Commit

Permalink
Improve recommendations performance
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed Oct 5, 2023
1 parent 7979ef7 commit 57ef6db
Show file tree
Hide file tree
Showing 11 changed files with 275 additions and 321 deletions.
230 changes: 57 additions & 173 deletions Core/ModuleInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ public void Upgrade(IEnumerable<CkanModule> modules,
var resolver = new RelationshipResolver(
modules,
modules.Select(m => registry.InstalledModule(m.identifier)?.Module).Where(m => m != null),
RelationshipResolver.DependsOnlyOpts(),
RelationshipResolverOptions.DependsOnlyOpts(),
registry,
ksp.VersionCriteria()
);
Expand Down Expand Up @@ -1292,20 +1292,6 @@ private void DownloadModules(IEnumerable<CkanModule> mods, IDownloader downloade
}
}

private static readonly RelationshipResolverOptions RecommenderOptions = new RelationshipResolverOptions()
{
// Only look at depends
with_recommends = false,

// Don't throw anything
without_toomanyprovides_kraken = true,
without_enforce_consistency = true,
proceed_with_inconsistencies = true,

// Skip relationships with suppress_recommendations==true
get_recommenders = true,
};

/// <summary>
/// Looks for optional related modules that could be installed alongside the given modules
/// </summary>
Expand All @@ -1317,148 +1303,50 @@ private void DownloadModules(IEnumerable<CkanModule> mods, IDownloader downloade
/// <returns>
/// true if anything found, false otherwise
/// </returns>
public bool FindRecommendations(
HashSet<CkanModule> sourceModules,
List<CkanModule> toInstall,
Registry registry,
out Dictionary<CkanModule, Tuple<bool, List<string>>> recommendations,
out Dictionary<CkanModule, List<string>> suggestions,
out Dictionary<CkanModule, HashSet<string>> supporters)
public bool FindRecommendations(HashSet<CkanModule> sourceModules,
List<CkanModule> toInstall,
Registry registry,
out Dictionary<CkanModule, Tuple<bool, List<string>>> recommendations,
out Dictionary<CkanModule, List<string>> suggestions,
out Dictionary<CkanModule, HashSet<string>> supporters)
{
// Get all dependencies except where suppress_recommendations==true
var resolver = new RelationshipResolver(sourceModules, null, RecommenderOptions,
registry, ksp.VersionCriteria());
var recommenders = resolver.ModList().ToList();

var dependersIndex = getDependersIndex(recommenders, registry, toInstall);
var instList = toInstall.ToList();
recommendations = new Dictionary<CkanModule, Tuple<bool, List<string>>>();
suggestions = new Dictionary<CkanModule, List<string>>();
supporters = new Dictionary<CkanModule, HashSet<string>>();
foreach (CkanModule mod in recommenders.Where(m => m.recommends != null))
{
foreach (RelationshipDescriptor rel in mod.recommends)
{
List<CkanModule> providers = rel.LatestAvailableWithProvides(
registry, ksp.VersionCriteria());
int i = 0;
foreach (CkanModule provider in providers)
{
if (!registry.IsInstalled(provider.identifier)
&& !toInstall.Any(m => m.identifier == provider.identifier)
&& dependersIndex.TryGetValue(provider, out List<string> dependers)
&& (provider.IsDLC || CanInstall(RelationshipResolver.DependsOnlyOpts(),
instList.Concat(new List<CkanModule>() { provider }).ToList(), registry)))
{
dependersIndex.Remove(provider);
recommendations.Add(
provider,
new Tuple<bool, List<string>>(
!provider.IsDLC && (i == 0 || provider.identifier == (rel as ModuleRelationshipDescriptor)?.name),
dependers));
++i;
}
}
}
}
foreach (CkanModule mod in recommenders.Where(m => m.suggests != null))
{
foreach (RelationshipDescriptor rel in mod.suggests)
{
List<CkanModule> providers = rel.LatestAvailableWithProvides(
registry, ksp.VersionCriteria());
foreach (CkanModule provider in providers)
{
if (!registry.IsInstalled(provider.identifier)
&& !toInstall.Any(m => m.identifier == provider.identifier)
&& dependersIndex.TryGetValue(provider, out List<string> dependers)
&& (provider.IsDLC || CanInstall(RelationshipResolver.DependsOnlyOpts(),
instList.Concat(new List<CkanModule>() { provider }).ToList(), registry)))
{
dependersIndex.Remove(provider);
suggestions.Add(provider, dependers);
}
}
}
}

// Find installable modules with "supports" relationships
var candidates = registry.CompatibleModules(ksp.VersionCriteria())
.Where(mod => !registry.IsInstalled(mod.identifier)
&& !toInstall.Any(m => m.identifier == mod.identifier))
.Where(m => m?.supports != null)
.Except(recommendations.Keys)
.Except(suggestions.Keys);
// Find each module that "supports" something we're installing
foreach (CkanModule mod in candidates)
{
foreach (RelationshipDescriptor rel in mod.supports)
{
if (rel.MatchesAny(recommenders, null, null))
{
var name = (rel as ModuleRelationshipDescriptor)?.name;
if (!string.IsNullOrEmpty(name))
{
if (supporters.TryGetValue(mod, out HashSet<string> others))
{
others.Add(name);
}
else
{
supporters.Add(mod, new HashSet<string>() { name });
}
}
}
}
}
supporters.RemoveWhere(kvp =>
!CanInstall(
RelationshipResolver.DependsOnlyOpts(),
instList.Concat(new List<CkanModule>() { kvp.Key }).ToList(),
registry));

return recommendations.Any() || suggestions.Any() || supporters.Any();
}

// Build up the list of who recommends what
private Dictionary<CkanModule, List<string>> getDependersIndex(
IEnumerable<CkanModule> sourceModules,
IRegistryQuerier registry,
List<CkanModule> toExclude)
{
Dictionary<CkanModule, List<string>> dependersIndex = new Dictionary<CkanModule, List<string>>();
foreach (CkanModule mod in sourceModules)
{
foreach (List<RelationshipDescriptor> relations in new List<List<RelationshipDescriptor>>() { mod.recommends, mod.suggests })
{
if (relations != null)
{
foreach (RelationshipDescriptor rel in relations)
{
List<CkanModule> providers = rel.LatestAvailableWithProvides(
registry, ksp.VersionCriteria());
foreach (CkanModule provider in providers)
{
if (!registry.IsInstalled(provider.identifier)
&& !toExclude.Any(m => m.identifier == provider.identifier))
{
if (dependersIndex.TryGetValue(provider, out List<string> dependers))
{
// Add the dependent mod to the list of reasons this dependency is shown.
dependers.Add(mod.identifier);
}
else
{
// Add a new entry if this provider isn't listed yet.
dependersIndex.Add(provider, new List<string>() { mod.identifier });
}
}
}
}
}
}
}
return dependersIndex;
var crit = ksp.VersionCriteria();
var resolver = new RelationshipResolver(sourceModules, null,
RelationshipResolverOptions.KitchenSinkOpts(),
registry, crit);
var recommenders = resolver.Dependencies().ToHashSet();

recommendations = resolver.Recommendations(recommenders)
.ToDictionary(m => m,
m => new Tuple<bool, List<string>>(
resolver.ReasonsFor(m)
.Any(r => (r as SelectionReason.Recommended)
?.ProvidesIndex == 0),
resolver.ReasonsFor(m)
.Where(r => r is SelectionReason.Recommended rec
&& recommenders.Contains(rec.Parent))
.Select(r => r.Parent.identifier)
.ToList()));
suggestions = resolver.Suggestions(recommenders,
recommendations.Keys.ToList())
.ToDictionary(m => m,
m => resolver.ReasonsFor(m)
.Where(r => r is SelectionReason.Suggested sug
&& recommenders.Contains(sug.Parent))
.Select(r => r.Parent.identifier)
.ToList());

var opts = RelationshipResolverOptions.DependsOnlyOpts();
supporters = resolver.Supporters(recommenders,
toInstall.Concat(recommendations.Keys)
.Concat(suggestions.Keys))
.Where(kvp => CanInstall(toInstall.Append(kvp.Key).ToList(),
opts, registry, crit))
.ToDictionary();

return recommendations.Count > 0
|| suggestions.Count > 0
|| supporters.Count > 0;
}

/// <summary>
Expand All @@ -1471,27 +1359,23 @@ private Dictionary<CkanModule, List<string>> getDependersIndex(
/// <returns>
/// True if it's possible to install these mods, false otherwise
/// </returns>
public bool CanInstall(
RelationshipResolverOptions opts,
List<CkanModule> toInstall,
IRegistryQuerier registry
)
public bool CanInstall(List<CkanModule> toInstall,
RelationshipResolverOptions opts,
IRegistryQuerier registry,
GameVersionCriteria crit)
{
string request = toInstall.Select(m => m.identifier).Aggregate((a, b) => $"{a}, {b}");
string request = string.Join(", ", toInstall.Select(m => m.identifier));
try
{
RelationshipResolver resolver = new RelationshipResolver(
toInstall,
toInstall.Select(m => registry.InstalledModule(m.identifier)?.Module).Where(m => m != null),
opts, registry, ksp.VersionCriteria()
);
var installed = toInstall.Select(m => registry.InstalledModule(m.identifier)?.Module)
.Where(m => m != null);
var resolver = new RelationshipResolver(toInstall, installed, opts, registry, crit);

if (resolver.ModList().Count() >= toInstall.Count(m => !m.IsMetapackage))
var resolverModList = resolver.ModList().ToList();
if (resolverModList.Count >= toInstall.Count(m => !m.IsMetapackage))
{
// We can install with no further dependencies
string recipe = resolver.ModList()
.Select(m => m.identifier)
.Aggregate((a, b) => $"{a}, {b}");
string recipe = string.Join(", ", resolverModList.Select(m => m.identifier));
log.Debug($"Installable: {request}: {recipe}");
return true;
}
Expand All @@ -1504,10 +1388,10 @@ IRegistryQuerier registry
catch (TooManyModsProvideKraken k)
{
// One of the dependencies is virtual
foreach (CkanModule mod in k.modules)
foreach (var mod in k.modules)
{
// Try each option recursively to see if any are successful
if (CanInstall(opts, toInstall.Concat(new List<CkanModule>() { mod }).ToList(), registry))
if (CanInstall(toInstall.Append(mod).ToList(), opts, registry, crit))
{
// Child call will emit debug output, so we don't need to here
return true;
Expand Down
2 changes: 1 addition & 1 deletion Core/Registry/IRegistryQuerier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ private static IEnumerable<InstalledModule> FindRemovableAutoInstalled(
var autoInstIds = autoInstMods.Select(im => im.Module.identifier).ToHashSet();

// Need to get the full changeset for this to work as intended
RelationshipResolverOptions opts = RelationshipResolver.DependsOnlyOpts();
RelationshipResolverOptions opts = RelationshipResolverOptions.DependsOnlyOpts();
opts.without_toomanyprovides_kraken = true;
opts.without_enforce_consistency = true;
opts.proceed_with_inconsistencies = true;
Expand Down
Loading

0 comments on commit 57ef6db

Please sign in to comment.