Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modularization #248

Merged
merged 7 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using UnityEngine;
using Logger = BepInEx.Logging.Logger;


namespace SpaceWarp.API.Assets;

[SpaceWarpLuaAPI("Assets")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using System.Text;
using JetBrains.Annotations;
using KSP.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using UnityEngine;
Expand All @@ -18,10 +19,9 @@ public class JsonConfigFile : IConfigFile
private readonly string _file;


public JsonConfigFile(DirectoryInfo path)
public JsonConfigFile(string file)
{
// Use .cfg as this is going to have comments and that will be an issue
var file = Path.Combine(path.FullName, "config.cfg");
if (File.Exists(file))
{
try
Expand Down Expand Up @@ -87,7 +87,7 @@ private static bool DumpEntry(StringBuilder result, bool hadPreviousKey, KeyValu
}
}

var serialized = JsonConvert.SerializeObject(entry.Value.Value, Formatting.Indented);
var serialized = IOProvider.ToJson(entry.Value.Value);
var serializedLines = serialized.Split('\n').Select(x => x.TrimEnd()).ToArray();
if (serializedLines.Length > 1)
{
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ public override void Log(LogLevel level, object x)
{
_log.Log((BepInEx.Logging.LogLevel)level, x);
}

public static implicit operator BepInExLogger(ManualLogSource log) => new(log);
}
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
using KSP.UI.Flight;
using MoonSharp.Interpreter;
using SpaceWarp.API.Assets;
using SpaceWarp.API.UI.Appbar;
using SpaceWarp.Backend.UI.Appbar;
using SpaceWarp.InternalUtilities;
using UnityEngine;
using UnityEngine.UIElements;
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ public IConfigFile SWConfiguration {
get;
internal set;
}

public SpaceWarpPluginDescriptor SWMetadata { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public virtual void OnPostInitialized()
public ILogger SWLogger => _logger ??= new BepInExLogger(Logger);
private BepInExConfigFile _configFile;
public IConfigFile SWConfiguration => _configFile ??= new BepInExConfigFile(Config);
public SpaceWarpPluginDescriptor SWMetadata { get; set; }

internal static string GetGuidBySpec(PluginInfo pluginInfo, ModInfo modInfo)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ public interface ISpaceWarpMod
public ILogger SWLogger { get; }

public IConfigFile SWConfiguration { get; }

public SpaceWarpPluginDescriptor SWMetadata { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ namespace SpaceWarp.API.Mods.JSON;
[JsonObject(MemberSerialization.OptIn)]
public sealed class SupportedVersionsInfo
{
internal const string DefaultMin = "0.0.0";
internal const string DefaultMax = "*";
public const string DefaultMin = "0.0.0";
public const string DefaultMax = "*";

[JsonProperty("min")] public string Min { get; internal set; } = DefaultMin;

Expand Down
290 changes: 290 additions & 0 deletions SpaceWarp.Core/API/Mods/PluginList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
using System;
using System.Collections.Generic;
using BepInEx;
using BepInEx.Bootstrap;
using SpaceWarp.API.Mods.JSON;
using SpaceWarp.API.Versions;
using SpaceWarpPatcher;
using UnityEngine.Yoga;
using Enumerable = UniLinq.Enumerable;

// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedType.Global

// Disable obsolete warning for Chainloader.Plugins
#pragma warning disable CS0618

namespace SpaceWarp.API.Mods;

/// <summary>
/// API for accessing information about currently loaded and disabled plugins.
/// </summary>
public static class PluginList
{
#region Reading Plugins

/// <summary>
/// Set if the plugin list is different in any way since last run (version differences, new mods, mods removed, mods disabled, description differences, any different in any swinfo file and the disabled mod list)
/// </summary>
public static bool ModListChangedSinceLastRun => ChainloaderPatch.ModListChangedSinceLastRun;
/// <summary>
/// Contains information about all currently loaded plugins. The key is the BepInEx GUID of the plugin.
/// </summary>
public static Dictionary<string, PluginInfo> LoadedPluginInfos { get; } = Chainloader.PluginInfos;

/// <summary>
/// Contains information about all currently disabled plugins. The key is the BepInEx GUID of the plugin.
/// </summary>
public static Dictionary<string, PluginInfo> DisabledPluginInfos { get; } = SpaceWarpPatcher.ChainloaderPatch
.DisabledPluginGuids.Zip(SpaceWarpPatcher.ChainloaderPatch.DisabledPlugins, (guid, info) => new { guid, info })
.ToDictionary(item => item.guid, item => item.info);

/// <summary>
/// Returns whether the plugin with the specified GUID is currently loaded.
/// </summary>
/// <param name="guid">GUID of the plugin</param>
/// <returns>Returns true if the plugin is loaded, false otherwise</returns>
public static bool IsLoaded(string guid)
{
return LoadedPluginInfos.ContainsKey(guid);
}

/// <summary>
/// Compares the version of the specified plugin with the given version.
/// </summary>
/// <param name="guid">GUID of the plugin</param>
/// <param name="version">Version to compare the plugin's version to</param>
/// <returns>Returns -1 if the plugin is older than the given version, 0 if it's the same version and 1 if it's newer</returns>
/// <exception cref="ArgumentException">Thrown if the plugin with the specified GUID is not loaded</exception>
public static int CompareVersion(string guid, Version version)
{
if (!IsLoaded(guid))
{
throw new ArgumentException($"Plugin with GUID {guid} is not loaded");
}

return LoadedPluginInfos[guid].Metadata.Version.CompareTo(version);
}

/// <summary>
/// Retrieves the <see cref="ModInfo"/> of the specified plugin. Returns null if the specified plugin guid doesn't
/// have an associated <see cref="ModInfo"/>.
/// </summary>
/// <param name="guid">GUID of the plugin</param>
/// <returns><see cref="ModInfo"/> of the plugin or null if not found</returns>
public static ModInfo TryGetSwinfo(string guid)
{
var swModInfo = AllEnabledAndActivePlugins
.FirstOrDefault(item => item.Guid == guid);

if (swModInfo != null)
{
return swModInfo.SWInfo;
}

var disabledModInfo = AllDisabledPlugins
.Where(item => item.Guid == guid)
.Select(item => item.SWInfo)
.FirstOrDefault();

return disabledModInfo;
}
/// <summary>
/// Retrieves the <see cref="SpaceWarpPluginDescriptor"/> of the specified plugin. Returns null if the specified plugin guid doesn't
/// have an associated <see cref="SpaceWarpPluginDescriptor"/>.
/// </summary>
/// <param name="guid">GUID of the plugin</param>
/// <returns><see cref="SpaceWarpPluginDescriptor"/> of the plugin or null if not found</returns>
public static SpaceWarpPluginDescriptor TryGetDescriptor(string guid)
{

return AllEnabledAndActivePlugins
.FirstOrDefault(item => item.Guid == guid);
}

/// <summary>
/// Retrieves the instance of the specified plugin class.
/// </summary>
/// <typeparam name="T">The type of the plugin class</typeparam>
/// <returns>Plugin instance or null if not found</returns>
public static T TryGetPlugin<T>() where T : BaseUnityPlugin => Chainloader.Plugins.OfType<T>().FirstOrDefault();

/// <summary>
/// Retrieves the instance of a plugin class with the given BepInEx GUID.
/// </summary>
/// <param name="guid">BepInEx GUID of the plugin</param>
/// <typeparam name="T">The type of the plugin class</typeparam>
/// <returns>Plugin instance or null if not found</returns>
public static T TryGetPlugin<T>(string guid) where T : BaseUnityPlugin =>
Chainloader.Plugins.Find(plugin => plugin.Info.Metadata.GUID == guid) as T;
#endregion

#region Registering Plugins


private static List<SpaceWarpPluginDescriptor> _allEnabledAndActivePlugins = new();
/// <summary>
/// All plugins that are enabled, and active (not errored)
/// </summary>
public static IReadOnlyList<SpaceWarpPluginDescriptor> AllEnabledAndActivePlugins => _allEnabledAndActivePlugins;

private static List<SpaceWarpPluginDescriptor> _allDisabledPlugins = new();

/// <summary>
/// All disabled plugins
/// </summary>
public static IReadOnlyList<SpaceWarpPluginDescriptor> AllDisabledPlugins => _allDisabledPlugins;

private static List<SpaceWarpErrorDescription> _allErroredPlugins = new();
public static IReadOnlyList<SpaceWarpErrorDescription> AllErroredPlugins => _allErroredPlugins;

public static IEnumerable<SpaceWarpPluginDescriptor> AllPlugins => _allEnabledAndActivePlugins
.Concat(_allDisabledPlugins).Concat(_allErroredPlugins.Select(x => x.Plugin));

public static void RegisterPlugin(SpaceWarpPluginDescriptor plugin)
{
if (AllPlugins.Any(x => x.Guid == plugin.Guid))
{
SpaceWarpPlugin.Logger.LogError($"Attempting to register a mod with a duplicate GUID: {plugin.Guid}");
}

SpaceWarpPlugin.Logger.LogInfo($"Registered plugin: {plugin.Guid}");
_allEnabledAndActivePlugins.Add(plugin);
}

public static void Disable(string guid)
{
var descriptor = _allEnabledAndActivePlugins.FirstOrDefault(x =>
string.Equals(x.Guid, guid, StringComparison.InvariantCultureIgnoreCase));
if (descriptor != null)
{
_allEnabledAndActivePlugins.Remove(descriptor);
_allDisabledPlugins.Add(descriptor);
}
}

public static SpaceWarpErrorDescription GetErrorDescriptor(SpaceWarpPluginDescriptor plugin)
{
if (_allErroredPlugins.Any(x => x.Plugin == plugin))
{
return _allErroredPlugins.First(x => x.Plugin == plugin);
}
if (_allEnabledAndActivePlugins.Any(x => x == plugin))
{
_allEnabledAndActivePlugins.Remove(plugin);
}
var newError = new SpaceWarpErrorDescription(plugin);
_allErroredPlugins.Add(newError);
return newError;
}

public static void NoteMissingSwinfoError(SpaceWarpPluginDescriptor plugin)
{
var errorDescriptor = GetErrorDescriptor(plugin);
errorDescriptor.MissingSwinfo = true;
}

public static void NoteBadDirectoryError(SpaceWarpPluginDescriptor plugin)
{
var errorDescriptor = GetErrorDescriptor(plugin);
errorDescriptor.BadDirectory = true;
}

public static void NoteBadIDError(SpaceWarpPluginDescriptor plugin)
{
var errorDescriptor = GetErrorDescriptor(plugin);
errorDescriptor.BadID = true;
}

public static void NoteMismatchedVersionError(SpaceWarpPluginDescriptor plugin)
{
var errorDescriptor = GetErrorDescriptor(plugin);
errorDescriptor.MismatchedVersion = true;
}

public static void NoteUnspecifiedDependencyError(SpaceWarpPluginDescriptor plugin, string dependency)
{
var errorDescriptor = GetErrorDescriptor(plugin);
errorDescriptor.UnspecifiedDependencies.Add(dependency);
}

private static bool DependencyResolved(SpaceWarpPluginDescriptor descriptor, List<SpaceWarpPluginDescriptor> resolvedPlugins)
{
if (descriptor.SWInfo.Spec < SpecVersion.V1_3) return true;
return !(from dependency in descriptor.SWInfo.Dependencies
let info = resolvedPlugins.FirstOrDefault(x => string.Equals(x.Guid,
dependency.ID,
StringComparison.InvariantCultureIgnoreCase))
where info == null ||
!VersionUtility.IsSupported(info.SWInfo.Version,
dependency.Version.Min,
dependency.Version.Max)
select dependency).Any();
}

private static void GetLoadOrder()
{
var changed = true;
List<SpaceWarpPluginDescriptor> newOrder = new();
while (changed)
{
changed = false;
for (var i = _allEnabledAndActivePlugins.Count - 1; i >= 0; i--)
{
if (!DependencyResolved(_allEnabledAndActivePlugins[i], newOrder)) continue;
newOrder.Add(_allEnabledAndActivePlugins[i]);
_allEnabledAndActivePlugins.RemoveAt(i);
changed = true;
}
}

for (var i = _allEnabledAndActivePlugins.Count - 1; i >= 0; i--)
{
var info = _allEnabledAndActivePlugins[i];
SpaceWarpPlugin.Logger.LogError($"Missing dependency for mod: {info.Name}, this mod will not be loaded");
var error = GetErrorDescriptor(info);
error.MissingDependencies = info.SWInfo.Dependencies.Select(x => x.ID).Where(x =>
!newOrder.Any(y => string.Equals(x, y.Guid, StringComparison.InvariantCultureIgnoreCase))).ToList();
}
_allEnabledAndActivePlugins = newOrder;
}

private static void GetDependencyErrors()
{
foreach (var erroredPlugin in _allErroredPlugins.Where(erroredPlugin =>
erroredPlugin.MissingDependencies.Count != 0))
{
for (var i = erroredPlugin.MissingDependencies.Count - 1; i >= 0; i--)
{
var dep = erroredPlugin.MissingDependencies[i];
if (AllEnabledAndActivePlugins.Any(x => string.Equals(x.Guid, dep, StringComparison.InvariantCultureIgnoreCase)))
{
erroredPlugin.UnsupportedDependencies.Add(dep);
erroredPlugin.MissingDependencies.RemoveAt(i);
} else if (AllErroredPlugins.Any(x => string.Equals(x.Plugin.Guid, dep, StringComparison.InvariantCultureIgnoreCase)))
{
erroredPlugin.ErroredDependencies.Add(dep);
erroredPlugin.MissingDependencies.RemoveAt(i);
} else if (AllDisabledPlugins.Any(x => string.Equals(x.Guid, dep, StringComparison.InvariantCultureIgnoreCase)))
{
erroredPlugin.DisabledDependencies.Add(dep);
erroredPlugin.MissingDependencies.RemoveAt(i);
}
}
}
}


/// <summary>
/// This is done after Awake/LoadModule(), so that everything else can use it
/// </summary>
internal static void ResolveDependenciesAndLoadOrder()
{
GetLoadOrder();
GetDependencyErrors();
}


#endregion

}
Loading