diff --git a/Projects/SerializationGenerator/ExampleSerialization.json b/Projects/SerializationGenerator/ExampleSerialization.json deleted file mode 100644 index 1b45b49c5..000000000 --- a/Projects/SerializationGenerator/ExampleSerialization.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "Server.Items.TestItem", - "version": 1, - "properties": [ - { - "name": "SomeProperty", - "type": "Server.Item", - "rule": "SerializableInterfaceMigrationRule", - "ruleArguments": ["Server.Items.Item"] - } - ] -} diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs index 6754f5a67..c50a40d4f 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs @@ -83,15 +83,13 @@ namespace SerializationGenerator return null; } - var version = classSymbol.GetAttributes() + var serializableAttribute = classSymbol.GetAttributes() .FirstOrDefault( attr => attr.AttributeClass?.Equals(serializableEntityAttribute, SymbolEqualityComparer.Default) ?? false - )?.ConstructorArguments.FirstOrDefault().Value?.ToString(); + ); - if (version == null) - { - return null; // We don't have the attribute - } + var version = (int)serializableAttribute?.ConstructorArguments[0].Value!; + var encodedVersion = (bool)serializableAttribute?.ConstructorArguments[1].Value!; var namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); var className = classSymbol.Name; @@ -112,7 +110,7 @@ namespace SerializationGenerator InstanceModifier.Const, "int", "_version", - version, + version.ToString(), true ); source.AppendLine(); @@ -200,22 +198,21 @@ namespace SerializationGenerator source.GenerateSerialCtor(context, className, isOverride); source.AppendLine(); - var versionValue = int.Parse(version); List migrations; - if (versionValue > 0) + if (version > 0) { migrations = SerializableMigration.GetMigrations( migrationPath, classSymbol, - versionValue, + version, jsonSerializerOptions ); for (var i = 0; i < migrations.Count; i++) { var migration = migrations[i]; - if (migration.Version < versionValue) + if (migration.Version < version) { source.GenerateMigrationContentStruct(migration); source.AppendLine(); @@ -231,6 +228,7 @@ namespace SerializationGenerator source.GenerateSerializeMethod( compilation, isOverride, + encodedVersion, serializableProperties ); source.AppendLine(); @@ -239,7 +237,8 @@ namespace SerializationGenerator source.GenerateDeserializeMethod( compilation, isOverride, - versionValue, + version, + encodedVersion, migrations, serializableProperties ); @@ -250,7 +249,7 @@ namespace SerializationGenerator // Write the migration file var newMigration = new SerializableMetadata { - Version = versionValue, + Version = version, Type = classSymbol.ToDisplayString(), Properties = serializableProperties }; diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs index 8ebdc27bf..10723278d 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs @@ -27,6 +27,7 @@ namespace SerializationGenerator Compilation compilation, bool isOverride, int version, + bool encodedVersion, List migrations, List properties ) @@ -43,8 +44,13 @@ namespace SerializationGenerator const string indent = " "; + if (isOverride) + { + source.AppendLine($"{indent}base.Deserialize(reader);"); + } + // Version - source.AppendLine($"{indent}var version = reader.ReadEncodedInt();"); + source.AppendLine($"{indent}var version = reader.{(encodedVersion ? "ReadEncodedInt" : "ReadInt")}();"); if (version > 0) { diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.MetadataTypes.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.MetadataTypes.cs index e992f1fa2..66038f715 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.MetadataTypes.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.MetadataTypes.cs @@ -33,6 +33,8 @@ namespace SerializationGenerator public const string GENERIC_WRITER_INTERFACE = "Server.IGenericWriter"; public const string GENERIC_READER_INTERFACE = "Server.IGenericReader"; public const string DELTA_DATE_TIME_ATTRIBUTE = "Server.DeltaDateTimeAttribute"; + public const string INTERN_STRING_ATTRIBUTE = "Server.InternStringAttribute"; + public const string ENCODED_INT_ATTRIBUTE = "Server.EncodedIntAttribute"; public const string POINT2D_STRUCT = "Server.Point2D"; public const string POINT3D_STRUCT = "Server.Point3D"; public const string RECTANGLE2D_STRUCT = "Server.Rectangle2D"; @@ -40,9 +42,15 @@ namespace SerializationGenerator public const string RACE_CLASS = "Server.Race"; public const string MAP_CLASS = "Server.Map"; + public static bool IsEncodedInt(this AttributeData attr, Compilation compilation) => + attr?.IsAttribute(compilation.GetTypeByMetadataName(ENCODED_INT_ATTRIBUTE)) == true; + public static bool IsDeltaDateTime(this AttributeData attr, Compilation compilation) => attr?.IsAttribute(compilation.GetTypeByMetadataName(DELTA_DATE_TIME_ATTRIBUTE)) == true; + public static bool IsInternString(this AttributeData attr, Compilation compilation) => + attr?.IsAttribute(compilation.GetTypeByMetadataName(INTERN_STRING_ATTRIBUTE)) == true; + public static bool IsAttribute(this AttributeData attr, ISymbol symbol) => attr?.AttributeClass?.Equals(symbol, SymbolEqualityComparer.Default) == true; diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs index d752eec3c..1bfe1a17e 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs @@ -26,6 +26,7 @@ namespace SerializationGenerator this StringBuilder source, Compilation compilation, bool isOverride, + bool encodedVersion, List properties ) { @@ -48,9 +49,15 @@ namespace SerializationGenerator {indent} return; {indent}}}"); + if (isOverride) + { + source.AppendLine(); + source.AppendLine($"{indent}base.Serialize(writer);"); + } + // Version source.AppendLine(); - source.AppendLine($"{indent}writer.WriteEncodedInt(_version);"); + source.AppendLine($"{indent}writer.{(encodedVersion ? "WriteEncodedInt" : "Write")}(_version);"); foreach (var property in properties) { diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/EnumMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/EnumMigrationRule.cs new file mode 100644 index 000000000..f3fca519d --- /dev/null +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/EnumMigrationRule.cs @@ -0,0 +1,69 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: EnumMigrationRule.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace SerializationGenerator +{ + public class EnumMigrationRule : ISerializableMigrationRule + { + public string RuleName => nameof(EnumMigrationRule); + + public bool GenerateRuleState( + Compilation compilation, + ISymbol symbol, + ImmutableArray attributes, + ImmutableArray serializableTypes, + out string[] ruleArguments + ) + { + if (symbol is not ITypeSymbol typeSymbol || !typeSymbol.IsEnum()) + { + ruleArguments = null; + return false; + } + + ruleArguments = Array.Empty(); + return true; + } + + public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property) + { + const string expectedRule = nameof(EnumMigrationRule); + var ruleName = property.Rule; + if (expectedRule != ruleName) + { + throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}."); + } + + source.AppendLine($"{indent}{property.Name} = reader.ReadEnum<{property.Type}>();"); + } + + public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property) + { + const string expectedRule = nameof(EnumMigrationRule); + var ruleName = property.Rule; + if (expectedRule != ruleName) + { + throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}."); + } + + source.AppendLine($"{indent}{property.Name} = reader.WriteEnum<{property.Type}>({property.Name});"); + } + } +} diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs index b083b726e..1c16055ae 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs @@ -63,14 +63,16 @@ namespace SerializationGenerator return false; } - if (typeSymbol.SpecialType == SpecialType.System_DateTime && attributes.Any(a => a.IsDeltaDateTime(compilation))) + ruleArguments = typeSymbol.SpecialType switch { - ruleArguments = new[] { "DeltaTime" }; - } - else - { - ruleArguments = Array.Empty(); - } + SpecialType.System_Int32 when attributes.Any(a => a.IsEncodedInt(compilation)) => + new[] { "EncodedInt" }, + SpecialType.System_DateTime when attributes.Any(a => a.IsDeltaDateTime(compilation)) => + new[] { "DeltaTime" }, + SpecialType.System_String when attributes.Any(a => a.IsInternString(compilation)) => + new[] { "InternString" }, + _ => Array.Empty() + }; return true; } @@ -85,11 +87,10 @@ namespace SerializationGenerator } var propertyName = property.Name; - string readMethod; const string ipAddress = SerializableEntityGeneration.IPADDRESS_CLASS; - readMethod = property.Type switch + var readMethod = property.Type switch { "bool" => "ReadBool", "sbyte" => "ReadSByte", @@ -100,7 +101,7 @@ namespace SerializationGenerator "ushort" => "ReadUShort", "uint" => "ReadUInt", "ulong" => "ReadULong", - "float" => "ReadFloat", + "float" => "ReadFloat", "double" => "ReadDouble", "string" => "ReadString", "decimal" => "ReadDecimal", @@ -111,7 +112,11 @@ namespace SerializationGenerator "ReadDateTime" }; - source.AppendLine($"{indent}{propertyName} = reader.{readMethod}();"); + var readArgument = readMethod == "ReadString" && + property.RuleArguments.Length >= 1 && + property.RuleArguments[0] == "InternString" ? "true" : ""; + + source.AppendLine($"{indent}{propertyName} = reader.{readMethod}({readArgument});"); } public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property) diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs b/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs index 6ddcd7bbe..8a053c843 100644 --- a/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs +++ b/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs @@ -28,14 +28,15 @@ namespace SerializationGenerator { var rules = new ISerializableMigrationRule[] { + new EnumMigrationRule(), + new ListMigrationRule(), new ArrayMigrationRule(), new HashSetMigrationRule(), new KeyValuePairMigrationRule(), - new ListMigrationRule(), new PrimitiveTypeMigrationRule(), new PrimitiveUOTypeMigrationRule(), new SerializableInterfaceMigrationRule(), - new SerializationMethodSignatureMigrationRule() + new SerializationMethodSignatureMigrationRule(), }; foreach (var rule in rules) diff --git a/Projects/SerializationGenerator/SerializerSyntaxReceiver.cs b/Projects/SerializationGenerator/SerializerSyntaxReceiver.cs index 637e9e746..fa226b51c 100755 --- a/Projects/SerializationGenerator/SerializerSyntaxReceiver.cs +++ b/Projects/SerializationGenerator/SerializerSyntaxReceiver.cs @@ -37,7 +37,7 @@ namespace SerializationGenerator return; } - if (!ClassAndFields.ContainsKey(classSymbol)) + if (classSymbol.GetAttributes().Any(ad => AttributeTypes.Contains(ad.AttributeClass?.ToDisplayString()) && !ClassAndFields.ContainsKey(classSymbol))) { ClassAndFields.Add(classSymbol, new List()); } diff --git a/Projects/Server/Serialization/EncodedIntAttribute.cs b/Projects/Server/Serialization/EncodedIntAttribute.cs new file mode 100644 index 000000000..3cfd5dbaa --- /dev/null +++ b/Projects/Server/Serialization/EncodedIntAttribute.cs @@ -0,0 +1,27 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: EncodedIntAttribute.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server +{ + /// + /// Hints to the source generator that a serializable int field or property should be encoded + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class EncodedIntAttribute : Attribute + { + } +} diff --git a/Projects/Server/Serialization/IGenericWriter.cs b/Projects/Server/Serialization/IGenericWriter.cs index 0716ff2d9..58a83edbd 100644 --- a/Projects/Server/Serialization/IGenericWriter.cs +++ b/Projects/Server/Serialization/IGenericWriter.cs @@ -116,7 +116,7 @@ namespace Server void Write(Map value) => Write((byte)(value?.MapIndex ?? 0xFF)); void Write(Race value) => Write((byte)(value?.RaceIndex ?? 0xFF)); void Write(ReadOnlySpan bytes); - unsafe void Write(T value) where T : unmanaged, Enum + unsafe void WriteEnum(T value) where T : unmanaged, Enum { var size = sizeof(T); @@ -149,6 +149,7 @@ namespace Server } } } + long Seek(long offset, SeekOrigin origin); } } diff --git a/Projects/Server/Serialization/InternStringAttribute.cs b/Projects/Server/Serialization/InternStringAttribute.cs new file mode 100644 index 000000000..968b0dd39 --- /dev/null +++ b/Projects/Server/Serialization/InternStringAttribute.cs @@ -0,0 +1,27 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: InternalizeString.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; + +namespace Server +{ + /// + /// Hints to the source generator that a serializable string field or property should be internalized on deserialization + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class InternStringAttribute : Attribute + { + } +} diff --git a/Projects/Server/Serialization/SerializableAttribute.cs b/Projects/Server/Serialization/SerializableAttribute.cs index a1b1654f7..e05af4a5e 100755 --- a/Projects/Server/Serialization/SerializableAttribute.cs +++ b/Projects/Server/Serialization/SerializableAttribute.cs @@ -21,7 +21,8 @@ namespace Server public sealed class SerializableAttribute : Attribute { public int Version { get; } + public bool EncodedVersion { get; } - public SerializableAttribute(int version) => Version = version; + public SerializableAttribute(int version, bool encodedVersion = true) => Version = version; } }