Browse Source

Implement dependency resolution part of new loader part

pull/94/head
Anairkoen Schno 3 years ago
parent
commit
cf0119038c
Signed by: DaNike GPG Key ID: BEFB74D5F3FC4387
3 changed files with 158 additions and 23 deletions
  1. +135
    -1
      IPA.Loader/Loader/PluginLoader.cs
  2. +19
    -18
      IPA.Loader/Loader/PluginManifest.cs
  3. +4
    -4
      IPA.Loader/Loader/PluginMetadata.cs

+ 135
- 1
IPA.Loader/Loader/PluginLoader.cs View File

@ -14,6 +14,7 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Version = SemVer.Version;
using SemVer;
using System.Diagnostics.CodeAnalysis;
#if NET4
using Task = System.Threading.Tasks.Task;
using TaskEx = System.Threading.Tasks.Task;
@ -52,6 +53,11 @@ namespace IPA.Loader
ResolveDependencies();
#endif
// Features contribute to load order considerations
InitFeatures();
DoOrderResolution();
});
internal static void YeetIfNeeded()
@ -159,6 +165,12 @@ namespace IPA.Loader
continue;
}
if (pluginManifest.Id == null)
{
Logger.loader.Warn($"Plugin '{pluginManifest.Name}' does not have a listed ID, using name");
pluginManifest.Id = pluginManifest.Name;
}
metadata.Manifest = pluginManifest;
void TryGetNamespacedPluginType(string ns, PluginMetadata meta)
@ -720,6 +732,125 @@ namespace IPA.Loader
}
#endif
internal static void DoOrderResolution()
{
PluginsMetadata.Sort((a, b) => a.Version.CompareTo(b.Version));
var metadataCache = new Dictionary<string, (PluginMetadata Meta, bool Enabled)>(PluginsMetadata.Count);
var pluginsToProcess = new List<PluginMetadata>(PluginsMetadata.Count);
var disabledIds = DisabledConfig.Instance.DisabledModIds;
var disabledPlugins = new List<PluginMetadata>();
// build metadata cache
foreach (var meta in PluginsMetadata)
{
if (!metadataCache.TryGetValue(meta.Id, out var existing))
{
if (disabledIds.Contains(meta.Id))
{
metadataCache.Add(meta.Id, (meta, false));
disabledPlugins.Add(meta);
}
else
{
metadataCache.Add(meta.Id, (meta, true));
pluginsToProcess.Add(meta);
}
}
else
{
Logger.loader.Warn($"Found duplicates of {meta.Id}, using newest");
ignoredPlugins.Add(meta, new(Reason.Duplicate)
{
ReasonText = $"Duplicate entry of same ID ({meta.Id})",
RelatedTo = existing.Meta
});
}
}
var loadedPlugins = new Dictionary<string, (PluginMetadata Meta, bool Disabled, bool Ignored)>();
var outputOrder = new List<PluginMetadata>(PluginsMetadata.Count);
{
bool TryResolveId(string id, [MaybeNullWhen(false)] out PluginMetadata meta, out bool disabled, out bool ignored)
{
meta = null;
disabled = false;
ignored = true;
if (loadedPlugins.TryGetValue(id, out var foundMeta))
{
meta = foundMeta.Meta;
disabled = foundMeta.Disabled;
ignored = foundMeta.Ignored;
return true;
}
if (metadataCache!.TryGetValue(id, out var plugin))
{
disabled = !plugin.Enabled;
meta = plugin.Meta;
if (!disabled)
{
Resolve(plugin.Meta, out disabled, out ignored);
}
loadedPlugins.Add(id, (plugin.Meta, disabled, ignored));
return true;
}
return false;
}
void Resolve(PluginMetadata plugin, out bool disabled, out bool ignored)
{
disabled = false;
ignored = false;
// first load dependencies
foreach (var dep in plugin.Manifest.Dependencies)
{
if (!TryResolveId(dep.Key, out var depMeta, out var depDisabled, out var depIgnored))
{
Logger.loader.Warn($"Dependency '{dep.Key}@{dep.Value}' for '{plugin.Id}' does not exist; ignoring '{plugin.Id}'");
ignoredPlugins.Add(plugin, new(Reason.Dependency)
{
ReasonText = $"Dependency '{dep.Key}@{dep.Value}' not found",
});
ignored = true;
return;
}
// make a point to propagate ignored
if (depIgnored)
{
Logger.loader.Warn($"Dependency '{dep.Key}' for '{plugin.Id}' previously ignored; ignoring '{plugin.Id}'");
ignoredPlugins.Add(plugin, new(Reason.Dependency)
{
ReasonText = $"Dependency '{dep.Key}' ignored",
RelatedTo = depMeta
});
ignored = true;
return;
}
// make a point to propagate disabled
if (depDisabled)
{
Logger.loader.Warn($"Dependency '{dep.Key}' for '{plugin.Id}' disabled; disabling");
disabledPlugins!.Add(plugin);
_ = disabledIds!.Add(plugin.Id);
disabled = true;
}
// we found our dep, lets save the metadata and keep going
_ = plugin.Dependencies.Add(depMeta);
}
}
}
DisabledConfig.Instance.Changed();
DisabledPlugins = disabledPlugins;
PluginsMetadata = outputOrder;
}
internal static void InitFeatures()
{
foreach (var meta in PluginsMetadata)
@ -772,7 +903,9 @@ namespace IPA.Loader
internal static void ReleaseAll(bool full = false)
{
if (full)
ignoredPlugins = new Dictionary<PluginMetadata, IgnoreReason>();
{
ignoredPlugins = new();
}
else
{
foreach (var m in PluginsMetadata)
@ -788,6 +921,7 @@ namespace IPA.Loader
DisabledPlugins = new List<PluginMetadata>();
Feature.Reset();
GC.Collect();
GC.WaitForPendingFinalizers();
}
internal static void Load(PluginMetadata meta)


+ 19
- 18
IPA.Loader/Loader/PluginManifest.cs View File

@ -1,4 +1,5 @@
using IPA.JsonConverters;
#nullable enable
using IPA.JsonConverters;
using IPA.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -17,31 +18,31 @@ namespace IPA.Loader
internal class PluginManifest
{
[JsonProperty("name", Required = Required.Always)]
public string Name;
public string Name = null!;
[JsonProperty("id", Required = Required.AllowNull)]
public string Id;
[JsonProperty("id", Required = Required.AllowNull)] // TODO: on major version bump, make this always
public string? Id;
[JsonProperty("description", Required = Required.Always), JsonConverter(typeof(MultilineStringConverter))]
public string Description;
public string Description = null!;
[JsonProperty("version", Required = Required.Always), JsonConverter(typeof(SemverVersionConverter))]
public Version Version;
public Version Version = null!;
[JsonProperty("gameVersion", Required = Required.Always), JsonConverter(typeof(AlmostVersionConverter))]
public AlmostVersion GameVersion;
public AlmostVersion GameVersion = null!;
[JsonProperty("author", Required = Required.Always)]
public string Author;
public string Author = null!;
[JsonProperty("dependsOn", Required = Required.DisallowNull, ItemConverterType = typeof(SemverRangeConverter))]
public Dictionary<string, Range> Dependencies = new Dictionary<string, Range>();
public Dictionary<string, Range> Dependencies = new();
[JsonProperty("conflictsWith", Required = Required.DisallowNull, ItemConverterType = typeof(SemverRangeConverter))]
public Dictionary<string, Range> Conflicts = new Dictionary<string, Range>();
public Dictionary<string, Range> Conflicts = new();
[JsonProperty("features", Required = Required.DisallowNull), JsonConverter(typeof(FeaturesFieldConverter))]
public Dictionary<string, JObject> Features = new Dictionary<string, JObject>();
public Dictionary<string, JObject> Features = new();
[JsonProperty("loadBefore", Required = Required.DisallowNull)]
public string[] LoadBefore = Array.Empty<string>();
@ -50,7 +51,7 @@ namespace IPA.Loader
public string[] LoadAfter = Array.Empty<string>();
[JsonProperty("icon", Required = Required.DisallowNull)]
public string IconPath = null;
public string? IconPath = null;
[JsonProperty("files", Required = Required.DisallowNull)]
public string[] Files = Array.Empty<string>();
@ -59,26 +60,26 @@ namespace IPA.Loader
public class LinksObject
{
[JsonProperty("project-home", Required = Required.DisallowNull)]
public Uri ProjectHome = null;
public Uri? ProjectHome = null;
[JsonProperty("project-source", Required = Required.DisallowNull)]
public Uri ProjectSource = null;
public Uri? ProjectSource = null;
[JsonProperty("donate", Required = Required.DisallowNull)]
public Uri Donate = null;
public Uri? Donate = null;
}
[JsonProperty("links", Required = Required.DisallowNull)]
public LinksObject Links = null;
public LinksObject? Links = null;
[Serializable]
public class MiscObject
{
[JsonProperty("plugin-hint", Required = Required.DisallowNull)]
public string PluginMainHint = null;
public string? PluginMainHint = null;
}
[JsonProperty("misc", Required = Required.DisallowNull)]
public MiscObject Misc = null;
public MiscObject? Misc = null;
}
}

+ 4
- 4
IPA.Loader/Loader/PluginMetadata.cs View File

@ -39,10 +39,10 @@ namespace IPA.Loader
public string Name => manifest.Name;
/// <summary>
/// The BeatMods ID of the plugin, or null if it doesn't have one.
/// The ID of the plugin.
/// </summary>
/// <value>the updater ID of the plugin</value>
public string Id => manifest.Id;
/// <value>the ID of the plugin</value>
public string Id => manifest.Id!; // by the time that this is publicly visible, it's always non-null
/// <summary>
/// The name of the author that wrote this plugin.
@ -91,7 +91,7 @@ namespace IPA.Loader
/// The name of the resource in the plugin assembly containing the plugin's icon.
/// </summary>
/// <value>the name of the plugin's icon</value>
public string IconName => manifest.IconPath;
public string? IconName => manifest.IconPath;
/// <summary>
/// A link to this plugin's home page, if any.


Loading…
Cancel
Save