From ca5e9342a04d5d6c6ea2628bbdb760a655015a93 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Thu, 19 Aug 2021 22:51:44 -0700 Subject: [PATCH] fix(codegen): Adds default codegen option for save flags. Fixes world load issues. (#707) --- .../SerializableEntityGeneration.Class.cs | 33 ++++++--- ...zableEntityGeneration.DeserializeMethod.cs | 74 ++++++++++++------- ...lizableEntityGeneration.SerializeMethod.cs | 13 ++-- .../SerializableFieldSaveFlagMethods.cs | 11 +++ ...lizationEntityGeneration.ContentStruct.cs} | 15 ++-- .../SerializableFieldFlagComparer.cs | 31 -------- .../SerializableMigrationRulesEngine.cs | 8 +- .../SymbolMetadata/SymbolMetadata.Builtin.cs | 4 + .../SymbolMetadata/SymbolMetadata.UO.cs | 1 + .../AfterDeserializationAttribute.cs} | 12 ++- ...d.cs => DeserializeTimerFieldAttribute.cs} | 2 +- .../ManualDirtyCheckingAttribute.cs} | 2 +- .../SerializableFieldDefaultAttribute.cs | 35 +++++++++ Projects/Server/Server.csproj | 3 + .../UOContent/Engines/Bulk Orders/BaseBOD.cs | 2 +- Projects/UOContent/Items/Armor/BaseArmor.cs | 37 +++++++++- .../Items/Armor/Glasses/ElvenGlasses.cs | 7 +- Projects/UOContent/Items/Books/BaseBook.cs | 21 +++--- Projects/UOContent/Items/Weapons/Fists.cs | 2 +- 19 files changed, 204 insertions(+), 109 deletions(-) create mode 100644 Projects/SerializationGenerator/SerializableEntityGeneration/SerializableFieldSaveFlagMethods.cs rename Projects/SerializationGenerator/SerializableEntityGeneration/{SerializationGenerator.ContentStruct.cs => SerializationEntityGeneration.ContentStruct.cs} (89%) delete mode 100644 Projects/SerializationGenerator/SerializableMigration/SerializableFieldFlagComparer.cs rename Projects/Server/Serialization/{AfterDeserialization.cs => Attributes/AfterDeserializationAttribute.cs} (67%) rename Projects/Server/Serialization/Attributes/{DeserializeTimerField.cs => DeserializeTimerFieldAttribute.cs} (96%) rename Projects/Server/Serialization/{ManualDirtyChecking.cs => Attributes/ManualDirtyCheckingAttribute.cs} (95%) create mode 100644 Projects/Server/Serialization/Attributes/SerializableFieldDefaultAttribute.cs diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs index 7d1e33a52..6ae601b23 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.Class.cs @@ -116,6 +116,8 @@ namespace SerializationGenerator compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_PARENT_ATTRIBUTE); var serializableFieldSaveFlagAttribute = compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_SAVE_FLAG_ATTRIBUTE); + var serializableFieldDefaultAttribute = + compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_DEFAULT_ATTRIBUTE); // If we have a parent that is or derives from ISerializable, then we are in override var isOverride = classSymbol.BaseType.ContainsInterface(serializableInterface); @@ -131,21 +133,28 @@ namespace SerializationGenerator var encodedVersion = (bool)serializableAttr.ConstructorArguments[1].Value!; // Let's find out if we need to do serialization flags - var serializablePropertyFlagGettersSet = new SortedSet<(IMethodSymbol, int)>(new SerializableFieldFlagComparer()); + var serializableFieldSaveFlags = new SortedDictionary(); foreach (var m in classSymbol.GetMembers().OfType()) { var getSaveFlagAttribute = m.GetAttribute(serializableFieldSaveFlagAttribute); - if (getSaveFlagAttribute == null) + var getDefaultValueAttribute = m.GetAttribute(serializableFieldDefaultAttribute); + + if (getSaveFlagAttribute == null && getDefaultValueAttribute == null) { continue; } - var attrCtorArgs = getSaveFlagAttribute.ConstructorArguments; + var attrCtorArgs = getSaveFlagAttribute?.ConstructorArguments ?? getDefaultValueAttribute.ConstructorArguments; var order = (int)attrCtorArgs[0].Value!; - serializablePropertyFlagGettersSet.Add((m, order)); + serializableFieldSaveFlags.TryGetValue(order, out var saveFlagMethods); + + serializableFieldSaveFlags[order] = new SerializableFieldSaveFlagMethods + { + DetermineFieldShouldSerialize = getSaveFlagAttribute != null ? m : saveFlagMethods?.DetermineFieldShouldSerialize, + GetFieldDefaultValue = getDefaultValueAttribute != null ? m : saveFlagMethods?.GetFieldDefaultValue + }; } - var serializablePropertyFlagGetters = serializablePropertyFlagGettersSet.ToImmutableArray(); var namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); var className = classSymbol.Name; @@ -243,6 +252,8 @@ namespace SerializationGenerator source.AppendLine(); } + serializableFieldSaveFlags.TryGetValue(order, out var serializableFieldSaveFlagMethods); + var serializableProperty = SerializableMigrationRulesEngine.GenerateSerializableProperty( compilation, fieldOrPropertySymbol, @@ -251,7 +262,7 @@ namespace SerializationGenerator serializableTypes, embeddedSerializableTypes, classSymbol, - serializablePropertyFlagGetters.FirstOrDefault(m => m.Item2 == order).Item1 + serializableFieldSaveFlagMethods ); serializablePropertySet.Add(serializableProperty); @@ -298,7 +309,7 @@ namespace SerializationGenerator var migration = migrations[i]; if (migration.Version < version) { - source.GenerateMigrationContentStruct(migration, classSymbol); + source.GenerateMigrationContentStruct(compilation, migration, classSymbol); source.AppendLine(); } } @@ -310,7 +321,7 @@ namespace SerializationGenerator isOverride, encodedVersion, serializableProperties, - serializablePropertyFlagGetters + serializableFieldSaveFlags ); source.AppendLine(); @@ -324,11 +335,11 @@ namespace SerializationGenerator migrations, serializableProperties, parentFieldOrProperty, - serializablePropertyFlagGetters + serializableFieldSaveFlags ); // Serialize SaveFlag enum class - if (serializablePropertyFlagGetters.Length > 0) + if (serializableFieldSaveFlags.Count > 0) { source.AppendLine(); source.GenerateEnumStart( @@ -340,7 +351,7 @@ namespace SerializationGenerator source.GenerateEnumValue(" ", true, "None", -1); int index = 0; - foreach (var (_, order) in serializablePropertyFlagGetters) + foreach (var (order, _) in serializableFieldSaveFlags) { source.GenerateEnumValue(" ", true, serializableProperties[order].Name, index++); } diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs index 663cce855..fbcf07ec6 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.DeserializeMethod.cs @@ -13,6 +13,7 @@ * along with this program. If not, see . * *************************************************************************/ +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; @@ -33,7 +34,7 @@ namespace SerializationGenerator ImmutableArray migrations, ImmutableArray properties, ISymbol parentFieldOrProperty, - ImmutableArray<(IMethodSymbol, int)> propertyFlagGetters + SortedDictionary serializableFieldSaveFlagMethodsDictionary ) { var genericReaderInterface = compilation.GetTypeByMetadataName(SymbolMetadata.GENERIC_READER_INTERFACE); @@ -59,18 +60,23 @@ namespace SerializationGenerator var afterDeserialization = classSymbol .GetMembers() .OfType() - .FirstOrDefault( + .Select( m => - m.ReturnsVoid && - m.Parameters.Length == 0 && - m.GetAttributes() - .Any( + { + if (!m.ReturnsVoid || m.Parameters.Length != 0) + { + return (m, null); + } + + return (m, m.GetAttributes() + .FirstOrDefault( attr => SymbolEqualityComparer.Default.Equals( attr.AttributeClass, compilation.GetTypeByMetadataName(SymbolMetadata.AFTERDESERIALIZATION_ATTRIBUTE) ) - ) - ); + )); + } + ).Where(m => m.Item2 != null).ToList(); // Version source.AppendLine($"{indent}var version = reader.{(encodedVersion ? "ReadEncodedInt" : "ReadInt")}();"); @@ -93,10 +99,7 @@ namespace SerializationGenerator source.AppendLine($"{indent}{{"); source.AppendLine($"{indent} MigrateFrom(new V{migrationVersion}Content(reader, this));"); source.AppendLine($"{indent} {parent}.MarkDirty();"); - if (afterDeserialization != null) - { - source.AppendLine($"{indent} Timer.DelayCall({afterDeserialization.Name});"); - } + source.GenerateAfterDeserialization($"{indent} ", afterDeserialization); source.AppendLine($"{indent} return;"); source.AppendLine($"{indent}}}"); } @@ -108,16 +111,13 @@ namespace SerializationGenerator source.AppendLine($"{indent}{{"); source.AppendLine($"{indent} Deserialize(reader, version);"); source.AppendLine($"{indent} {parent}.MarkDirty();"); - if (afterDeserialization != null) - { - source.AppendLine($"{indent} Timer.DelayCall({afterDeserialization.Name});"); - } + source.GenerateAfterDeserialization($"{indent} ", afterDeserialization); source.AppendLine($"{indent} return;"); source.AppendLine($"{indent}}}"); } } - if (propertyFlagGetters.Length > 0) + if (serializableFieldSaveFlagMethodsDictionary.Count > 0) { source.AppendLine(); source.AppendLine($"{indent}var saveFlags = reader.ReadEnum();"); @@ -125,10 +125,13 @@ namespace SerializationGenerator foreach (var property in properties) { - var usesSaveFlag = propertyFlagGetters.Any(m => m.Item2 == property.Order); var rule = SerializableMigrationRulesEngine.Rules[property.Rule]; - if (usesSaveFlag) + + if (serializableFieldSaveFlagMethodsDictionary.TryGetValue( + property.Order, + out var serializableFieldSaveFlagMethods + )) { source.AppendLine(); // Special case @@ -146,6 +149,15 @@ namespace SerializationGenerator "this" ); (rule as IPostDeserializeMethod)?.PostDeserializeMethod(source, innerIndent, property, compilation, classSymbol); + + if (serializableFieldSaveFlagMethods.GetFieldDefaultValue != null) + { + source.AppendLine($"{indent}}}\n{indent}else\n{indent}{{"); + source.AppendLine( + $"{indent} {property.Name} = {serializableFieldSaveFlagMethods.GetFieldDefaultValue.Name}();" + ); + } + source.AppendLine($"{indent}}}"); } } @@ -162,13 +174,25 @@ namespace SerializationGenerator } } - if (afterDeserialization != null) - { - source.AppendLine(); - source.AppendLine($"{indent}Timer.DelayCall({afterDeserialization.Name});"); - } - + source.GenerateAfterDeserialization($"{indent}", afterDeserialization); source.GenerateMethodEnd(" "); } + + private static void GenerateAfterDeserialization( + this StringBuilder source, string indent, IList<(IMethodSymbol, AttributeData?)> afterDeserialization + ) + { + foreach (var (method, attr) in afterDeserialization) + { + if ((bool)attr.ConstructorArguments[0].Value!) + { + source.AppendLine($"{indent}{method.Name}();"); + } + else + { + source.AppendLine($"{indent}Timer.DelayCall({method.Name});"); + } + } + } } } diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs index 0cf851d56..d1c2d3344 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs @@ -13,6 +13,7 @@ * along with this program. If not, see . * *************************************************************************/ +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; @@ -29,7 +30,7 @@ namespace SerializationGenerator bool isOverride, bool encodedVersion, ImmutableArray properties, - ImmutableArray<(IMethodSymbol, int)> propertyFlagGetters + SortedDictionary serializableFieldSaveFlagMethodsDictionary ) { var genericWriterInterface = compilation.GetTypeByMetadataName(SymbolMetadata.GENERIC_WRITER_INTERFACE); @@ -56,13 +57,13 @@ namespace SerializationGenerator source.AppendLine($"{indent}writer.{(encodedVersion ? "WriteEncodedInt" : "Write")}(_version);"); // Let's collect the flags - if (propertyFlagGetters.Length > 0) + if (serializableFieldSaveFlagMethodsDictionary.Count > 0) { source.AppendLine($"\n{indent}var saveFlags = SaveFlag.None;"); - foreach (var (m, order) in propertyFlagGetters) + foreach (var (order, saveFlagMethods) in serializableFieldSaveFlagMethodsDictionary) { - source.AppendLine($"{indent}if ({m.Name}())\n{indent}{{"); + source.AppendLine($"{indent}if ({saveFlagMethods.DetermineFieldShouldSerialize!.Name}())\n{indent}{{"); var propertyName = properties[order].Name; source.AppendLine($"{innerIndent}saveFlags |= SaveFlag.{propertyName};"); @@ -75,9 +76,7 @@ namespace SerializationGenerator foreach (var property in properties) { - var usesSaveFlag = propertyFlagGetters.Any(m => m.Item2 == property.Order); - - if (usesSaveFlag) + if (serializableFieldSaveFlagMethodsDictionary.ContainsKey(property.Order)) { // Special case if (property.Type != "bool") diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableFieldSaveFlagMethods.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableFieldSaveFlagMethods.cs new file mode 100644 index 000000000..55a087914 --- /dev/null +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableFieldSaveFlagMethods.cs @@ -0,0 +1,11 @@ +using Microsoft.CodeAnalysis; + +namespace SerializationGenerator +{ + public record SerializableFieldSaveFlagMethods + { + public IMethodSymbol? DetermineFieldShouldSerialize { get; init; } + + public IMethodSymbol? GetFieldDefaultValue { get; init; } + } +} diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializationGenerator.ContentStruct.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializationEntityGeneration.ContentStruct.cs similarity index 89% rename from Projects/SerializationGenerator/SerializableEntityGeneration/SerializationGenerator.ContentStruct.cs rename to Projects/SerializationGenerator/SerializableEntityGeneration/SerializationEntityGeneration.ContentStruct.cs index 276fb64e2..31415e0ef 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializationGenerator.ContentStruct.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializationEntityGeneration.ContentStruct.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2021 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: SerializationGenerator.ContentStruct.cs * + * File: SerializationEntityGeneration.ContentStruct.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 * @@ -25,6 +25,7 @@ namespace SerializationGenerator { public static void GenerateMigrationContentStruct( this StringBuilder source, + Compilation compilation, SerializableMetadata migration, INamedTypeSymbol classSymbol ) @@ -37,10 +38,14 @@ namespace SerializationGenerator foreach (var serializableProperty in properties) { - source.AppendLine($"{indent} internal readonly {serializableProperty.Type} {serializableProperty.Name};"); + var type = compilation.GetTypeByMetadataName(serializableProperty.Type)?.IsValueType == true + || SymbolMetadata.IsPrimitiveFromTypeDisplayString(serializableProperty.Type) + ? $"{serializableProperty.Type}?" : serializableProperty.Type; + + source.AppendLine($"{indent} internal readonly {type} {serializableProperty.Name};"); } - var innerIndent = $"{indent} "; + const string innerIndent = $"{indent} "; var usesSaveFlags = properties.Any(p => p.UsesSaveFlag == true); @@ -54,7 +59,7 @@ namespace SerializationGenerator Accessibility.Private ); - source.GenerateEnumValue(" ", true, "None", -1); + source.GenerateEnumValue(innerIndent, true, "None", -1); int index = 0; foreach (var property in properties) { @@ -77,7 +82,6 @@ namespace SerializationGenerator if (properties.Length > 0) { - source.AppendLine(); foreach (var property in properties) { if (property.UsesSaveFlag == true) @@ -91,6 +95,7 @@ namespace SerializationGenerator else { source.AppendLine($"{innerIndent}if ((saveFlags & V{migration.Version}SaveFlag.{property.Name}) != 0)\n{innerIndent}{{"); + SerializableMigrationRulesEngine.Rules[property.Rule].GenerateDeserializationMethod( source, $"{innerIndent} ", diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializableFieldFlagComparer.cs b/Projects/SerializationGenerator/SerializableMigration/SerializableFieldFlagComparer.cs deleted file mode 100644 index 2b5320f68..000000000 --- a/Projects/SerializationGenerator/SerializableMigration/SerializableFieldFlagComparer.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using Microsoft.CodeAnalysis; - -namespace SerializableMigration -{ - public class SerializableFieldFlagComparer : IComparer<(IMethodSymbol, int)> - { - public int Compare((IMethodSymbol, int) x, (IMethodSymbol, int) y) - { - var (methodSymbolX, orderX) = x; - var (methodSymbolY, orderY) = y; - - if (ReferenceEquals(methodSymbolX, methodSymbolY)) - { - return 0; - } - - if (ReferenceEquals(null, methodSymbolY)) - { - return 1; - } - - if (ReferenceEquals(null, methodSymbolX)) - { - return -1; - } - - return orderX.CompareTo(orderY); - } - } -} diff --git a/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs b/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs index 4287b3e48..37fccfe5a 100644 --- a/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs +++ b/Projects/SerializationGenerator/SerializableMigration/SerializableMigrationRulesEngine.cs @@ -56,7 +56,7 @@ namespace SerializableMigration ImmutableArray serializableTypes, ImmutableArray embeddedSerializableTypes, ISymbol? parentSymbol, - IMethodSymbol? serializablePropertyFlagGetter + SerializableFieldSaveFlagMethods? serializableFieldSaveFlagMethods ) { string propertyName; @@ -86,7 +86,7 @@ namespace SerializableMigration serializableTypes, embeddedSerializableTypes, parentSymbol, - serializablePropertyFlagGetter + serializableFieldSaveFlagMethods ); } @@ -99,7 +99,7 @@ namespace SerializableMigration ImmutableArray serializableTypes, ImmutableArray embeddedSerializableTypes, ISymbol? parentSymbol, - IMethodSymbol? serializablePropertyFlagGetter + SerializableFieldSaveFlagMethods? serializableFieldSaveFlagMethods ) { foreach (var rule in Rules.Values) @@ -119,7 +119,7 @@ namespace SerializableMigration Name = propertyName, Type = propertyType.ToDisplayString(), Order = order, - UsesSaveFlag = serializablePropertyFlagGetter != null ? true : null, + UsesSaveFlag = serializableFieldSaveFlagMethods?.DetermineFieldShouldSerialize != null ? true : null, Rule = rule.RuleName, RuleArguments = ruleArguments.Length > 0 ? ruleArguments : null }; diff --git a/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.Builtin.cs b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.Builtin.cs index 541a114b6..af2bc3241 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.Builtin.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.Builtin.cs @@ -54,5 +54,9 @@ namespace SerializationGenerator compilation.GetTypeByMetadataName(HASHSET_CLASS), SymbolEqualityComparer.Default ) == true; + + public static bool IsPrimitiveFromTypeDisplayString(string type) => + type is "bool" or "sbyte" or "short" or "int" or "long" or "byte" or "ushort" + or "uint" or "ulong" or "float" or "double" or "string" or "decimal"; } } diff --git a/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs index 7aa689bb6..9b88b7493 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs @@ -45,6 +45,7 @@ namespace SerializationGenerator public const string TIMER_DRIFT_ATTRIBUTE = "Server.TimerDriftAttribute"; public const string DESERIALIZE_TIMER_FIELD_ATTRIBUTE = "Server.DeserializeTimerFieldAttribute"; public const string SERIALIZABLE_FIELD_SAVE_FLAG_ATTRIBUTE = "Server.SerializableFieldSaveFlagAttribute"; + public const string SERIALIZABLE_FIELD_DEFAULT_ATTRIBUTE = "Server.SerializableFieldDefaultAttribute"; public const string RAW_SERIALIZABLE_INTERFACE = "Server.IRawSerializable"; public static bool IsTimerDrift(this AttributeData attr, Compilation compilation) => diff --git a/Projects/Server/Serialization/AfterDeserialization.cs b/Projects/Server/Serialization/Attributes/AfterDeserializationAttribute.cs similarity index 67% rename from Projects/Server/Serialization/AfterDeserialization.cs rename to Projects/Server/Serialization/Attributes/AfterDeserializationAttribute.cs index ee9be83a5..97386a53d 100644 --- a/Projects/Server/Serialization/AfterDeserialization.cs +++ b/Projects/Server/Serialization/Attributes/AfterDeserializationAttribute.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2021 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: AfterDeserialization.cs * + * File: AfterDeserializationAttribute.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 * @@ -24,5 +24,15 @@ namespace Server [AttributeUsage(AttributeTargets.Method)] public class AfterDeserializationAttribute : Attribute { + /// + /// Indicates whether the source generator should execute the method this is attached to immediately, or when + /// this is set to false, execute it using a Timer Delay. + /// + /// Note: Use false when the after deserialization involves deleting objects. This is to prevent corrupted + /// deserialization by removing an object before it has finished deserializing. + /// + public bool Synchronous { get; set; } + + public AfterDeserializationAttribute(bool synchronous = true) => Synchronous = true; } } diff --git a/Projects/Server/Serialization/Attributes/DeserializeTimerField.cs b/Projects/Server/Serialization/Attributes/DeserializeTimerFieldAttribute.cs similarity index 96% rename from Projects/Server/Serialization/Attributes/DeserializeTimerField.cs rename to Projects/Server/Serialization/Attributes/DeserializeTimerFieldAttribute.cs index 764ee2c7d..fb33ce62b 100644 --- a/Projects/Server/Serialization/Attributes/DeserializeTimerField.cs +++ b/Projects/Server/Serialization/Attributes/DeserializeTimerFieldAttribute.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2021 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: DeserializeTimerField.cs * + * File: DeserializeTimerFieldAttribute.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 * diff --git a/Projects/Server/Serialization/ManualDirtyChecking.cs b/Projects/Server/Serialization/Attributes/ManualDirtyCheckingAttribute.cs similarity index 95% rename from Projects/Server/Serialization/ManualDirtyChecking.cs rename to Projects/Server/Serialization/Attributes/ManualDirtyCheckingAttribute.cs index 6042a5c8e..4a15de156 100644 --- a/Projects/Server/Serialization/ManualDirtyChecking.cs +++ b/Projects/Server/Serialization/Attributes/ManualDirtyCheckingAttribute.cs @@ -2,7 +2,7 @@ * ModernUO * * Copyright 2019-2021 - ModernUO Development Team * * Email: hi@modernuo.com * - * File: ManualDirtyChecking.cs * + * File: ManualDirtyCheckingAttribute.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 * diff --git a/Projects/Server/Serialization/Attributes/SerializableFieldDefaultAttribute.cs b/Projects/Server/Serialization/Attributes/SerializableFieldDefaultAttribute.cs new file mode 100644 index 000000000..35ba44256 --- /dev/null +++ b/Projects/Server/Serialization/Attributes/SerializableFieldDefaultAttribute.cs @@ -0,0 +1,35 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: SerializableFieldDefaultAttribute.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 the field with the same order should use this default value + /// while deserializing. The default is used when the save flag indicates that we don't need to serialize the value + /// because this default can be used instead. + /// + /// Note: This is only used for the current version, not previous versions. Previous versions will always use a default + /// value for that type if it is not deserialized. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class SerializableFieldDefaultAttribute : Attribute + { + public int Order { get; } + + public SerializableFieldDefaultAttribute(int order) => Order = order; + } +} diff --git a/Projects/Server/Server.csproj b/Projects/Server/Server.csproj index 64567d630..60936169a 100755 --- a/Projects/Server/Server.csproj +++ b/Projects/Server/Server.csproj @@ -58,6 +58,9 @@ + + + true Generated diff --git a/Projects/UOContent/Engines/Bulk Orders/BaseBOD.cs b/Projects/UOContent/Engines/Bulk Orders/BaseBOD.cs index 95c9b9d95..3d1b55177 100644 --- a/Projects/UOContent/Engines/Bulk Orders/BaseBOD.cs +++ b/Projects/UOContent/Engines/Bulk Orders/BaseBOD.cs @@ -113,7 +113,7 @@ namespace Server.Engines.BulkOrders } } - [AfterDeserialization] + [AfterDeserialization(false)] private void AfterDeserialization() { if (Parent == null && Map == Map.Internal && Location == Point3D.Zero) diff --git a/Projects/UOContent/Items/Armor/BaseArmor.cs b/Projects/UOContent/Items/Armor/BaseArmor.cs index 23ef3153b..f69a7fbbd 100644 --- a/Projects/UOContent/Items/Armor/BaseArmor.cs +++ b/Projects/UOContent/Items/Armor/BaseArmor.cs @@ -20,6 +20,9 @@ namespace Server.Items [SerializableFieldSaveFlag(0)] private bool ShouldSerializeAosAttributes() => !_attributes.IsEmpty; + [SerializableFieldDefault(0)] + private AosAttributes AttributesDefaultValue() => new(this); + [SerializableField(1, setter: "private")] [SerializableFieldAttr("[CommandProperty(AccessLevel.GameMaster, canModify: true)]")] private AosArmorAttributes _armorAttributes; @@ -27,6 +30,9 @@ namespace Server.Items [SerializableFieldSaveFlag(1)] private bool ShouldSerializeArmorAttributes() => !_armorAttributes.IsEmpty; + [SerializableFieldDefault(1)] + private AosArmorAttributes ArmorAttributesDefaultValue() => new(this); + [InvalidateProperties] [SerializableField(2)] [SerializableFieldAttr("[CommandProperty(AccessLevel.GameMaster)]")] @@ -135,6 +141,9 @@ namespace Server.Items [SerializableFieldSaveFlag(23)] private bool ShouldSerializeSkillBonuses() => !_skillBonuses.IsEmpty; + [SerializableFieldDefault(23)] + private AosSkillBonuses SkillBonusesDefaultValue() => new(this); + [SerializableField(24)] [SerializableFieldAttr("[CommandProperty(AccessLevel.GameMaster)]")] public bool _playerConstructed; @@ -269,6 +278,9 @@ namespace Server.Items [SerializableFieldSaveFlag(14)] private bool ShouldSerializeResource() => _resource != DefaultResource; + [SerializableFieldDefault(14)] + private CraftResource ResourceDefaultValue() => DefaultResource; + [SerializableField(15)] [CommandProperty(AccessLevel.GameMaster)] public int BaseArmorRating @@ -285,6 +297,9 @@ namespace Server.Items [SerializableFieldSaveFlag(15)] private bool ShouldSerializeArmorBase() => _armorBase != -1; + [SerializableFieldDefault(15)] + private int ArmorBaseDefaultValue() => -1; + public double BaseArmorRatingScaled => BaseArmorRating * ArmorScalar; public virtual double ArmorRating @@ -358,6 +373,9 @@ namespace Server.Items [SerializableFieldSaveFlag(16)] private bool ShouldSerializeStrBonus() => _strBonus != -1; + [SerializableFieldDefault(16)] + private int StrBonusDefaultValue() => -1; + [SerializableField(17)] [CommandProperty(AccessLevel.GameMaster)] public int DexBonus @@ -374,6 +392,9 @@ namespace Server.Items [SerializableFieldSaveFlag(17)] private bool ShouldSerializeDexBonus() => _dexBonus != -1; + [SerializableFieldDefault(17)] + private int DexBonusDefaultValue() => -1; + [SerializableField(18)] [CommandProperty(AccessLevel.GameMaster)] public int IntBonus @@ -390,6 +411,9 @@ namespace Server.Items [SerializableFieldSaveFlag(18)] private bool ShouldSerializeIntBonus() => _intBonus != -1; + [SerializableFieldDefault(18)] + private int IntBonusDefaultValue() => -1; + [SerializableField(19)] [CommandProperty(AccessLevel.GameMaster)] public int StrRequirement @@ -406,6 +430,9 @@ namespace Server.Items [SerializableFieldSaveFlag(19)] private bool ShouldSerializeStrReq() => _strReq != -1; + [SerializableFieldDefault(19)] + private int StrReqDefaultValue() => -1; + [SerializableField(20)] [CommandProperty(AccessLevel.GameMaster)] public int DexRequirement @@ -422,6 +449,9 @@ namespace Server.Items [SerializableFieldSaveFlag(20)] private bool ShouldSerializeDexReq() => _dexReq != -1; + [SerializableFieldDefault(20)] + private int DexReqDefaultValue() => -1; + [SerializableField(21)] [CommandProperty(AccessLevel.GameMaster)] public int IntRequirement @@ -438,6 +468,9 @@ namespace Server.Items [SerializableFieldSaveFlag(21)] private bool ShouldSerializeIntReq() => _intReq != -1; + [SerializableFieldDefault(21)] + private int IntReqDefaultValue() => -1; + [SerializableField(22)] [CommandProperty(AccessLevel.GameMaster)] public AMA MeditationAllowance @@ -1044,10 +1077,6 @@ namespace Server.Items [AfterDeserialization] private void AfterDeserialization() { - Attributes ??= new AosAttributes(this); - ArmorAttributes ??= new AosArmorAttributes(this); - SkillBonuses ??= new AosSkillBonuses(this); - var m = Parent as Mobile; if (Core.AOS && m != null) diff --git a/Projects/UOContent/Items/Armor/Glasses/ElvenGlasses.cs b/Projects/UOContent/Items/Armor/Glasses/ElvenGlasses.cs index e61df4770..1b4bbd3e3 100644 --- a/Projects/UOContent/Items/Armor/Glasses/ElvenGlasses.cs +++ b/Projects/UOContent/Items/Armor/Glasses/ElvenGlasses.cs @@ -37,11 +37,8 @@ namespace Server.Items [SerializableFieldSaveFlag(0)] private bool ShouldSerializeWeaponAttributes() => !_weaponAttributes.IsEmpty; - [AfterDeserialization] - private void AfterDeserialization() - { - _weaponAttributes ??= new AosWeaponAttributes(this); - } + [SerializableFieldDefault(0)] + private AosWeaponAttributes WeaponAttributesDefaultValue() => new(this); public override void AppendChildNameProperties(ObjectPropertyList list) { diff --git a/Projects/UOContent/Items/Books/BaseBook.cs b/Projects/UOContent/Items/Books/BaseBook.cs index d75da70c3..52a703f82 100644 --- a/Projects/UOContent/Items/Books/BaseBook.cs +++ b/Projects/UOContent/Items/Books/BaseBook.cs @@ -23,6 +23,9 @@ namespace Server.Items [SerializableFieldSaveFlag(1)] private bool ShouldSerializeTitle() => _title != DefaultContent?.Title; + [SerializableFieldDefault(1)] + private string TitleDefaultValue() => DefaultContent?.Title; + [InvalidateProperties] [SerializableField(2)] [SerializableFieldAttr("[CommandProperty(AccessLevel.GameMaster)]")] @@ -31,6 +34,9 @@ namespace Server.Items [SerializableFieldSaveFlag(2)] private bool ShouldSerializeAuthor() => _author != DefaultContent?.Author; + [SerializableFieldDefault(2)] + private string AuthorDefaultValue() => DefaultContent?.Author; + [SerializableField(3)] [SerializableFieldAttr("[CommandProperty(AccessLevel.GameMaster)]")] private bool _writable; @@ -44,6 +50,9 @@ namespace Server.Items [SerializableFieldSaveFlag(4)] private bool ShouldSerializePages() => DefaultContent?.IsMatch(_pages) != true; + [SerializableFieldDefault(4)] + private BookPageInfo[] PagesDefaultvalue() => DefaultContent?.Copy() ?? Array.Empty(); + [Constructible] public BaseBook(int itemID, int pageCount = 20, bool writable = true) : this(itemID, null, null, pageCount, writable) { @@ -122,18 +131,6 @@ namespace Server.Items SetSecureLevelEntry.AddTo(from, this, list); } - [AfterDeserialization] - private void AfterDeserialization() - { - var content = DefaultContent; - - if (content != null) - { - _title ??= content.Title; - _author ??= content.Author; - } - } - private void Deserialize(IGenericReader reader, int version) { Level = (SecureLevel)reader.ReadInt(); diff --git a/Projects/UOContent/Items/Weapons/Fists.cs b/Projects/UOContent/Items/Weapons/Fists.cs index 40e6fb4e9..8be6fe5ac 100644 --- a/Projects/UOContent/Items/Weapons/Fists.cs +++ b/Projects/UOContent/Items/Weapons/Fists.cs @@ -172,7 +172,7 @@ namespace Server.Items base.PlaySwingAnimation( attacker ); }*/ - [AfterDeserialization] + [AfterDeserialization(false)] private void OnAfterDeserialization() { Delete();