mirror of
				https://github.com/godotengine/godot.git
				synced 2025-11-03 23:21:15 +00:00 
			
		
		
		
	
		
			
	
	
		
			218 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			218 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| 
								 | 
							
								using System.Linq;
							 | 
						||
| 
								 | 
							
								using System.Text;
							 | 
						||
| 
								 | 
							
								using Microsoft.CodeAnalysis;
							 | 
						||
| 
								 | 
							
								using Microsoft.CodeAnalysis.CSharp.Syntax;
							 | 
						||
| 
								 | 
							
								using Microsoft.CodeAnalysis.Text;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								namespace Godot.SourceGenerators
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								    [Generator]
							 | 
						||
| 
								 | 
							
								    public class ScriptSerializationGenerator : ISourceGenerator
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        public void Initialize(GeneratorInitializationContext context)
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        public void Execute(GeneratorExecutionContext context)
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								            if (context.AreGodotSourceGeneratorsDisabled())
							 | 
						||
| 
								 | 
							
								                return;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            INamedTypeSymbol[] godotClasses = context
							 | 
						||
| 
								 | 
							
								                .Compilation.SyntaxTrees
							 | 
						||
| 
								 | 
							
								                .SelectMany(tree =>
							 | 
						||
| 
								 | 
							
								                    tree.GetRoot().DescendantNodes()
							 | 
						||
| 
								 | 
							
								                        .OfType<ClassDeclarationSyntax>()
							 | 
						||
| 
								 | 
							
								                        .SelectGodotScriptClasses(context.Compilation)
							 | 
						||
| 
								 | 
							
								                        // Report and skip non-partial classes
							 | 
						||
| 
								 | 
							
								                        .Where(x =>
							 | 
						||
| 
								 | 
							
								                        {
							 | 
						||
| 
								 | 
							
								                            if (x.cds.IsPartial())
							 | 
						||
| 
								 | 
							
								                            {
							 | 
						||
| 
								 | 
							
								                                if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial))
							 | 
						||
| 
								 | 
							
								                                {
							 | 
						||
| 
								 | 
							
								                                    Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!);
							 | 
						||
| 
								 | 
							
								                                    return false;
							 | 
						||
| 
								 | 
							
								                                }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                                return true;
							 | 
						||
| 
								 | 
							
								                            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                            Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
							 | 
						||
| 
								 | 
							
								                            return false;
							 | 
						||
| 
								 | 
							
								                        })
							 | 
						||
| 
								 | 
							
								                        .Select(x => x.symbol)
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								                .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
							 | 
						||
| 
								 | 
							
								                .ToArray();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if (godotClasses.Length > 0)
							 | 
						||
