From b1ad92a7a0820ec39767ab1c51eef43f188dfc04 Mon Sep 17 00:00:00 2001 From: Christopher Whitley <103014489+AristurtleDev@users.noreply.github.com> Date: Tue, 27 Aug 2024 23:58:42 -0400 Subject: [PATCH] Read particle file issue (#938) * Correct exception message * Pass serializer options as param for MultiDimensional reads * Refactor to use TryGetRegion * Update for System.Text.Json parsing --- .../Serialization/ProfileJsonConverter.cs | 303 +++++++++++++++++- .../Serialization/Json/RangeJsonConverter.cs | 2 +- .../Json/RectangleFJsonConverter.cs | 2 +- .../Serialization/Json/Size2JsonConverter.cs | 2 +- .../Serialization/Json/SizeJsonConverter.cs | 2 +- .../Json/TextureRegionService.cs | 11 +- .../Json/ThicknessJsonConverter.cs | 2 +- .../Json/Utf8JsonReaderExtensions.cs | 15 +- .../Json/Vector2JsonConverter.cs | 4 +- 9 files changed, 315 insertions(+), 28 deletions(-) diff --git a/source/MonoGame.Extended/Particles/Serialization/ProfileJsonConverter.cs b/source/MonoGame.Extended/Particles/Serialization/ProfileJsonConverter.cs index 3f4285aaf..8e59f75cd 100644 --- a/source/MonoGame.Extended/Particles/Serialization/ProfileJsonConverter.cs +++ b/source/MonoGame.Extended/Particles/Serialization/ProfileJsonConverter.cs @@ -1,25 +1,306 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Security.Cryptography; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Unicode; +using Microsoft.Xna.Framework; using MonoGame.Extended.Particles.Profiles; using MonoGame.Extended.Serialization.Json; namespace MonoGame.Extended.Particles.Serialization { - public class ProfileJsonConverter : BaseTypeJsonConverter + public class ProfileJsonConverter : JsonConverter { - public ProfileJsonConverter() - : base(GetSupportedTypes(), nameof(Profile)) + /// + public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Profile); + + /// + public override Profile Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException($"Expected {nameof(JsonTokenType.StartObject)} token"); + } + + Profile profile = null; + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + if (reader.TokenType == JsonTokenType.PropertyName) + { + var propertyName = reader.GetString(); + reader.Read(); + + if (propertyName.Equals("type", StringComparison.InvariantCultureIgnoreCase)) + { + var type = reader.GetString(); + profile = type switch + { + nameof(Profile.Point) => Profile.Point(), + nameof(Profile.Line) => ReadLineProfile(ref reader), + nameof(Profile.Ring) => ReadRingProfile(ref reader), + nameof(Profile.Box) => ReadBoxProfile(ref reader), + nameof(Profile.BoxFill) => ReadBoxFillProfile(ref reader), + nameof(Profile.BoxUniform) => ReadBoxUniformProfile(ref reader), + nameof(Profile.Circle) => ReadCircleProfile(ref reader), + nameof(Profile.Spray) => ReadSprayProfile(ref reader), + + _ => throw new NotSupportedException($"The profile type {type} is not supported at this time") + }; + } + } + } + + return profile; + } + + private static Profile ReadLineProfile(ref Utf8JsonReader reader) + { + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("axis", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + string[] split = reader.GetString().Split(" ", StringSplitOptions.RemoveEmptyEntries); + Debug.Assert(split.Length == 2); + Vector2 axis = new Vector2(float.Parse(split[0]), float.Parse(split[1])); + + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("length", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + float length = reader.GetSingle(); + + return Profile.Line(axis, length); + } + + private static Profile ReadRingProfile(ref Utf8JsonReader reader) + { + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("radius", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + float radius = reader.GetSingle(); + + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("radiate", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + int radiate = reader.GetInt32(); + + return Profile.Ring(radius, (Profile.CircleRadiation)radiate); + } + + private static Profile ReadBoxProfile(ref Utf8JsonReader reader) + { + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("width", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + float width = reader.GetSingle(); + + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("height", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + float height = reader.GetSingle(); + + return Profile.Box(width, height); + } + + private static Profile ReadBoxFillProfile(ref Utf8JsonReader reader) + { + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("width", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + float width = reader.GetSingle(); + + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("height", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + float height = reader.GetSingle(); + + return Profile.BoxFill(width, height); + } + + private static Profile ReadBoxUniformProfile(ref Utf8JsonReader reader) + { + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("width", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + float width = reader.GetSingle(); + + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("height", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + float height = reader.GetSingle(); + + return Profile.BoxUniform(width, height); + } + + private static Profile ReadCircleProfile(ref Utf8JsonReader reader) + { + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("radius", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + float radius = reader.GetSingle(); + + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("radiate", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + int radiate = reader.GetInt32(); + + return Profile.Circle(radius, (Profile.CircleRadiation)radiate); + + } + + private static Profile ReadSprayProfile(ref Utf8JsonReader reader) + { + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("direction", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + string[] split = reader.GetString().Split(" ", StringSplitOptions.RemoveEmptyEntries); + Debug.Assert(split.Length == 2); + Vector2 direction = new Vector2(float.Parse(split[0]), float.Parse(split[1])); + + reader.Read(); + Debug.Assert(reader.TokenType == JsonTokenType.PropertyName); + Debug.Assert(reader.GetString().Equals("spread", StringComparison.InvariantCultureIgnoreCase)); + reader.Read(); + float spread = reader.GetSingle(); + + return Profile.Spray(direction, spread); + } + + /// + public override void Write(Utf8JsonWriter writer, Profile value, JsonSerializerOptions options) + { + ArgumentNullException.ThrowIfNull(writer); + var type = value.GetType().ToString(); + switch (type) + { + case nameof(PointProfile): + WritePointProfile(ref writer, (PointProfile)value); + break; + + case nameof(LineProfile): + WriteLineProfile(ref writer, (LineProfile)value); + break; + + case nameof(RingProfile): + WriteRingProfile(ref writer, (RingProfile)value); + break; + + case nameof(BoxProfile): + WriteBoxProfile(ref writer, (BoxProfile)value); + break; + + case nameof(BoxFillProfile): + WriteBoxFillProfile(ref writer, (BoxFillProfile)value); + break; + + case nameof(BoxUniformProfile): + WriteBoxUniformProfile(ref writer, (BoxUniformProfile)value); + break; + + case nameof(CircleProfile): + WriteCircleProfile(ref writer, (CircleProfile)value); + break; + + case nameof(SprayProfile): + WriteSprayProfile(ref writer, (SprayProfile)value); + break; + + default: + throw new InvalidOperationException("Unknown profile type"); + } + } + + private static void WritePointProfile(ref Utf8JsonWriter writer, PointProfile value) { + writer.WriteStartObject(); + writer.WritePropertyName("type"); + writer.WriteStringValue(value.GetType().ToString()); + writer.WriteEndObject(); + } + + private static void WriteLineProfile(ref Utf8JsonWriter writer, LineProfile value) + { + writer.WriteStartObject(); + writer.WriteString("type", value.GetType().ToString()); + writer.WriteString("axis", $"{value.Axis.X} {value.Axis.Y}"); + writer.WriteNumber("length", value.Length); + writer.WriteEndObject(); + } + + private static void WriteRingProfile(ref Utf8JsonWriter writer, RingProfile value) + { + writer.WriteStartObject(); + writer.WriteString("type", value.GetType().ToString()); + writer.WriteNumber("radius", value.Radius); + writer.WriteNumber("radiate", (int)value.Radiate); + writer.WriteEndObject(); + } + + private static void WriteBoxProfile(ref Utf8JsonWriter writer, BoxProfile value) + { + writer.WriteStartObject(); + writer.WriteString("type", value.GetType().ToString()); + writer.WriteNumber("width", value.Width); + writer.WriteNumber("height", value.Height); + writer.WriteEndObject(); + } + + private static void WriteBoxFillProfile(ref Utf8JsonWriter writer, BoxFillProfile value) + { + writer.WriteStartObject(); + writer.WriteString("type", value.GetType().ToString()); + writer.WriteNumber("width", value.Width); + writer.WriteNumber("height", value.Height); + writer.WriteEndObject(); + } + + private static void WriteBoxUniformProfile(ref Utf8JsonWriter writer, BoxUniformProfile value) + { + writer.WriteStartObject(); + writer.WriteString("type", value.GetType().ToString()); + writer.WriteNumber("width", value.Width); + writer.WriteNumber("height", value.Height); + writer.WriteEndObject(); + } + + private static void WriteCircleProfile(ref Utf8JsonWriter writer, CircleProfile value) + { + writer.WriteStartObject(); + writer.WriteString("type", value.GetType().ToString()); + writer.WriteNumber("radius", value.Radius); + writer.WriteNumber("radiate", (int)value.Radiate); + writer.WriteEndObject(); + } - private static IEnumerable GetSupportedTypes() + private static void WriteSprayProfile(ref Utf8JsonWriter writer, SprayProfile value) { - return typeof(Profile) - .GetTypeInfo() - .Assembly - .DefinedTypes - .Where(type => type.IsSubclassOf(typeof(Profile)) && !type.IsAbstract); + writer.WriteStartObject(); + writer.WriteString("type", value.GetType().ToString()); + writer.WriteString("direction", $"{value.Direction.X} {value.Direction.Y}"); + writer.WriteNumber("spread", value.Spread); + writer.WriteEndObject(); } } -} \ No newline at end of file +} diff --git a/source/MonoGame.Extended/Serialization/Json/RangeJsonConverter.cs b/source/MonoGame.Extended/Serialization/Json/RangeJsonConverter.cs index 1405f512f..f573b97dd 100644 --- a/source/MonoGame.Extended/Serialization/Json/RangeJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/Json/RangeJsonConverter.cs @@ -15,7 +15,7 @@ public class RangeJsonConverter : JsonConverter> where T : IComparab /// public override Range Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - Span values = reader.ReadAsMultiDimensional(); + Span values = reader.ReadAsMultiDimensional(options); if (values.Length == 2) { diff --git a/source/MonoGame.Extended/Serialization/Json/RectangleFJsonConverter.cs b/source/MonoGame.Extended/Serialization/Json/RectangleFJsonConverter.cs index 865ab4075..3a3f88dc4 100644 --- a/source/MonoGame.Extended/Serialization/Json/RectangleFJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/Json/RectangleFJsonConverter.cs @@ -15,7 +15,7 @@ public class RectangleFJsonConverter : JsonConverter /// public override RectangleF Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var values = reader.ReadAsMultiDimensional(); + var values = reader.ReadAsMultiDimensional(options); return new RectangleF(values[0], values[1], values[2], values[3]); } diff --git a/source/MonoGame.Extended/Serialization/Json/Size2JsonConverter.cs b/source/MonoGame.Extended/Serialization/Json/Size2JsonConverter.cs index e64a84331..4222977da 100644 --- a/source/MonoGame.Extended/Serialization/Json/Size2JsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/Json/Size2JsonConverter.cs @@ -18,7 +18,7 @@ public class Size2JsonConverter : JsonConverter /// public override SizeF Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var values = reader.ReadAsMultiDimensional(); + var values = reader.ReadAsMultiDimensional(options); if (values.Length == 2) { diff --git a/source/MonoGame.Extended/Serialization/Json/SizeJsonConverter.cs b/source/MonoGame.Extended/Serialization/Json/SizeJsonConverter.cs index b4ab69d95..be2102928 100644 --- a/source/MonoGame.Extended/Serialization/Json/SizeJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/Json/SizeJsonConverter.cs @@ -18,7 +18,7 @@ public class SizeJsonConverter : JsonConverter /// public override Size Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var values = reader.ReadAsMultiDimensional(); + var values = reader.ReadAsMultiDimensional(options); if (values.Length == 2) { diff --git a/source/MonoGame.Extended/Serialization/Json/TextureRegionService.cs b/source/MonoGame.Extended/Serialization/Json/TextureRegionService.cs index f7f2c967c..ea1084fc3 100644 --- a/source/MonoGame.Extended/Serialization/Json/TextureRegionService.cs +++ b/source/MonoGame.Extended/Serialization/Json/TextureRegionService.cs @@ -20,9 +20,14 @@ public TextureRegionService() public Texture2DRegion GetTextureRegion(string name) { - return TextureAtlases - .Select(textureAtlas => textureAtlas.GetRegion(name)) - .FirstOrDefault(region => region != null); + foreach (Texture2DAtlas atlas in TextureAtlases) + { + if (atlas.TryGetRegion(name, out Texture2DRegion region)) + { + return region; + } + } + return null; } } } diff --git a/source/MonoGame.Extended/Serialization/Json/ThicknessJsonConverter.cs b/source/MonoGame.Extended/Serialization/Json/ThicknessJsonConverter.cs index a34a94f08..4e70dd41c 100644 --- a/source/MonoGame.Extended/Serialization/Json/ThicknessJsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/Json/ThicknessJsonConverter.cs @@ -15,7 +15,7 @@ public class ThicknessJsonConverter : JsonConverter /// public override Thickness Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var values = reader.ReadAsMultiDimensional(); + var values = reader.ReadAsMultiDimensional(options); return Thickness.FromValues(values); } diff --git a/source/MonoGame.Extended/Serialization/Json/Utf8JsonReaderExtensions.cs b/source/MonoGame.Extended/Serialization/Json/Utf8JsonReaderExtensions.cs index ca6132136..18f939a9e 100644 --- a/source/MonoGame.Extended/Serialization/Json/Utf8JsonReaderExtensions.cs +++ b/source/MonoGame.Extended/Serialization/Json/Utf8JsonReaderExtensions.cs @@ -22,36 +22,37 @@ public static class Utf8JsonReaderExtensions /// /// The type of the array elements. /// The to read from. + /// An object that specifies serialization options to use. /// An array of the specified type. /// Thrown when the token type is not supported. - public static T[] ReadAsMultiDimensional(this ref Utf8JsonReader reader) + public static T[] ReadAsMultiDimensional(this ref Utf8JsonReader reader, JsonSerializerOptions options) { var tokenType = reader.TokenType; switch (tokenType) { case JsonTokenType.StartArray: - return reader.ReadAsJArray(); + return reader.ReadAsJArray(options); case JsonTokenType.String: return reader.ReadAsDelimitedString(); case JsonTokenType.Number: - return reader.ReadAsSingleValue(); + return reader.ReadAsSingleValue(options); default: throw new NotSupportedException($"{tokenType} is not currently supported in the multi-dimensional parser"); } } - private static T[] ReadAsSingleValue(this ref Utf8JsonReader reader) + private static T[] ReadAsSingleValue(this ref Utf8JsonReader reader, JsonSerializerOptions options) { var token = JsonDocument.ParseValue(ref reader).RootElement; - var value = JsonSerializer.Deserialize(token.GetRawText()); + var value = JsonSerializer.Deserialize(token.GetRawText(), options); return new T[] { value }; } - private static T[] ReadAsJArray(this ref Utf8JsonReader reader) + private static T[] ReadAsJArray(this ref Utf8JsonReader reader, JsonSerializerOptions options) { var items = new List(); while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) @@ -61,7 +62,7 @@ private static T[] ReadAsJArray(this ref Utf8JsonReader reader) break; } - items.Add(JsonSerializer.Deserialize(ref reader)); + items.Add(JsonSerializer.Deserialize(ref reader, options)); } return items.ToArray(); diff --git a/source/MonoGame.Extended/Serialization/Json/Vector2JsonConverter.cs b/source/MonoGame.Extended/Serialization/Json/Vector2JsonConverter.cs index c0fad1677..499a95979 100644 --- a/source/MonoGame.Extended/Serialization/Json/Vector2JsonConverter.cs +++ b/source/MonoGame.Extended/Serialization/Json/Vector2JsonConverter.cs @@ -19,7 +19,7 @@ public class Vector2JsonConverter : JsonConverter /// public override Vector2 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var values = reader.ReadAsMultiDimensional(); + var values = reader.ReadAsMultiDimensional(options); if (values.Length == 2) { @@ -31,7 +31,7 @@ public override Vector2 Read(ref Utf8JsonReader reader, Type typeToConvert, Json return new Vector2(values[0]); } - throw new JsonException("Invalid Size2 property value"); + throw new JsonException("Invalid Vector2 property value"); } ///