2022-07-28 17:41:47 +02:00
using System.Collections.Generic ;
2022-05-28 04:56:46 +02:00
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 )
{
2023-01-08 02:04:15 +01:00
if ( context . IsGodotSourceGeneratorDisabled ( "ScriptSerialization" ) )
2022-05-28 04:56:46 +02:00
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 )
{
2022-08-15 05:57:52 +02:00
var typeCache = new MarshalUtils . TypeCache ( context . Compilation ) ;
2022-05-28 04:56:46 +02:00
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 ?
2022-11-24 01:04:15 +01:00
namespaceSymbol . FullQualifiedNameOmitGlobal ( ) :
2022-05-28 04:56:46 +02:00
string . Empty ;
bool hasNamespace = classNs . Length ! = 0 ;
bool isInnerClass = symbol . ContainingType ! = null ;
2022-11-24 01:04:15 +01:00
string uniqueHint = symbol . FullQualifiedNameOmitGlobal ( ) . SanitizeQualifiedNameForUniqueHint ( )
2022-10-22 23:13:52 +02:00
+ "_ScriptSerialization.generated" ;
2022-05-28 04:56:46 +02:00
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 ;
2023-10-18 03:25:24 +02:00
AppendPartialContainingTypeDeclarations ( containingType ) ;
2022-05-28 04:56:46 +02:00
2023-10-18 03:25:24 +02:00
void AppendPartialContainingTypeDeclarations ( INamedTypeSymbol ? containingType )
2022-05-28 04:56:46 +02:00
{
2023-10-18 03:25:24 +02:00
if ( containingType = = null )
return ;
AppendPartialContainingTypeDeclarations ( containingType . ContainingType ) ;
2022-05-28 04:56:46 +02:00
source . Append ( "partial " ) ;
source . Append ( containingType . GetDeclarationKeyword ( ) ) ;
source . Append ( " " ) ;
source . Append ( containingType . NameWithTypeParameters ( ) ) ;
source . Append ( "\n{\n" ) ;
}
}
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 )
2022-08-28 18:16:57 +02:00
. Cast < IPropertySymbol > ( )
2023-03-04 19:16:48 +01:00
. Where ( s = > ! s . IsIndexer & & s . ExplicitInterfaceImplementations . Length = = 0 ) ;
2022-05-28 04:56:46 +02:00
var fieldSymbols = members
. Where ( s = > ! s . IsStatic & & s . Kind = = SymbolKind . Field & & ! s . IsImplicitlyDeclared )
. Cast < IFieldSymbol > ( ) ;
2023-08-14 00:35:10 +02:00
// TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
// Ignore properties without a getter, without a setter or with an init-only setter. Godot properties must be both readable and writable.
var godotClassProperties = propertySymbols . Where ( property = > ! ( property . IsReadOnly | | property . IsWriteOnly | | property . SetMethod ! . IsInitOnly ) )
. WhereIsGodotCompatibleType ( typeCache )
. ToArray ( ) ;
var godotClassFields = fieldSymbols . Where ( property = > ! property . IsReadOnly )
. WhereIsGodotCompatibleType ( typeCache )
. ToArray ( ) ;
2022-05-28 04:56:46 +02:00
2022-07-28 17:41:47 +02:00
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 ) ) ;
}
2023-07-09 14:14:36 +02:00
source . Append ( " /// <inheritdoc/>\n" ) ;
source . Append ( " [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n" ) ;
2022-05-28 04:56:46 +02:00
source . Append (
" protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n" ) ;
source . Append ( " base.SaveGodotObjectData(info);\n" ) ;
2022-07-28 17:41:47 +02:00
// Save properties
2022-05-28 04:56:46 +02:00
foreach ( var property in godotClassProperties )
{
string propertyName = property . PropertySymbol . Name ;
2022-09-06 14:43:40 +02:00
source . Append ( " info.AddProperty(PropertyName." )
2022-05-28 04:56:46 +02:00
. Append ( propertyName )
2022-07-28 17:41:50 +02:00
. Append ( ", " )
2022-12-01 01:45:11 +01:00
. AppendManagedToVariantExpr ( string . Concat ( "this." , propertyName ) ,
property . PropertySymbol . Type , property . Type )
2022-05-28 04:56:46 +02:00
. Append ( ");\n" ) ;
}
2022-07-28 17:41:47 +02:00
// Save fields
2022-05-28 04:56:46 +02:00
foreach ( var field in godotClassFields )
{
string fieldName = field . FieldSymbol . Name ;
2022-09-06 14:43:40 +02:00
source . Append ( " info.AddProperty(PropertyName." )
2022-05-28 04:56:46 +02:00
. Append ( fieldName )
2022-07-28 17:41:50 +02:00
. Append ( ", " )
2022-12-01 01:45:11 +01:00
. AppendManagedToVariantExpr ( string . Concat ( "this." , fieldName ) ,
field . FieldSymbol . Type , field . Type )
2022-05-28 04:56:46 +02:00
. Append ( ");\n" ) ;
}
2022-07-28 17:41:47 +02:00
// Save signal events
foreach ( var signalDelegate in godotSignalDelegates )
{
string signalName = signalDelegate . Name ;
2022-09-06 14:43:40 +02:00
source . Append ( " info.AddSignalEventDelegate(SignalName." )
2022-07-28 17:41:47 +02:00
. Append ( signalName )
. Append ( ", this.backing_" )
. Append ( signalName )
. Append ( ");\n" ) ;
}
2022-05-28 04:56:46 +02:00
source . Append ( " }\n" ) ;
2023-07-09 14:14:36 +02:00
source . Append ( " /// <inheritdoc/>\n" ) ;
source . Append ( " [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n" ) ;
2022-05-28 04:56:46 +02:00
source . Append (
" protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n" ) ;
source . Append ( " base.RestoreGodotObjectData(info);\n" ) ;
2022-07-28 17:41:47 +02:00
// Restore properties
2022-05-28 04:56:46 +02:00
foreach ( var property in godotClassProperties )
{
string propertyName = property . PropertySymbol . Name ;
2022-09-06 14:43:40 +02:00
source . Append ( " if (info.TryGetProperty(PropertyName." )
2022-05-28 04:56:46 +02:00
. Append ( propertyName )
. Append ( ", out var _value_" )
. Append ( propertyName )
. Append ( "))\n" )
. Append ( " this." )
. Append ( propertyName )
2022-07-28 17:41:50 +02:00
. Append ( " = " )
. AppendVariantToManagedExpr ( string . Concat ( "_value_" , propertyName ) ,
property . PropertySymbol . Type , property . Type )
2022-05-28 04:56:46 +02:00
. Append ( ";\n" ) ;
}
2022-07-28 17:41:47 +02:00
// Restore fields
2022-05-28 04:56:46 +02:00
foreach ( var field in godotClassFields )
{
string fieldName = field . FieldSymbol . Name ;
2022-09-06 14:43:40 +02:00
source . Append ( " if (info.TryGetProperty(PropertyName." )
2022-05-28 04:56:46 +02:00
. Append ( fieldName )
. Append ( ", out var _value_" )
. Append ( fieldName )
. Append ( "))\n" )
. Append ( " this." )
. Append ( fieldName )
2022-07-28 17:41:50 +02:00
. Append ( " = " )
. AppendVariantToManagedExpr ( string . Concat ( "_value_" , fieldName ) ,
field . FieldSymbol . Type , field . Type )
2022-05-28 04:56:46 +02:00
. Append ( ";\n" ) ;
}
2022-07-28 17:41:47 +02:00
// Restore signal events
foreach ( var signalDelegate in godotSignalDelegates )
{
string signalName = signalDelegate . Name ;
2022-11-24 01:04:15 +01:00
string signalDelegateQualifiedName = signalDelegate . DelegateSymbol . FullQualifiedNameIncludeGlobal ( ) ;
2022-07-28 17:41:47 +02:00
source . Append ( " if (info.TryGetSignalEventDelegate<" )
. Append ( signalDelegateQualifiedName )
2022-09-06 14:43:40 +02:00
. Append ( ">(SignalName." )
2022-07-28 17:41:47 +02:00
. Append ( signalName )
. Append ( ", out var _value_" )
. Append ( signalName )
. Append ( "))\n" )
. Append ( " this.backing_" )
. Append ( signalName )
. Append ( " = _value_" )
. Append ( signalName )
. Append ( ";\n" ) ;
}
2022-05-28 04:56:46 +02:00
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 ) ) ;
}
}
}