| 
								 | 
							
								            {
							 | 
						||
| 
								 | 
							
								                var typeCache = new MarshalUtils.TypeCache(context);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                foreach (var godotClass in godotClasses)
							 | 
						||
| 
								 | 
							
								                {
							 | 
						||
| 
								 | 
							
								                    VisitGodotScriptClass(context, typeCache, godotClass);
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        private static void VisitGodotScriptClass(
							 | 
						||
| 
								 | 
							
								            GeneratorExecutionContext context,
							 | 
						||
| 
								 | 
							
								            MarshalUtils.TypeCache typeCache,
							 | 
						||
| 
								 | 
							
								            INamedTypeSymbol symbol
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								            INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
							 | 
						||
| 
								 | 
							
								            string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
							 | 
						||
| 
								 | 
							
								                namespaceSymbol.FullQualifiedName() :
							 | 
						||
| 
								 | 
							
								                string.Empty;
							 | 
						||
| 
								 | 
							
								            bool hasNamespace = classNs.Length != 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            bool isInnerClass = symbol.ContainingType != null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
							 | 
						||
| 
								 | 
							
								                                + "_ScriptSerialization_Generated";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            var source = new StringBuilder();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            source.Append("using Godot;\n");
							 | 
						||
| 
								 | 
							
								            source.Append("using Godot.NativeInterop;\n");
							 | 
						||
| 
								 | 
							
								            source.Append("\n");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if (hasNamespace)
							 | 
						||
| 
								 | 
							
								            {
							 | 
						||
| 
								 | 
							
								                source.Append("namespace ");
							 | 
						||
| 
								 | 
							
								                source.Append(classNs);
							 | 
						||
| 
								 | 
							
								                source.Append(" {\n\n");
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if (isInnerClass)
							 | 
						||
| 
								 | 
							
								            {
							 | 
						||
| 
								 | 
							
								                var containingType = symbol.ContainingType;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                while (containingType != null)
							 | 
						||
| 
								 | 
							
								                {
							 | 
						||
| 
								 | 
							
								                    source.Append("partial ");
							 | 
						||
| 
								 | 
							
								                    source.Append(containingType.GetDeclarationKeyword());
							 | 
						||
| 
								 | 
							
								                    source.Append(" ");
							 | 
						||
| 
								 | 
							
								                    source.Append(containingType.NameWithTypeParameters());
							 | 
						||
| 
								 | 
							
								                    source.Append("\n{\n");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                    containingType = containingType.ContainingType;
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            source.Append("partial class ");
							 | 
						||
| 
								 | 
							
								            source.Append(symbol.NameWithTypeParameters());
							 | 
						||
| 
								 | 
							
								            source.Append("\n{\n");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            var members = symbol.GetMembers();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            var propertySymbols = members
							 | 
						||
| 
								 | 
							
								                .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
							 | 
						||
| 
								 | 
							
								                .Cast<IPropertySymbol>();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            var fieldSymbols = members
							 | 
						||
| 
								 | 
							
								                .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
							 | 
						||
| 
								 | 
							
								                .Cast<IFieldSymbol>();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
							 | 
						||
| 
								 | 
							
								            var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            source.Append(
							 | 
						||
| 
								 | 
							
								                "    protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n    {\n");
							 | 
						||
| 
								 | 
							
								            source.Append("        base.SaveGodotObjectData(info);\n");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            foreach (var property in godotClassProperties)
							 | 
						||
| 
								 | 
							
								            {
							 | 
						||
| 
								 | 
							
								                string propertyName = property.PropertySymbol.Name;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                source.Append("        info.AddProperty(GodotInternal.PropName_")
							 | 
						||
| 
								 | 
							
								                    .Append(propertyName)
							 | 
						||
| 
								 | 
							
								                    .Append(", this.")
							 | 
						||
| 
								 | 
							
								                    .Append(propertyName)
							 | 
						||
| 
								 | 
							
								                    .Append(");\n");
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            foreach (var field in godotClassFields)
							 | 
						||
| 
								 | 
							
								            {
							 | 
						||
| 
								 | 
							
								                string fieldName = field.FieldSymbol.Name;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                source.Append("        info.AddProperty(GodotInternal.PropName_")
							 | 
						||
| 
								 | 
							
								                    .Append(fieldName)
							 | 
						||
| 
								 | 
							
								                    .Append(", this.")
							 | 
						||
| 
								 | 
							
								                    .Append(fieldName)
							 | 
						||
| 
								 | 
							
								                    .Append(");\n");
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            source.Append("    }\n");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            source.Append(
							 | 
						||
| 
								 | 
							
								                "    protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n    {\n");
							 | 
						||
| 
								 | 
							
								            source.Append("        base.RestoreGodotObjectData(info);\n");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            foreach (var property in godotClassProperties)
							 | 
						||
| 
								 | 
							
								            {
							 | 
						||
| 
								 | 
							
								                string propertyName = property.PropertySymbol.Name;
							 | 
						||
| 
								 | 
							
								                string propertyTypeQualifiedName = property.PropertySymbol.Type.FullQualifiedName();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                source.Append("        if (info.TryGetProperty<")
							 | 
						||
| 
								 | 
							
								                    .Append(propertyTypeQualifiedName)
							 | 
						||
| 
								 | 
							
								                    .Append(">(GodotInternal.PropName_")
							 | 
						||
| 
								 | 
							
								                    .Append(propertyName)
							 | 
						||
| 
								 | 
							
								                    .Append(", out var _value_")
							 | 
						||
| 
								 | 
							
								                    .Append(propertyName)
							 | 
						||
| 
								 | 
							
								                    .Append("))\n")
							 | 
						||
| 
								 | 
							
								                    .Append("            this.")
							 | 
						||
| 
								 | 
							
								                    .Append(propertyName)
							 | 
						||
| 
								 | 
							
								                    .Append(" = _value_")
							 | 
						||
| 
								 | 
							
								                    .Append(propertyName)
							 | 
						||
| 
								 | 
							
								                    .Append(";\n");
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            foreach (var field in godotClassFields)
							 | 
						||
| 
								 | 
							
								            {
							 | 
						||
| 
								 | 
							
								                string fieldName = field.FieldSymbol.Name;
							 | 
						||
| 
								 | 
							
								                string fieldTypeQualifiedName = field.FieldSymbol.Type.FullQualifiedName();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                source.Append("        if (info.TryGetProperty<")
							 | 
						||
| 
								 | 
							
								                    .Append(fieldTypeQualifiedName)
							 | 
						||
| 
								 | 
							
								                    .Append(">(GodotInternal.PropName_")
							 | 
						||
| 
								 | 
							
								                    .Append(fieldName)
							 | 
						||
| 
								 | 
							
								                    .Append(", out var _value_")
							 | 
						||
| 
								 | 
							
								                    .Append(fieldName)
							 | 
						||
| 
								 | 
							
								                    .Append("))\n")
							 | 
						||
| 
								 | 
							
								                    .Append("            this.")
							 | 
						||
| 
								 | 
							
								                    .Append(fieldName)
							 | 
						||
| 
								 | 
							
								                    .Append(" = _value_")
							 | 
						||
| 
								 | 
							
								                    .Append(fieldName)
							 | 
						||
| 
								 | 
							
								                    .Append(";\n");
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            source.Append("    }\n");
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            source.Append("}\n"); // partial class
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if (isInnerClass)
							 | 
						||
| 
								 | 
							
								            {
							 | 
						||
| 
								 | 
							
								                var containingType = symbol.ContainingType;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                while (containingType != null)
							 | 
						||
| 
								 | 
							
								                {
							 | 
						||
| 
								 | 
							
								                    source.Append("}\n"); // outer class
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                    containingType = containingType.ContainingType;
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if (hasNamespace)
							 | 
						||
| 
								 | 
							
								            {
							 | 
						||
| 
								 | 
							
								                source.Append("\n}\n");
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |