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 @@ +