Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 895163b

Browse files
committedJun 15, 2024·
Add GetNode Attribute for CSharp
1 parent e96ad5a commit 895163b

File tree

8 files changed

+296
-3
lines changed

8 files changed

+296
-3
lines changed
 

‎modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/AnalyzerReleases.Unshipped.md

+7
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,10 @@
33
Rule ID | Category | Severity | Notes
44
--------|----------|----------|--------------------
55
GD0003 | Usage | Error | ScriptPathAttributeGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0003.html)
6+
GD0501 | Usage | Error | ScriptMethodsGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0501.html)
7+
GD0502 | Usage | Error | ScriptMethodsGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0502.html)
8+
GD0503 | Usage | Error | ScriptMethodsGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0503.html)
9+
GD0504 | Usage | Error | ScriptMethodsGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0504.html)
10+
GD0505 | Usage | Error | ScriptMethodsGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0505.html)
11+
GD0506 | Usage | Error | ScriptMethodsGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0506.html)
12+
GD0507 | Usage | Error | ScriptMethodsGenerator, [Documentation](https://docs.godotengine.org/en/latest/tutorials/scripting/c_sharp/diagnostics/GD0507.html)

‎modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs

+70
Original file line numberDiff line numberDiff line change
@@ -186,5 +186,75 @@ public static partial class Common
186186
isEnabledByDefault: true,
187187
"The class must not be generic. Make the class non-generic, or remove the '[GlobalClass]' attribute.",
188188
helpLinkUri: string.Format(_helpLinkFormat, "GD0402"));
189+
190+
public static readonly DiagnosticDescriptor GetNodeMemberIsStaticRule =
191+
new DiagnosticDescriptor(id: "GD0501",
192+
title: "The GetNode member is static",
193+
messageFormat: "The GetNode member '{0}' is static",
194+
category: "Usage",
195+
DiagnosticSeverity.Error,
196+
isEnabledByDefault: true,
197+
"The GetNode member is static. Only instance fields and properties can be GetNode. Remove the 'static' modifier, or the '[GetNode]' attribute.",
198+
helpLinkUri: string.Format(_helpLinkFormat, "GD0501"));
199+
200+
public static readonly DiagnosticDescriptor GetNodeMemberIsReadOnlyRule =
201+
new DiagnosticDescriptor(id: "GD0502",
202+
title: "The GetNode member is read-only",
203+
messageFormat: "The GetNode member '{0}' is read-only",
204+
category: "Usage",
205+
DiagnosticSeverity.Error,
206+
isEnabledByDefault: true,
207+
"The GetNode member is read-only. GetNode member must be writable.",
208+
helpLinkUri: string.Format(_helpLinkFormat, "GD0501"));
209+
210+
public static readonly DiagnosticDescriptor GetNodeMemberIsIndexerRule =
211+
new DiagnosticDescriptor(id: "GD0503",
212+
title: "The GetNode property is an indexer",
213+
messageFormat: "The GetNode property '{0}' is an indexer",
214+
category: "Usage",
215+
DiagnosticSeverity.Error,
216+
isEnabledByDefault: true,
217+
"The GetNode property is an indexer. Remove the '[GetNode]' attribute.",
218+
helpLinkUri: string.Format(_helpLinkFormat, "GD0503"));
219+
220+
public static readonly DiagnosticDescriptor GetNodeMemberIsExplicitInterfaceImplementationRule =
221+
new DiagnosticDescriptor(id: "GD0504",
222+
title: "The GetNode property is an explicit interface implementation",
223+
messageFormat: "The GetNode property '{0}' is an explicit interface implementation",
224+
category: "Usage",
225+
DiagnosticSeverity.Error,
226+
isEnabledByDefault: true,
227+
"The GetNode property is an explicit interface implementation. Remove the '[GetNode]' attribute.",
228+
helpLinkUri: string.Format(_helpLinkFormat, "GD0504"));
229+
230+
public static readonly DiagnosticDescriptor GetNodeAttributePathIsEmpty =
231+
new DiagnosticDescriptor(id: "GD0505",
232+
title: "The GetNode attribute path is empty",
233+
messageFormat: "The GetNode attribute path for member '{0}' is empty",
234+
category: "Usage",
235+
DiagnosticSeverity.Error,
236+
isEnabledByDefault: true,
237+
"The GetNode attribute path is empty. Set the 'Path' parameter in the '[GetNode]' attribute.",
238+
helpLinkUri: string.Format(_helpLinkFormat, "GD0505"));
239+
240+
public static readonly DiagnosticDescriptor OnlyNodesShouldHaveGetNodeRule =
241+
new DiagnosticDescriptor(id: "GD0506",
242+
title: "Types not derived from Node should not have GetNode members",
243+
messageFormat: "Types not derived from Node should not have GetNode members",
244+
category: "Usage",
245+
DiagnosticSeverity.Error,
246+
isEnabledByDefault: true,
247+
"Types not derived from Node should not have GetNode members. GetNode is only supported in Node-derived classes.",
248+
helpLinkUri: string.Format(_helpLinkFormat, "GD0506"));
249+
250+
public static readonly DiagnosticDescriptor GetNodeMemberShouldBeNodes =
251+
new DiagnosticDescriptor(id: "GD0507",
252+
title: "The GetNode member type should derived from Node",
253+
messageFormat: "The GetNode member type for member '{0}' should derived from Node",
254+
category: "Usage",
255+
DiagnosticSeverity.Error,
256+
isEnabledByDefault: true,
257+
"The GetNode member type should derived from Node. Remove the '[GetNode]' attribute or use a member type deriving from Node.",
258+
helpLinkUri: string.Format(_helpLinkFormat, "GD0507"));
189259
}
190260
}

