EnumDeclarationSyntax GenerateChangedFieldsEnum(GClass cl) { var changedFieldsEnum = EnumDeclaration(Identifier(ChangedFieldsTypeName(cl))); int count = 0; void AddValue(string name) { var value = 1ul << count; changedFieldsEnum = changedFieldsEnum.AddMembers( EnumMemberDeclaration(name) .WithEqualsValue(EqualsValueClause(ParseExpression(value.ToString())))); count++; } foreach (var prop in cl.Properties) { AddValue(prop.Name); if (prop.Animated) { AddValue(prop.Name + "Animated"); } } var baseType = count <= 8 ? "byte" : count <= 16 ? "ushort" : count <= 32 ? "uint" : "ulong"; return(changedFieldsEnum.AddBaseListTypes(SimpleBaseType(ParseTypeName(baseType))) .AddAttributeLists(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("System.Flags")))))); }
StatementSyntax GeneratePropertySetterAssignment(GClass cl, GProperty prop, bool isObject, bool isNullable) { var code = @$ " // Update the backing value {PropertyBackingFieldName(prop)} = value; // Register object for serialization in the next batch {ChangedFieldsFieldName(cl)} |= {ChangedFieldsTypeName(cl)}.{prop.Name};
void GenerateClass(GClass cl) { var list = cl as GList; var unit = Unit(); var clientNs = NamespaceDeclaration(IdentifierName("Avalonia.Rendering.Composition")); var serverNs = NamespaceDeclaration(IdentifierName("Avalonia.Rendering.Composition.Server")); var transportNs = NamespaceDeclaration(IdentifierName("Avalonia.Rendering.Composition.Transport")); var inherits = cl.Inherits ?? "CompositionObject"; var abstractModifier = cl.Abstract ? new[] { SyntaxKind.AbstractKeyword } : null; var client = ClassDeclaration(cl.Name) .AddModifiers(abstractModifier) .AddModifiers(SyntaxKind.PublicKeyword, SyntaxKind.UnsafeKeyword, SyntaxKind.PartialKeyword) .WithBaseType(inherits); var serverName = ServerName(cl.Name); var serverBase = cl.ServerBase ?? ServerName(cl.Inherits); if (list != null) { serverBase = "ServerList<" + ServerName(list.ItemType) + ">"; } var server = ClassDeclaration(serverName) .AddModifiers(abstractModifier) .AddModifiers(SyntaxKind.UnsafeKeyword, SyntaxKind.PartialKeyword) .WithBaseType(serverBase); string changesName = ChangesName(cl.Name); string changedFieldsTypeName = ChangedFieldsTypeName(cl); string changedFieldsName = ChangedFieldsFieldName(cl); if (cl.Properties.Count > 0) { client = client .AddMembers(DeclareField(changedFieldsTypeName, changedFieldsName)); } if (!cl.CustomCtor) { client = client.AddMembers(PropertyDeclaration(ParseTypeName(serverName), "Server") .AddModifiers(SyntaxKind.InternalKeyword, SyntaxKind.NewKeyword) .AddAccessorListAccessors(AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) .WithSemicolonToken(Semicolon()))); client = client.AddMembers( ConstructorDeclaration(cl.Name) .AddModifiers(SyntaxKind.InternalKeyword) .WithParameterList(ParameterList(SeparatedList(new[] { Parameter(Identifier("compositor")).WithType(ParseTypeName("Compositor")), Parameter(Identifier("server")).WithType(ParseTypeName(serverName)), }))) .WithInitializer(ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, ArgumentList(SeparatedList(new[] { Argument(IdentifierName("compositor")), Argument(IdentifierName("server")), })))).WithBody(Block( ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, IdentifierName("Server"), CastExpression(ParseTypeName(serverName), IdentifierName("server")))), ExpressionStatement(InvocationExpression(IdentifierName("InitializeDefaults"))) ))); } if (!cl.CustomServerCtor) { server = server.AddMembers( ConstructorDeclaration(serverName) .AddModifiers(SyntaxKind.InternalKeyword) .WithParameterList(ParameterList(SeparatedList(new[] { Parameter(Identifier("compositor")).WithType(ParseTypeName("ServerCompositor")), }))) .WithInitializer(ConstructorInitializer(SyntaxKind.BaseConstructorInitializer, ArgumentList(SeparatedList(new[] { Argument(IdentifierName("compositor")), })))).WithBody(Block(ParseStatement("Initialize();")))); } server = server.AddMembers( MethodDeclaration(ParseTypeName("void"), "Initialize") .AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())); var changesVarName = "c"; var changesVar = IdentifierName(changesVarName); server = server.AddMembers( MethodDeclaration(ParseTypeName("void"), "DeserializeChangesExtra") .AddParameterListParameters(Parameter(Identifier("c")).WithType(ParseTypeName("BatchStreamReader"))) .AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())); var resetBody = Block(); var startAnimationBody = Block(); var serverGetPropertyBody = Block(); var serverGetCompositionPropertyBody = Block(); var serializeMethodBody = SerializeChangesPrologue(cl); var deserializeMethodBody = DeserializeChangesPrologue(cl); var defaultsMethodBody = Block(ParseStatement("InitializeDefaultsExtra();")); foreach (var prop in cl.Properties) { var fieldName = PropertyBackingFieldName(prop); var propType = ParseTypeName(prop.Type); var filteredPropertyType = prop.Type.TrimEnd('?'); var isObject = _objects.Contains(filteredPropertyType); var isNullable = prop.Type.EndsWith("?"); bool isPassthrough = false; client = GenerateClientProperty(client, cl, prop, propType, isObject, isNullable); var animatedServer = prop.Animated; var serverPropertyType = ((isObject ? "Server" : "") + prop.Type); if (_manuals.TryGetValue(filteredPropertyType, out var manual)) { if (manual.Passthrough) { isPassthrough = true; serverPropertyType = prop.Type; } if (manual.ServerName != null) { serverPropertyType = manual.ServerName + (isNullable ? "?" : ""); } } if (animatedServer) { server = server.AddMembers( DeclareField(serverPropertyType, fieldName), PropertyDeclaration(ParseTypeName(serverPropertyType), prop.Name) .AddModifiers(SyntaxKind.PublicKeyword) .WithExpressionBody(ArrowExpressionClause( InvocationExpression(IdentifierName("GetAnimatedValue"), ArgumentList(SeparatedList(new[] { Argument(IdentifierName(CompositionPropertyField(prop))), Argument(null, Token(SyntaxKind.RefKeyword), IdentifierName(fieldName)) } ))))) .WithSemicolonToken(Semicolon()) ); } else { server = server .AddMembers(DeclareField(serverPropertyType, fieldName)) .AddMembers(PropertyDeclaration(ParseTypeName(serverPropertyType), prop.Name) .AddModifiers(SyntaxKind.PublicKeyword) .AddAccessorListAccessors( AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, Block(ReturnStatement( InvocationExpression(IdentifierName("GetValue"), ArgumentList(SeparatedList(new[] { Argument(IdentifierName(CompositionPropertyField(prop))), Argument(null, Token(SyntaxKind.RefKeyword), IdentifierName(fieldName)) } )))))), AccessorDeclaration(SyntaxKind.SetAccessorDeclaration, Block( ParseStatement("var changed = false;"), IfStatement(BinaryExpression(SyntaxKind.NotEqualsExpression, IdentifierName(fieldName), IdentifierName("value")), Block( ParseStatement("On" + prop.Name + "Changing();"), ParseStatement($"changed = true;")) ), ExpressionStatement(InvocationExpression(IdentifierName("SetValue"), ArgumentList(SeparatedList(new[] { Argument(IdentifierName(CompositionPropertyField(prop))), Argument(null, Token(SyntaxKind.OutKeyword), IdentifierName(fieldName)), Argument(IdentifierName("value")) } )))), ParseStatement($"if(changed) On" + prop.Name + "Changed();") )) )) .AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changed") .AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())) .AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changing") .AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())); } resetBody = resetBody.AddStatements( ExpressionStatement(InvocationExpression(MemberAccess(prop.Name, "Reset")))); serializeMethodBody = ApplySerializeField(serializeMethodBody, cl, prop, isObject, isPassthrough); deserializeMethodBody = ApplyDeserializeField(deserializeMethodBody, cl, prop, serverPropertyType, isObject); if (animatedServer) { startAnimationBody = ApplyStartAnimation(startAnimationBody, cl, prop); } serverGetPropertyBody = ApplyGetProperty(serverGetPropertyBody, prop); serverGetCompositionPropertyBody = ApplyGetProperty(serverGetCompositionPropertyBody, prop, CompositionPropertyField(prop)); server = server.AddMembers(DeclareField("CompositionProperty", CompositionPropertyField(prop), EqualsValueClause(ParseExpression("CompositionProperty.Register()")), SyntaxKind.InternalKeyword, SyntaxKind.StaticKeyword)); if (prop.DefaultValue != null) { defaultsMethodBody = defaultsMethodBody.AddStatements( ExpressionStatement( AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName(prop.Name), ParseExpression(prop.DefaultValue)))); } } if (cl.Properties.Count > 0) { server = server.AddMembers(((MethodDeclarationSyntax)ParseMemberDeclaration( $"protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt){{}}") !) .WithBody(ApplyDeserializeChangesEpilogue(deserializeMethodBody, cl))); server = server.AddMembers(MethodDeclaration(ParseTypeName("void"), "OnFieldsDeserialized") .WithParameterList(ParameterList(SingletonSeparatedList(Parameter(Identifier("changed")) .WithType(ParseTypeName(ChangedFieldsTypeName(cl)))))) .AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())); } client = client.AddMembers( MethodDeclaration(ParseTypeName("void"), "InitializeDefaults").WithBody(defaultsMethodBody)) .AddMembers( MethodDeclaration(ParseTypeName("void"), "InitializeDefaultsExtra") .AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())); if (cl.Properties.Count > 0) { serializeMethodBody = serializeMethodBody.AddStatements(SerializeChangesEpilogue(cl)); client = client.AddMembers(((MethodDeclarationSyntax)ParseMemberDeclaration( $"private protected override void SerializeChangesCore(BatchStreamWriter writer){{}}") !) .WithBody(serializeMethodBody)); } if (list != null) { client = AppendListProxy(list, client); } if (startAnimationBody.Statements.Count != 0) { client = WithStartAnimation(client, startAnimationBody); } server = WithGetPropertyForAnimation(server, serverGetPropertyBody); server = WithGetCompositionProperty(server, serverGetCompositionPropertyBody); if (cl.Implements.Count > 0) { foreach (var impl in cl.Implements) { client = client.WithBaseList(client.BaseList.AddTypes(SimpleBaseType(ParseTypeName(impl.Name)))); if (impl.ServerName != null) { server = server.WithBaseList( server.BaseList.AddTypes(SimpleBaseType(ParseTypeName(impl.ServerName)))); } client = client.AddMembers( ParseMemberDeclaration($"{impl.ServerName} {impl.Name}.Server => Server;")); } } SaveTo(unit.AddMembers(GenerateChangedFieldsEnum(cl)), "Transport", ChangedFieldsTypeName(cl) + ".generated.cs"); SaveTo(unit.AddMembers(clientNs.AddMembers(client)), cl.Name + ".generated.cs"); SaveTo(unit.AddMembers(serverNs.AddMembers(server)), "Server", "Server" + cl.Name + ".generated.cs"); }
ExpressionSyntax ClientProperty(GClass c, GProperty p) => MemberAccess(ServerName(c.Name), CompositionPropertyField(p));
string ChangedFieldsFieldName(GClass c) => "_changedFieldsOf" + c.Name;
string ChangedFieldsTypeName(GClass c) => c.Name + "ChangedFields";
private ClassDeclarationSyntax GenerateClientProperty(ClassDeclarationSyntax client, GClass cl, GProperty prop, TypeSyntax propType, bool isObject, bool isNullable) { var fieldName = PropertyBackingFieldName(prop); return(client .AddMembers(DeclareField(prop.Type, fieldName)) .AddMembers(PropertyDeclaration(propType, prop.Name) .AddModifiers(prop.Internal ? SyntaxKind.InternalKeyword : SyntaxKind.PublicKeyword) .AddAccessorListAccessors( AccessorDeclaration(SyntaxKind.GetAccessorDeclaration, Block(ReturnStatement(IdentifierName(fieldName)))), AccessorDeclaration(SyntaxKind.SetAccessorDeclaration, Block( ParseStatement("var changed = false;"), IfStatement(BinaryExpression(SyntaxKind.NotEqualsExpression, IdentifierName(fieldName), IdentifierName("value")), Block( ParseStatement("On" + prop.Name + "Changing();"), ParseStatement("changed = true;"), GeneratePropertySetterAssignment(cl, prop, isObject, isNullable)) ), ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName(fieldName), IdentifierName("value"))), ParseStatement($"if(changed) On" + prop.Name + "Changed();") )).WithModifiers(TokenList(prop.InternalSet ? new[] { Token(SyntaxKind.InternalKeyword) } : Array.Empty <SyntaxToken>())) )) .AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changed") .AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon())) .AddMembers(MethodDeclaration(ParseTypeName("void"), "On" + prop.Name + "Changing") .AddModifiers(SyntaxKind.PartialKeyword).WithSemicolonToken(Semicolon()))); }