From a7d2fc1e6f55ec3076a1593c078b95f864806b89 Mon Sep 17 00:00:00 2001 From: Wulferis Date: Mon, 25 Nov 2024 23:38:22 +1100 Subject: [PATCH] Renames and significant cleanup --- Echo/Echo.Compound.cs | 104 ++++++++ ...erializedProperty.List.cs => Echo.List.cs} | 14 +- Echo/{SerializedProperty.cs => Echo.cs} | 72 +++-- Echo/FileFormats/BinaryTagConverter.cs | 20 +- Echo/FileFormats/StringTagConverter.cs | 76 +++--- Echo/Formatters/AnyObjectFormat.cs | 16 +- Echo/Formatters/CollectionFormat.cs | 22 +- Echo/Formatters/DateTimeFormat.cs | 4 +- Echo/Formatters/DictionaryFormat.cs | 18 +- Echo/Formatters/EnumFormat.cs | 4 +- Echo/Formatters/GuidFormat.cs | 4 +- Echo/Formatters/HashSetFormat.cs | 8 +- Echo/Formatters/NullableFormat.cs | 6 +- Echo/Formatters/PrimitiveFormat.cs | 4 +- Echo/ISerializable.cs | 4 +- Echo/ISerializationFormat.cs | 4 +- Echo/SerializationAttributes.cs | 4 +- Echo/SerializationContext.cs | 1 + Echo/SerializedProperty.Compound.cs | 246 ------------------ Echo/Serializer.cs | 18 +- README.md | 133 +++++++++- Tests/SerializerTests.cs | 12 +- 22 files changed, 389 insertions(+), 405 deletions(-) create mode 100644 Echo/Echo.Compound.cs rename Echo/{SerializedProperty.List.cs => Echo.List.cs} (69%) rename Echo/{SerializedProperty.cs => Echo.cs} (78%) delete mode 100644 Echo/SerializedProperty.Compound.cs diff --git a/Echo/Echo.Compound.cs b/Echo/Echo.Compound.cs new file mode 100644 index 0000000..1fa24dd --- /dev/null +++ b/Echo/Echo.Compound.cs @@ -0,0 +1,104 @@ +// This file is part of the Prowl Game Engine +// Licensed under the MIT License. See the LICENSE file in the project root for details. + +namespace Prowl.Echo; + +public sealed partial class Echo +{ + public Dictionary Tags => (Value as Dictionary)!; + + public Echo this[string tagName] + { + get { return Get(tagName); } + set + { + if (TagType != PropertyType.Compound) + throw new InvalidOperationException("Cannot set tag on non-compound tag"); + else if (tagName == null) + throw new ArgumentNullException(nameof(tagName)); + else if (value == null) + throw new ArgumentNullException(nameof(value)); + Tags[tagName] = value; + value.Parent = this; + } + } + + /// Gets a collection containing all tag names in this CompoundTag. + public IEnumerable GetNames() => Tags.Keys; + + /// Gets a collection containing all tags in this CompoundTag. + public IEnumerable GetAllTags() => Tags.Values; + + public Echo? Get(string tagName) + { + if (TagType != PropertyType.Compound) + throw new InvalidOperationException("Cannot get tag from non-compound tag"); + else if (tagName == null) + throw new ArgumentNullException(nameof(tagName)); + return Tags.TryGetValue(tagName, out var result) ? result : null; + } + + public bool TryGet(string tagName, out Echo? result) + { + if (TagType != PropertyType.Compound) + throw new InvalidOperationException("Cannot get tag from non-compound tag"); + return tagName != null ? Tags.TryGetValue(tagName, out result) : throw new ArgumentNullException(nameof(tagName)); + } + + public bool Contains(string tagName) + { + if (TagType != PropertyType.Compound) + throw new InvalidOperationException("Cannot get tag from non-compound tag"); + return tagName != null ? Tags.ContainsKey(tagName) : throw new ArgumentNullException(nameof(tagName)); + } + + public void Add(string name, Echo newTag) + { + if (TagType != PropertyType.Compound) + throw new InvalidOperationException("Cannot get tag from non-compound tag"); + if (newTag == null) + throw new ArgumentNullException(nameof(newTag)); + else if (newTag == this) + throw new ArgumentException("Cannot add tag to self"); + Tags.Add(name, newTag); + newTag.Parent = this; + } + + public bool Remove(string name) + { + if (TagType != PropertyType.Compound) + throw new InvalidOperationException("Cannot get tag from non-compound tag"); + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); + return Tags.Remove(name); + } + + public bool TryFind(string path, out Echo? tag) + { + tag = Find(path); + return tag != null; + } + + public Echo? Find(string path) + { + if (TagType != PropertyType.Compound) + throw new InvalidOperationException("Cannot get tag from non-compound tag"); + Echo currentTag = this; + while (true) + { + var i = path.IndexOf('/'); + var name = i < 0 ? path : path[..i]; + if (!currentTag.TryGet(name, out Echo? tag) || tag == null) + return null; + + if (i < 0) + return tag; + + if (tag.TagType != PropertyType.Compound) + return null; + + currentTag = tag; + path = path[(i + 1)..]; + } + } +} diff --git a/Echo/SerializedProperty.List.cs b/Echo/Echo.List.cs similarity index 69% rename from Echo/SerializedProperty.List.cs rename to Echo/Echo.List.cs index 79848a4..fda2a0c 100644 --- a/Echo/SerializedProperty.List.cs +++ b/Echo/Echo.List.cs @@ -1,28 +1,26 @@ // This file is part of the Prowl Game Engine // Licensed under the MIT License. See the LICENSE file in the project root for details. -using System.Collections.Generic; - namespace Prowl.Echo; -public sealed partial class SerializedProperty +public sealed partial class Echo { - public List List => (Value as List)!; + public List List => (Value as List)!; - public SerializedProperty this[int tagIdx] + public Echo this[int tagIdx] { get { return Get(tagIdx); } set { List[tagIdx] = value; } } - public SerializedProperty Get(int tagIdx) + public Echo Get(int tagIdx) { if (TagType != PropertyType.List) throw new System.InvalidOperationException("Cannot get tag from non-list tag"); return List[tagIdx]; } - public void ListAdd(SerializedProperty tag) + public void ListAdd(Echo tag) { if (TagType != PropertyType.List) throw new System.InvalidOperationException("Cannot add tag to non-list tag"); @@ -30,7 +28,7 @@ public void ListAdd(SerializedProperty tag) tag.Parent = this; } - public void ListRemove(SerializedProperty tag) + public void ListRemove(Echo tag) { if (TagType != PropertyType.List) throw new System.InvalidOperationException("Cannot remove tag from non-list tag"); diff --git a/Echo/SerializedProperty.cs b/Echo/Echo.cs similarity index 78% rename from Echo/SerializedProperty.cs rename to Echo/Echo.cs index 3cda0bc..aef9ef3 100644 --- a/Echo/SerializedProperty.cs +++ b/Echo/Echo.cs @@ -1,8 +1,6 @@ // This file is part of the Prowl Game Engine // Licensed under the MIT License. See the LICENSE file in the project root for details. -using System; -using System.Collections.Generic; using System.Globalization; namespace Prowl.Echo; @@ -30,11 +28,11 @@ public enum PropertyType public class PropertyChangeEventArgs : EventArgs { - public SerializedProperty Property { get; } + public Echo Property { get; } public object? OldValue { get; } public object? NewValue { get; } - public PropertyChangeEventArgs(SerializedProperty property, object? oldValue, object? newValue) + public PropertyChangeEventArgs(Echo property, object? oldValue, object? newValue) { Property = property; OldValue = oldValue; @@ -42,7 +40,7 @@ public PropertyChangeEventArgs(SerializedProperty property, object? oldValue, ob } } -public sealed partial class SerializedProperty +public sealed partial class Echo { public event EventHandler? PropertyChanged; @@ -51,52 +49,52 @@ public sealed partial class SerializedProperty public PropertyType TagType { get; private set; } - public SerializedProperty? Parent { get; private set; } - - public SerializedProperty() { } - public SerializedProperty(byte i) { _value = i; TagType = PropertyType.Byte; } - public SerializedProperty(sbyte i) { _value = i; TagType = PropertyType.sByte; } - public SerializedProperty(short i) { _value = i; TagType = PropertyType.Short; } - public SerializedProperty(int i) { _value = i; TagType = PropertyType.Int; } - public SerializedProperty(long i) { _value = i; TagType = PropertyType.Long; } - public SerializedProperty(ushort i) { _value = i; TagType = PropertyType.UShort; } - public SerializedProperty(uint i) { _value = i; TagType = PropertyType.UInt; } - public SerializedProperty(ulong i) { _value = i; TagType = PropertyType.ULong; } - public SerializedProperty(float i) { _value = i; TagType = PropertyType.Float; } - public SerializedProperty(double i) { _value = i; TagType = PropertyType.Double; } - public SerializedProperty(decimal i) { _value = i; TagType = PropertyType.Decimal; } - public SerializedProperty(string i) { _value = i; TagType = PropertyType.String; } - public SerializedProperty(byte[] i) { _value = i; TagType = PropertyType.ByteArray; } - public SerializedProperty(bool i) { _value = i; TagType = PropertyType.Bool; } - public SerializedProperty(PropertyType type, object? value) + public Echo? Parent { get; private set; } + + public Echo() { } + public Echo(byte i) { _value = i; TagType = PropertyType.Byte; } + public Echo(sbyte i) { _value = i; TagType = PropertyType.sByte; } + public Echo(short i) { _value = i; TagType = PropertyType.Short; } + public Echo(int i) { _value = i; TagType = PropertyType.Int; } + public Echo(long i) { _value = i; TagType = PropertyType.Long; } + public Echo(ushort i) { _value = i; TagType = PropertyType.UShort; } + public Echo(uint i) { _value = i; TagType = PropertyType.UInt; } + public Echo(ulong i) { _value = i; TagType = PropertyType.ULong; } + public Echo(float i) { _value = i; TagType = PropertyType.Float; } + public Echo(double i) { _value = i; TagType = PropertyType.Double; } + public Echo(decimal i) { _value = i; TagType = PropertyType.Decimal; } + public Echo(string i) { _value = i; TagType = PropertyType.String; } + public Echo(byte[] i) { _value = i; TagType = PropertyType.ByteArray; } + public Echo(bool i) { _value = i; TagType = PropertyType.Bool; } + public Echo(PropertyType type, object? value) { TagType = type; if (type == PropertyType.List && value == null) - _value = new List(); + _value = new List(); else if (type == PropertyType.Compound && value == null) - _value = new Dictionary(); + _value = new Dictionary(); else _value = value; } - public SerializedProperty(List tags) + public Echo(List tags) { TagType = PropertyType.List; _value = tags; } - public static SerializedProperty NewCompound() => new(PropertyType.Compound, new Dictionary()); - public static SerializedProperty NewList() => new(PropertyType.List, new List()); + public static Echo NewCompound() => new(PropertyType.Compound, new Dictionary()); + public static Echo NewList() => new(PropertyType.List, new List()); public void GetAllAssetRefs(ref HashSet refs) { if (TagType == PropertyType.List) { - foreach (var tag in (List)Value!) + foreach (var tag in (List)Value!) tag.GetAllAssetRefs(ref refs); } else if (TagType == PropertyType.Compound) { - var dict = (Dictionary)Value!; + var dict = (Dictionary)Value!; if (TryGet("$type", out var typeName)) { // This isnt a perfect solution since maybe theres a class named AssetRefCollection or something @@ -113,22 +111,22 @@ public void GetAllAssetRefs(ref HashSet refs) } } - public SerializedProperty Clone() + public Echo Clone() { if (TagType == PropertyType.Null) return new(PropertyType.Null, null); else if (TagType == PropertyType.List) { // Value is a List - var list = (List)Value!; - var newList = new List(list.Count); + var list = (List)Value!; + var newList = new List(list.Count); foreach (var tag in list) newList.Add(tag.Clone()); } else if (TagType == PropertyType.Compound) { // Value is a Dictionary - var dict = (Dictionary)Value!; - var newDict = new Dictionary(dict.Count); + var dict = (Dictionary)Value!; + var newDict = new Dictionary(dict.Count); foreach (var (key, tag) in dict) newDict.Add(key, tag.Clone()); } @@ -151,8 +149,8 @@ public int Count { get { - if (TagType == PropertyType.Compound) return ((Dictionary)Value!).Count; - else if (TagType == PropertyType.List) return ((List)Value!).Count; + if (TagType == PropertyType.Compound) return ((Dictionary)Value!).Count; + else if (TagType == PropertyType.List) return ((List)Value!).Count; else return 0; } } diff --git a/Echo/FileFormats/BinaryTagConverter.cs b/Echo/FileFormats/BinaryTagConverter.cs index 36db112..42b3212 100644 --- a/Echo/FileFormats/BinaryTagConverter.cs +++ b/Echo/FileFormats/BinaryTagConverter.cs @@ -7,16 +7,16 @@ public static class BinaryTagConverter { #region Writing - public static void WriteToFile(SerializedProperty tag, FileInfo file) + public static void WriteToFile(Echo tag, FileInfo file) { using var stream = file.OpenWrite(); using var writer = new BinaryWriter(stream); WriteTo(tag, writer); } - public static void WriteTo(SerializedProperty tag, BinaryWriter writer) => WriteCompound(tag, writer); + public static void WriteTo(Echo tag, BinaryWriter writer) => WriteCompound(tag, writer); - private static void WriteCompound(SerializedProperty tag, BinaryWriter writer) + private static void WriteCompound(Echo tag, BinaryWriter writer) { writer.Write(tag.GetAllTags().Count()); foreach (var subTag in tag.Tags) @@ -26,7 +26,7 @@ private static void WriteCompound(SerializedProperty tag, BinaryWriter writer) } } - private static void WriteTag(SerializedProperty tag, BinaryWriter writer) + private static void WriteTag(Echo tag, BinaryWriter writer) { var type = tag.TagType; writer.Write((byte)type); @@ -64,25 +64,25 @@ private static void WriteTag(SerializedProperty tag, BinaryWriter writer) #region Reading - public static SerializedProperty ReadFromFile(FileInfo file) + public static Echo ReadFromFile(FileInfo file) { using var stream = file.OpenRead(); using var reader = new BinaryReader(stream); return ReadFrom(reader); } - public static SerializedProperty ReadFrom(BinaryReader reader) => ReadCompound(reader); + public static Echo ReadFrom(BinaryReader reader) => ReadCompound(reader); - private static SerializedProperty ReadCompound(BinaryReader reader) + private static Echo ReadCompound(BinaryReader reader) { - SerializedProperty tag = SerializedProperty.NewCompound(); + Echo tag = Echo.NewCompound(); var tagCount = reader.ReadInt32(); for (int i = 0; i < tagCount; i++) tag.Add(reader.ReadString(), ReadTag(reader)); return tag; } - private static SerializedProperty ReadTag(BinaryReader reader) + private static Echo ReadTag(BinaryReader reader) { var type = (PropertyType)reader.ReadByte(); if (type == PropertyType.Null) return new(PropertyType.Null, null); @@ -102,7 +102,7 @@ private static SerializedProperty ReadTag(BinaryReader reader) else if (type == PropertyType.Bool) return new(PropertyType.Bool, reader.ReadBoolean()); else if (type == PropertyType.List) { - var listTag = SerializedProperty.NewList(); + var listTag = Echo.NewList(); var tagCount = reader.ReadInt32(); for (int i = 0; i < tagCount; i++) listTag.ListAdd(ReadTag(reader)); diff --git a/Echo/FileFormats/StringTagConverter.cs b/Echo/FileFormats/StringTagConverter.cs index 56e114a..aa1fa20 100644 --- a/Echo/FileFormats/StringTagConverter.cs +++ b/Echo/FileFormats/StringTagConverter.cs @@ -9,24 +9,24 @@ public static partial class StringTagConverter { // Writing: - public static void WriteToFile(SerializedProperty tag, FileInfo file) + public static void WriteToFile(Echo tag, FileInfo file) { File.WriteAllText(file.FullName, Write(tag)); } - public static string Write(SerializedProperty prop) + public static string Write(Echo prop) { using var writer = new StringWriter(); Serialize(prop, writer, 0); return writer.ToString(); } - public static SerializedProperty ReadFromFile(FileInfo file) + public static Echo ReadFromFile(FileInfo file) { return Read(File.ReadAllText(file.FullName)); } - public static SerializedProperty Read(string input) + public static Echo Read(string input) { StringTagTokenizer parser = new(input.ToCharArray()); @@ -44,7 +44,7 @@ public static SerializedProperty Read(string input) } } - private static void Serialize(SerializedProperty prop, TextWriter writer, int indentLevel) + private static void Serialize(Echo prop, TextWriter writer, int indentLevel) { var indent = new string(' ', indentLevel * 2); @@ -107,7 +107,7 @@ private static void Serialize(SerializedProperty prop, TextWriter writer, int in break; case PropertyType.List: writer.WriteLine("["); - var list = (List)prop.Value!; + var list = (List)prop.Value!; for (int i = 0; i < list.Count; i++) { writer.Write(indent); @@ -124,7 +124,7 @@ private static void Serialize(SerializedProperty prop, TextWriter writer, int in writer.Write("]"); break; case PropertyType.Compound: - WriteCompound(writer, (Dictionary)prop.Value!, indentLevel); + WriteCompound(writer, (Dictionary)prop.Value!, indentLevel); break; } } @@ -166,7 +166,7 @@ private static void WriteByteArray(TextWriter writer, byte[] value) writer.Write(']'); } - private static void WriteCompound(TextWriter writer, Dictionary dict, int indentLevel) + private static void WriteCompound(TextWriter writer, Dictionary dict, int indentLevel) { var indent = new string(' ', indentLevel * 2); @@ -217,7 +217,7 @@ private static void WriteCompound(TextWriter writer, Dictionary dict, int indentLevel, string indent) + private static void WriteCompoundElement(string key, TextWriter writer, Dictionary dict, int indentLevel, string indent) { writer.Write(indent); writer.Write(" "); @@ -241,7 +241,7 @@ public enum TextTokenType Value } - private static SerializedProperty ReadTag(StringTagTokenizer parser) + private static Echo ReadTag(StringTagTokenizer parser) { return parser.TokenType switch { @@ -254,17 +254,17 @@ private static SerializedProperty ReadTag(StringTagTokenizer parser) }; } - private static SerializedProperty ReadCompoundTag(StringTagTokenizer parser) + private static Echo ReadCompoundTag(StringTagTokenizer parser) { var startPosition = parser.TokenPosition; - var dict = new Dictionary(); + var dict = new Dictionary(); while (parser.MoveNext()) { switch (parser.TokenType) { case TextTokenType.EndCompound: - return new SerializedProperty(PropertyType.Compound, dict); + return new Echo(PropertyType.Compound, dict); case TextTokenType.Separator: continue; case TextTokenType.Value: @@ -292,18 +292,18 @@ private static SerializedProperty ReadCompoundTag(StringTagTokenizer parser) throw new InvalidDataException($"End of input reached while reading a compound property starting at position {startPosition}"); } - private static SerializedProperty ReadListTag(StringTagTokenizer parser) + private static Echo ReadListTag(StringTagTokenizer parser) { var startPosition = parser.TokenPosition; - var items = new List(); + var items = new List(); while (parser.MoveNext()) { switch (parser.TokenType) { case TextTokenType.EndList: - return new SerializedProperty(PropertyType.List, items); + return new Echo(PropertyType.List, items); case TextTokenType.Separator: continue; } @@ -316,7 +316,7 @@ private static SerializedProperty ReadListTag(StringTagTokenizer parser) throw new InvalidDataException($"End of input reached while reading a list property starting at position {startPosition}"); } - private static SerializedProperty ReadArrayTag(StringTagTokenizer parser) + private static Echo ReadArrayTag(StringTagTokenizer parser) { return parser.Token[1] switch { @@ -325,7 +325,7 @@ private static SerializedProperty ReadArrayTag(StringTagTokenizer parser) }; } - private static SerializedProperty ReadByteArrayTag(StringTagTokenizer parser) + private static Echo ReadByteArrayTag(StringTagTokenizer parser) { var startPosition = parser.TokenPosition; @@ -335,7 +335,7 @@ private static SerializedProperty ReadByteArrayTag(StringTagTokenizer parser) switch (parser.TokenType) { case TextTokenType.EndList: - return new SerializedProperty(arr!); + return new Echo(arr!); case TextTokenType.Separator: continue; case TextTokenType.Value: @@ -349,21 +349,21 @@ private static SerializedProperty ReadByteArrayTag(StringTagTokenizer parser) throw new InvalidDataException($"End of input reached while reading a byte array starting at position {startPosition}"); } - private static SerializedProperty ReadValueTag(StringTagTokenizer parser) + private static Echo ReadValueTag(StringTagTokenizer parser) { // null - if (parser.Token.SequenceEqual("NULL")) return new SerializedProperty(PropertyType.Null, null); + if (parser.Token.SequenceEqual("NULL")) return new Echo(PropertyType.Null, null); // boolean - if (parser.Token.SequenceEqual("false")) return new SerializedProperty(false); - if (parser.Token.SequenceEqual("true")) return new SerializedProperty(true); + if (parser.Token.SequenceEqual("false")) return new Echo(false); + if (parser.Token.SequenceEqual("true")) return new Echo(true); // string if (parser.Token[0] is '"' or '\'') - return new SerializedProperty(parser.ParseQuotedStringValue()); + return new Echo(parser.ParseQuotedStringValue()); if (char.IsLetter(parser.Token[0])) - return new SerializedProperty(new string(parser.Token)); + return new Echo(new string(parser.Token)); // number if (parser.Token[0] >= '0' && parser.Token[0] <= '9' || parser.Token[0] is '+' or '-' or '.') @@ -372,25 +372,25 @@ private static SerializedProperty ReadValueTag(StringTagTokenizer parser) throw new InvalidDataException($"Invalid value \"{parser.Token}\" found while reading a tag at position {parser.TokenPosition}"); } - private static SerializedProperty ReadNumberTag(StringTagTokenizer parser) + private static Echo ReadNumberTag(StringTagTokenizer parser) { static T ParsePrimitive(StringTagTokenizer parser) where T : unmanaged => (T)Convert.ChangeType(new string(parser.Token[..^1]), typeof(T)); return parser.Token[^1] switch { - 'B' => new SerializedProperty(ParsePrimitive(parser)), - 'N' => new SerializedProperty(ParsePrimitive(parser)), - 'S' => new SerializedProperty(ParsePrimitive(parser)), - 'I' => new SerializedProperty(ParsePrimitive(parser)), - 'L' => new SerializedProperty(ParsePrimitive(parser)), - 'V' => new SerializedProperty(ParsePrimitive(parser)), - 'U' => new SerializedProperty(ParsePrimitive(parser)), - 'C' => new SerializedProperty(ParsePrimitive(parser)), - 'F' => new SerializedProperty(ParsePrimitive(parser)), - 'D' => new SerializedProperty(ParsePrimitive(parser)), - 'M' => new SerializedProperty(ParsePrimitive(parser)), - >= '0' and <= '9' => new SerializedProperty((int)Convert.ChangeType(new string(parser.Token), typeof(int))), + 'B' => new Echo(ParsePrimitive(parser)), + 'N' => new Echo(ParsePrimitive(parser)), + 'S' => new Echo(ParsePrimitive(parser)), + 'I' => new Echo(ParsePrimitive(parser)), + 'L' => new Echo(ParsePrimitive(parser)), + 'V' => new Echo(ParsePrimitive(parser)), + 'U' => new Echo(ParsePrimitive(parser)), + 'C' => new Echo(ParsePrimitive(parser)), + 'F' => new Echo(ParsePrimitive(parser)), + 'D' => new Echo(ParsePrimitive(parser)), + 'M' => new Echo(ParsePrimitive(parser)), + >= '0' and <= '9' => new Echo((int)Convert.ChangeType(new string(parser.Token), typeof(int))), _ => throw new InvalidDataException($"Invalid number type indicator found while reading a number \"{parser.Token}\" at position {parser.TokenPosition}") }; } diff --git a/Echo/Formatters/AnyObjectFormat.cs b/Echo/Formatters/AnyObjectFormat.cs index 77d7fe4..c210ab1 100644 --- a/Echo/Formatters/AnyObjectFormat.cs +++ b/Echo/Formatters/AnyObjectFormat.cs @@ -7,9 +7,9 @@ public sealed class AnyObjectFormat : ISerializationFormat { public bool CanHandle(Type type) => true; // Fallback format for any object - public SerializedProperty Serialize(object value, SerializationContext context) + public Echo Serialize(object value, SerializationContext context) { - var compound = SerializedProperty.NewCompound(); + var compound = Echo.NewCompound(); Type type = value.GetType(); if (context.objectToId.TryGetValue(value, out int id)) @@ -46,7 +46,7 @@ public SerializedProperty Serialize(object value, SerializationContext context) } else { - SerializedProperty tag = Serializer.Serialize(propValue, context); + Echo tag = Serializer.Serialize(propValue, context); compound.Add(field.Name, tag); } } @@ -65,13 +65,13 @@ public SerializedProperty Serialize(object value, SerializationContext context) return compound; } - public object? Deserialize(SerializedProperty value, Type targetType, SerializationContext context) + public object? Deserialize(Echo value, Type targetType, SerializationContext context) { - if (value.TryGet("$id", out SerializedProperty? id) && + if (value.TryGet("$id", out Echo? id) && context.idToObject.TryGetValue(id.IntValue, out object? existingObj)) return existingObj; - if (!value.TryGet("$type", out SerializedProperty? typeProperty)) + if (!value.TryGet("$type", out Echo? typeProperty)) { Serializer.Logger.Error($"Failed to deserialize object, missing type info"); return null; @@ -98,7 +98,7 @@ public SerializedProperty Serialize(object value, SerializationContext context) { foreach (System.Reflection.FieldInfo field in result.GetSerializableFields()) { - if (!TryGetFieldValue(value, field, out SerializedProperty? fieldValue)) + if (!TryGetFieldValue(value, field, out Echo? fieldValue)) continue; try @@ -121,7 +121,7 @@ public SerializedProperty Serialize(object value, SerializationContext context) return result; } - private bool TryGetFieldValue(SerializedProperty compound, System.Reflection.FieldInfo field, out SerializedProperty value) + private bool TryGetFieldValue(Echo compound, System.Reflection.FieldInfo field, out Echo value) { if (compound.TryGet(field.Name, out value)) return true; diff --git a/Echo/Formatters/CollectionFormat.cs b/Echo/Formatters/CollectionFormat.cs index d0c5a1b..0b038af 100644 --- a/Echo/Formatters/CollectionFormat.cs +++ b/Echo/Formatters/CollectionFormat.cs @@ -11,22 +11,22 @@ public bool CanHandle(Type type) => type.IsArray || (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)); - public SerializedProperty Serialize(object value, SerializationContext context) + public Echo Serialize(object value, SerializationContext context) { if (value is Array array) { if (array.Rank == 1) { // Single dimensional array - List tags = new(); + List tags = new(); foreach (var item in array) tags.Add(Serializer.Serialize(item, context)); - return new SerializedProperty(tags); + return new Echo(tags); } else { // Multi-dimensional array - var compound = SerializedProperty.NewCompound(); + var compound = Echo.NewCompound(); // Store dimensions var dimensions = new int[array.Rank]; @@ -36,9 +36,9 @@ public SerializedProperty Serialize(object value, SerializationContext context) compound["dimensions"] = Serializer.Serialize(dimensions, context); // Store elements - List elements = new(); + List elements = new(); SerializeMultiDimensionalArray(array, new int[array.Rank], 0, elements, context); - compound["elements"] = new SerializedProperty(elements); + compound["elements"] = new Echo(elements); return compound; } @@ -46,14 +46,14 @@ public SerializedProperty Serialize(object value, SerializationContext context) else { var list = value as IList ?? throw new InvalidOperationException("Expected IList type"); - List tags = new(); + List tags = new(); foreach (var item in list) tags.Add(Serializer.Serialize(item, context)); - return new SerializedProperty(tags); + return new Echo(tags); } } - private void SerializeMultiDimensionalArray(Array array, int[] indices, int dimension, List elements, SerializationContext context) + private void SerializeMultiDimensionalArray(Array array, int[] indices, int dimension, List elements, SerializationContext context) { if (dimension == array.Rank) { @@ -68,7 +68,7 @@ private void SerializeMultiDimensionalArray(Array array, int[] indices, int dime } } - public object? Deserialize(SerializedProperty value, Type targetType, SerializationContext context) + public object? Deserialize(Echo value, Type targetType, SerializationContext context) { if (targetType.IsArray) { @@ -118,7 +118,7 @@ private void SerializeMultiDimensionalArray(Array array, int[] indices, int dime } } - private void DeserializeMultiDimensionalArray(Array array, int[] indices, int dimension, List elements, ref int elementIndex, Type elementType, SerializationContext context) + private void DeserializeMultiDimensionalArray(Array array, int[] indices, int dimension, List elements, ref int elementIndex, Type elementType, SerializationContext context) { if (dimension == array.Rank) { diff --git a/Echo/Formatters/DateTimeFormat.cs b/Echo/Formatters/DateTimeFormat.cs index f32b1aa..2b676cc 100644 --- a/Echo/Formatters/DateTimeFormat.cs +++ b/Echo/Formatters/DateTimeFormat.cs @@ -6,7 +6,7 @@ internal sealed class DateTimeFormat : ISerializationFormat { public bool CanHandle(Type type) => type == typeof(DateTime); - public SerializedProperty Serialize(object value, SerializationContext context) + public Echo Serialize(object value, SerializationContext context) { if (value is DateTime date) return new(PropertyType.Long, date.ToBinary()); @@ -14,7 +14,7 @@ public SerializedProperty Serialize(object value, SerializationContext context) throw new NotSupportedException($"Type '{value.GetType()}' is not supported by DateTimeFormat."); } - public object? Deserialize(SerializedProperty value, Type targetType, SerializationContext context) + public object? Deserialize(Echo value, Type targetType, SerializationContext context) { if (value.TagType != PropertyType.Long) throw new Exception($"Expected Long type for DateTime, got {value.TagType}"); diff --git a/Echo/Formatters/DictionaryFormat.cs b/Echo/Formatters/DictionaryFormat.cs index 7174099..4509e8e 100644 --- a/Echo/Formatters/DictionaryFormat.cs +++ b/Echo/Formatters/DictionaryFormat.cs @@ -11,7 +11,7 @@ public bool CanHandle(Type type) => type.IsAssignableTo(typeof(IDictionary)) && type.IsGenericType; - public SerializedProperty Serialize(object value, SerializationContext context) + public Echo Serialize(object value, SerializationContext context) { var dict = (IDictionary)value; var type = value.GetType(); @@ -20,7 +20,7 @@ public SerializedProperty Serialize(object value, SerializationContext context) if (keyType == typeof(string)) { // string-key behavior - var tag = SerializedProperty.NewCompound(); + var tag = Echo.NewCompound(); foreach (DictionaryEntry kvp in dict) tag.Add((string)kvp.Key, Serializer.Serialize(kvp.Value, context)); return tag; @@ -28,24 +28,24 @@ public SerializedProperty Serialize(object value, SerializationContext context) else { // Non-string key behavior - var compound = SerializedProperty.NewCompound(); - var entries = new List(); + var compound = Echo.NewCompound(); + var entries = new List(); foreach (DictionaryEntry kvp in dict) { - var entryCompound = SerializedProperty.NewCompound(); + var entryCompound = Echo.NewCompound(); entryCompound.Add("key", Serializer.Serialize(kvp.Key, context)); entryCompound.Add("value", Serializer.Serialize(kvp.Value, context)); entries.Add(entryCompound); } - compound.Add("$type", new SerializedProperty(PropertyType.String, type.FullName)); - compound.Add("entries", new SerializedProperty(entries)); + compound.Add("$type", new Echo(PropertyType.String, type.FullName)); + compound.Add("entries", new Echo(entries)); return compound; } } - public object? Deserialize(SerializedProperty value, Type targetType, SerializationContext context) + public object? Deserialize(Echo value, Type targetType, SerializationContext context) { Type keyType = targetType.GetGenericArguments()[0]; Type valueType = targetType.GetGenericArguments()[1]; @@ -56,7 +56,7 @@ public SerializedProperty Serialize(object value, SerializationContext context) if (keyType == typeof(string)) { // string-key behavior - foreach (KeyValuePair tag in value.Tags) + foreach (KeyValuePair tag in value.Tags) dict.Add(tag.Key, Serializer.Deserialize(tag.Value, valueType, context)); } else diff --git a/Echo/Formatters/EnumFormat.cs b/Echo/Formatters/EnumFormat.cs index c52014b..6969281 100644 --- a/Echo/Formatters/EnumFormat.cs +++ b/Echo/Formatters/EnumFormat.cs @@ -9,7 +9,7 @@ internal sealed class EnumFormat : ISerializationFormat { public bool CanHandle(Type type) => type.IsEnum; - public SerializedProperty Serialize(object value, SerializationContext context) + public Echo Serialize(object value, SerializationContext context) { if (value is Enum e) return new(PropertyType.Int, Convert.ToInt32(e)); @@ -17,7 +17,7 @@ public SerializedProperty Serialize(object value, SerializationContext context) throw new NotSupportedException($"Type '{value.GetType()}' is not supported by EnumFormat."); } - public object? Deserialize(SerializedProperty value, Type targetType, SerializationContext context) + public object? Deserialize(Echo value, Type targetType, SerializationContext context) { if (value.TagType != PropertyType.Int) throw new Exception($"Expected Int type for Enum, got {value.TagType}"); diff --git a/Echo/Formatters/GuidFormat.cs b/Echo/Formatters/GuidFormat.cs index 1720d24..b6416bc 100644 --- a/Echo/Formatters/GuidFormat.cs +++ b/Echo/Formatters/GuidFormat.cs @@ -7,7 +7,7 @@ internal sealed class GuidFormat : ISerializationFormat { public bool CanHandle(Type type) => type == typeof(Guid); - public SerializedProperty Serialize(object value, SerializationContext context) + public Echo Serialize(object value, SerializationContext context) { if (value is Guid guid) return new(PropertyType.String, guid.ToString()); @@ -15,7 +15,7 @@ public SerializedProperty Serialize(object value, SerializationContext context) throw new NotSupportedException($"Type '{value.GetType()}' is not supported by GuidFormat."); } - public object? Deserialize(SerializedProperty value, Type targetType, SerializationContext context) + public object? Deserialize(Echo value, Type targetType, SerializationContext context) { if (value.TagType != PropertyType.String) throw new Exception($"Expected String type for Guid, got {value.TagType}"); diff --git a/Echo/Formatters/HashSetFormat.cs b/Echo/Formatters/HashSetFormat.cs index 6efafd9..bccedad 100644 --- a/Echo/Formatters/HashSetFormat.cs +++ b/Echo/Formatters/HashSetFormat.cs @@ -10,16 +10,16 @@ internal sealed class HashSetFormat : ISerializationFormat public bool CanHandle(Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>); - public SerializedProperty Serialize(object value, SerializationContext context) + public Echo Serialize(object value, SerializationContext context) { var hashSet = (IEnumerable)value; - List tags = new(); + List tags = new(); foreach (var item in hashSet) tags.Add(Serializer.Serialize(item, context)); - return new SerializedProperty(tags); + return new Echo(tags); } - public object? Deserialize(SerializedProperty value, Type targetType, SerializationContext context) + public object? Deserialize(Echo value, Type targetType, SerializationContext context) { Type elementType = targetType.GetGenericArguments()[0]; dynamic hashSet = Activator.CreateInstance(targetType) diff --git a/Echo/Formatters/NullableFormat.cs b/Echo/Formatters/NullableFormat.cs index 56ae6ff..c101a72 100644 --- a/Echo/Formatters/NullableFormat.cs +++ b/Echo/Formatters/NullableFormat.cs @@ -8,18 +8,18 @@ internal sealed class NullableFormat : ISerializationFormat public bool CanHandle(Type type) => Nullable.GetUnderlyingType(type) != null; - public SerializedProperty Serialize(object value, SerializationContext context) + public Echo Serialize(object value, SerializationContext context) { var underlyingType = Nullable.GetUnderlyingType(value.GetType()) ?? throw new InvalidOperationException("Not a nullable type"); // Create compound to store nullable info - var compound = SerializedProperty.NewCompound(); + var compound = Echo.NewCompound(); compound.Add("value", Serializer.Serialize(value, context)); return compound; } - public object? Deserialize(SerializedProperty value, Type targetType, SerializationContext context) + public object? Deserialize(Echo value, Type targetType, SerializationContext context) { var underlyingType = Nullable.GetUnderlyingType(targetType) ?? throw new InvalidOperationException("Not a nullable type"); diff --git a/Echo/Formatters/PrimitiveFormat.cs b/Echo/Formatters/PrimitiveFormat.cs index 513128f..6529543 100644 --- a/Echo/Formatters/PrimitiveFormat.cs +++ b/Echo/Formatters/PrimitiveFormat.cs @@ -10,7 +10,7 @@ public bool CanHandle(Type type) => type == typeof(decimal) || type == typeof(byte[]); - public SerializedProperty Serialize(object value, SerializationContext context) + public Echo Serialize(object value, SerializationContext context) { return value switch { @@ -33,7 +33,7 @@ public SerializedProperty Serialize(object value, SerializationContext context) }; } - public object? Deserialize(SerializedProperty value, Type targetType, SerializationContext context) + public object? Deserialize(Echo value, Type targetType, SerializationContext context) { try { diff --git a/Echo/ISerializable.cs b/Echo/ISerializable.cs index 0dfc910..6ef01f3 100644 --- a/Echo/ISerializable.cs +++ b/Echo/ISerializable.cs @@ -5,7 +5,7 @@ namespace Prowl.Echo; public interface ISerializable { - public SerializedProperty Serialize(SerializationContext ctx); - public void Deserialize(SerializedProperty value, SerializationContext ctx); + public Echo Serialize(SerializationContext ctx); + public void Deserialize(Echo value, SerializationContext ctx); } diff --git a/Echo/ISerializationFormat.cs b/Echo/ISerializationFormat.cs index 8623bde..7379b23 100644 --- a/Echo/ISerializationFormat.cs +++ b/Echo/ISerializationFormat.cs @@ -6,6 +6,6 @@ namespace Prowl.Echo; public interface ISerializationFormat { bool CanHandle(Type type); - SerializedProperty Serialize(object value, SerializationContext context); - object? Deserialize(SerializedProperty value, Type targetType, SerializationContext context); + Echo Serialize(object value, SerializationContext context); + object? Deserialize(Echo value, Type targetType, SerializationContext context); } diff --git a/Echo/SerializationAttributes.cs b/Echo/SerializationAttributes.cs index 28222f2..d87fe40 100644 --- a/Echo/SerializationAttributes.cs +++ b/Echo/SerializationAttributes.cs @@ -5,7 +5,7 @@ namespace Prowl.Echo; -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)] +[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] public sealed class IgnoreOnNullAttribute : Attribute { } [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] @@ -18,7 +18,7 @@ public class SerializeFieldAttribute : Attribute { } -[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)] +[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] public class FormerlySerializedAsAttribute : Attribute { public string oldName { get; set; } diff --git a/Echo/SerializationContext.cs b/Echo/SerializationContext.cs index a437f67..017555f 100644 --- a/Echo/SerializationContext.cs +++ b/Echo/SerializationContext.cs @@ -10,6 +10,7 @@ private class NullKey { } public Dictionary objectToId = new(ReferenceEqualityComparer.Instance); public Dictionary idToObject = new(); public int nextId = 1; + private int dependencyCounter = 0; public HashSet dependencies = new(); diff --git a/Echo/SerializedProperty.Compound.cs b/Echo/SerializedProperty.Compound.cs deleted file mode 100644 index cb979ef..0000000 --- a/Echo/SerializedProperty.Compound.cs +++ /dev/null @@ -1,246 +0,0 @@ -// This file is part of the Prowl Game Engine -// Licensed under the MIT License. See the LICENSE file in the project root for details. - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Prowl.Echo; - -public sealed partial class SerializedProperty -{ - public Dictionary Tags => (Value as Dictionary)!; - - public SerializedProperty this[string tagName] - { - get { return Get(tagName); } - set - { - if (TagType != PropertyType.Compound) - throw new InvalidOperationException("Cannot set tag on non-compound tag"); - else if (tagName == null) - throw new ArgumentNullException(nameof(tagName)); - else if (value == null) - throw new ArgumentNullException(nameof(value)); - Tags[tagName] = value; - value.Parent = this; - } - } - - /// Gets a collection containing all tag names in this CompoundTag. - public IEnumerable GetNames() => Tags.Keys; - - /// Gets a collection containing all tags in this CompoundTag. - public IEnumerable GetAllTags() => Tags.Values; - - public SerializedProperty? Get(string tagName) - { - if (TagType != PropertyType.Compound) - throw new InvalidOperationException("Cannot get tag from non-compound tag"); - else if (tagName == null) - throw new ArgumentNullException(nameof(tagName)); - return Tags.TryGetValue(tagName, out var result) ? result : null; - } - - public bool TryGet(string tagName, out SerializedProperty? result) - { - if (TagType != PropertyType.Compound) - throw new InvalidOperationException("Cannot get tag from non-compound tag"); - return tagName != null ? Tags.TryGetValue(tagName, out result) : throw new ArgumentNullException(nameof(tagName)); - } - - public bool Contains(string tagName) - { - if (TagType != PropertyType.Compound) - throw new InvalidOperationException("Cannot get tag from non-compound tag"); - return tagName != null ? Tags.ContainsKey(tagName) : throw new ArgumentNullException(nameof(tagName)); - } - - public void Add(string name, SerializedProperty newTag) - { - if (TagType != PropertyType.Compound) - throw new InvalidOperationException("Cannot get tag from non-compound tag"); - if (newTag == null) - throw new ArgumentNullException(nameof(newTag)); - else if (newTag == this) - throw new ArgumentException("Cannot add tag to self"); - Tags.Add(name, newTag); - newTag.Parent = this; - } - - public bool Remove(string name) - { - if (TagType != PropertyType.Compound) - throw new InvalidOperationException("Cannot get tag from non-compound tag"); - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name)); - return Tags.Remove(name); - } - - public bool TryFind(string path, out SerializedProperty? tag) - { - tag = Find(path); - return tag != null; - } - - public SerializedProperty? Find(string path) - { - if (TagType != PropertyType.Compound) - throw new InvalidOperationException("Cannot get tag from non-compound tag"); - SerializedProperty currentTag = this; - while (true) - { - var i = path.IndexOf('/'); - var name = i < 0 ? path : path[..i]; - if (!currentTag.TryGet(name, out SerializedProperty? tag) || tag == null) - return null; - - if (i < 0) - return tag; - - if (tag.TagType != PropertyType.Compound) - return null; - - currentTag = tag; - path = path[(i + 1)..]; - } - } - - /// - /// Returns a new compound with all the tags/paths combined - /// If a tag is found with identical Path/Name, and values are the same its kept, otherwise its discarded - /// NOTE: Completely Untested - /// - public static SerializedProperty Merge(List allTags) - { - SerializedProperty result = NewCompound(); - if (allTags.Count == 0) return result; - - var referenceTag = allTags[0]; - if (referenceTag.TagType != PropertyType.Compound) return result; - - foreach (var nameVal in referenceTag.Tags) - { - SerializedProperty mergedTag = null; - - if (allTags.Skip(1).All(tag => - { - if (tag.TagType != PropertyType.Compound) - return false; - - if (!tag.TryGet(nameVal.Key, out SerializedProperty? nTag)) - return false; - - if (nTag.TagType != nameVal.Value.TagType) - return false; - - switch (nameVal.Value.TagType) - { - case PropertyType.Compound: - mergedTag = Merge(allTags.Select(t => t.Get(nameVal.Key)).ToList()); - return mergedTag != null; - case PropertyType.List: - mergedTag = Merge(allTags.Select(t => t.Get(nameVal.Key)).ToList()); - return mergedTag != null; - default: - if (nameVal.Value.Value?.Equals(nTag.Value) != false) - { - mergedTag = nameVal.Value; - return true; - } - return false; - } - })) - { - result.Add(nameVal.Key, mergedTag); - } - } - - return result; - } - - /// - /// Apply the given CompoundTag to this CompoundTag - /// All tags inside the given CompoundTag will be added to this CompoundTag or replaced if they already exist - /// All instances of this CompoundTag will remain the same just their values will be updated - /// NOTE: Completely Untested - /// - /// - public void Apply(SerializedProperty toApply) - { - if (TagType != PropertyType.Compound) - throw new InvalidOperationException("Cannot apply to a non-compound tag"); - if (toApply.TagType != PropertyType.Compound) - throw new InvalidOperationException("Cannot apply a non-compound tag"); - - foreach (var applyTag in toApply.Tags) - { - if (Tags.TryGetValue(applyTag.Key, out SerializedProperty? tag)) - { - if (tag.TagType == PropertyType.Compound) - { - if (applyTag.Value.TagType == PropertyType.Compound) - tag.Apply(applyTag.Value); - else - throw new InvalidOperationException("Cannot apply a non-compound tag to a compound tag"); - } - else - { - // Clone it so that Value isnt a reference - SerializedProperty cloned = applyTag.Value.Clone(); - tag.Value = cloned.Value; - } - } - else - { - // Add the new tag - Add(applyTag.Key, applyTag.Value); - } - } - } - - /// - /// Returns a new CompoundTag that contains all the tags from us who vary from the given CompoundTag - /// For example if we have an additional tag that the given CompoundTag does not have, it will be included in the result - /// Or if we have a tag with a different value, it will be included in the result - /// This will occur recursively for CompoundTags - /// NOTE: Completely Untested - /// - /// The Given CompoundTag To Compare Against - public SerializedProperty DifferenceFrom(SerializedProperty from) - { - if (TagType != PropertyType.Compound) - throw new InvalidOperationException("Cannot get the difference with a non-compound tag"); - if (from.TagType != PropertyType.Compound) - throw new InvalidOperationException("Cannot get the difference from a non-compound tag"); - - - SerializedProperty result = NewCompound(); - - foreach (var kvp in Tags) - { - if (!from.Tags.TryGetValue(kvp.Key, out SerializedProperty? value)) - { - // Tag exists in this tag but not in the 'from' tag - result.Add(kvp.Key, kvp.Value); - } - else if (kvp.Value.TagType == PropertyType.Compound && value.TagType == PropertyType.Compound) - { - // Recursively get the difference for compound tags - SerializedProperty subDifference = kvp.Value.DifferenceFrom(value); - if (subDifference.Tags.Count > 0) - { - result.Add(kvp.Key, subDifference); - } - } - else if (!kvp.Value.Value.Equals(value.Value)) - { - // Tag values are different - result.Add(kvp.Key, kvp.Value); - } - } - - return result; - - } -} diff --git a/Echo/Serializer.cs b/Echo/Serializer.cs index 5d94205..e471afc 100644 --- a/Echo/Serializer.cs +++ b/Echo/Serializer.cs @@ -32,15 +32,15 @@ public static void RegisterFormat(ISerializationFormat format) formats.Insert(0, format); // Add to start for precedence - Also ensures ObjectFormat is last } - public static SerializedProperty Serialize(object? value) => Serialize(value, new SerializationContext()); + public static Echo Serialize(object? value) => Serialize(value, new SerializationContext()); - public static SerializedProperty Serialize(object? value, SerializationContext context) + public static Echo Serialize(object? value, SerializationContext context) { - if (value == null) return new SerializedProperty(PropertyType.Null, null); + if (value == null) return new Echo(PropertyType.Null, null); - if (value is SerializedProperty property) + if (value is Echo property) { - SerializedProperty clone = property.Clone(); + Echo clone = property.Clone(); HashSet deps = new(); clone.GetAllAssetRefs(ref deps); foreach (Guid dep in deps) @@ -54,10 +54,10 @@ public static SerializedProperty Serialize(object? value, SerializationContext c return format.Serialize(value, context); } - public static T? Deserialize(SerializedProperty value) => (T?)Deserialize(value, typeof(T)); - public static object? Deserialize(SerializedProperty value, Type targetType) => Deserialize(value, targetType, new SerializationContext()); - public static T? Deserialize(SerializedProperty value, SerializationContext context) => (T?)Deserialize(value, typeof(T), context); - public static object? Deserialize(SerializedProperty value, Type targetType, SerializationContext context) + public static T? Deserialize(Echo value) => (T?)Deserialize(value, typeof(T)); + public static object? Deserialize(Echo value, Type targetType) => Deserialize(value, targetType, new SerializationContext()); + public static T? Deserialize(Echo value, SerializationContext context) => (T?)Deserialize(value, typeof(T), context); + public static object? Deserialize(Echo value, Type targetType, SerializationContext context) { if (value == null || value.TagType == PropertyType.Null) return null; diff --git a/README.md b/README.md index 780e48c..2e438e7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,131 @@ -# Prowl.Echo - A lightweight, flexible serializer, with support for complex object graphs, circular references, and much more. +# Prowl.Echo Serializer + +A lightweight, flexible serialization system (Built for the Prowl Game Engine). The serializer supports complex object graphs, circular references, and custom serialization behaviors. + +Echo does what the name suggests, and create an "Echo" an intermediate representation of the target object. +This allows for fast inspection and modification before converting to Binary or Text. + +## Features + +- **Type Support** + - Primitives (int, float, double, string, bool, etc.) + - Complex objects and nested types + - Collections (List, Array, HashSet) + - Dictionaries + - Enums + - DateTime and Guid + - Nullable types + - Circular references + - Multi-dimensional and jagged arrays + - Support for custom serializable objects + +- **Flexible Serialization Control** + - Custom serialization through `ISerializable` interface + - Attribute-based control (`[FormerlySerializedAs]`, `[IgnoreOnNull]`) + - Support for legacy data through attribute mapping + +- **Misc** + - Battle Tested in the Prowl Game Engine + - Supports both String & Binary formats + - Mimics Unity's Serializer + + +## Usage + +### Basic Serialization + +```csharp +// Serialize an object +var myObject = new MyClass { Value = 42 }; +var serialized = Serializer.Serialize(myObject); + +// Deserialize back +var deserialized = Serializer.Deserialize(serialized); +``` + +### Serializating to Text + +```csharp +var serialized = Serializer.Serialize(myObject); + +// Save to Text +string text = StringTagConverter.Write(serialized); + +// Read to From +var fromText = StringTagConverter.Read(text); + +var deserialized = Serializer.Deserialize(fromText); +``` + +### Custom Serialization + +```csharp +public class CustomObject : ISerializable +{ + public int Value = 42; + public string Text = "Custom"; + + public SerializedProperty Serialize(SerializationContext ctx) + { + var compound = SerializedProperty.NewCompound(); + compound.Add("customValue", new SerializedProperty(PropertyType.Int, Value)); + compound.Add("customText", new SerializedProperty(PropertyType.String, Text)); + return compound; + } + + public void Deserialize(SerializedProperty tag, SerializationContext ctx) + { + Value = tag.Get("customValue").IntValue; + Text = tag.Get("customText").StringValue; + } +} +``` + +### Working with Collections + +```csharp +// Lists +var list = new List { "one", "two", "three" }; +var serializedList = Serializer.Serialize(list); + +// Dictionaries +var dict = new Dictionary { + { "one", 1 }, + { "two", 2 } +}; +var serializedDict = Serializer.Serialize(dict); + +// Arrays +var array = new int[] { 1, 2, 3, 4, 5 }; +var serializedArray = Serializer.Serialize(array); +``` + +### Handling Circular References + +```csharp +var parent = new CircularObject(); +parent.Child = new CircularObject(); +parent.Child.Child = parent; // Circular reference +var serialized = Serializer.Serialize(parent); +``` + +### Using Attributes + +```csharp +public class MyClass +{ + [FormerlySerializedAs("oldName")] + public string NewName = "Test"; + + [IgnoreOnNull] + public string? OptionalField = null; +} +``` + +## Limitations + - Does not serialize Properties + - No benchmarks exist but performance is expected to be lacking + +## License + +This component is part of the Prowl Game Engine and is licensed under the MIT License. See the LICENSE file in the project root for details. \ No newline at end of file diff --git a/Tests/SerializerTests.cs b/Tests/SerializerTests.cs index f40e80c..bf97ffe 100644 --- a/Tests/SerializerTests.cs +++ b/Tests/SerializerTests.cs @@ -45,15 +45,15 @@ public class CustomSerializableObject : ISerializable public int Value = 42; public string Text = "Custom"; - public SerializedProperty Serialize(SerializationContext ctx) + public Echo Serialize(SerializationContext ctx) { - var compound = SerializedProperty.NewCompound(); - compound.Add("customValue", new SerializedProperty(PropertyType.Int, Value)); - compound.Add("customText", new SerializedProperty(PropertyType.String, Text)); + var compound = Echo.NewCompound(); + compound.Add("customValue", new Echo(PropertyType.Int, Value)); + compound.Add("customText", new Echo(PropertyType.String, Text)); return compound; } - public void Deserialize(SerializedProperty tag, SerializationContext ctx) + public void Deserialize(Echo tag, SerializationContext ctx) { Value = tag.Get("customValue").IntValue; Text = tag.Get("customText").StringValue; @@ -297,7 +297,7 @@ public void TestFormerlySerializedAs() var original = new ObjectWithAttributes { NewName = "Updated" }; var serialized = Serializer.Serialize(original); serialized.Remove("NewName"); - serialized.Add("oldName", new SerializedProperty(PropertyType.String, "Updated")); + serialized.Add("oldName", new Echo(PropertyType.String, "Updated")); var deserialized = Serializer.Deserialize(serialized); Assert.Equal(original.NewName, deserialized.NewName); }