‎modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs

+3
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ public static bool IsGodotGlobalClassAttribute(this INamedTypeSymbol symbol)
264264
public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
265265
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.SystemFlagsAttr;
266266

267+
public static bool IsGodotGetNodeAttribute(this INamedTypeSymbol symbol)
268+
=> symbol.FullQualifiedNameOmitGlobal() == GodotClasses.GetNodeAttr;
269+
267270
public static GodotMethodData? HasGodotCompatibleSignature(
268271
this IMethodSymbol method,
269272
MarshalUtils.TypeCache typeCache

‎modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs

+1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ public static class GodotClasses
1414
public const string GodotClassNameAttr = "Godot.GodotClassNameAttribute";
1515
public const string GlobalClassAttr = "Godot.GlobalClassAttribute";
1616
public const string SystemFlagsAttr = "System.FlagsAttribute";
17+
public const string GetNodeAttr = "Godot.GetNodeAttribute";
1718
}
1819
}

‎modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs

+173-3
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ INamedTypeSymbol symbol
8282
bool hasNamespace = classNs.Length != 0;
8383

8484
bool isInnerClass = symbol.ContainingType != null;
85+
bool isNode = symbol.InheritsFrom("GodotSharp", GodotClasses.Node);
8586

8687
string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint()
8788
+ "_ScriptMethods.generated";
@@ -134,6 +135,27 @@ void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType)
134135
.Distinct(new MethodOverloadEqualityComparer())
135136
.ToArray();
136137

138+
List<ISymbol> getNode = new List<ISymbol>();
139+
getNode.AddRange(members.Where(s => s.Kind == SymbolKind.Property)
140+
.Cast<IPropertySymbol>()
141+
.Where(s => s.GetAttributes()
142+
.Any(a => a.AttributeClass?.IsGodotGetNodeAttribute() ?? false)));
143+
144+
getNode.AddRange(members.Where(s => s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
145+
.Cast<IFieldSymbol>()
146+
.Where(s => s.GetAttributes()
147+
.Any(a => a.AttributeClass?.IsGodotGetNodeAttribute() ?? false)));
148+
149+
150+
if (!isNode && getNode.Count > 0)
151+
{
152+
context.ReportDiagnostic(Diagnostic.Create(
153+
Common.OnlyNodesShouldHaveGetNodeRule,
154+
symbol.Locations.FirstLocationWithSourceTreeOrDefault()
155+
));
156+
getNode.Clear();
157+
}
158+
137159
source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
138160

139161
source.Append(" /// <summary>\n")
@@ -148,7 +170,10 @@ void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType)
148170
var distinctMethodNames = godotClassMethods
149171
.Select(m => m.Method.Name)
150172
.Distinct()
151-
.ToArray();
173+
.ToList();
174+
175+
if (getNode.Count > 0)
176+
distinctMethodNames.Add("_InitGodotGetNodeMembers");
152177

153178
foreach (string methodName in distinctMethodNames)
154179
{
@@ -205,13 +230,34 @@ void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType)
205230

206231
// Generate InvokeGodotClassMethod
207232

