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 ba6c10aa312c..dbb7d113a089 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.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]
@@ -81,6 +74,10 @@ INamedTypeSymbol symbol
bool isInnerClass = symbol.ContainingType != null;
+
+ string fullName = symbol.FullQualifiedNameIncludeGlobal();
+ string baseFullName = symbol.BaseType.FullQualifiedNameIncludeGlobal();
+
string uniqueHint = symbol.FullQualifiedNameOmitGlobal().SanitizeQualifiedNameForUniqueHint()
+ "_ScriptSignals.generated";
@@ -177,7 +174,7 @@ INamedTypeSymbol symbol
source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
source.Append(
- $" public new class SignalName : {symbol.BaseType.FullQualifiedNameIncludeGlobal()}.SignalName {{\n");
+ $" public new class SignalName : {baseFullName}.SignalName {{\n");
// Generate cached StringNames for methods and properties, for fast lookup
@@ -272,6 +269,63 @@ INamedTypeSymbol symbol
source.Append(" }\n");
}
+ // Generate SignalEmitter
+
+ source.Append(" public new class SignalEmitter : ")
+ .Append(baseFullName)
+ .Append(".SignalEmitter\n")
+ .Append(" {\n");
+
+ foreach (var signalDelegate in godotSignalDelegates)
+ {
+ source.Append(" public struct ")
+ .Append(signalDelegate.Name)
+ .Append(" : ISignalEmitter\n")
+ .Append(" {\n");
+
+ source.Append(" public Object Bound { get; set; }\n");
+
+ // Generate Emit method
+
+ var parameters = signalDelegate.InvokeMethodData.Method.Parameters;
+ string @params = "";
+ string paramsCall = "";
+
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ IParameterSymbol parameter = parameters[i];
+
+ if (i > 0)
+ @params += ", ";
+
+ @params += $"{parameter.Type.FullQualifiedNameIncludeGlobal()} {parameter.Name}";
+
+ // Enums must be converted to the underlying type before they can be implicitly converted to Variant
+ if (parameter.Type.TypeKind == TypeKind.Enum)
+ {
+ var underlyingType = ((INamedTypeSymbol)parameter.Type).EnumUnderlyingType!;
+ source.Append($", ({underlyingType.FullQualifiedNameIncludeGlobal()}){parameter.Name}");
+ continue;
+ }
+
+ paramsCall += $", {parameter.Name}";
+ }
+
+ source.Append(" public void Emit(")
+ .Append(@params)
+ .Append(")\n")
+ .Append(" => Bound.EmitSignal(")
+ .Append(fullName)
+ .Append(".SignalName.")
+ .Append(signalDelegate.Name)
+ .Append(paramsCall)
+ .Append(");\n");
+
+ source.Append(" }\n");
+ }
+
+ source.Append(" }\n");
+
source.Append("}\n"); // partial class
if (isInnerClass)
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 6559cbf75d92..dd5fb0ef28b2 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -1762,6 +1762,51 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
}
output << INDENT1 "}\n";
+ // SignalEmitter
+ if (is_inherit) {
+ output << MEMBER_BEGIN "public new class SignalEmitter : " << obj_types[itype.base_name].proxy_name << ".SignalEmitter";
+ } else {
+ output << MEMBER_BEGIN "public class SignalEmitter";
+ }
+ output << "\n"
+ << INDENT1 "{\n";
+ for (const SignalInterface &isignal : itype.signals_) {
+ // Get arguments
+ StringBuilder arg_sig;
+ StringBuilder arg_call_sig;
+ const ArgumentInterface &first_arg = isignal.arguments.front()->get();
+ for (const ArgumentInterface &iarg : isignal.arguments) {
+ const TypeInterface *arg_type = _get_type_or_null(iarg.type);
+
+ if (&iarg != &first_arg) {
+ arg_sig << ", ";
+ }
+ arg_call_sig << ", ";
+
+ // Fix for arg types with the same name as the signal (e.g. CollisionObject2D.InputEvent)
+ if (arg_type->cs_type == isignal.proxy_name) {
+ arg_sig << "Godot.";
+ }
+ arg_sig << arg_type->cs_type << " " << iarg.name;
+
+ if (arg_type->is_enum) {
+ arg_call_sig << "(long)";
+ }
+ arg_call_sig << iarg.name;
+ }
+
+ output << INDENT2 "public struct " << isignal.proxy_name << " : Godot.ISignalEmitter\n"
+ << INDENT2 "{\n";
+
+ output << INDENT3 "public Object Bound { get; set; }\n";
+
+ output << INDENT3 "public void Emit(" << arg_sig << ") => Bound.EmitSignal("
+ << "SignalName." << isignal.proxy_name << arg_call_sig << ");\n";
+
+ output << INDENT2 "}\n";
+ }
+ output << INDENT1 "}\n";
+
output.append(CLOSE_BLOCK /* class */);
output.append("\n"
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs
index 4094ceeb2272..647eafd15387 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs
@@ -44,5 +44,19 @@ public static WeakRef WeakRef(Object obj)
return (WeakRef)InteropUtils.UnmanagedGetManaged(weakRef.Reference);
}
}
+
+ ///
+ /// Gets a to emit a signal.
+ /// Example:
+ /// GetEmitter<SignalEmit.SignalName>().Emit();
+ ///
+ /// A ready to call on.
+ protected TSignalEmitter GetEmitter()
+ where TSignalEmitter : struct, ISignalEmitter
+ {
+ var signalEmitter = default(TSignalEmitter);
+ signalEmitter.Bound = this;
+ return signalEmitter;
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Interfaces/ISignalEmitter.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Interfaces/ISignalEmitter.cs
new file mode 100644
index 000000000000..0d15c4c09ead
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Interfaces/ISignalEmitter.cs
@@ -0,0 +1,7 @@
+namespace Godot
+{
+ public interface ISignalEmitter
+ {
+ Godot.Object Bound { get; set; }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index 644212c74da6..0a4c17ea80f7 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -93,6 +93,7 @@
+