fix(codegen): Adds codegen for embedded objects and timer fields (#686)

* Adds code genning for embedded objects. See `AquariumState` as an example.
* Adds code genning for fields that are `Timer`. See `Aquarium` as an example.
* Codegens aquariums
* Fixes missing option for most primitive field types.
This commit is contained in:
Kamron Batman 2021-08-16 14:48:18 -07:00 committed by GitHub
parent 4c265562bb
commit 1323162628
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
126 changed files with 1287 additions and 1129 deletions

View file

@ -39,6 +39,7 @@ namespace SerializationGenerator
var jsonOptions = SerializableMigrationSchema.GetJsonSerializerOptions();
// List of types that _will_ become ISerializable
var serializableList = receiver.SerializableList;
var embeddedSerializableList = receiver.EmbeddedSerializableList;
foreach (var (classSymbol, (serializableAttr, fieldsList)) in receiver.ClassAndFields)
{
@ -50,9 +51,34 @@ namespace SerializationGenerator
string classSource = context.GenerateSerializationPartialClass(
classSymbol,
serializableAttr,
false,
fieldsList.ToImmutableArray(),
jsonOptions,
serializableList
serializableList,
embeddedSerializableList
);
if (classSource != null)
{
context.AddSource($"{classSymbol.ToDisplayString()}.Serialization.cs", SourceText.From(classSource, Encoding.UTF8));
}
}
foreach (var (classSymbol, (embeddedSerializableAttr, fieldsList)) in receiver.EmbeddedClassAndFields)
{
if (embeddedSerializableAttr == null)
{
continue;
}
string classSource = context.GenerateSerializationPartialClass(
classSymbol,
embeddedSerializableAttr,
true,
fieldsList.ToImmutableArray(),
jsonOptions,
serializableList,
embeddedSerializableList
);
if (classSource != null)

View file

@ -30,9 +30,11 @@ namespace SerializationGenerator
this GeneratorExecutionContext context,
INamedTypeSymbol classSymbol,
AttributeData serializableAttr,
bool embedded,
ImmutableArray<ISymbol> fieldsAndProperties,
JsonSerializerOptions jsonSerializerOptions,
ImmutableArray<INamedTypeSymbol> serializableTypes
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes
)
{
var version = (int)serializableAttr.ConstructorArguments[0].Value!;
@ -47,10 +49,12 @@ namespace SerializationGenerator
classSymbol,
serializableAttr,
null, // Do not generate schema
embedded,
null,
migrations.ToImmutableArray(),
fieldsAndProperties,
serializableTypes
serializableTypes,
embeddedSerializableTypes
);
}
@ -59,9 +63,11 @@ namespace SerializationGenerator
INamedTypeSymbol classSymbol,
AttributeData serializableAttr,
string? migrationPath,
bool embedded,
JsonSerializerOptions? jsonSerializerOptions,
ImmutableArray<ISymbol> fieldsAndProperties,
ImmutableArray<INamedTypeSymbol> serializableTypes
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes
)
{
var version = (int)serializableAttr.ConstructorArguments[0].Value!;
@ -77,10 +83,12 @@ namespace SerializationGenerator
classSymbol,
serializableAttr,
migrationPath,
embedded,
jsonSerializerOptions,
migrations.ToImmutableArray(),
fieldsAndProperties,
serializableTypes
serializableTypes,
embeddedSerializableTypes
);
}
@ -89,22 +97,27 @@ namespace SerializationGenerator
INamedTypeSymbol classSymbol,
AttributeData serializableAttr,
string? migrationPath,
bool embedded,
JsonSerializerOptions? jsonSerializerOptions,
ImmutableArray<SerializableMetadata> migrations,
ImmutableArray<ISymbol> fieldsAndProperties,
ImmutableArray<INamedTypeSymbol> serializableTypes
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes
)
{
var serializableFieldAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTRIBUTE);
var serializableFieldAttrAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTR_ATTRIBUTE);
var serializableInterface = compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_INTERFACE);
var serializableInterface =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_INTERFACE);
var parentSerializableAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_PARENT_ATTRIBUTE);
// If we have a parent that is or derives from ISerializable, then we are in override
var isOverride = classSymbol.BaseType.ContainsInterface(serializableInterface);
if (!isOverride && !classSymbol.ContainsInterface(serializableInterface))
if (!(embedded || isOverride || classSymbol.ContainsInterface(serializableInterface)))
{
return null;
}
@ -120,10 +133,7 @@ namespace SerializationGenerator
source.AppendLine("#pragma warning disable\n");
source.GenerateNamespaceStart(namespaceName);
source.GenerateClassStart(
className,
ImmutableArray<ITypeSymbol>.Empty
);
source.GenerateClassStart(className, ImmutableArray<ITypeSymbol>.Empty);
const string indent = " ";
@ -136,6 +146,14 @@ namespace SerializationGenerator
);
source.AppendLine();
var parentFieldOrProperty = embedded ? fieldsAndProperties.FirstOrDefault(
fieldOrPropertySymbol => fieldOrPropertySymbol.GetAttributes()
.FirstOrDefault(
attr =>
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, parentSerializableAttribute)
) != null
) : null;
var serializablePropertySet = new SortedSet<SerializableProperty>(new SerializablePropertyComparer());
foreach (var fieldOrPropertySymbol in fieldsAndProperties)
@ -193,7 +211,8 @@ namespace SerializationGenerator
fieldSymbol,
getterAccessor,
setterAccessor,
virtualProperty
virtualProperty,
parentFieldOrProperty
);
source.AppendLine();
}
@ -204,6 +223,7 @@ namespace SerializationGenerator
order,
allAttributes,
serializableTypes,
embeddedSerializableTypes,
classSymbol
);
@ -213,7 +233,7 @@ namespace SerializationGenerator
var serializableProperties = serializablePropertySet.ToImmutableArray();
// If we are not inheriting ISerializable, then we need to define some stuff
if (!isOverride)
if (!(isOverride || embedded))
{
// long ISerializable.SavePosition { get; set; } = -1;
source.GenerateAutoProperty(
@ -237,9 +257,12 @@ namespace SerializationGenerator
);
}
// Serial constructor
source.GenerateSerialCtor(compilation, className, isOverride);
source.AppendLine();
if (!embedded)
{
// Serial constructor
source.GenerateSerialCtor(compilation, className, isOverride);
source.AppendLine();
}
if (version > 0)
{
@ -271,7 +294,8 @@ namespace SerializationGenerator
version,
encodedVersion,
migrations,
serializableProperties
serializableProperties,
parentFieldOrProperty
);
source.GenerateClassEnd();