208-
if (godotClassMethods.Length > 0)
233+
if (getNode.Count > 0)
234+
{
235+
source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n");
236+
source.Append(" protected override void _InitGodotGetNodeMembers() {\n");
237+
source.Append(" base._InitGodotGetNodeMembers();\n");
238+
foreach (var member in getNode)
239+
{
240+
AppendGetNodeSetter(context, source, member);
241+
}
242+
source.Append(" }\n");
243+
}
244+
245+
if (godotClassMethods.Length > 0 || getNode.Count > 0)
209246
{
210247
source.Append(" /// <inheritdoc/>\n");
211248
source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n");
212249
source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, ");
213250
source.Append("NativeVariantPtrArgs args, out godot_variant ret)\n {\n");
214251

252+
if (getNode.Count > 0)
253+
{
254+
source.Append(" if(method == global::Godot.Node.MethodName._Ready && args.Count == 0) {\n");
255+
source.Append(" _InitGodotGetNodeMembers();\n");
256+
source.Append(" ret = default;\n");
257+
source.Append(" return true;\n");
258+
source.Append(" }\n");
259+
}
260+
215261
foreach (var method in godotClassMethods)
216262
{
217263
GenerateMethodInvoker(method, source);
@@ -247,7 +293,7 @@ void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType)
247293

248294
// Generate HasGodotClassMethod
249295

250-
if (distinctMethodNames.Length > 0)
296+
if (distinctMethodNames.Count > 0)
251297
{
252298
source.Append(" /// <inheritdoc/>\n");
253299
source.Append(" [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n");
@@ -285,6 +331,130 @@ void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType)
285331
context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
286332
}
287333

334+
private static void AppendGetNodeSetter(GeneratorExecutionContext context, StringBuilder source, ISymbol symbol)
335+
{
336+
var attr = symbol.GetAttributes()
337+
.Where(a => a.AttributeClass?.IsGodotGetNodeAttribute() ?? false)
338+
.First();
339+
340+
string path = attr.ConstructorArguments.Length >= 0 ? attr.ConstructorArguments[0].Value as string ?? string.Empty : string.Empty;
341+
if (string.IsNullOrEmpty(path))
342+
{
343+
context.ReportDiagnostic(Diagnostic.Create(
344+
Common.GetNodeAttributePathIsEmpty,
345+
symbol.Locations.FirstLocationWithSourceTreeOrDefault(),
346+
symbol.ToDisplayString()
347+
));
348+
return;
349+
}
350+
351+
ITypeSymbol type;
352+
if (symbol is IPropertySymbol)
353+
{
354+
var propertySymbol = (IPropertySymbol)symbol;
355+
type = propertySymbol.Type;
356+
357+
if (propertySymbol.IsStatic)
358+
{
359+
context.ReportDiagnostic(Diagnostic.Create(
360+
Common.GetNodeMemberIsStaticRule,
361+
symbol.Locations.FirstLocationWithSourceTreeOrDefault(),
362+
symbol.ToDisplayString()
363+
));
364+
return;
365+
}
366+
367+
if (propertySymbol.SetMethod == null || propertySymbol.SetMethod.IsInitOnly)
368+
{
369+
context.ReportDiagnostic(Diagnostic.Create(
370+
Common.GetNodeMemberIsReadOnlyRule,
371+
symbol.Locations.FirstLocationWithSourceTreeOrDefault(),
372+
symbol.ToDisplayString()
373+
));
374+
return;
375+
}
376+
377+
if (propertySymbol.IsIndexer)
378+
{
379+
context.ReportDiagnostic(Diagnostic.Create(
380+
Common.GetNodeMemberIsIndexerRule,
381+
propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
382+
propertySymbol.ToDisplayString()
383+
));
384+
return;
385+
}
386+
387+
if (propertySymbol.ExplicitInterfaceImplementations.Length > 0)
388+
{
389+
context.ReportDiagnostic(Diagnostic.Create(
390+
Common.GetNodeMemberIsExplicitInterfaceImplementationRule,
391+
propertySymbol.Locations.FirstLocationWithSourceTreeOrDefault(),
392+
propertySymbol.ToDisplayString()
393+
));
394+
return;
395+
}
396+
}
397+
else if (symbol is IFieldSymbol)
398+
{
399+
var fieldSymbol = (IFieldSymbol)symbol;
400+
type = fieldSymbol.Type;
401+
402+
if (fieldSymbol.IsStatic)
403+
{
404+
context.ReportDiagnostic(Diagnostic.Create(
405+
Common.GetNodeMemberIsStaticRule,
406+
symbol.Locations.FirstLocationWithSourceTreeOrDefault(),
407+
symbol.ToDisplayString()
408+
));
409+
return;
410+
}
411+
412+
if (fieldSymbol.IsReadOnly)
413+
{
414+
context.ReportDiagnostic(Diagnostic.Create(
415+
Common.GetNodeMemberIsReadOnlyRule,
416+
symbol.Locations.FirstLocationWithSourceTreeOrDefault(),
417+
symbol.ToDisplayString()
418+
));
419+
return;
420+
}
421+
422+
}
423+
else
424+
{
425+
//Should not happen.
426+
return;
427+
}
428+
429+
430+
if (!type.InheritsFrom("GodotSharp", GodotClasses.Node))
431+
{
432+
context.ReportDiagnostic(Diagnostic.Create(
433+
Common.GetNodeMemberShouldBeNodes,
434+
symbol.Locations.FirstLocationWithSourceTreeOrDefault(),
435+
symbol.ToDisplayString()
436+
));
437+
return;
438+
}
439+
440+
source.Append(" try\n");
441+
source.Append(" {\n");
442+
source.Append(" ")
443+
.Append(symbol.Name)
444+
.Append(" = GetNode<")
445+
.Append(type.Name)
446+
.Append(">(\"")
447+
.Append(attr.ConstructorArguments[0].Value)
448+
.Append("\");\n");
449+
source.Append(" }\n");
450+
source.Append(" catch (global::System.Exception e)\n");
451+
source.Append(" {\n");
452+
source.Append(" global::Godot.GD.PushError($\"Error GetNode for '{this.Name}.")
453+
.Append(symbol.Name)
454+
.Append("': {e.Message}\");\n");
455+
source.Append(" }\n");
456+
}
457+
288458
private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo)
289459
{
290460
source.Append(" methods.Add(new(name: MethodName.")

‎modules/mono/editor/bindings_generator.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) {
8787
#define CS_METHOD_INVOKE_GODOT_CLASS_METHOD "InvokeGodotClassMethod"
8888
#define CS_METHOD_HAS_GODOT_CLASS_METHOD "HasGodotClassMethod"
8989
#define CS_METHOD_HAS_GODOT_CLASS_SIGNAL "HasGodotClassSignal"
90+
#define CS_METHOD_INIT_GODOT_GET_NODE_MEMBERS "_InitGodotGetNodeMembers"
9091

9192
#define CS_STATIC_FIELD_NATIVE_CTOR "NativeCtor"
9293
#define CS_STATIC_FIELD_METHOD_BIND_PREFIX "MethodBind"
@@ -2431,6 +2432,21 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
24312432
}
24322433

