diff options
author | Rémi Verschelde <rverschelde@gmail.com> | 2024-09-27 13:53:11 +0200 |
---|---|---|
committer | Rémi Verschelde <rverschelde@gmail.com> | 2024-09-27 13:53:11 +0200 |
commit | 543fa16b4ce821a0118da3ef0904a105aaa046e9 (patch) | |
tree | ad25bcdfee152e36803555033370accbd0793a84 | |
parent | 506d6e427a4eecfc1e5e5ee1180019a876119701 (diff) | |
parent | da37998dc84dcf05c91fdfe7481445c0cb10af2e (diff) | |
download | redot-engine-543fa16b4ce821a0118da3ef0904a105aaa046e9.tar.gz |
Merge pull request #68233 from raulsntos/dotnet/raise-events
C#: Generate strongly-typed method to raise signal events and fix event accessibility
4 files changed, 108 insertions, 8 deletions
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs index cc45e5746f..188972e6fe 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs +++ b/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) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index 957d5789df..62fa7b0a36 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/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 ? diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 54f2212339..0dda43ab4c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/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 diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index a467aae2e9..89655e0b56 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/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; |