You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-11 13:10:58 +00:00
Merge pull request #68233 from raulsntos/dotnet/raise-events
C#: Generate strongly-typed method to raise signal events and fix event accessibility
This commit is contained in:
@@ -31,6 +31,10 @@ partial class EventSignals
|
|||||||
public event global::EventSignals.MySignalEventHandler @MySignal {
|
public event global::EventSignals.MySignalEventHandler @MySignal {
|
||||||
add => backing_MySignal += value;
|
add => backing_MySignal += value;
|
||||||
remove => backing_MySignal -= value;
|
remove => backing_MySignal -= value;
|
||||||
|
}
|
||||||
|
protected void OnMySignal(string str, int num)
|
||||||
|
{
|
||||||
|
EmitSignal(SignalName.MySignal, str, num);
|
||||||
}
|
}
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||||
|
|||||||
@@ -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)
|
public static string NameWithTypeParameters(this INamedTypeSymbol symbol)
|
||||||
{
|
{
|
||||||
return symbol.IsGenericType ?
|
return symbol.IsGenericType ?
|
||||||
|
|||||||
@@ -5,13 +5,6 @@ using Microsoft.CodeAnalysis;
|
|||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
using Microsoft.CodeAnalysis.Text;
|
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
|
namespace Godot.SourceGenerators
|
||||||
{
|
{
|
||||||
[Generator]
|
[Generator]
|
||||||
@@ -276,7 +269,7 @@ namespace Godot.SourceGenerators
|
|||||||
source.Append(
|
source.Append(
|
||||||
$" /// <inheritdoc cref=\"{signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal()}\"/>\n");
|
$" /// <inheritdoc cref=\"{signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal()}\"/>\n");
|
||||||
|
|
||||||
source.Append(" public event ")
|
source.Append($" {signalDelegate.DelegateSymbol.GetAccessibilityKeyword()} event ")
|
||||||
.Append(signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal())
|
.Append(signalDelegate.DelegateSymbol.FullQualifiedNameIncludeGlobal())
|
||||||
.Append(" @")
|
.Append(" @")
|
||||||
.Append(signalName)
|
.Append(signalName)
|
||||||
@@ -288,6 +281,43 @@ namespace Godot.SourceGenerators
|
|||||||
.Append(signalName)
|
.Append(signalName)
|
||||||
.Append(" -= value;\n")
|
.Append(" -= value;\n")
|
||||||
.Append("}\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
|
// Generate RaiseGodotClassSignalCallbacks
|
||||||
|
|||||||
@@ -3227,6 +3227,46 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf
|
|||||||
}
|
}
|
||||||
|
|
||||||
p_output.append(CLOSE_BLOCK_L1);
|
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;
|
return OK;
|
||||||
|
|||||||
Reference in New Issue
Block a user