Browse Source

Fixed up some sorting rules for plugin load ordering

pull/1/head
Anairkoen Schno 5 years ago
parent
commit
54301f089e
4 changed files with 100 additions and 38 deletions
  1. +3
    -1
      IPA.Loader/IPA.Loader.csproj
  2. +82
    -33
      IPA.Loader/Loader/PluginLoader.cs
  3. +3
    -4
      IPA.Loader/Loader/PluginManager.cs
  4. +12
    -0
      IPA.Loader/Loader/manifest.json

+ 3
- 1
IPA.Loader/IPA.Loader.csproj View File

@ -108,6 +108,8 @@
<Version>1.2.0</Version> <Version>1.2.0</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup />
<ItemGroup>
<EmbeddedResource Include="Loader\manifest.json" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

+ 82
- 33
IPA.Loader/Loader/PluginLoader.cs View File

@ -24,7 +24,9 @@ namespace IPA.Loader
internal static Task LoadTask() => Task.Run(() => internal static Task LoadTask() => Task.Run(() =>
{ {
LoadMetadata(); LoadMetadata();
Logger.log.Debug(string.Join(", ", PluginsMetadata));
Resolve(); Resolve();
Logger.log.Debug(string.Join(", ", PluginsMetadata));
ComputeLoadOrder(); ComputeLoadOrder();
}); });
@ -79,7 +81,7 @@ namespace IPA.Loader
} }
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() => $"{Name}({Id}@{Version})({PluginType.AssemblyQualifiedName}) from '{File.Name}'";
public override string ToString() => $"{Name}({Id}@{Version})({PluginType?.AssemblyQualifiedName}) from '{File.Name}'";
} }
/// <summary> /// <summary>
@ -101,7 +103,32 @@ namespace IPA.Loader
{ {
string[] plugins = Directory.GetFiles(PluginsDirectory, "*.dll"); string[] plugins = Directory.GetFiles(PluginsDirectory, "*.dll");
Assembly.ReflectionOnlyLoadFrom(Assembly.GetExecutingAssembly().Location); // load self as reflection only
try
{
var selfmeta = new PluginMetadata
{
Assembly = Assembly.ReflectionOnlyLoadFrom(Assembly.GetExecutingAssembly()
.Location), // load self as reflection only
File = new FileInfo(Path.Combine(BeatSaber.InstallPath, "IPA.exe")),
PluginType = null
};
string manifest;
using (var manifestReader =
new StreamReader(
selfmeta.Assembly.GetManifestResourceStream(typeof(PluginLoader), "manifest.json") ??
throw new InvalidOperationException()))
manifest = manifestReader.ReadToEnd();
selfmeta.Manifest = JsonConvert.DeserializeObject<PluginManifest>(manifest);
PluginsMetadata.Add(selfmeta);
}
catch (Exception e)
{
Logger.loader.Critical("Error loading own manifest");
Logger.loader.Critical(e);
}
foreach (var plugin in plugins) foreach (var plugin in plugins)
{ // should probably do patching first /shrug { // should probably do patching first /shrug
@ -134,7 +161,7 @@ namespace IPA.Loader
if (metadata.PluginType == null) if (metadata.PluginType == null)
{ {
Logger.log.Warn($"Could not find plugin type for {Path.GetFileName(plugin)}");
Logger.loader.Warn($"Could not find plugin type for {Path.GetFileName(plugin)}");
continue; continue;
} }
@ -144,13 +171,13 @@ namespace IPA.Loader
metadataStream = assembly.GetManifestResourceStream(metadata.PluginType, "manifest.json"); metadataStream = assembly.GetManifestResourceStream(metadata.PluginType, "manifest.json");
if (metadataStream == null) if (metadataStream == null)
{ {
Logger.log.Error($"manifest.json not found in plugin {Path.GetFileName(plugin)}");
Logger.loader.Error($"manifest.json not found in plugin {Path.GetFileName(plugin)}");
continue; continue;
} }
} }
catch (FileNotFoundException) catch (FileNotFoundException)
{ {
Logger.log.Error($"manifest.json not found in plugin {Path.GetFileName(plugin)}");
Logger.loader.Error($"manifest.json not found in plugin {Path.GetFileName(plugin)}");
continue; continue;
} }
@ -164,8 +191,8 @@ namespace IPA.Loader
} }
catch (Exception e) catch (Exception e)
{ {
Logger.log.Error($"Could not load data for plugin {Path.GetFileName(plugin)}");
Logger.log.Error(e);
Logger.loader.Error($"Could not load data for plugin {Path.GetFileName(plugin)}");
Logger.loader.Error(e);
} }
} }
} }
@ -173,7 +200,7 @@ namespace IPA.Loader
internal static void Resolve() internal static void Resolve()
{ // resolves duplicates and conflicts, etc { // resolves duplicates and conflicts, etc
PluginsMetadata.Sort((a, b) => a.Version.CompareTo(b.Version)); PluginsMetadata.Sort((a, b) => a.Version.CompareTo(b.Version));
var ids = new HashSet<string>(); var ids = new HashSet<string>();
var ignore = new HashSet<PluginMetadata>(); var ignore = new HashSet<PluginMetadata>();
var resolved = new List<PluginMetadata>(PluginsMetadata.Count); var resolved = new List<PluginMetadata>(PluginsMetadata.Count);
@ -183,7 +210,8 @@ namespace IPA.Loader
{ {
if (ids.Contains(meta.Id)) if (ids.Contains(meta.Id))
{ {
Logger.log.Warn($"Found duplicates of {meta.Id}, using newest");
Logger.loader.Warn($"Found duplicates of {meta.Id}, using newest");
ignore.Add(meta);
continue; // because of sorted order, hightest order will always be the first one continue; // because of sorted order, hightest order will always be the first one
} }
@ -196,27 +224,24 @@ namespace IPA.Loader
processedLater = true; processedLater = true;
continue; continue;
} }
if (meta2.Manifest.Conflicts.ContainsKey(meta.Id))
if (!meta2.Manifest.Conflicts.ContainsKey(meta.Id)) continue;
var range = meta2.Manifest.Conflicts[meta.Id];
if (!range.IsSatisfied(meta.Version)) continue;
Logger.loader.Warn($"{meta.Id}@{meta.Version} conflicts with {meta2.Name}");
if (processedLater)
{
Logger.loader.Warn($"Ignoring {meta2.Name}");
ignore.Add(meta2);
}
else
{ {
var range = meta2.Manifest.Conflicts[meta.Id];
if (range.IsSatisfied(meta.Version))
{
//TODO: actually choose the one most depended on
Logger.log.Warn($"{meta.Id}@{meta.Version} conflicts with {meta2.Name}");
if (processedLater)
{
Logger.log.Warn($"Ignoring {meta2.Name}");
ignore.Add(meta2);
}
else
{
Logger.log.Warn($"Ignoring {meta.Name}");
ignore.Add(meta);
break;
}
}
Logger.loader.Warn($"Ignoring {meta.Name}");
ignore.Add(meta);
break;
} }
} }
} }
@ -237,17 +262,41 @@ namespace IPA.Loader
if (a.Id == b.Id) return 0; if (a.Id == b.Id) return 0;
if (a.Id != null) if (a.Id != null)
{ {
if (b.Manifest.Dependencies.ContainsKey(a.Id) || b.Manifest.LoadAfter.Contains(a.Id)) return 1;
if (b.Manifest.LoadBefore.Contains(a.Id)) return -1;
if (b.Manifest.Dependencies.ContainsKey(a.Id) || b.Manifest.LoadAfter.Contains(a.Id)) return -1;
if (b.Manifest.LoadBefore.Contains(a.Id)) return 1;
} }
if (b.Id != null) if (b.Id != null)
{ {
if (a.Manifest.Dependencies.ContainsKey(b.Id) || a.Manifest.LoadAfter.Contains(b.Id)) return -1;
if (a.Manifest.LoadBefore.Contains(b.Id)) return 1;
if (a.Manifest.Dependencies.ContainsKey(b.Id) || a.Manifest.LoadAfter.Contains(b.Id)) return 1;
if (a.Manifest.LoadBefore.Contains(b.Id)) return -1;
} }
return 0; return 0;
}); });
Logger.log.Debug(string.Join(", ", PluginsMetadata));
var metadata = new List<PluginMetadata>();
var pluginsToLoad = new Dictionary<string, Version>();
foreach (var meta in PluginsMetadata)
{
bool load = true;
foreach (var dep in meta.Manifest.Dependencies)
{
if (pluginsToLoad.ContainsKey(dep.Key) && dep.Value.IsSatisfied(pluginsToLoad[dep.Key])) continue;
load = false;
Logger.loader.Warn($"{meta.Name} is missing dependency {dep.Key}@{dep.Value}");
}
if (load)
{
metadata.Add(meta);
if (meta.Id != null)
pluginsToLoad.Add(meta.Id, meta.Version);
}
}
PluginsMetadata = metadata;
} }
internal static void LoadPlugins() internal static void LoadPlugins()


