ScriptMemberInvokerGenerator.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. using System.Linq;
  2. using System.Text;
  3. using Microsoft.CodeAnalysis;
  4. using Microsoft.CodeAnalysis.CSharp.Syntax;
  5. using Microsoft.CodeAnalysis.Text;
  6. namespace Godot.SourceGenerators
  7. {
  8. [Generator]
  9. public class ScriptMemberInvokerGenerator : ISourceGenerator
  10. {
  11. public void Execute(GeneratorExecutionContext context)
  12. {
  13. if (context.AreGodotSourceGeneratorsDisabled())
  14. return;
  15. INamedTypeSymbol[] godotClasses = context
  16. .Compilation.SyntaxTrees
  17. .SelectMany(tree =>
  18. tree.GetRoot().DescendantNodes()
  19. .OfType<ClassDeclarationSyntax>()
  20. .SelectGodotScriptClasses(context.Compilation)
  21. // Report and skip non-partial classes
  22. .Where(x =>
  23. {
  24. if (x.cds.IsPartial())
  25. {
  26. if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial))
  27. {
  28. Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!);
  29. return false;
  30. }
  31. return true;
  32. }
  33. Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
  34. return false;
  35. })
  36. .Select(x => x.symbol)
  37. )
  38. .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
  39. .ToArray();
  40. if (godotClasses.Length > 0)
  41. {
  42. var typeCache = new MarshalUtils.TypeCache(context);
  43. foreach (var godotClass in godotClasses)
  44. {
  45. VisitGodotScriptClass(context, typeCache, godotClass);
  46. }
  47. }
  48. }
  49. private static void VisitGodotScriptClass(
  50. GeneratorExecutionContext context,
  51. MarshalUtils.TypeCache typeCache,
  52. INamedTypeSymbol symbol
  53. )
  54. {
  55. INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
  56. string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
  57. namespaceSymbol.FullQualifiedName() :
  58. string.Empty;
  59. bool hasNamespace = classNs.Length != 0;
  60. bool isInnerClass = symbol.ContainingType != null;
  61. string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
  62. + "_ScriptMemberInvoker_Generated";
  63. var source = new StringBuilder();
  64. source.Append("using Godot;\n");
  65. source.Append("using Godot.NativeInterop;\n");
  66. source.Append("\n");
  67. if (hasNamespace)
  68. {
  69. source.Append("namespace ");
  70. source.Append(classNs);
  71. source.Append(" {\n\n");
  72. }
  73. if (isInnerClass)
  74. {
  75. var containingType = symbol.ContainingType;
  76. while (containingType != null)
  77. {
  78. source.Append("partial ");
  79. source.Append(containingType.GetDeclarationKeyword());
  80. source.Append(" ");
  81. source.Append(containingType.NameWithTypeParameters());
  82. source.Append("\n{\n");
  83. containingType = containingType.ContainingType;
  84. }
  85. }
  86. source.Append("partial class ");
  87. source.Append(symbol.NameWithTypeParameters());
  88. source.Append("\n{\n");
  89. var members = symbol.GetMembers();
  90. var methodSymbols = members
  91. .Where(s => !s.IsStatic && s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared)
  92. .Cast<IMethodSymbol>()
  93. .Where(m => m.MethodKind == MethodKind.Ordinary);
  94. var propertySymbols = members
  95. .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
  96. .Cast<IPropertySymbol>();
  97. var fieldSymbols = members
  98. .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
  99. .Cast<IFieldSymbol>();
  100. var godotClassMethods = methodSymbols.WhereHasGodotCompatibleSignature(typeCache).ToArray();
  101. var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
  102. var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
  103. source.Append(" private partial class GodotInternal {\n");
  104. // Generate cached StringNames for methods and properties, for fast lookup
  105. // TODO: Move the generation of these cached StringNames to its own generator
  106. foreach (var method in godotClassMethods)
  107. {
  108. string methodName = method.Method.Name;
  109. source.Append(" public static readonly StringName MethodName_");
  110. source.Append(methodName);
  111. source.Append(" = \"");
  112. source.Append(methodName);
  113. source.Append("\";\n");
  114. }
  115. source.Append(" }\n"); // class GodotInternal
  116. // Generate InvokeGodotClassMethod
  117. if (godotClassMethods.Length > 0)
  118. {
  119. source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, ");
  120. source.Append("NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n {\n");
  121. foreach (var method in godotClassMethods)
  122. {
  123. GenerateMethodInvoker(method, source);
  124. }
  125. source.Append(" return base.InvokeGodotClassMethod(method, args, argCount, out ret);\n");
  126. source.Append(" }\n");
  127. }
  128. // Generate Set/GetGodotClassPropertyValue
  129. if (godotClassProperties.Length > 0 || godotClassFields.Length > 0)
  130. {
  131. bool isFirstEntry;
  132. // Setters
  133. bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.FieldSymbol.IsReadOnly) &&
  134. godotClassProperties.All(pi => pi.PropertySymbol.IsReadOnly);
  135. if (!allPropertiesAreReadOnly)
  136. {
  137. source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, ");
  138. source.Append("in godot_variant value)\n {\n");
  139. isFirstEntry = true;
  140. foreach (var property in godotClassProperties)
  141. {
  142. if (property.PropertySymbol.IsReadOnly)
  143. continue;
  144. GeneratePropertySetter(property.PropertySymbol.Name,
  145. property.PropertySymbol.Type, property.Type, source, isFirstEntry);
  146. isFirstEntry = false;
  147. }
  148. foreach (var field in godotClassFields)
  149. {
  150. if (field.FieldSymbol.IsReadOnly)
  151. continue;
  152. GeneratePropertySetter(field.FieldSymbol.Name,
  153. field.FieldSymbol.Type, field.Type, source, isFirstEntry);
  154. isFirstEntry = false;
  155. }
  156. source.Append(" return base.SetGodotClassPropertyValue(name, value);\n");
  157. source.Append(" }\n");
  158. }
  159. // Getters
  160. source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, ");
  161. source.Append("out godot_variant value)\n {\n");
  162. isFirstEntry = true;
  163. foreach (var property in godotClassProperties)
  164. {
  165. GeneratePropertyGetter(property.PropertySymbol.Name,
  166. property.Type, source, isFirstEntry);
  167. isFirstEntry = false;
  168. }
  169. foreach (var field in godotClassFields)
  170. {
  171. GeneratePropertyGetter(field.FieldSymbol.Name,
  172. field.Type, source, isFirstEntry);
  173. isFirstEntry = false;
  174. }
  175. source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n");
  176. source.Append(" }\n");
  177. }
  178. // Generate HasGodotClassMethod
  179. if (godotClassMethods.Length > 0)
  180. {
  181. source.Append(" protected override bool HasGodotClassMethod(in godot_string_name method)\n {\n");
  182. bool isFirstEntry = true;
  183. foreach (var method in godotClassMethods)
  184. {
  185. GenerateHasMethodEntry(method, source, isFirstEntry);
  186. isFirstEntry = false;
  187. }
  188. source.Append(" return base.HasGodotClassMethod(method);\n");
  189. source.Append(" }\n");
  190. }
  191. source.Append("}\n"); // partial class
  192. if (isInnerClass)
  193. {
  194. var containingType = symbol.ContainingType;
  195. while (containingType != null)
  196. {
  197. source.Append("}\n"); // outer class
  198. containingType = containingType.ContainingType;
  199. }
  200. }
  201. if (hasNamespace)
  202. {
  203. source.Append("\n}\n");
  204. }
  205. context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
  206. }
  207. private static void GenerateMethodInvoker(
  208. GodotMethodData method,
  209. StringBuilder source
  210. )
  211. {
  212. string methodName = method.Method.Name;
  213. source.Append(" if (method == GodotInternal.MethodName_");
  214. source.Append(methodName);
  215. source.Append(" && argCount == ");
  216. source.Append(method.ParamTypes.Length);
  217. source.Append(") {\n");
  218. if (method.RetType != null)
  219. source.Append(" var callRet = ");
  220. else
  221. source.Append(" ");
  222. source.Append(methodName);
  223. source.Append("(");
  224. for (int i = 0; i < method.ParamTypes.Length; i++)
  225. {
  226. if (i != 0)
  227. source.Append(", ");
  228. source.AppendVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"),
  229. method.ParamTypeSymbols[i], method.ParamTypes[i]);
  230. }
  231. source.Append(");\n");
  232. if (method.RetType != null)
  233. {
  234. source.Append(" ret = ");
  235. source.AppendManagedToVariantExpr("callRet", method.RetType.Value);
  236. source.Append(";\n");
  237. source.Append(" return true;\n");
  238. }
  239. else
  240. {
  241. source.Append(" ret = default;\n");
  242. source.Append(" return true;\n");
  243. }
  244. source.Append(" }\n");
  245. }
  246. private static void GeneratePropertySetter(
  247. string propertyMemberName,
  248. ITypeSymbol propertyTypeSymbol,
  249. MarshalType propertyMarshalType,
  250. StringBuilder source,
  251. bool isFirstEntry
  252. )
  253. {
  254. source.Append(" ");
  255. if (!isFirstEntry)
  256. source.Append("else ");
  257. source.Append("if (name == GodotInternal.PropName_")
  258. .Append(propertyMemberName)
  259. .Append(") {\n")
  260. .Append(" ")
  261. .Append(propertyMemberName)
  262. .Append(" = ")
  263. .AppendVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType)
  264. .Append(";\n")
  265. .Append(" return true;\n")
  266. .Append(" }\n");
  267. }
  268. private static void GeneratePropertyGetter(
  269. string propertyMemberName,
  270. MarshalType propertyMarshalType,
  271. StringBuilder source,
  272. bool isFirstEntry
  273. )
  274. {
  275. source.Append(" ");
  276. if (!isFirstEntry)
  277. source.Append("else ");
  278. source.Append("if (name == GodotInternal.PropName_")
  279. .Append(propertyMemberName)
  280. .Append(") {\n")
  281. .Append(" value = ")
  282. .AppendManagedToVariantExpr(propertyMemberName, propertyMarshalType)
  283. .Append(";\n")
  284. .Append(" return true;\n")
  285. .Append(" }\n");
  286. }
  287. private static void GenerateHasMethodEntry(
  288. GodotMethodData method,
  289. StringBuilder source,
  290. bool isFirstEntry
  291. )
  292. {
  293. string methodName = method.Method.Name;
  294. source.Append(" ");
  295. if (!isFirstEntry)
  296. source.Append("else ");
  297. source.Append("if (method == GodotInternal.MethodName_");
  298. source.Append(methodName);
  299. source.Append(") {\n return true;\n }\n");
  300. }
  301. public void Initialize(GeneratorInitializationContext context)
  302. {
  303. }
  304. }
  305. }