瀏覽代碼

Merge pull request #68233 from raulsntos/dotnet/raise-events

C#: Generate strongly-typed method to raise signal events and fix event accessibility
Rémi Verschelde 10 月之前
父節點
當前提交
543fa16b4c

+ 4 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs

@@ -32,6 +32,10 @@ partial class EventSignals
         add => backing_MySignal += value;
         remove => backing_MySignal -= value;
 }
+    protected void OnMySignal(string str, int num)
+    {
+        EmitSignal(SignalName.MySignal, str, num);
+    }
     /// <inheritdoc/>
     [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
     protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, NativeVariantPtrArgs args)

+ 26 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs

@@ -155,6 +155,32 @@ namespace Godot.SourceGenerators
             };
         }
 
+        public static string GetAccessibilityKeyword(this INamedTypeSymbol namedTypeSymbol)
+        {
+            if (namedTypeSymbol.DeclaredAccessibility == Accessibility.NotApplicable)
+            {
+                // Accessibility not specified. Get the default accessibility.
+                return namedTypeSymbol.ContainingSymbol switch
+                {
+                    null or INamespaceSymbol => "internal",
+                    ITypeSymbol { TypeKind: TypeKind.Class or TypeKind.Struct } => "private",
+                    ITypeSymbol { TypeKind: TypeKind.Interface } => "public",
+                    _ => "",
+                };
+            }
+
+            return namedTypeSymbol.DeclaredAccessibility switch
+            {
+                Accessibility.Private => "private",
+                Accessibility.Protected => "protected",
+                Accessibility.Internal => "internal",
+                Accessibility.ProtectedAndInternal => "private",
+                Accessibility.ProtectedOrInternal => "private",
+                Accessibility.Public => "public",
+                _ => "",
+            };
+        }
+
         public static string NameWithTypeParameters(this INamedTypeSymbol symbol)
         {
             return symbol.IsGenericType ?

+ 38 - 8
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs

@@ -5,13 +5,6 @@ using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
 using Microsoft.CodeAnalysis.Text;
 
-// TODO:
-//   Determine a proper way to emit the signal.
-//   'Emit(nameof(TheEvent))' creates a StringName every time and has the overhead of string marshaling.
-//   I haven't decided on the best option yet. Some possibilities:
-//     - Expose the generated StringName fields to the user, for use with 'Emit(...)'.
-//     - Generate a 'EmitSignalName' method for each event signal.
-
 namespace Godot.SourceGenerators
 {
     [Generator]
@@ -276,7 +269,7 @@ namespace Godot.SourceGenerators
                 source.Append(
                     $"    /// <inheritdoc cref=\"{signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal()}\"/>\n");
 
-                source.Append("    public event ")
+                source.Append($"    {signalDelegate.DelegateSymbol.GetAccessibilityKeyword()} event ")
                     .Append(signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal())
                     .Append(" @")
                     .Append(signalName)
@@ -288,6 +281,43 @@ namespace Godot.SourceGenerators
                     .Append(signalName)
                     .Append(" -= value;\n")
                     .Append("}\n");
+
+                // Generate On{EventName} method to raise the event
+
+                var invokeMethodSymbol = signalDelegate.InvokeMethodData.Method;
+                int paramCount = invokeMethodSymbol.Parameters.Length;
+
+                string raiseMethodModifiers = signalDelegate.DelegateSymbol.ContainingType.IsSealed ?
+                    "private" :
+                    "protected";
+
+                source.Append($"    {raiseMethodModifiers} void On{signalName}(");
+                for (int i = 0; i < paramCount; i++)
+                {
+                    var paramSymbol = invokeMethodSymbol.Parameters[i];
+                    source.Append($"{paramSymbol.Type.FullQualifiedNameIncludeGlobal()} {paramSymbol.Name}");
+                    if (i < paramCount - 1)
+                    {
+                        source.Append(", ");
+                    }
+                }
+                source.Append(")\n");
+                source.Append("    {\n");
+                source.Append($"        EmitSignal(SignalName.{signalName}");
+                foreach (var paramSymbol in invokeMethodSymbol.Parameters)
+                {
+                    // Enums must be converted to the underlying type before they can be implicitly converted to Variant
+                    if (paramSymbol.Type.TypeKind == TypeKind.Enum)
+                    {
+                        var underlyingType = ((INamedTypeSymbol)paramSymbol.Type).EnumUnderlyingType;
+                        source.Append($", ({underlyingType.FullQualifiedNameIncludeGlobal()}){paramSymbol.Name}");
+                        continue;
+                    }
+
+                    source.Append($", {paramSymbol.Name}");
+                }
+                source.Append(");\n");
+                source.Append("    }\n");
             }
 
             // Generate RaiseGodotClassSignalCallbacks

+ 40 - 0
modules/mono/editor/bindings_generator.cpp

@@ -3227,6 +3227,46 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
 		}
 
 		p_output.append(CLOSE_BLOCK_L1);
+
+		// Generate On{EventName} method to raise the event.
+		if (!p_itype.is_singleton) {
+			p_output.append(MEMBER_BEGIN "protected void ");
+			p_output << "On" << p_isignal.proxy_name;
+			if (is_parameterless) {
+				p_output.append("()\n" OPEN_BLOCK_L1 INDENT2);
+				p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ");\n";
+				p_output.append(CLOSE_BLOCK_L1);
+			} else {
+				p_output.append("(");
+
+				StringBuilder cs_emitsignal_params;
+
+				int idx = 0;
+				for (const ArgumentInterface &iarg : p_isignal.arguments) {
+					const TypeInterface *arg_type = _get_type_or_null(iarg.type);
+					ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");
+
+					if (idx != 0) {
+						p_output << ", ";
+						cs_emitsignal_params << ", ";
+					}
+
+					p_output << arg_type->cs_type << " " << iarg.name;
+
+					if (arg_type->is_enum) {
+						cs_emitsignal_params << "(long)";
+					}
+
+					cs_emitsignal_params << iarg.name;
+
+					idx++;
+				}
+
+				p_output.append(")\n" OPEN_BLOCK_L1 INDENT2);
+				p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ", " << cs_emitsignal_params << ");\n";
+				p_output.append(CLOSE_BLOCK_L1);
+			}
+		}
 	}
 
 	return OK;