Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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";

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down
45 changes: 45 additions & 0 deletions modules/mono/editor/bindings_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,19 @@ public static WeakRef WeakRef(Object obj)
return (WeakRef)InteropUtils.UnmanagedGetManaged(weakRef.Reference);
}
}

/// <summary>
/// <para>Gets a <see cref="ISignalEmitter"/> to emit a signal.</para>
/// <para>Example:</para>
/// <code>GetEmitter&lt;SignalEmit.SignalName&gt;().Emit();</code>
/// </summary>
/// <returns>A <typeparamref name="TSignalEmitter"/> ready to call <see langword="Emit"/> on.</returns>
protected TSignalEmitter GetEmitter<TSignalEmitter>()
where TSignalEmitter : struct, ISignalEmitter
{
var signalEmitter = default(TSignalEmitter);
signalEmitter.Bound = this;
return signalEmitter;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Godot
{
public interface ISignalEmitter
{
Godot.Object Bound { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
<Compile Include="Core\Interfaces\IAwaitable.cs" />
<Compile Include="Core\Interfaces\IAwaiter.cs" />
<Compile Include="Core\Interfaces\ISerializationListener.cs" />
<Compile Include="Core\Interfaces\ISignalEmitter.cs" />
<Compile Include="Core\Mathf.cs" />
<Compile Include="Core\MathfEx.cs" />
<Compile Include="Core\NativeInterop\CustomUnsafe.cs" />
Expand Down