Skip to content

Commit f7b9f24

Browse files
committed
Merge pull request godotengine#68233 from raulsntos/dotnet/raise-events
C#: Generate strongly-typed method to raise signal events and fix event accessibility
2 parents b1b8685 + fa8a07d commit f7b9f24

4 files changed

Lines changed: 108 additions & 8 deletions

File tree

modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/EventSignals_ScriptSignals.generated.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ partial class EventSignals
3232
add => backing_MySignal += value;
3333
remove => backing_MySignal -= value;
3434
}
35+
protected void OnMySignal(string str, int num)
36+
{
37+
EmitSignal(SignalName.MySignal, str, num);
38+
}
3539
/// <inheritdoc/>
3640
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
3741
protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, NativeVariantPtrArgs args)

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,32 @@ public static string GetDeclarationKeyword(this INamedTypeSymbol namedTypeSymbol
155155
};
156156
}
157157

158+
public static string GetAccessibilityKeyword(this INamedTypeSymbol namedTypeSymbol)
159+
{
160+
if (namedTypeSymbol.DeclaredAccessibility == Accessibility.NotApplicable)
161+
{
162+
// Accessibility not specified. Get the default accessibility.
163+
return namedTypeSymbol.ContainingSymbol switch
164+
{
165+
null or INamespaceSymbol => "internal",
166+
ITypeSymbol { TypeKind: TypeKind.Class or TypeKind.Struct } => "private",
167+
ITypeSymbol { TypeKind: TypeKind.Interface } => "public",
168+
_ => "",
169+
};
170+
}
171+
172+
return namedTypeSymbol.DeclaredAccessibility switch
173+
{
174+
Accessibility.Private => "private",
175+
Accessibility.Protected => "protected",
176+
Accessibility.Internal => "internal",
177+
Accessibility.ProtectedAndInternal => "private",
178+
Accessibility.ProtectedOrInternal => "private",
179+
Accessibility.Public => "public",
180+
_ => "",
181+
};
182+
}
183+
158184
public static string NameWithTypeParameters(this INamedTypeSymbol symbol)
159185
{
160186
return symbol.IsGenericType ?

modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,6 @@
55
using Microsoft.CodeAnalysis.CSharp.Syntax;
66
using Microsoft.CodeAnalysis.Text;
77

8-
// TODO:
9-
// Determine a proper way to emit the signal.
10-
// 'Emit(nameof(TheEvent))' creates a StringName every time and has the overhead of string marshaling.
11-
// I haven't decided on the best option yet. Some possibilities:
12-
// - Expose the generated StringName fields to the user, for use with 'Emit(...)'.
13-
// - Generate a 'EmitSignalName' method for each event signal.
14-
158
namespace Godot.SourceGenerators
169
{
1710
[Generator]
@@ -276,7 +269,7 @@ void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType)
276269
source.Append(
277270
$" /// <inheritdoc cref=\"{signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal()}\"/>\n");
278271

279-
source.Append(" public event ")
272+
source.Append($" {signalDelegate.DelegateSymbol.GetAccessibilityKeyword()} event ")
280273
.Append(signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal())
281274
.Append(" @")
282275
.Append(signalName)
@@ -288,6 +281,43 @@ void AppendPartialContainingTypeDeclarations(INamedTypeSymbol? containingType)
288281
.Append(signalName)
289282
.Append(" -= value;\n")
290283
.Append("}\n");
284+
285+
// Generate On{EventName} method to raise the event
286+
287+
var invokeMethodSymbol = signalDelegate.InvokeMethodData.Method;
288+
int paramCount = invokeMethodSymbol.Parameters.Length;
289+
290+
string raiseMethodModifiers = signalDelegate.DelegateSymbol.ContainingType.IsSealed ?
291+
"private" :
292+
"protected";
293+
294+
source.Append($" {raiseMethodModifiers} void On{signalName}(");
295+
for (int i = 0; i < paramCount; i++)
296+
{
297+
var paramSymbol = invokeMethodSymbol.Parameters[i];
298+
source.Append($"{paramSymbol.Type.FullQualifiedNameIncludeGlobal()} {paramSymbol.Name}");
299+
if (i < paramCount - 1)
300+
{
301+
source.Append(", ");
302+
}
303+
}
304+
source.Append(")\n");
305+
source.Append(" {\n");
306+
source.Append($" EmitSignal(SignalName.{signalName}");
307+
foreach (var paramSymbol in invokeMethodSymbol.Parameters)
308+
{
309+
// Enums must be converted to the underlying type before they can be implicitly converted to Variant
310+
if (paramSymbol.Type.TypeKind == TypeKind.Enum)
311+
{
312+
var underlyingType = ((INamedTypeSymbol)paramSymbol.Type).EnumUnderlyingType;
313+
source.Append($", ({underlyingType.FullQualifiedNameIncludeGlobal()}){paramSymbol.Name}");
314+
continue;
315+
}
316+
317+
source.Append($", {paramSymbol.Name}");
318+
}
319+
source.Append(");\n");
320+
source.Append(" }\n");
291321
}
292322

293323
// Generate RaiseGodotClassSignalCallbacks

modules/mono/editor/bindings_generator.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3227,6 +3227,46 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
32273227
}
32283228

32293229
p_output.append(CLOSE_BLOCK_L1);
3230+
3231+
// Generate On{EventName} method to raise the event.
3232+
if (!p_itype.is_singleton) {
3233+
p_output.append(MEMBER_BEGIN "protected void ");
3234+
p_output << "On" << p_isignal.proxy_name;
3235+
if (is_parameterless) {
3236+
p_output.append("()\n" OPEN_BLOCK_L1 INDENT2);
3237+
p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ");\n";
3238+
p_output.append(CLOSE_BLOCK_L1);
3239+
} else {
3240+
p_output.append("(");
3241+
3242+
StringBuilder cs_emitsignal_params;
3243+
3244+
int idx = 0;
3245+
for (const ArgumentInterface &iarg : p_isignal.arguments) {
3246+
const TypeInterface *arg_type = _get_type_or_null(iarg.type);
3247+
ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found.");
3248+
3249+
if (idx != 0) {
3250+
p_output << ", ";
3251+
cs_emitsignal_params << ", ";
3252+
}
3253+
3254+
p_output << arg_type->cs_type << " " << iarg.name;
3255+
3256+
if (arg_type->is_enum) {
3257+
cs_emitsignal_params << "(long)";
3258+
}
3259+
3260+
cs_emitsignal_params << iarg.name;
3261+
3262+
idx++;
3263+
}
3264+
3265+
p_output.append(")\n" OPEN_BLOCK_L1 INDENT2);
3266+
p_output << "EmitSignal(SignalName." << p_isignal.proxy_name << ", " << cs_emitsignal_params << ");\n";
3267+
p_output.append(CLOSE_BLOCK_L1);
3268+
}
3269+
}
32303270
}
32313271

32323272
return OK;

0 commit comments

Comments
 (0)