217 lines
7.8 KiB
C#
217 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));
|
|
}
|
|
}
|
|
}
|