123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- using System.Collections.Generic;
- 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();
- var signalDelegateSymbols = members
- .Where(s => s.Kind == SymbolKind.NamedType)
- .Cast<INamedTypeSymbol>()
- .Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate)
- .Where(s => s.GetAttributes()
- .Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false));
- List<GodotSignalDelegateData> godotSignalDelegates = new();
- foreach (var signalDelegateSymbol in signalDelegateSymbols)
- {
- if (!signalDelegateSymbol.Name.EndsWith(ScriptSignalsGenerator.SignalDelegateSuffix))
- continue;
- string signalName = signalDelegateSymbol.Name;
- signalName = signalName.Substring(0,
- signalName.Length - ScriptSignalsGenerator.SignalDelegateSuffix.Length);
- var invokeMethodData = signalDelegateSymbol
- .DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache);
- if (invokeMethodData == null)
- continue;
- godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value));
- }
- source.Append(
- " protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n");
- source.Append(" base.SaveGodotObjectData(info);\n");
- // Save properties
- foreach (var property in godotClassProperties)
- {
- string propertyName = property.PropertySymbol.Name;
- source.Append(" info.AddProperty(GodotInternal.PropName_")
- .Append(propertyName)
- .Append(", this.")
- .Append(propertyName)
- .Append(");\n");
- }
- // Save fields
- foreach (var field in godotClassFields)
- {
- string fieldName = field.FieldSymbol.Name;
- source.Append(" info.AddProperty(GodotInternal.PropName_")
- .Append(fieldName)
- .Append(", this.")
- .Append(fieldName)
- .Append(");\n");
- }
- // Save signal events
- foreach (var signalDelegate in godotSignalDelegates)
- {
- string signalName = signalDelegate.Name;
- source.Append(" info.AddSignalEventDelegate(GodotInternal.SignalName_")
- .Append(signalName)
- .Append(", this.backing_")
- .Append(signalName)
- .Append(");\n");
- }
- source.Append(" }\n");
- source.Append(
- " protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n");
- source.Append(" base.RestoreGodotObjectData(info);\n");
- // Restore properties
- 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");
- }
- // Restore fields
- 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");
- }
- // Restore signal events
- foreach (var signalDelegate in godotSignalDelegates)
- {
- string signalName = signalDelegate.Name;
- string signalDelegateQualifiedName = signalDelegate.DelegateSymbol.FullQualifiedName();
- source.Append(" if (info.TryGetSignalEventDelegate<")
- .Append(signalDelegateQualifiedName)
- .Append(">(GodotInternal.SignalName_")
- .Append(signalName)
- .Append(", out var _value_")
- .Append(signalName)
- .Append("))\n")
- .Append(" this.backing_")
- .Append(signalName)
- .Append(" = _value_")
- .Append(signalName)
- .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));
- }
- }
- }
|