+ 3
- 4
IPA.Loader/Loader/PluginManager.cs View File

@ -13,7 +13,6 @@ using IPA.Old;
using IPA.Updating; using IPA.Updating;
using IPA.Utilities; using IPA.Utilities;
using Mono.Cecil; using Mono.Cecil;
using SemVer;
using UnityEngine; using UnityEngine;
using Logger = IPA.Logging.Logger; using Logger = IPA.Logging.Logger;
using static IPA.Loader.PluginLoader; using static IPA.Loader.PluginLoader;
@ -127,7 +126,7 @@ namespace IPA.Loader
string[] originalPlugins = Directory.GetFiles(pluginDirectory, "*.dll"); string[] originalPlugins = Directory.GetFiles(pluginDirectory, "*.dll");
foreach (string s in originalPlugins) foreach (string s in originalPlugins)
{ {
if (PluginLoader.PluginsMetadata.Select(m => m.File.Name).Contains(s)) continue;
if (PluginsMetadata.Select(m => m.File.Name).Contains(s)) continue;
string pluginCopy = Path.Combine(cacheDir, Path.GetFileName(s)); string pluginCopy = Path.Combine(cacheDir, Path.GetFileName(s));
#region Fix assemblies for refactor #region Fix assemblies for refactor
@ -198,7 +197,7 @@ namespace IPA.Loader
string[] copiedPlugins = Directory.GetFiles(cacheDir, "*.dll"); string[] copiedPlugins = Directory.GetFiles(cacheDir, "*.dll");
foreach (string s in copiedPlugins) foreach (string s in copiedPlugins)
{ {
var result = LoadPluginsFromFile(s, exeName);
var result = LoadPluginsFromFile(s);
_bsPlugins.AddRange(result.Item1); _bsPlugins.AddRange(result.Item1);
_ipaPlugins.AddRange(result.Item2); _ipaPlugins.AddRange(result.Item2);
} }
@ -221,7 +220,7 @@ namespace IPA.Loader
Logger.log.Info("-----------------------------"); Logger.log.Info("-----------------------------");
} }
private static Tuple<IEnumerable<PluginInfo>, IEnumerable<IPlugin>> LoadPluginsFromFile(string file, string exeName)
private static Tuple<IEnumerable<PluginInfo>, IEnumerable<IPlugin>> LoadPluginsFromFile(string file)
{ {
List<PluginInfo> bsPlugins = new List<PluginInfo>(); List<PluginInfo> bsPlugins = new List<PluginInfo>();
List<IPlugin> ipaPlugins = new List<IPlugin>(); List<IPlugin> ipaPlugins = new List<IPlugin>();


+ 12
- 0
IPA.Loader/Loader/manifest.json View File

@ -0,0 +1,12 @@
{
"$schema": "https://raw.githubusercontent.com/nike4613/ModSaber-MetadataFileSchema/master/Schema.json",
"author": "DaNike",
"description": "A mod loader specifically for Beat Saber",
"gameVersion": "0.12.2",
"id": "beatsaber-ipa-reloaded",
"name": "BSIPA",
"version": "3.12.0",
"features": [
"early-load"
]
}

Loading…
Cancel
Save