From c3515aabf495bc057bb0d9726a43fd4745ca1f14 Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Mon, 25 Oct 2021 15:32:08 -0500 Subject: [PATCH] Features can now have multiple definitions of each by way of an array of objects --- .../JsonConverters/FeaturesFieldConverter.cs | 31 ++++++++++++++++--- .../Loader/Features/ConfigProviderFeature.cs | 7 +++-- IPA.Loader/Loader/Features/DefineFeature.cs | 11 ++++--- IPA.Loader/Loader/PluginLoader.cs | 4 ++- IPA.Loader/Loader/PluginManifest.cs | 2 +- 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/IPA.Loader/JsonConverters/FeaturesFieldConverter.cs b/IPA.Loader/JsonConverters/FeaturesFieldConverter.cs index 97cccb18..8dc3c635 100644 --- a/IPA.Loader/JsonConverters/FeaturesFieldConverter.cs +++ b/IPA.Loader/JsonConverters/FeaturesFieldConverter.cs @@ -3,15 +3,24 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; namespace IPA.JsonConverters { - internal class FeaturesFieldConverter : JsonConverter> + internal class FeaturesFieldConverter : JsonConverter>> { - public override Dictionary ReadJson(JsonReader reader, Type objectType, Dictionary existingValue, bool hasExistingValue, JsonSerializer serializer) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Assert([DoesNotReturnIf(false)] bool condition) + { + if (!condition) + throw new InvalidOperationException(); + } + + public override Dictionary> ReadJson(JsonReader reader, Type objectType, Dictionary> existingValue, bool hasExistingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.StartArray) { @@ -20,10 +29,24 @@ namespace IPA.JsonConverters return existingValue; } - return serializer.Deserialize>(reader); + var dict = new Dictionary>(); + Assert(reader.TokenType == JsonToken.StartObject && reader.Read()); + + while (reader.TokenType == JsonToken.PropertyName) + { + var name = reader.ReadAsString(); + var list = reader.TokenType == JsonToken.StartObject + ? (new() { serializer.Deserialize(reader) }) + : serializer.Deserialize>(reader); + dict.Add(name, list); + } + + Assert(reader.TokenType == JsonToken.EndObject && reader.Read()); + + return dict; } - public override void WriteJson(JsonWriter writer, Dictionary value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, Dictionary> value, JsonSerializer serializer) { serializer.Serialize(writer, value); } diff --git a/IPA.Loader/Loader/Features/ConfigProviderFeature.cs b/IPA.Loader/Loader/Features/ConfigProviderFeature.cs index 61ab95f9..cd8e9cce 100644 --- a/IPA.Loader/Loader/Features/ConfigProviderFeature.cs +++ b/IPA.Loader/Loader/Features/ConfigProviderFeature.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +#nullable enable +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.IO; @@ -18,7 +19,7 @@ namespace IPA.Loader.Features DataModel data; try { - data = featureData.ToObject(); + data = featureData.ToObject() ?? throw new InvalidOperationException("Feature data is null"); } catch (Exception e) { @@ -36,7 +37,7 @@ namespace IPA.Loader.Features InvalidMessage = $"Invalid type name {data.TypeName}"; return false; } - catch (Exception e) when (e is FileNotFoundException || e is FileLoadException || e is BadImageFormatException) + catch (Exception e) when (e is FileNotFoundException or FileLoadException or BadImageFormatException) { string filename; diff --git a/IPA.Loader/Loader/Features/DefineFeature.cs b/IPA.Loader/Loader/Features/DefineFeature.cs index 9cba0a8c..6590df1a 100644 --- a/IPA.Loader/Loader/Features/DefineFeature.cs +++ b/IPA.Loader/Loader/Features/DefineFeature.cs @@ -1,4 +1,5 @@ -using IPA.Logging; +#nullable enable +using IPA.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -15,12 +16,12 @@ namespace IPA.Loader.Features [JsonProperty("type", Required = Required.Always)] public string TypeName = ""; [JsonProperty("name", Required = Required.DisallowNull)] - public string ActualName = null; + public string? ActualName = null; public string Name => ActualName ?? TypeName; } - private DataModel data; + private DataModel data = null!; protected override bool Initialize(PluginMetadata meta, JObject featureData) { @@ -28,7 +29,7 @@ namespace IPA.Loader.Features try { - data = featureData.ToObject(); + data = featureData.ToObject() ?? throw new InvalidOperationException("Feature data is null"); } catch (Exception e) { @@ -54,7 +55,7 @@ namespace IPA.Loader.Features Logger.features.Error($"Invalid type name {data.TypeName}"); return; } - catch (Exception e) when (e is FileNotFoundException || e is FileLoadException || e is BadImageFormatException) + catch (Exception e) when (e is FileNotFoundException or FileLoadException or BadImageFormatException) { var filename = ""; diff --git a/IPA.Loader/Loader/PluginLoader.cs b/IPA.Loader/Loader/PluginLoader.cs index 2de08d7e..b9b05d65 100644 --- a/IPA.Loader/Loader/PluginLoader.cs +++ b/IPA.Loader/Loader/PluginLoader.cs @@ -809,7 +809,9 @@ namespace IPA.Loader { foreach (var meta in PluginsMetadata) { - foreach (var feature in meta.Manifest.Features.Select(f => new Feature.Instance(meta, f.Key, f.Value))) + foreach (var feature in meta.Manifest.Features + .SelectMany(f => f.Value.Select(o => (f.Key, o))) + .Select(t => new Feature.Instance(meta, t.Key, t.o))) { if (feature.TryGetDefiningPlugin(out var plugin) && plugin == null) { // this is a DefineFeature, so we want to initialize it early diff --git a/IPA.Loader/Loader/PluginManifest.cs b/IPA.Loader/Loader/PluginManifest.cs index 6a394f2a..a8da82f4 100644 --- a/IPA.Loader/Loader/PluginManifest.cs +++ b/IPA.Loader/Loader/PluginManifest.cs @@ -43,7 +43,7 @@ namespace IPA.Loader public Dictionary Conflicts = new(); [JsonProperty("features", Required = Required.DisallowNull), JsonConverter(typeof(FeaturesFieldConverter))] - public Dictionary Features = new(); + public Dictionary> Features = new(); [JsonProperty("loadBefore", Required = Required.DisallowNull)] public string[] LoadBefore = Array.Empty();