View file

@ -31,7 +31,8 @@ namespace SerializationGenerator
int version,
bool encodedVersion,
ImmutableArray<SerializableMetadata> migrations,
ImmutableArray<SerializableProperty> properties
ImmutableArray<SerializableProperty> properties,
ISymbol parentFieldOrProperty
)
{
var genericReaderInterface = compilation.GetTypeByMetadataName(SymbolMetadata.GENERIC_READER_INTERFACE);
@ -74,6 +75,7 @@ namespace SerializationGenerator
if (version > 0)
{
var parent = parentFieldOrProperty?.Name ?? "this";
var nextVersion = 0;
for (var i = 0; i < migrations.Length; i++)
@ -88,7 +90,7 @@ namespace SerializationGenerator
source.AppendLine($"{indent}if (version == {migrationVersion})");
source.AppendLine($"{indent}{{");
source.AppendLine($"{indent} MigrateFrom(new V{migrationVersion}Content(reader));");
source.AppendLine($"{indent} ((Server.ISerializable)this).MarkDirty();");
source.AppendLine($"{indent} {parent}.MarkDirty();");
if (afterDeserialization != null)
{
source.AppendLine($"{indent} Timer.DelayCall({afterDeserialization.Name});");
@ -103,7 +105,7 @@ namespace SerializationGenerator
source.AppendLine($"{indent}if (version < _version)");
source.AppendLine($"{indent}{{");
source.AppendLine($"{indent} Deserialize(reader, version);");
source.AppendLine($"{indent} ((Server.ISerializable)this).MarkDirty();");
source.AppendLine($"{indent} {parent}.MarkDirty();");
if (afterDeserialization != null)
{
source.AppendLine($"{indent} Timer.DelayCall({afterDeserialization.Name});");
@ -116,11 +118,14 @@ namespace SerializationGenerator
foreach (var property in properties)
{
source.AppendLine();
SerializableMigrationRulesEngine.Rules[property.Rule].GenerateDeserializationMethod(
var rule = SerializableMigrationRulesEngine.Rules[property.Rule];
rule.GenerateDeserializationMethod(
source,
indent,
property
);
(rule as IPostDeserializeMethod)?.PostDeserializeMethod(source, indent, property, compilation, classSymbol);
}
if (afterDeserialization != null)

View file

@ -27,7 +27,8 @@ namespace SerializationGenerator
IFieldSymbol fieldSymbol,
Accessibility getter,
Accessibility? setter,
bool isVirtual
bool isVirtual,
ISymbol? parentFieldOrProperty = null
)
{
var fieldName = fieldSymbol.Name;
@ -54,16 +55,18 @@ namespace SerializationGenerator
// Getter
source.GeneratePropertyGetterReturnsField(propertyIndent, fieldSymbol, getterAccessor);
if (setter != null)
if (setter != null && setter != Accessibility.NotApplicable)
{
var setterAccessor = setter == propertyAccessor ? Accessibility.NotApplicable : setter;
var parentSymbol = parentFieldOrProperty?.Name ?? "this";
// Setter
source.GeneratePropertySetterStart(propertyIndent, false, setterAccessor.Value);
source.AppendLine($"{innerIndent}if (value != {fieldName})");
source.AppendLine($"{innerIndent}{{");
source.AppendLine($"{innerIndent} {fieldName} = value;");
source.AppendLine($"{innerIndent} ((ISerializable)this).MarkDirty();");
source.AppendLine($"{innerIndent} {parentSymbol}.MarkDirty();");
if (invalidatePropertiesAttribute != null)
{

View file

@ -0,0 +1,31 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: IPostDeserializeMethod.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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializableMigration
{
public interface IPostDeserializeMethod
{
public void PostDeserializeMethod(
StringBuilder source,
string indent,
SerializableProperty property,
Compilation compilation,
INamedTypeSymbol classSymbol
);
}
}

View file

@ -28,6 +28,7 @@ namespace SerializableMigration
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
);

View file

@ -29,6 +29,7 @@ namespace SerializableMigration
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
@ -46,6 +47,7 @@ namespace SerializableMigration
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol
);
@ -60,7 +62,7 @@ namespace SerializableMigration
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(ArrayMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
@ -91,7 +93,7 @@ namespace SerializableMigration
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(ArrayMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{

View file

@ -0,0 +1,81 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: EmbeddedSerializableMigrationRule.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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public class EmbeddedSerializableMigrationRule : ISerializableMigrationRule
{
public string RuleName => nameof(EmbeddedSerializableMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not INamedTypeSymbol namedTypeSymbol)
{
ruleArguments = null;
return false;
}
if (!embeddedSerializableTypes.Contains(namedTypeSymbol))
{
ruleArguments = null;
return false;
}
ruleArguments = new[] { "" };
return true;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}{propertyName} = new {property.Type}(this);");
source.AppendLine($"{indent}{propertyName}.Deserialize(reader);");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}{propertyName}.Serialize(writer);");
}
}
}

View file

@ -30,6 +30,7 @@ namespace SerializableMigration
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
@ -46,7 +47,7 @@ namespace SerializableMigration
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(EnumMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
@ -58,7 +59,7 @@ namespace SerializableMigration
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(EnumMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{

View file

@ -31,6 +31,7 @@ namespace SerializableMigration
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
@ -50,6 +51,7 @@ namespace SerializableMigration
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol
);
@ -71,7 +73,7 @@ namespace SerializableMigration
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(HashSetMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
@ -114,7 +116,7 @@ namespace SerializableMigration
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(HashSetMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{

View file

@ -30,6 +30,7 @@ namespace SerializableMigration
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
@ -49,6 +50,7 @@ namespace SerializableMigration
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol
);
@ -59,6 +61,7 @@ namespace SerializableMigration
1,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol
);
@ -80,7 +83,7 @@ namespace SerializableMigration
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(KeyValuePairMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
@ -134,7 +137,7 @@ namespace SerializableMigration
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(KeyValuePairMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{

View file

@ -31,6 +31,7 @@ namespace SerializableMigration
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
@ -50,6 +51,7 @@ namespace SerializableMigration
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol
);
@ -71,7 +73,7 @@ namespace SerializableMigration
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(ListMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
@ -115,7 +117,7 @@ namespace SerializableMigration
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(ListMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{

View file

@ -31,6 +31,7 @@ namespace SerializableMigration
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
@ -73,7 +74,7 @@ namespace SerializableMigration
new[] { "DeltaTime" },
SpecialType.System_String when attributes.Any(a => a.IsInternString(compilation)) =>
new[] { "InternString" },
_ => Array.Empty<string>()
_ => new[] { "" }
};
return true;
@ -81,7 +82,7 @@ namespace SerializableMigration
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(PrimitiveTypeMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
@ -124,7 +125,7 @@ namespace SerializableMigration
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(PrimitiveTypeMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{

View file

@ -30,6 +30,7 @@ namespace SerializableMigration
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
@ -50,7 +51,7 @@ namespace SerializableMigration
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(PrimitiveUOTypeMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
@ -63,7 +64,7 @@ namespace SerializableMigration
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(PrimitiveUOTypeMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{

View file

@ -30,6 +30,7 @@ namespace SerializableMigration
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
@ -46,7 +47,7 @@ namespace SerializableMigration
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(SerializableInterfaceMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
@ -59,7 +60,7 @@ namespace SerializableMigration
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(SerializableInterfaceMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{

View file

@ -30,6 +30,7 @@ namespace SerializableMigration
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
@ -53,7 +54,7 @@ namespace SerializableMigration
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(SerializationMethodSignatureMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
@ -69,7 +70,7 @@ namespace SerializableMigration
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
const string expectedRule = nameof(SerializationMethodSignatureMigrationRule);
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{

View file

@ -0,0 +1,125 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: TimerMigrationRule.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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public class TimerMigrationRule : ISerializableMigrationRule, IPostDeserializeMethod
{
public string RuleName => nameof(TimerMigrationRule);
public bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (!(symbol is ITypeSymbol typeSymbol && typeSymbol.IsTimer(compilation)))
{
ruleArguments = null;
return false;
}
ruleArguments = attributes.Any(a => a.IsTimerDrift(compilation))
? new[] { "@TimerDrift" }
: new[] { "" };
return true;
}
public void GenerateDeserializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
var ruleArguments = property.RuleArguments;
var driftTimer = ruleArguments[0].Contains("@TimerDrift");
var readTimer = driftTimer ? "reader.ReadDeltaTime()" : "reader.ReadDateTime()";
source.AppendLine($"{indent}var {propertyName}Delay = {readTimer} - Core.Now;");
}
public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
var ruleArguments = property.RuleArguments;
var driftTimer = ruleArguments[0].Contains("@TimerDrift");
var writerMethod = driftTimer ? "WriteDeltaTime" : "Write";
source.AppendLine($"{indent}writer.{writerMethod}({propertyName}.Next);");
}
public void PostDeserializeMethod(
StringBuilder source, string indent, SerializableProperty property, Compilation compilation, INamedTypeSymbol classSymbol
)
{
var deserializeTimerMethod = classSymbol
.GetMembers()
.OfType<IMethodSymbol>()
.FirstOrDefault(
m =>
{
if (!m.ReturnsVoid || m.Parameters.Length != 1 || !m.Parameters[0].Type.IsTimeSpan(compilation))
{
return false;
}
return m.GetAttributes()
.FirstOrDefault(
attr =>
{
if (!SymbolEqualityComparer.Default.Equals(
attr.AttributeClass,
compilation.GetTypeByMetadataName(
SymbolMetadata.DESERIALIZE_TIMER_FIELD_ATTRIBUTE
)
))
{
return false;
}
var order = (int)attr.ConstructorArguments[0].Value!;
return order == property.Order;
}
) != null;
}
) ?? throw new Exception("Serializing a timer requires a method with the DeserializeTimerField attribute to handle creating the timer itself.");
source.AppendLine($"{indent}{deserializeTimerMethod.Name}({property.Name}Delay);");
}
}
}

View file

@ -38,6 +38,8 @@ namespace SerializableMigration
new PrimitiveUOTypeMigrationRule(),
new SerializableInterfaceMigrationRule(),
new SerializationMethodSignatureMigrationRule(),
new EmbeddedSerializableMigrationRule(),
new TimerMigrationRule()
};
foreach (var rule in rules)
@ -52,6 +54,7 @@ namespace SerializableMigration
int order,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol = default
)
{
@ -80,6 +83,7 @@ namespace SerializableMigration
order,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol
);
}
@ -91,6 +95,7 @@ namespace SerializableMigration
int order,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol = default
)
{
@ -101,6 +106,7 @@ namespace SerializableMigration
propertyType,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
out var ruleArguments
))

View file

@ -24,10 +24,13 @@ namespace SerializationGenerator
{
#pragma warning disable RS1024
public Dictionary<INamedTypeSymbol, (AttributeData?, List<ISymbol>)> ClassAndFields { get; } = new(SymbolEqualityComparer.Default);
public Dictionary<INamedTypeSymbol, (AttributeData?, List<ISymbol>)> EmbeddedClassAndFields { get; } = new(SymbolEqualityComparer.Default);
#pragma warning restore RS1024
public ImmutableArray<INamedTypeSymbol> SerializableList => ClassAndFields.Keys.ToImmutableArray();
public ImmutableArray<INamedTypeSymbol> EmbeddedSerializableList => EmbeddedClassAndFields.Keys.ToImmutableArray();
public void OnVisitSyntaxNode(SyntaxNode node, SemanticModel semanticModel)
{
var compilation = semanticModel.Compilation;
@ -39,7 +42,19 @@ namespace SerializationGenerator
return;
}
if (classSymbol.WillBeSerializable(compilation, out var attrData))
if (classSymbol.IsEmbeddedSerializable(compilation, out var attrData))
{
if (EmbeddedClassAndFields.TryGetValue(classSymbol, out var value))
{
var (_, fieldsList) = value;
EmbeddedClassAndFields[classSymbol] = (attrData, fieldsList);
}
else
{
EmbeddedClassAndFields.Add(classSymbol, (attrData, new List<ISymbol>()));
}
}
else if (classSymbol.WillBeSerializable(compilation, out attrData))
{
if (ClassAndFields.TryGetValue(classSymbol, out var value))
{
@ -64,8 +79,11 @@ namespace SerializationGenerator
AddFieldOrProperty(fieldSymbol, compilation);
}
}
return;
}
else if (node is PropertyDeclarationSyntax { AttributeLists: { Count: > 0 } } propertyDeclarationSyntax)
if (node is PropertyDeclarationSyntax { AttributeLists: { Count: > 0 } } propertyDeclarationSyntax)
{
if (semanticModel.GetDeclaredSymbol(propertyDeclarationSyntax) is IPropertySymbol propertySymbol)
{
@ -80,8 +98,9 @@ namespace SerializationGenerator
private void AddFieldOrProperty(ISymbol symbol, Compilation compilation)
{
var serializableFieldAttr = compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTRIBUTE);
var parentAttr = compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_PARENT_ATTRIBUTE);
if (symbol.GetAttribute(serializableFieldAttr) == null)
if (symbol.GetAttribute(serializableFieldAttr) == null && symbol.GetAttribute(parentAttr) == null)
{
return;
}
@ -94,10 +113,21 @@ namespace SerializationGenerator
return;
}
if (EmbeddedClassAndFields.TryGetValue(classSymbol, out value))
{
var (_, fieldsList) = value;
fieldsList.Add(symbol);
return;
}
if (classSymbol.WillBeSerializable(compilation, out var attrData))
{
ClassAndFields.Add(classSymbol, (attrData, new List<ISymbol> { symbol }));
}
else if (classSymbol.IsEmbeddedSerializable(compilation, out attrData))
{
EmbeddedClassAndFields.Add(classSymbol, (attrData, new List<ISymbol> { symbol }));
}
}
}
}

View file

@ -55,5 +55,11 @@ namespace SerializationGenerator
"private protected" => Accessibility.ProtectedAndInternal,
_ => Accessibility.NotApplicable
};
public static bool CanBeConstructedFrom(this ITypeSymbol? symbol, ISymbol classSymbol) =>
symbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.ConstructedFrom.Equals(
classSymbol,
SymbolEqualityComparer.Default
) || symbol != null && CanBeConstructedFrom(symbol.BaseType, classSymbol);
}
}

View file

@ -24,6 +24,8 @@ namespace SerializationGenerator
public const string INVALIDATEPROPERTIES_ATTRIBUTE = "Server.InvalidatePropertiesAttribute";
public const string AFTERDESERIALIZATION_ATTRIBUTE = "Server.AfterDeserializationAttribute";
public const string SERIALIZABLE_ATTRIBUTE = "Server.SerializableAttribute";
public const string EMBEDDED_SERIALIZABLE_ATTRIBUTE = "Server.EmbeddedSerializableAttribute";
public const string SERIALIZABLE_PARENT_ATTRIBUTE = "Server.SerializableParentAttribute";
public const string SERIALIZABLE_FIELD_ATTRIBUTE = "Server.SerializableFieldAttribute";
public const string SERIALIZABLE_FIELD_ATTR_ATTRIBUTE = "Server.SerializableFieldAttrAttribute";
public const string SERIALIZABLE_INTERFACE = "Server.ISerializable";
@ -39,6 +41,15 @@ namespace SerializationGenerator
public const string RECTANGLE3D_STRUCT = "Server.Rectangle3D";
public const string RACE_CLASS = "Server.Race";
public const string MAP_CLASS = "Server.Map";
public const string TIMER_CLASS = "Server.Timer";
public const string TIMER_DRIFT_ATTRIBUTE = "Server.TimerDriftAttribute";
public const string DESERIALIZE_TIMER_FIELD_ATTRIBUTE = "Server.DeserializeTimerFieldAttribute";
public static bool IsTimerDrift(this AttributeData attr, Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(TIMER_DRIFT_ATTRIBUTE)) == true;
public static bool IsTimer(this ITypeSymbol symbol, Compilation compilation) =>
symbol.CanBeConstructedFrom(compilation.GetTypeByMetadataName(TIMER_CLASS));
public static bool IsEncodedInt(this AttributeData attr, Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(ENCODED_INT_ATTRIBUTE)) == true;
@ -112,6 +123,29 @@ namespace SerializationGenerator
);
}
public static bool HasPublicDeserializeMethod(
this ITypeSymbol symbol,
Compilation compilation,
ImmutableArray<INamedTypeSymbol> serializableTypes
)
{
if (symbol.HasSerializableInterface(compilation, serializableTypes))
{
return true;
}
var genericReaderInterface = compilation.GetTypeByMetadataName(GENERIC_READER_INTERFACE);
return symbol.GetAllMethods("Deserialize")
.Any(
m => !m.IsStatic &&
m.ReturnsVoid &&
m.Parameters.Length == 1 &&
SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, genericReaderInterface) &&
m.DeclaredAccessibility == Accessibility.Public
);
}
public static bool IsPoint2D(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(POINT2D_STRUCT),
@ -171,5 +205,14 @@ namespace SerializationGenerator
attributeData = classSymbol.GetAttribute(serializableEntityAttribute);
return attributeData != null;
}
public static bool IsEmbeddedSerializable(this INamedTypeSymbol classSymbol, Compilation compilation, out AttributeData? attributeData)
{
var embeddedSerializableEntityAttribute =
compilation.GetTypeByMetadataName(EMBEDDED_SERIALIZABLE_ATTRIBUTE);
attributeData = classSymbol.GetAttribute(embeddedSerializableEntityAttribute);
return attributeData != null;
}
}
}