24332434
output << INDENT1 "}\n";
2435+
2436+
if (itype.proxy_name == "Node") {
2437+
// Generate _InitGodotGetNodeMembers only for Node has the base method.
2438+
// If the [GetNode] attribute is used, the ScriptMethodsGenerator will generate an override for this method
2439+
// and put the assignations for GetNode property and fields into it.
2440+
output << MEMBER_BEGIN "/// <summary>\n"
2441+
<< INDENT1 "/// Initialize the properties and fields based on [GetNode] attribute.\n"
2442+
<< INDENT1 "/// This method is used internally by Godot.\n"
2443+
<< INDENT1 "/// Do not call or override this method.\n"
2444+
<< INDENT1 "/// </summary>\n";
2445+
2446+
output << INDENT1 "protected internal virtual void " CS_METHOD_INIT_GODOT_GET_NODE_MEMBERS "()\n"
2447+
<< INDENT1 "{\n";
2448+
output << INDENT1 "}\n";
2449+
}
24342450
}
24352451

24362452
//Generate StringName for all class members
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
3+
namespace Godot
4+
{
5+
/// <summary>
6+
/// Define a resource to populate with a GetNode before _Ready.
7+
/// </summary>
8+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
9+
public sealed class GetNodeAttribute : Attribute
10+
{
11+
/// <summary>
12+
/// Path of the resource in the scene.
13+
/// </summary>
14+
public string Path { get; }
15+
16+
/// <summary>
17+
/// Constructs a new GetNodeAttribute Instance.
18+
/// </summary>
19+
/// <param name="path">Path of the resource in the scene.</param>
20+
public GetNodeAttribute(string path)
21+
{
22+
Path = path;
23+
}
24+
}
25+
}

‎modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
<!-- Sources -->
4949
<ItemGroup>
5050
<Compile Include="Core\Aabb.cs" />
51+
<Compile Include="Core\Attributes\GetNodeAttribute.cs" />
5152
<Compile Include="Core\Bridge\GodotSerializationInfo.cs" />
5253
<Compile Include="Core\Bridge\MethodInfo.cs" />
5354
<Compile Include="Core\Callable.generics.cs" />

0 commit comments

Comments
 (0)
Please sign in to comment.