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
This commit is contained in:
Christopher Whitley
2024-08-27 23:58:42 -04:00
committed by GitHub
parent da81c353f6
commit b1ad92a7a0
9 changed files with 315 additions and 28 deletions
@@ -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<Profile>
public class ProfileJsonConverter : JsonConverter<Profile>
{
public ProfileJsonConverter()
: base(GetSupportedTypes(), nameof(Profile))
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(Profile);
/// <inheritdoc />
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 IEnumerable<TypeInfo> GetSupportedTypes()
private static Profile ReadLineProfile(ref Utf8JsonReader reader)
{
return typeof(Profile)
.GetTypeInfo()
.Assembly
.DefinedTypes
.Where(type => type.IsSubclassOf(typeof(Profile)) && !type.IsAbstract);
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);
}
/// <inheritdoc />
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 void WriteSprayProfile(ref Utf8JsonWriter writer, SprayProfile value)
{
writer.WriteStartObject();
writer.WriteString("type", value.GetType().ToString());
writer.WriteString("direction", $"{value.Direction.X} {value.Direction.Y}");
writer.WriteNumber("spread", value.Spread);
writer.WriteEndObject();
}
}
}
}
@@ -15,7 +15,7 @@ public class RangeJsonConverter<T> : JsonConverter<Range<T>> where T : IComparab
/// <inheritdoc />
public override Range<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Span<T> values = reader.ReadAsMultiDimensional<T>();
Span<T> values = reader.ReadAsMultiDimensional<T>(options);
if (values.Length == 2)
{
@@ -15,7 +15,7 @@ public class RectangleFJsonConverter : JsonConverter<RectangleF>
/// <inheritdoc />
public override RectangleF Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var values = reader.ReadAsMultiDimensional<float>();
var values = reader.ReadAsMultiDimensional<float>(options);
return new RectangleF(values[0], values[1], values[2], values[3]);
}
@@ -18,7 +18,7 @@ public class Size2JsonConverter : JsonConverter<SizeF>
/// </exception>
public override SizeF Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var values = reader.ReadAsMultiDimensional<float>();
var values = reader.ReadAsMultiDimensional<float>(options);
if (values.Length == 2)
{
@@ -18,7 +18,7 @@ public class SizeJsonConverter : JsonConverter<Size>
/// </exception>
public override Size Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var values = reader.ReadAsMultiDimensional<int>();
var values = reader.ReadAsMultiDimensional<int>(options);
if (values.Length == 2)
{
@@ -20,9 +20,14 @@ namespace MonoGame.Extended.Serialization.Json
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;
}
}
}
@@ -15,7 +15,7 @@ public class ThicknessJsonConverter : JsonConverter<Thickness>
/// <inheritdoc />
public override Thickness Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var values = reader.ReadAsMultiDimensional<int>();
var values = reader.ReadAsMultiDimensional<int>(options);
return Thickness.FromValues(values);
}
@@ -22,36 +22,37 @@ public static class Utf8JsonReaderExtensions
/// </summary>
/// <typeparam name="T">The type of the array elements.</typeparam>
/// <param name="reader">The <see cref="Utf8JsonReader"/> to read from.</param>
/// <param name="options">An object that specifies serialization options to use.</param>
/// <returns>An array of the specified type.</returns>
/// <exception cref="NotSupportedException">Thrown when the token type is not supported.</exception>
public static T[] ReadAsMultiDimensional<T>(this ref Utf8JsonReader reader)
public static T[] ReadAsMultiDimensional<T>(this ref Utf8JsonReader reader, JsonSerializerOptions options)
{
var tokenType = reader.TokenType;
switch (tokenType)
{
case JsonTokenType.StartArray:
return reader.ReadAsJArray<T>();
return reader.ReadAsJArray<T>(options);
case JsonTokenType.String:
return reader.ReadAsDelimitedString<T>();
case JsonTokenType.Number:
return reader.ReadAsSingleValue<T>();
return reader.ReadAsSingleValue<T>(options);
default:
throw new NotSupportedException($"{tokenType} is not currently supported in the multi-dimensional parser");
}
}
private static T[] ReadAsSingleValue<T>(this ref Utf8JsonReader reader)
private static T[] ReadAsSingleValue<T>(this ref Utf8JsonReader reader, JsonSerializerOptions options)
{
var token = JsonDocument.ParseValue(ref reader).RootElement;
var value = JsonSerializer.Deserialize<T>(token.GetRawText());
var value = JsonSerializer.Deserialize<T>(token.GetRawText(), options);
return new T[] { value };
}
private static T[] ReadAsJArray<T>(this ref Utf8JsonReader reader)
private static T[] ReadAsJArray<T>(this ref Utf8JsonReader reader, JsonSerializerOptions options)
{
var items = new List<T>();
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
@@ -61,7 +62,7 @@ public static class Utf8JsonReaderExtensions
break;
}
items.Add(JsonSerializer.Deserialize<T>(ref reader));
items.Add(JsonSerializer.Deserialize<T>(ref reader, options));
}
return items.ToArray();
@@ -19,7 +19,7 @@ public class Vector2JsonConverter : JsonConverter<Vector2>
/// </exception>
public override Vector2 Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var values = reader.ReadAsMultiDimensional<float>();
var values = reader.ReadAsMultiDimensional<float>(options);
if (values.Length == 2)
{
@@ -31,7 +31,7 @@ public class Vector2JsonConverter : JsonConverter<Vector2>
return new Vector2(values[0]);
}
throw new JsonException("Invalid Size2 property value");
throw new JsonException("Invalid Vector2 property value");
}
/// <inheritdoc />