summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorIgnacio Roldán Etcheverry <ignalfonsore@gmail.com>2022-07-28 17:41:47 +0200
committerIgnacio Roldán Etcheverry <ignalfonsore@gmail.com>2022-08-22 03:36:52 +0200
commit97713ff77a339faa72d54bd596e3d8c2b8520ce0 (patch)
tree22e97aa97c7ff55d5e3cd92c3cc4130a041aa6ef /modules
parentf033764ffe5892f963a9416e8cbcfd0fb5225103 (diff)
C#: Add source generator for signals as events
Changed the signal declaration signal to: ``` // The following generates a MySignal event [Signal] public delegate void MySignalEventHandler(int param); ```
Diffstat (limited to 'modules')
-rw-r--r--modules/mono/csharp_script.cpp110
-rw-r--r--modules/mono/csharp_script.h11
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs7
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs49
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs64
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs1
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs14
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs14
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs24
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs23
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMemberInvokerGenerator.cs115
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs31
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs70
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs360
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs3
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs6
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs38
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs6
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs25
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs40
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs289
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs8
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs6
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs155
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs6
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj1
-rw-r--r--modules/mono/glue/runtime_interop.cpp10
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp20
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.cpp2
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h6
31 files changed, 996 insertions, 522 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index e54e5ac0bb..5235c6d176 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -104,7 +104,7 @@ Error CSharpLanguage::execute_file(const String &p_path) {
return OK;
}
-extern void *godotsharp_pinvoke_funcs[186];
+extern void *godotsharp_pinvoke_funcs[185];
[[maybe_unused]] volatile void **do_not_strip_godotsharp_pinvoke_funcs;
#ifdef TOOLS_ENABLED
extern void *godotsharp_editor_pinvoke_funcs[30];
@@ -1452,6 +1452,8 @@ void CSharpLanguage::tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_int
CSharpInstance *csharp_instance = CSharpInstance::create_for_managed_type(p_unmanaged, script.ptr(), gchandle);
p_unmanaged->set_script_and_instance(script, csharp_instance);
+
+ csharp_instance->connect_event_signals();
}
void CSharpLanguage::tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged) {
@@ -1480,6 +1482,8 @@ void CSharpLanguage::tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gc
// instances is a set, so it's safe to insert multiple times (e.g.: from _internal_new_managed)
instance->script->instances.insert(instance->owner);
}
+
+ instance->connect_event_signals();
}
CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) {
@@ -1774,13 +1778,22 @@ void CSharpInstance::mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_f
}
}
-void CSharpInstance::connect_event_signal(const StringName &p_event_signal) {
- // TODO: Use pooling for ManagedCallable instances.
- EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, p_event_signal));
+void CSharpInstance::connect_event_signals() {
+ CSharpScript *top = script.ptr();
+ while (top != nullptr) {
+ for (CSharpScript::EventSignalInfo &signal : top->get_script_event_signals()) {
+ String signal_name = signal.name;
- Callable callable(event_signal_callable);
- connected_event_signals.push_back(callable);
- owner->connect(p_event_signal, callable);
+ // TODO: Use pooling for ManagedCallable instances.
+ EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, signal_name));
+
+ Callable callable(event_signal_callable);
+ connected_event_signals.push_back(callable);
+ owner->connect(signal_name, callable);
+ }
+
+ top = top->base_script.ptr();
+ }
}
void CSharpInstance::disconnect_event_signals() {
@@ -2169,20 +2182,56 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) {
// Extract information about the script using the mono class.
void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
bool tool = false;
+
Dictionary rpc_functions_dict;
// Destructor won't be called from C#, and I don't want to include the GDNative header
// only for this, so need to call the destructor manually before passing this to C#.
rpc_functions_dict.~Dictionary();
+ Dictionary signals_dict;
+ // Destructor won't be called from C#, and I don't want to include the GDNative header
+ // only for this, so need to call the destructor manually before passing this to C#.
+ signals_dict.~Dictionary();
+
Ref<CSharpScript> base_script;
GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo(
- p_script.ptr(), &tool, &rpc_functions_dict, &base_script);
+ p_script.ptr(), &tool, &rpc_functions_dict, &signals_dict, &base_script);
p_script->tool = tool;
p_script->rpc_config.clear();
p_script->rpc_config = rpc_functions_dict;
+ // Event signals
+
+ // Performance is not critical here as this will be replaced with source generators.
+
+ p_script->event_signals.clear();
+
+ // Sigh... can't we just have capacity?
+ p_script->event_signals.resize(signals_dict.size());
+ int push_index = 0;
+
+ for (const Variant *s = signals_dict.next(nullptr); s != nullptr; s = signals_dict.next(s)) {
+ StringName name = *s;
+
+ MethodInfo mi;
+ mi.name = name;
+
+ Array params = signals_dict[*s];
+
+ for (int i = 0; i < params.size(); i++) {
+ Dictionary param = params[i];
+
+ Variant::Type param_type = (Variant::Type)(int)param["type"];
+ PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]);
+ arg_info.usage = (uint32_t)param["usage"];
+ mi.arguments.push_back(arg_info);
+ }
+
+ p_script->event_signals.set(push_index++, EventSignalInfo{ name, mi });
+ }
+
p_script->base_script = base_script;
}
@@ -2467,9 +2516,13 @@ bool CSharpScript::has_script_signal(const StringName &p_signal) const {
return false;
}
- String signal = p_signal;
+ for (const EventSignalInfo &signal : event_signals) {
+ if (signal.name == p_signal) {
+ return true;
+ }
+ }
- return GDMonoCache::managed_callbacks.ScriptManagerBridge_HasScriptSignal(this, &signal);
+ return false;
}
void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
@@ -2477,38 +2530,17 @@ void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
return;
}
- // Performance is not critical here as this will be replaced with source generators.
-
- if (!GDMonoCache::godot_api_cache_updated) {
- return;
+ for (const EventSignalInfo &signal : get_script_event_signals()) {
+ r_signals->push_back(signal.method_info);
}
+}
- Dictionary signals_dict;
- // Destructor won't be called from C#, and I don't want to include the GDNative header
- // only for this, so need to call the destructor manually before passing this to C#.
- signals_dict.~Dictionary();
-
- GDMonoCache::managed_callbacks.ScriptManagerBridge_GetScriptSignalList(this, &signals_dict);
-
- for (const Variant *s = signals_dict.next(nullptr); s != nullptr; s = signals_dict.next(s)) {
- MethodInfo mi;
- mi.name = *s;
-
- Array params = signals_dict[*s];
-
- for (int i = 0; i < params.size(); i++) {
- Dictionary param = params[i];
-
- Variant::Type param_type = (Variant::Type)(int)param["type"];
- PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]);
- if (param_type == Variant::NIL && (bool)param["nil_is_variant"]) {
- arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
- }
- mi.arguments.push_back(arg_info);
- }
-
- r_signals->push_back(mi);
+Vector<CSharpScript::EventSignalInfo> CSharpScript::get_script_event_signals() const {
+ if (!valid) {
+ return Vector<EventSignalInfo>();
}
+
+ return event_signals;
}
bool CSharpScript::inherits_script(const Ref<Script> &p_script) const {
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 29a36bcc1e..f3a8ead319 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -98,6 +98,13 @@ class CSharpScript : public Script {
Dictionary rpc_config;
+ struct EventSignalInfo {
+ StringName name; // MethodInfo stores a string...
+ MethodInfo method_info;
+ };
+
+ Vector<EventSignalInfo> event_signals;
+
#ifdef TOOLS_ENABLED
List<PropertyInfo> exported_members_cache; // members_cache
HashMap<StringName, Variant> exported_members_defval_cache; // member_default_values_cache
@@ -158,6 +165,8 @@ public:
bool has_script_signal(const StringName &p_signal) const override;
void get_script_signal_list(List<MethodInfo> *r_signals) const override;
+ Vector<EventSignalInfo> get_script_event_signals() const;
+
bool get_property_default_value(const StringName &p_property, Variant &r_value) const override;
void get_script_property_list(List<PropertyInfo> *r_list) const override;
void update_exports() override;
@@ -254,7 +263,7 @@ public:
*/
void mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_free, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance);
- void connect_event_signal(const StringName &p_event_signal);
+ void connect_event_signals();
void disconnect_event_signals();
void refcount_incremented() override;
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs
new file mode 100644
index 0000000000..764ba8f121
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs
@@ -0,0 +1,7 @@
+namespace Godot.SourceGenerators.Sample;
+
+public partial class EventSignals : Godot.Object
+{
+ [Signal]
+ public delegate void MySignalEventHandler(string str, int num);
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
index 0b8a2777e5..c1ae993251 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
@@ -166,5 +166,54 @@ namespace Godot.SourceGenerators
location,
location?.SourceTree?.FilePath));
}
+
+ public static void ReportSignalDelegateMissingSuffix(
+ GeneratorExecutionContext context,
+ INamedTypeSymbol delegateSymbol)
+ {
+ var locations = delegateSymbol.Locations;
+ var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
+
+ string message = "The name of the delegate must end with 'EventHandler': " +
+ delegateSymbol.ToDisplayString() +
+ $". Did you mean '{delegateSymbol.Name}EventHandler'?";
+
+ string description = $"{message}. Rename the delegate accordingly or remove the '[Signal]' attribute.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GODOT-G0201",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ location,
+ location?.SourceTree?.FilePath));
+ }
+
+ public static void ReportSignalDelegateSignatureNotSupported(
+ GeneratorExecutionContext context,
+ INamedTypeSymbol delegateSymbol)
+ {
+ var locations = delegateSymbol.Locations;
+ var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
+
+ string message = "The delegate signature of the signal " +
+ $"is not supported: '{delegateSymbol.ToDisplayString()}'";
+
+ string description = $"{message}. Use supported types only or remove the '[Signal]' attribute.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GODOT-G0202",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ location,
+ location?.SourceTree?.FilePath));
+ }
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
index c218212f04..bac4708165 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
@@ -174,46 +174,60 @@ namespace Godot.SourceGenerators
public static bool IsGodotExportAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.ExportAttr;
+ public static bool IsGodotSignalAttribute(this INamedTypeSymbol symbol)
+ => symbol.ToString() == GodotClasses.SignalAttr;
+
public static bool IsGodotClassNameAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.GodotClassNameAttr;
public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
=> symbol.ToString() == GodotClasses.SystemFlagsAttr;
- public static IEnumerable<GodotMethodData> WhereHasGodotCompatibleSignature(
- this IEnumerable<IMethodSymbol> methods,
+ public static GodotMethodData? HasGodotCompatibleSignature(
+ this IMethodSymbol method,
MarshalUtils.TypeCache typeCache
)
{
- foreach (var method in methods)
- {
- if (method.IsGenericMethod)
- continue;
+ if (method.IsGenericMethod)
+ return null;
- var retSymbol = method.ReturnType;
- var retType = method.ReturnsVoid ?
- null :
- MarshalUtils.ConvertManagedTypeToMarshalType(method.ReturnType, typeCache);
+ var retSymbol = method.ReturnType;
+ var retType = method.ReturnsVoid ?
+ null :
+ MarshalUtils.ConvertManagedTypeToMarshalType(method.ReturnType, typeCache);
- if (retType == null && !method.ReturnsVoid)
- continue;
+ if (retType == null && !method.ReturnsVoid)
+ return null;
- var parameters = method.Parameters;
+ var parameters = method.Parameters;
- var paramTypes = parameters
- // Currently we don't support `ref`, `out`, `in`, `ref readonly` parameters (and we never may)
- .Where(p => p.RefKind == RefKind.None)
- // Attempt to determine the variant type
- .Select(p => MarshalUtils.ConvertManagedTypeToMarshalType(p.Type, typeCache))
- // Discard parameter types that couldn't be determined (null entries)
- .Where(t => t != null).Cast<MarshalType>().ToImmutableArray();
+ var paramTypes = parameters
+ // Currently we don't support `ref`, `out`, `in`, `ref readonly` parameters (and we never may)
+ .Where(p => p.RefKind == RefKind.None)
+ // Attempt to determine the variant type
+ .Select(p => MarshalUtils.ConvertManagedTypeToMarshalType(p.Type, typeCache))
+ // Discard parameter types that couldn't be determined (null entries)
+ .Where(t => t != null).Cast<MarshalType>().ToImmutableArray();
- // If any parameter type was incompatible, it was discarded so the length won't match
- if (parameters.Length > paramTypes.Length)
- continue;
+ // If any parameter type was incompatible, it was discarded so the length won't match
+ if (parameters.Length > paramTypes.Length)
+ return null; // Ignore incompatible method
+
+ return new GodotMethodData(method, paramTypes, parameters
+ .Select(p => p.Type).ToImmutableArray(), retType, retSymbol);
+ }
+
+ public static IEnumerable<GodotMethodData> WhereHasGodotCompatibleSignature(
+ this IEnumerable<IMethodSymbol> methods,
+ MarshalUtils.TypeCache typeCache
+ )
+ {
+ foreach (var method in methods)
+ {
+ var methodData = HasGodotCompatibleSignature(method, typeCache);
- yield return new GodotMethodData(method, paramTypes, parameters
- .Select(p => p.Type).ToImmutableArray(), retType, retSymbol);
+ if (methodData != null)
+ yield return methodData.Value;
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
index 0ea1b2f5ce..9ba8bb89b8 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
@@ -5,6 +5,7 @@ namespace Godot.SourceGenerators
public const string Object = "Godot.Object";
public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute";
public const string ExportAttr = "Godot.ExportAttribute";
+ public const string SignalAttr = "Godot.SignalAttribute";
public const string GodotClassNameAttr = "Godot.GodotClassName";
public const string SystemFlagsAttr = "System.FlagsAttribute";
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
index 99d3a49546..1a25d684a0 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
@@ -2,6 +2,8 @@ using System;
namespace Godot.SourceGenerators
{
+ // TODO: May need to think about compatibility here. Could Godot change these values between minor versions?
+
internal enum VariantType
{
Nil = 0,
@@ -131,4 +133,16 @@ namespace Godot.SourceGenerators
DefaultIntl = 38,
NoEditor = 2
}
+
+ public enum MethodFlags
+ {
+ Normal = 1,
+ Editor = 2,
+ Const = 4,
+ Virtual = 8,
+ Vararg = 16,
+ Static = 32,
+ ObjectCore = 64,
+ Default = 1
+ }
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs
index 1a01ba2b9d..a3ad8cbabd 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs
@@ -22,6 +22,20 @@ namespace Godot.SourceGenerators
public ITypeSymbol? RetSymbol { get; }
}
+ public struct GodotSignalDelegateData
+ {
+ public GodotSignalDelegateData(string name, INamedTypeSymbol delegateSymbol, GodotMethodData invokeMethodData)
+ {
+ Name = name;
+ DelegateSymbol = delegateSymbol;
+ InvokeMethodData = invokeMethodData;
+ }
+
+ public string Name { get; }
+ public INamedTypeSymbol DelegateSymbol { get; }
+ public GodotMethodData InvokeMethodData { get; }
+ }
+
public struct GodotPropertyData
{
public GodotPropertyData(IPropertySymbol propertySymbol, MarshalType type)
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs
new file mode 100644
index 0000000000..81c6f2b7d5
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+
+namespace Godot.SourceGenerators
+{
+ internal struct MethodInfo
+ {
+ public MethodInfo(string name, PropertyInfo returnVal, MethodFlags flags,
+ List<PropertyInfo>? arguments,
+ List<string?>? defaultArguments)
+ {
+ Name = name;
+ ReturnVal = returnVal;
+ Flags = flags;
+ Arguments = arguments;
+ DefaultArguments = defaultArguments;
+ }
+
+ public string Name { get; }
+ public PropertyInfo ReturnVal { get; }
+ public MethodFlags Flags { get; }
+ public List<PropertyInfo>? Arguments { get; }
+ public List<string?>? DefaultArguments { get; }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs
new file mode 100644
index 0000000000..b345f5f84d
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs
@@ -0,0 +1,23 @@
+namespace Godot.SourceGenerators
+{
+ internal struct PropertyInfo
+ {
+ public PropertyInfo(VariantType type, string name, PropertyHint hint,
+ string? hintString, PropertyUsageFlags usage, bool exported)
+ {
+ Type = type;
+ Name = name;
+ Hint = hint;
+ HintString = hintString;
+ Usage = usage;
+ Exported = exported;
+ }
+
+ public VariantType Type { get; }
+ public string Name { get; }
+ public PropertyHint Hint { get; }
+ public string? HintString { get; }
+ public PropertyUsageFlags Usage { get; }
+ public bool Exported { get; }
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMemberInvokerGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMemberInvokerGenerator.cs
index 303326e11a..f10942fc24 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMemberInvokerGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMemberInvokerGenerator.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
@@ -122,6 +123,33 @@ namespace Godot.SourceGenerators
var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
+ var signalDelegateSymbols = members
+ .Where(s => s.Kind == SymbolKind.NamedType)
+ .Cast<INamedTypeSymbol>()
+ .Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate)
+ .Where(s => s.GetAttributes()
+ .Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false));
+
+ List<GodotSignalDelegateData> godotSignalDelegates = new();
+
+ foreach (var signalDelegateSymbol in signalDelegateSymbols)
+ {
+ if (!signalDelegateSymbol.Name.EndsWith(ScriptSignalsGenerator.SignalDelegateSuffix))
+ continue;
+
+ string signalName = signalDelegateSymbol.Name;
+ signalName = signalName.Substring(0,
+ signalName.Length - ScriptSignalsGenerator.SignalDelegateSuffix.Length);
+
+ var invokeMethodData = signalDelegateSymbol
+ .DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache);
+
+ if (invokeMethodData == null)
+ continue;
+
+ godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value));
+ }
+
source.Append(" private partial class GodotInternal {\n");
// Generate cached StringNames for methods and properties, for fast lookup
@@ -157,6 +185,42 @@ namespace Godot.SourceGenerators
source.Append(" }\n");
}
+ // Generate HasGodotClassMethod
+
+ if (godotClassMethods.Length > 0)
+ {
+ source.Append(" protected override bool HasGodotClassMethod(in godot_string_name method)\n {\n");
+
+ bool isFirstEntry = true;
+ foreach (var method in godotClassMethods)
+ {
+ GenerateHasMethodEntry(method, source, isFirstEntry);
+ isFirstEntry = false;
+ }
+
+ source.Append(" return base.HasGodotClassMethod(method);\n");
+
+ source.Append(" }\n");
+ }
+
+ // Generate RaiseGodotClassSignalCallbacks
+
+ if (godotSignalDelegates.Count > 0)
+ {
+ source.Append(
+ " protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, ");
+ source.Append("NativeVariantPtrArgs args, int argCount)\n {\n");
+
+ foreach (var signal in godotSignalDelegates)
+ {
+ GenerateSignalEventInvoker(signal, source);
+ }
+
+ source.Append(" base.RaiseGodotClassSignalCallbacks(signal, args, argCount);\n");
+
+ source.Append(" }\n");
+ }
+
// Generate Set/GetGodotClassPropertyValue
if (godotClassProperties.Length > 0 || godotClassFields.Length > 0)
@@ -224,24 +288,6 @@ namespace Godot.SourceGenerators
source.Append(" }\n");
}
- // Generate HasGodotClassMethod
-
- if (godotClassMethods.Length > 0)
- {
- source.Append(" protected override bool HasGodotClassMethod(in godot_string_name method)\n {\n");
-
- bool isFirstEntry = true;
- foreach (var method in godotClassMethods)
- {
- GenerateHasMethodEntry(method, source, isFirstEntry);
- isFirstEntry = false;
- }
-
- source.Append(" return base.HasGodotClassMethod(method);\n");
-
- source.Append(" }\n");
- }
-
source.Append("}\n"); // partial class
if (isInnerClass)
@@ -314,6 +360,39 @@ namespace Godot.SourceGenerators
source.Append(" }\n");
}
+ private static void GenerateSignalEventInvoker(
+ GodotSignalDelegateData signal,
+ StringBuilder source
+ )
+ {
+ string signalName = signal.Name;
+ var invokeMethodData = signal.InvokeMethodData;
+
+ source.Append(" if (signal == GodotInternal.SignalName_");
+ source.Append(signalName);
+ source.Append(" && argCount == ");
+ source.Append(invokeMethodData.ParamTypes.Length);
+ source.Append(") {\n");
+ source.Append(" backing_");
+ source.Append(signalName);
+ source.Append("?.Invoke(");
+
+ for (int i = 0; i < invokeMethodData.ParamTypes.Length; i++)
+ {
+ if (i != 0)
+ source.Append(", ");
+
+ source.AppendVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"),
+ invokeMethodData.ParamTypeSymbols[i], invokeMethodData.ParamTypes[i]);
+ }
+
+ source.Append(");\n");
+
+ source.Append(" return;\n");
+
+ source.Append(" }\n");
+ }
+
private static void GeneratePropertySetter(
string propertyMemberName,
ITypeSymbol propertyTypeSymbol,
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
index e10521b78e..c527f738b3 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -146,7 +146,7 @@ namespace Godot.SourceGenerators
source.Append(" }\n"); // class GodotInternal
- // Generate GetGodotPropertiesMetadata
+ // Generate GetGodotPropertyList
if (godotClassProperties.Length > 0 || godotClassFields.Length > 0)
{
@@ -156,7 +156,7 @@ namespace Godot.SourceGenerators
source.Append(" internal new static ")
.Append(dictionaryType)
- .Append(" GetGodotPropertiesMetadata()\n {\n");
+ .Append(" GetGodotPropertyList()\n {\n");
source.Append(" var properties = new ")
.Append(dictionaryType)
@@ -164,7 +164,7 @@ namespace Godot.SourceGenerators
foreach (var property in godotClassProperties)
{
- var propertyInfo = GetPropertyMetadata(context, typeCache,
+ var propertyInfo = DeterminePropertyInfo(context, typeCache,
property.PropertySymbol, property.Type);
if (propertyInfo == null)
@@ -175,7 +175,7 @@ namespace Godot.SourceGenerators
foreach (var field in godotClassFields)
{
- var propertyInfo = GetPropertyMetadata(context, typeCache,
+ var propertyInfo = DeterminePropertyInfo(context, typeCache,
field.FieldSymbol, field.Type);
if (propertyInfo == null)
@@ -229,28 +229,7 @@ namespace Godot.SourceGenerators
.Append("));\n");
}
- private struct PropertyInfo
- {
- public PropertyInfo(VariantType type, string name, PropertyHint hint,
- string? hintString, PropertyUsageFlags usage, bool exported)
- {
- Type = type;
- Name = name;
- Hint = hint;
- HintString = hintString;
- Usage = usage;
- Exported = exported;
- }
-
- public VariantType Type { get; }
- public string Name { get; }
- public PropertyHint Hint { get; }
- public string? HintString { get; }
- public PropertyUsageFlags Usage { get; }
- public bool Exported { get; }
- }
-
- private static PropertyInfo? GetPropertyMetadata(
+ private static PropertyInfo? DeterminePropertyInfo(
GeneratorExecutionContext context,
MarshalUtils.TypeCache typeCache,
ISymbol memberSymbol,
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
index 3a7086a2be..224a2d0a50 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
@@ -120,10 +121,39 @@ namespace Godot.SourceGenerators
var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
+ var signalDelegateSymbols = members
+ .Where(s => s.Kind == SymbolKind.NamedType)
+ .Cast<INamedTypeSymbol>()
+ .Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate)
+ .Where(s => s.GetAttributes()
+ .Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false));
+
+ List<GodotSignalDelegateData> godotSignalDelegates = new();
+
+ foreach (var signalDelegateSymbol in signalDelegateSymbols)
+ {
+ if (!signalDelegateSymbol.Name.EndsWith(ScriptSignalsGenerator.SignalDelegateSuffix))
+ continue;
+
+ string signalName = signalDelegateSymbol.Name;
+ signalName = signalName.Substring(0,
+ signalName.Length - ScriptSignalsGenerator.SignalDelegateSuffix.Length);
+
+ var invokeMethodData = signalDelegateSymbol
+ .DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache);
+
+ if (invokeMethodData == null)
+ continue;
+
+ godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value));
+ }
+
source.Append(
" protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n");
source.Append(" base.SaveGodotObjectData(info);\n");
+ // Save properties
+
foreach (var property in godotClassProperties)
{
string propertyName = property.PropertySymbol.Name;
@@ -135,6 +165,8 @@ namespace Godot.SourceGenerators
.Append(");\n");
}
+ // Save fields
+
foreach (var field in godotClassFields)
{
string fieldName = field.FieldSymbol.Name;
@@ -146,12 +178,27 @@ namespace Godot.SourceGenerators
.Append(");\n");
}
+ // Save signal events
+
+ foreach (var signalDelegate in godotSignalDelegates)
+ {
+ string signalName = signalDelegate.Name;
+
+ source.Append(" info.AddSignalEventDelegate(GodotInternal.SignalName_")
+ .Append(signalName)
+ .Append(", this.backing_")
+ .Append(signalName)
+ .Append(");\n");
+ }
+
source.Append(" }\n");
source.Append(
" protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n");
source.Append(" base.RestoreGodotObjectData(info);\n");
+ // Restore properties
+
foreach (var property in godotClassProperties)
{
string propertyName = property.PropertySymbol.Name;
@@ -171,6 +218,8 @@ namespace Godot.SourceGenerators
.Append(";\n");
}
+ // Restore fields
+
foreach (var field in godotClassFields)
{
string fieldName = field.FieldSymbol.Name;
@@ -190,6 +239,27 @@ namespace Godot.SourceGenerators
.Append(";\n");
}
+ // Restore signal events
+
+ foreach (var signalDelegate in godotSignalDelegates)
+ {
+ string signalName = signalDelegate.Name;
+ string signalDelegateQualifiedName = signalDelegate.DelegateSymbol.FullQualifiedName();
+
+ source.Append(" if (info.TryGetSignalEventDelegate<")
+ .Append(signalDelegateQualifiedName)
+ .Append(">(GodotInternal.SignalName_")
+ .Append(signalName)
+ .Append(", out var _value_")
+ .Append(signalName)
+ .Append("))\n")
+ .Append(" this.backing_")
+ .Append(signalName)
+ .Append(" = _value_")
+ .Append(signalName)
+ .Append(";\n");
+ }
+
source.Append(" }\n");
source.Append("}\n"); // partial class
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
new file mode 100644
index 0000000000..31cc8e220b
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs
@@ -0,0 +1,360 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+// TODO:
+// Determine a proper way to emit the signal.
+// 'Emit(nameof(TheEvent))' creates a StringName everytime 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]
+ public class ScriptSignalsGenerator : ISourceGenerator
+ {
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ if (context.AreGodotSourceGeneratorsDisabled())
+ return;
+
+ INamedTypeSymbol[] godotClasses = context
+ .Compilation.SyntaxTrees
+ .SelectMany(tree =>
+ tree.GetRoot().DescendantNodes()
+ .OfType<ClassDeclarationSyntax>()
+ .SelectGodotScriptClasses(context.Compilation)
+ // Report and skip non-partial classes
+ .Where(x =>
+ {
+ if (x.cds.IsPartial())
+ {
+ if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial))
+ {
+ Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!);
+ return false;
+ }
+
+ return true;
+ }
+
+ Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
+ return false;
+ })
+ .Select(x => x.symbol)
+ )
+ .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
+ .ToArray();
+
+ if (godotClasses.Length > 0)
+ {
+ var typeCache = new MarshalUtils.TypeCache(context);
+
+ foreach (var godotClass in godotClasses)
+ {
+ VisitGodotScriptClass(context, typeCache, godotClass);
+ }
+ }
+ }
+
+ internal static string SignalDelegateSuffix = "EventHandler";
+
+ private static void VisitGodotScriptClass(
+ GeneratorExecutionContext context,
+ MarshalUtils.TypeCache typeCache,
+ INamedTypeSymbol symbol
+ )
+ {
+ INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
+ string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
+ namespaceSymbol.FullQualifiedName() :
+ string.Empty;
+ bool hasNamespace = classNs.Length != 0;
+
+ bool isInnerClass = symbol.ContainingType != null;
+
+ string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ + "_ScriptSignals_Generated";
+
+ var source = new StringBuilder();
+
+ source.Append("using Godot;\n");
+ source.Append("using Godot.NativeInterop;\n");
+ source.Append("\n");
+
+ if (hasNamespace)
+ {
+ source.Append("namespace ");
+ source.Append(classNs);
+ source.Append(" {\n\n");
+ }
+
+ if (isInnerClass)
+ {
+ var containingType = symbol.ContainingType;
+
+ while (containingType != null)
+ {
+ source.Append("partial ");
+ source.Append(containingType.GetDeclarationKeyword());
+ source.Append(" ");
+ source.Append(containingType.NameWithTypeParameters());
+ source.Append("\n{\n");
+
+ containingType = containingType.ContainingType;
+ }
+ }
+
+ source.Append("partial class ");
+ source.Append(symbol.NameWithTypeParameters());
+ source.Append("\n{\n");
+
+ // TODO:
+ // The delegate name already needs to end with 'Signal' to avoid collision with the event name.
+ // Requiring SignalAttribute is redundant. Should we remove it to make declaration shorter?
+
+ var members = symbol.GetMembers();
+
+ var signalDelegateSymbols = members
+ .Where(s => s.Kind == SymbolKind.NamedType)
+ .Cast<INamedTypeSymbol>()
+ .Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate)
+ .Where(s => s.GetAttributes()
+ .Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false));
+
+ List<GodotSignalDelegateData> godotSignalDelegates = new();
+
+ foreach (var signalDelegateSymbol in signalDelegateSymbols)
+ {
+ if (!signalDelegateSymbol.Name.EndsWith(SignalDelegateSuffix))
+ {
+ Common.ReportSignalDelegateMissingSuffix(context, signalDelegateSymbol);
+ continue;
+ }
+
+ string signalName = signalDelegateSymbol.Name;
+ signalName = signalName.Substring(0, signalName.Length - SignalDelegateSuffix.Length);
+
+ var invokeMethodData = signalDelegateSymbol
+ .DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache);
+
+ if (invokeMethodData == null)
+ {
+ // TODO: Better error for incompatible signature. We should indicate incompatible argument types, as we do with exported properties.
+ Common.ReportSignalDelegateSignatureNotSupported(context, signalDelegateSymbol);
+ continue;
+ }
+
+ godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value));
+ }
+
+ source.Append(" private partial class GodotInternal {\n");
+
+ // Generate cached StringNames for methods and properties, for fast lookup
+
+ foreach (var signalDelegate in godotSignalDelegates)
+ {
+ string signalName = signalDelegate.Name;
+ source.Append(" public static readonly StringName SignalName_");
+ source.Append(signalName);
+ source.Append(" = \"");
+ source.Append(signalName);
+ source.Append("\";\n");
+ }
+
+ source.Append(" }\n"); // class GodotInternal
+
+ // Generate GetGodotSignalList
+
+ if (godotSignalDelegates.Count > 0)
+ {
+ source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
+
+ const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>";
+
+ source.Append(" internal new static ")
+ .Append(listType)
+ .Append(" GetGodotSignalList()\n {\n");
+
+ source.Append(" var signals = new ")
+ .Append(listType)
+ .Append("(")
+ .Append(godotSignalDelegates.Count)
+ .Append(");\n");
+
+ foreach (var signalDelegateData in godotSignalDelegates)
+ {
+ var methodInfo = DetermineMethodInfo(signalDelegateData);
+ AppendMethodInfo(source, methodInfo);
+ }
+
+ source.Append(" return signals;\n");
+ source.Append(" }\n");
+
+ source.Append("#pragma warning restore CS0109\n");
+ }
+
+ // Generate signal event
+
+ foreach (var signalDelegate in godotSignalDelegates)
+ {
+ string signalName = signalDelegate.Name;
+
+ // TODO: Hide backing event from code-completion and debugger
+ // The reason we have a backing field is to hide the invoke method from the event,
+ // as it doesn't emit the signal, only the event delegates. This can confuse users.
+ // Maybe we should directly connect the delegates, as we do with native signals?
+ source.Append(" private ")
+ .Append(signalDelegate.DelegateSymbol.FullQualifiedName())
+ .Append(" backing_")
+ .Append(signalName)
+ .Append(";\n");
+
+ source.Append(" public event ")
+ .Append(signalDelegate.DelegateSymbol.FullQualifiedName())
+ .Append(" ")
+ .Append(signalName)
+ .Append(" {\n")
+ .Append(" add => backing_")
+ .Append(signalName)
+ .Append(" += value;\n")
+ .Append(" remove => backing_")
+ .Append(signalName)
+ .Append(" -= value;\n")
+ .Append("}\n");
+ }
+
+ source.Append("}\n"); // partial class
+
+ if (isInnerClass)
+ {
+ var containingType = symbol.ContainingType;
+
+ while (containingType != null)
+ {
+ source.Append("}\n"); // outer class
+
+ containingType = containingType.ContainingType;
+ }
+ }
+
+ if (hasNamespace)
+ {
+ source.Append("\n}\n");
+ }
+
+ context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
+ }
+
+ private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo)
+ {
+ source.Append(" signals.Add(new(name: GodotInternal.SignalName_")
+ .Append(methodInfo.Name)
+ .Append(", returnVal: ");
+
+ AppendPropertyInfo(source, methodInfo.ReturnVal);
+
+ source.Append(", flags: (Godot.MethodFlags)")
+ .Append((int)methodInfo.Flags)
+ .Append(", arguments: ");
+
+ if (methodInfo.Arguments is { Count: > 0 })
+ {
+ source.Append("new() { ");
+
+ foreach (var param in methodInfo.Arguments)
+ {
+ AppendPropertyInfo(source, param);
+
+ // C# allows colon after the last element
+ source.Append(", ");
+ }
+
+ source.Append(" }");
+ }
+ else
+ {
+ source.Append("null");
+ }
+
+ source.Append(", defaultArguments: null));\n");
+ }
+
+ private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
+ {
+ source.Append("new(type: (Godot.Variant.Type)")
+ .Append((int)propertyInfo.Type)
+ .Append(", name: \"")
+ .Append(propertyInfo.Name)
+ .Append("\", hint: (Godot.PropertyHint)")
+ .Append((int)propertyInfo.Hint)
+ .Append(", hintString: \"")
+ .Append(propertyInfo.HintString)
+ .Append("\", usage: (Godot.PropertyUsageFlags)")
+ .Append((int)propertyInfo.Usage)
+ .Append(", exported: ")
+ .Append(propertyInfo.Exported ? "true" : "false")
+ .Append(")");
+ }
+
+ private static MethodInfo DetermineMethodInfo(GodotSignalDelegateData signalDelegateData)
+ {
+ var invokeMethodData = signalDelegateData.InvokeMethodData;
+
+ PropertyInfo returnVal;
+
+ if (invokeMethodData.RetType != null)
+ {
+ returnVal = DeterminePropertyInfo(invokeMethodData.RetType.Value, name: string.Empty);
+ }
+ else
+ {
+ returnVal = new PropertyInfo(VariantType.Nil, string.Empty, PropertyHint.None,
+ hintString: null, PropertyUsageFlags.Default, exported: false);
+ }
+
+ int paramCount = invokeMethodData.ParamTypes.Length;
+
+ List<PropertyInfo>? arguments;
+
+ if (paramCount > 0)
+ {
+ arguments = new(capacity: paramCount);
+
+ for (int i = 0; i < paramCount; i++)
+ {
+ arguments.Add(DeterminePropertyInfo(invokeMethodData.ParamTypes[i],
+ name: invokeMethodData.Method.Parameters[i].Name));
+ }
+ }
+ else
+ {
+ arguments = null;
+ }
+
+ return new MethodInfo(signalDelegateData.Name, returnVal, MethodFlags.Default, arguments,
+ defaultArguments: null);
+ }
+
+ private static PropertyInfo DeterminePropertyInfo(MarshalType marshalType, string name)
+ {
+ var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value;
+
+ var propUsage = PropertyUsageFlags.Default;
+
+ if (memberVariantType == VariantType.Nil)
+ propUsage |= PropertyUsageFlags.NilIsVariant;
+
+ return new PropertyInfo(memberVariantType, name,
+ PropertyHint.None, string.Empty, propUsage, exported: false);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
index bac5464fd6..5ba3e3e4c0 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
@@ -22,7 +22,8 @@ namespace GodotTools.Build
public string ProjectFile { get; set; }
}
- [Signal] public event Action BuildStateChanged;
+ [Signal]
+ public delegate void BuildStateChangedEventHandler();
public bool HasBuildExited { get; private set; } = false;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
index 3a1a8ac563..afe295126e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
@@ -98,7 +98,11 @@ namespace Godot.Collections
{
// Always dispose `NativeValue` even if disposing is true
NativeValue.DangerousSelfRef.Dispose();
- DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
+
+ if (_weakReferenceToSelf != null)
+ {
+ DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
+ }
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs
index 26fbed8cac..53aeff8207 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs
@@ -46,22 +46,34 @@ public class GodotSerializationInfo
}
}
- public Delegate GetSignalEventDelegate(StringName name)
+ public bool TryGetSignalEventDelegate<T>(StringName name, [MaybeNullWhen(false)] out T value)
+ where T : Delegate
{
- if (DelegateUtils.TryDeserializeDelegate(_signalEvents[name], out var eventDelegate))
+ if (_signalEvents.TryGetValue(name, out Collections.Array serializedData))
{
- return eventDelegate;
- }
- else if (OS.IsStdoutVerbose())
- {
- Console.WriteLine($"Failed to deserialize event signal delegate: {name}");
- }
+ if (DelegateUtils.TryDeserializeDelegate(serializedData, out var eventDelegate))
+ {
+ value = eventDelegate as T;
- return null;
- }
+ if (value == null)
+ {
+ Console.WriteLine($"Cannot cast the deserialized event signal delegate: {name}. " +
+ $"Expected '{typeof(T).FullName}'; got '{eventDelegate.GetType().FullName}'.");
+ return false;
+ }
- public IEnumerable<StringName> GetSignalEventsList()
- {
- return _signalEvents.Keys;
+ return true;
+ }
+ else if (OS.IsStdoutVerbose())
+ {
+ Console.WriteLine($"Failed to deserialize event signal delegate: {name}");
+ }
+
+ value = null;
+ return false;
+ }
+
+ value = null;
+ return false;
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index a6e5f6da1a..5d3f140cba 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
@@ -19,14 +19,12 @@ namespace Godot.Bridge
public delegate* unmanaged<IntPtr, godot_string_name*, void> ScriptManagerBridge_GetScriptNativeName;
public delegate* unmanaged<IntPtr, IntPtr, void> ScriptManagerBridge_SetGodotObjectPtr;
public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_bool*, void> ScriptManagerBridge_RaiseEventSignal;
- public delegate* unmanaged<IntPtr, godot_dictionary*, void> ScriptManagerBridge_GetScriptSignalList;
- public delegate* unmanaged<IntPtr, godot_string*, godot_bool> ScriptManagerBridge_HasScriptSignal;
public delegate* unmanaged<IntPtr, IntPtr, godot_bool> ScriptManagerBridge_ScriptIsOrInherits;
public delegate* unmanaged<IntPtr, godot_string*, godot_bool> ScriptManagerBridge_AddScriptBridge;
public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
- public delegate* unmanaged<IntPtr, godot_bool*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
+ public delegate* unmanaged<IntPtr, godot_bool*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList;
public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues;
@@ -60,8 +58,6 @@ namespace Godot.Bridge
ScriptManagerBridge_GetScriptNativeName = &ScriptManagerBridge.GetScriptNativeName,
ScriptManagerBridge_SetGodotObjectPtr = &ScriptManagerBridge.SetGodotObjectPtr,
ScriptManagerBridge_RaiseEventSignal = &ScriptManagerBridge.RaiseEventSignal,
- ScriptManagerBridge_GetScriptSignalList = &ScriptManagerBridge.GetScriptSignalList,
- ScriptManagerBridge_HasScriptSignal = &ScriptManagerBridge.HasScriptSignal,
ScriptManagerBridge_ScriptIsOrInherits = &ScriptManagerBridge.ScriptIsOrInherits,
ScriptManagerBridge_AddScriptBridge = &ScriptManagerBridge.AddScriptBridge,
ScriptManagerBridge_GetOrCreateScriptBridgeForPath = &ScriptManagerBridge.GetOrCreateScriptBridgeForPath,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs
new file mode 100644
index 0000000000..50260163bd
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+
+namespace Godot.Bridge;
+
+#nullable enable
+
+public struct MethodInfo
+{
+ public StringName Name { get; init; }
+ public PropertyInfo ReturnVal { get; init; }
+ public MethodFlags Flags { get; init; }
+ public int Id { get; init; } = 0;
+ public List<PropertyInfo>? Arguments { get; init; }
+ public List<object>? DefaultArguments { get; init; }
+
+ public MethodInfo(StringName name, PropertyInfo returnVal, MethodFlags flags, List<PropertyInfo>? arguments,
+ List<object>? defaultArguments)
+ {
+ Name = name;
+ ReturnVal = returnVal;
+ Flags = flags;
+ Arguments = arguments;
+ DefaultArguments = defaultArguments;
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs
index cfdfe2dab3..80d6f7b4a5 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs
@@ -1,26 +1,24 @@
-using System;
-using Godot.NativeInterop;
+namespace Godot.Bridge;
-namespace Godot.Bridge
+#nullable enable
+
+public struct PropertyInfo
{
- public struct PropertyInfo
- {
- public Variant.Type Type { get; init; }
- public StringName Name { get; init; }
- public PropertyHint Hint { get; init; }
- public string HintString { get; init; }
- public PropertyUsageFlags Usage { get; init; }
- public bool Exported { get; init; }
+ public Variant.Type Type { get; init; }
+ public StringName Name { get; init; }
+ public PropertyHint Hint { get; init; }
+ public string HintString { get; init; }
+ public PropertyUsageFlags Usage { get; init; }
+ public bool Exported { get; init; }
- public PropertyInfo(Variant.Type type, StringName name, PropertyHint hint, string hintString,
- PropertyUsageFlags usage, bool exported)
- {
- Type = type;
- Name = name;
- Hint = hint;
- HintString = hintString;
- Usage = usage;
- Exported = exported;
- }
+ public PropertyInfo(Variant.Type type, StringName name, PropertyHint hint, string hintString,
+ PropertyUsageFlags usage, bool exported)
+ {
+ Type = type;
+ Name = name;
+ Hint = hint;
+ HintString = hintString;
+ Usage = usage;
+ Exported = exported;
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 40f1235e7e..4b9f851925 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Concurrent;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
@@ -9,7 +10,6 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Runtime.Serialization;
-using Godot.Collections;
using Godot.NativeInterop;
namespace Godot.Bridge
@@ -337,7 +337,7 @@ namespace Godot.Bridge
*outOwnerIsNull = godot_bool.False;
- owner.InternalRaiseEventSignal(CustomUnsafe.AsRef(eventSignalName),
+ owner.RaiseGodotClassSignalCallbacks(CustomUnsafe.AsRef(eventSignalName),
new NativeVariantPtrArgs(args), argCount);
}
catch (Exception e)
@@ -348,151 +348,6 @@ namespace Godot.Bridge
}
[UnmanagedCallersOnly]
- internal static unsafe void GetScriptSignalList(IntPtr scriptPtr, godot_dictionary* outRetSignals)
- {
- try
- {
- // Performance is not critical here as this will be replaced with source generators.
- using var signals = new Dictionary();
-
- Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr);
- Type native = Object.InternalGetClassNativeBase(top);
-
- while (top != null && top != native)
- {
- // Legacy signals
-
- foreach (var signalDelegate in top
- .GetNestedTypes(BindingFlags.DeclaredOnly | BindingFlags.NonPublic |
- BindingFlags.Public)
- .Where(nestedType => typeof(Delegate).IsAssignableFrom(nestedType))
- .Where(@delegate => @delegate.GetCustomAttributes().OfType<SignalAttribute>().Any()))
- {
- var invokeMethod = signalDelegate.GetMethod("Invoke");
-
- if (invokeMethod == null)
- throw new MissingMethodException(signalDelegate.FullName, "Invoke");
-
- var signalParams = new Collections.Array();
-
- foreach (var parameters in invokeMethod.GetParameters())
- {
- var paramType = Marshaling.ConvertManagedTypeToVariantType(
- parameters.ParameterType, out bool nilIsVariant);
- signalParams.Add(new Dictionary()
- {
- { "name", parameters.Name },
- { "type", paramType },
- { "nil_is_variant", nilIsVariant }
- });
- }
-
- signals.Add(signalDelegate.Name, signalParams);
- }
-
- // Event signals
-
- var foundEventSignals = top.GetEvents(
- BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public)
- .Where(ev => ev.GetCustomAttributes().OfType<SignalAttribute>().Any())
- .Select(ev => ev.Name);
-
- var fields = top.GetFields(
- BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public);
-
- foreach (var eventSignalField in fields
- .Where(f => typeof(Delegate).IsAssignableFrom(f.FieldType))
- .Where(f => foundEventSignals.Contains(f.Name)))
- {
- var delegateType = eventSignalField.FieldType;
- var invokeMethod = delegateType.GetMethod("Invoke");
-
- if (invokeMethod == null)
- throw new MissingMethodException(delegateType.FullName, "Invoke");
-
- var signalParams = new Collections.Array();
-
- foreach (var parameters in invokeMethod.GetParameters())
- {
- var paramType = Marshaling.ConvertManagedTypeToVariantType(
- parameters.ParameterType, out bool nilIsVariant);
- signalParams.Add(new Dictionary()
- {
- { "name", parameters.Name },
- { "type", paramType },
- { "nil_is_variant", nilIsVariant }
- });
- }
-
- signals.Add(eventSignalField.Name, signalParams);
- }
-
- top = top.BaseType;
- }
-
- *outRetSignals = NativeFuncs.godotsharp_dictionary_new_copy((godot_dictionary)signals.NativeValue);
- }
- catch (Exception e)
- {
- ExceptionUtils.LogException(e);
- *outRetSignals = NativeFuncs.godotsharp_dictionary_new();
- }
- }
-
- [UnmanagedCallersOnly]
- internal static unsafe godot_bool HasScriptSignal(IntPtr scriptPtr, godot_string* signalName)
- {
- try
- {
- // Performance is not critical here as this will be replaced with source generators.
- using var signals = new Dictionary();
-
- string signalNameStr = Marshaling.ConvertStringToManaged(*signalName);
-
- Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr);
- Type native = Object.InternalGetClassNativeBase(top);
-
- while (top != null && top != native)
- {
- // Legacy signals
-
- if (top
- .GetNestedTypes(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public)
- .Where(nestedType => typeof(Delegate).IsAssignableFrom(nestedType))
- .Where(@delegate => @delegate.GetCustomAttributes().OfType<SignalAttribute>().Any())
- .Any(signalDelegate => signalDelegate.Name == signalNameStr)
- )
- {
- return godot_bool.True;
- }
-
- // Event signals
-
- if (top.GetEvents(
- BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public)
- .Where(ev => ev.GetCustomAttributes().OfType<SignalAttribute>().Any())
- .Any(eventSignal => eventSignal.Name == signalNameStr)
- )
- {
- return godot_bool.True;
- }
-
- top = top.BaseType;
- }
-
- return godot_bool.False;
- }
- catch (Exception e)
- {
- ExceptionUtils.LogException(e);
- return godot_bool.False;
- }
- }
-
- [UnmanagedCallersOnly]
internal static godot_bool ScriptIsOrInherits(IntPtr scriptPtr, IntPtr scriptPtrMaybeBase)
{
try
@@ -702,7 +557,7 @@ namespace Godot.Bridge
[UnmanagedCallersOnly]
internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool* outTool,
- godot_dictionary* outRpcFunctionsDest, godot_ref* outBaseScript)
+ godot_dictionary* outRpcFunctionsDest, godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript)
{
try
{
@@ -725,7 +580,7 @@ namespace Godot.Bridge
// RPC functions
- Dictionary<string, Dictionary> rpcFunctions = new();
+ Collections.Dictionary<string, Collections.Dictionary> rpcFunctions = new();
Type? top = scriptType;
Type native = Object.InternalGetClassNativeBase(top);
@@ -749,7 +604,7 @@ namespace Godot.Bridge
if (rpcAttr == null)
continue;
- var rpcConfig = new Dictionary();
+ var rpcConfig = new Collections.Dictionary();
rpcConfig["rpc_mode"] = (long)rpcAttr.Mode;
rpcConfig["call_local"] = rpcAttr.CallLocal;
@@ -764,7 +619,54 @@ namespace Godot.Bridge
*outRpcFunctionsDest =
NativeFuncs.godotsharp_dictionary_new_copy(
- (godot_dictionary)((Dictionary)rpcFunctions).NativeValue);
+ (godot_dictionary)((Collections.Dictionary)rpcFunctions).NativeValue);
+
+ // Event signals
+
+ // Performance is not critical here as this will be replaced with source generators.
+ using var signals = new Collections.Dictionary();
+
+ top = scriptType;
+
+ while (top != null && top != native)
+ {
+ var signalList = GetSignalListForType(top);
+
+ if (signalList != null)
+ {
+ foreach (var signal in signalList)
+ {
+ string signalName = signal.Name;
+
+ if (signals.ContainsKey(signalName))
+ continue;
+
+ var signalParams = new Collections.Array();
+
+ if (signal.Arguments != null)
+ {
+ foreach (var param in signal.Arguments)
+ {
+ signalParams.Add(new Collections.Dictionary()
+ {
+ { "name", param.Name },
+ { "type", param.Type },
+ { "usage", param.Usage }
+ });
+ }
+ }
+
+ signals.Add(signalName, signalParams);
+ }
+ }
+
+ top = top.BaseType;
+ }
+
+ *outEventSignalsDest =
+ NativeFuncs.godotsharp_dictionary_new_copy((godot_dictionary)signals.NativeValue);
+
+ // Base script
var baseType = scriptType.BaseType;
if (baseType != null && baseType != native)
@@ -781,41 +683,22 @@ namespace Godot.Bridge
ExceptionUtils.LogException(e);
*outTool = godot_bool.False;
*outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new();
+ *outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new();
+ *outBaseScript = default;
}
}
- [UnmanagedCallersOnly]
- internal static unsafe godot_bool SwapGCHandleForType(IntPtr oldGCHandlePtr, IntPtr* outNewGCHandlePtr,
- godot_bool createWeak)
+ private static List<MethodInfo>? GetSignalListForType(Type type)
{
- try
- {
- var oldGCHandle = GCHandle.FromIntPtr(oldGCHandlePtr);
-
- object? target = oldGCHandle.Target;
-
- if (target == null)
- {
- CustomGCHandle.Free(oldGCHandle);
- *outNewGCHandlePtr = IntPtr.Zero;
- return godot_bool.False; // Called after the managed side was collected, so nothing to do here
- }
+ var getGodotSignalListMethod = type.GetMethod(
+ "GetGodotSignalList",
+ BindingFlags.DeclaredOnly | BindingFlags.Static |
+ BindingFlags.NonPublic | BindingFlags.Public);
- // Release the current weak handle and replace it with a strong handle.
- var newGCHandle = createWeak.ToBool() ?
- CustomGCHandle.AllocWeak(target) :
- CustomGCHandle.AllocStrong(target);
+ if (getGodotSignalListMethod == null)
+ return null;
- CustomGCHandle.Free(oldGCHandle);
- *outNewGCHandlePtr = GCHandle.ToIntPtr(newGCHandle);
- return godot_bool.True;
- }
- catch (Exception e)
- {
- ExceptionUtils.LogException(e);
- *outNewGCHandlePtr = IntPtr.Zero;
- return godot_bool.False;
- }
+ return (List<MethodInfo>?)getGodotSignalListMethod.Invoke(null, null);
}
// ReSharper disable once InconsistentNaming
@@ -857,16 +740,16 @@ namespace Godot.Bridge
{
try
{
- var getGodotPropertiesMetadataMethod = type.GetMethod(
- "GetGodotPropertiesMetadata",
+ var getGodotPropertyListMethod = type.GetMethod(
+ "GetGodotPropertyList",
BindingFlags.DeclaredOnly | BindingFlags.Static |
BindingFlags.NonPublic | BindingFlags.Public);
- if (getGodotPropertiesMetadataMethod == null)
+ if (getGodotPropertyListMethod == null)
return;
- var properties = (System.Collections.Generic.List<PropertyInfo>?)
- getGodotPropertiesMetadataMethod.Invoke(null, null);
+ var properties = (List<PropertyInfo>?)
+ getGodotPropertyListMethod.Invoke(null, null);
if (properties == null || properties.Count <= 0)
return;
@@ -985,7 +868,7 @@ namespace Godot.Bridge
if (getGodotPropertyDefaultValuesMethod == null)
return;
- var defaultValues = (System.Collections.Generic.Dictionary<StringName, object>?)
+ var defaultValues = (Dictionary<StringName, object>?)
getGodotPropertyDefaultValuesMethod.Invoke(null, null);
if (defaultValues == null || defaultValues.Count <= 0)
@@ -1048,5 +931,39 @@ namespace Godot.Bridge
ExceptionUtils.LogException(e);
}
}
+
+ [UnmanagedCallersOnly]
+ internal static unsafe godot_bool SwapGCHandleForType(IntPtr oldGCHandlePtr, IntPtr* outNewGCHandlePtr,
+ godot_bool createWeak)
+ {
+ try
+ {
+ var oldGCHandle = GCHandle.FromIntPtr(oldGCHandlePtr);
+
+ object? target = oldGCHandle.Target;
+
+ if (target == null)
+ {
+ CustomGCHandle.Free(oldGCHandle);
+ *outNewGCHandlePtr = IntPtr.Zero;
+ return godot_bool.False; // Called after the managed side was collected, so nothing to do here
+ }
+
+ // Release the current weak handle and replace it with a strong handle.
+ var newGCHandle = createWeak.ToBool() ?
+ CustomGCHandle.AllocWeak(target) :
+ CustomGCHandle.AllocStrong(target);
+
+ CustomGCHandle.Free(oldGCHandle);
+ *outNewGCHandlePtr = GCHandle.ToIntPtr(newGCHandle);
+ return godot_bool.True;
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.LogException(e);
+ *outNewGCHandlePtr = IntPtr.Zero;
+ return godot_bool.False;
+ }
+ }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index 13aae72660..106cd7619f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -76,7 +76,11 @@ namespace Godot.Collections
{
// Always dispose `NativeValue` even if disposing is true
NativeValue.DangerousSelfRef.Dispose();
- DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
+
+ if (_weakReferenceToSelf != null)
+ {
+ DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
+ }
}
/// <summary>
@@ -704,7 +708,7 @@ namespace Godot.Collections
return found;
}
- // TODO: This is temporary. It's needed for the serialization generator. It won't be needed once we replace Sysme.Object with a Variant type.
+ // TODO: This is temporary. It's needed for the serialization generator. It won't be needed once we replace System.Object with a Variant type.
internal bool TryGetValueAsType<TValueCustom>(TKey key, [MaybeNullWhen(false)] out TValueCustom value)
{
using godot_variant variantKey = Marshaling.ConvertManagedObjectToVariant(key);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
index 1ab2b4c0bf..b2df6e16b1 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
@@ -63,10 +63,6 @@ namespace Godot.NativeInterop
godot_bool isFinalizer);
[DllImport(GodotDllName)]
- internal static extern void godotsharp_internal_object_connect_event_signal(IntPtr obj,
- in godot_string_name eventSignal);
-
- [DllImport(GodotDllName)]
internal static extern Error godotsharp_internal_signal_awaiter_connect(IntPtr source,
in godot_string_name signal,
IntPtr target, IntPtr awaiterHandlePtr);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
index 6edc19c4d6..b02bd167a1 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
@@ -63,7 +63,11 @@ namespace Godot
{
// Always dispose `NativeValue` even if disposing is true
NativeValue.DangerousSelfRef.Dispose();
- DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
+
+ if (_weakReferenceToSelf != null)
+ {
+ DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
+ }
}
private NodePath(godot_node_path nativeValueToOwn)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
index 8d683c6d1e..dd3f8477d9 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
@@ -49,30 +49,6 @@ namespace Godot
}
_weakReferenceToSelf = DisposablesTracker.RegisterGodotObject(this);
-
- _InitializeGodotScriptInstanceInternals();
- }
-
- internal void _InitializeGodotScriptInstanceInternals()
- {
- // Performance is not critical here as this will be replaced with source generators.
- Type top = GetType();
- Type native = InternalGetClassNativeBase(top);
-
- while (top != null && top != native)
- {
- foreach (var eventSignal in top.GetEvents(
- BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public)
- .Where(ev => ev.GetCustomAttributes().OfType<SignalAttribute>().Any()))
- {
- using var eventSignalName = new StringName(eventSignal.Name);
- var eventSignalNameSelf = (godot_string_name)eventSignalName.NativeValue;
- NativeFuncs.godotsharp_internal_object_connect_event_signal(NativePtr, eventSignalNameSelf);
- }
-
- top = top.BaseType;
- }
}
internal Object(bool memoryOwn)
@@ -238,72 +214,10 @@ namespace Godot
return false;
}
- internal void InternalRaiseEventSignal(in godot_string_name eventSignalName, NativeVariantPtrArgs args,
- int argc)
+ // ReSharper disable once VirtualMemberNeverOverridden.Global
+ protected internal virtual void RaiseGodotClassSignalCallbacks(in godot_string_name signal,
+ NativeVariantPtrArgs args, int argCount)
{
- // Performance is not critical here as this will be replaced with source generators.
-
- using var stringName = StringName.CreateTakingOwnershipOfDisposableValue(
- NativeFuncs.godotsharp_string_name_new_copy(eventSignalName));
- string eventSignalNameStr = stringName.ToString();
-
- Type top = GetType();
- Type native = InternalGetClassNativeBase(top);
-
- while (top != null && top != native)
- {
- var foundEventSignals = top.GetEvents(
- BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public)
- .Where(ev => ev.GetCustomAttributes().OfType<SignalAttribute>().Any())
- .Select(ev => ev.Name);
-
- var fields = top.GetFields(
- BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public);
-
- var eventSignalField = fields
- .Where(f => typeof(Delegate).IsAssignableFrom(f.FieldType))
- .Where(f => foundEventSignals.Contains(f.Name))
- .FirstOrDefault(f => f.Name == eventSignalNameStr);
-
- if (eventSignalField != null)
- {
- var @delegate = (Delegate)eventSignalField.GetValue(this);
-
- if (@delegate == null)
- continue;
-
- var delegateType = eventSignalField.FieldType;
-
- var invokeMethod = delegateType.GetMethod("Invoke");
-
- if (invokeMethod == null)
- throw new MissingMethodException(delegateType.FullName, "Invoke");
-
- var parameterInfos = invokeMethod.GetParameters();
- var paramsLength = parameterInfos.Length;
-
- if (argc != paramsLength)
- {
- throw new InvalidOperationException(
- $"The event delegate expects {paramsLength} arguments, but received {argc}.");
- }
-
- var managedArgs = new object[argc];
-
- for (int i = 0; i < argc; i++)
- {
- managedArgs[i] = Marshaling.ConvertVariantToManagedObjectOfType(
- args[i], parameterInfos[i].ParameterType);
- }
-
- invokeMethod.Invoke(@delegate, managedArgs);
- return;
- }
-
- top = top.BaseType;
- }
}
internal static IntPtr ClassDB_get_method(StringName type, StringName method)
@@ -332,74 +246,11 @@ namespace Godot
protected internal virtual void SaveGodotObjectData(GodotSerializationInfo info)
{
- // Temporary solution via reflection until we add a signals events source generator
-
- Type top = GetType();
- Type native = InternalGetClassNativeBase(top);
-
- while (top != null && top != native)
- {
- var foundEventSignals = top.GetEvents(
- BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public)
- .Where(ev => ev.GetCustomAttributes().OfType<SignalAttribute>().Any())
- .Select(ev => ev.Name);
-
- var fields = top.GetFields(
- BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public);
-
- foreach (var eventSignalField in fields
- .Where(f => typeof(Delegate).IsAssignableFrom(f.FieldType))
- .Where(f => foundEventSignals.Contains(f.Name)))
- {
- var eventSignalDelegate = (Delegate)eventSignalField.GetValue(this);
- info.AddSignalEventDelegate(eventSignalField.Name, eventSignalDelegate);
- }
-
- top = top.BaseType;
- }
}
// TODO: Should this be a constructor overload?
protected internal virtual void RestoreGodotObjectData(GodotSerializationInfo info)
{
- // Temporary solution via reflection until we add a signals events source generator
-
- void RestoreSignalEvent(StringName signalEventName)
- {
- Type top = GetType();
- Type native = InternalGetClassNativeBase(top);
-
- while (top != null && top != native)
- {
- var foundEventSignal = top.GetEvent(signalEventName,
- BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public);
-
- if (foundEventSignal != null &&
- foundEventSignal.GetCustomAttributes().OfType<SignalAttribute>().Any())
- {
- var field = top.GetField(foundEventSignal.Name,
- BindingFlags.DeclaredOnly | BindingFlags.Instance |
- BindingFlags.NonPublic | BindingFlags.Public);
-
- if (field != null && typeof(Delegate).IsAssignableFrom(field.FieldType))
- {
- var eventSignalDelegate = info.GetSignalEventDelegate(signalEventName);
- field.SetValue(this, eventSignalDelegate);
- return;
- }
- }
-
- top = top.BaseType;
- }
- }
-
- foreach (var signalEventName in info.GetSignalEventsList())
- {
- RestoreSignalEvent(signalEventName);
- }
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
index b993a1b3e9..10739c02a7 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
@@ -34,7 +34,11 @@ namespace Godot
{
// Always dispose `NativeValue` even if disposing is true
NativeValue.DangerousSelfRef.Dispose();
- DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
+
+ if (_weakReferenceToSelf != null)
+ {
+ DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
+ }
}
private StringName(godot_string_name nativeValueToOwn)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index d0897fe85e..d95b839767 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -50,6 +50,7 @@
<ItemGroup>
<Compile Include="Core\AABB.cs" />
<Compile Include="Core\Bridge\GodotSerializationInfo.cs" />
+ <Compile Include="Core\Bridge\MethodInfo.cs" />
<Compile Include="Core\CustomGCHandle.cs" />
<Compile Include="Core\Array.cs" />
<Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index 637db00706..fc28e7beb2 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/modules/mono/glue/runtime_interop.cpp
@@ -208,13 +208,6 @@ GD_PINVOKE_EXPORT void godotsharp_internal_refcounted_disposed(Object *p_ptr, GC
}
}
-GD_PINVOKE_EXPORT void godotsharp_internal_object_connect_event_signal(Object *p_ptr, const StringName *p_event_signal) {
- CSharpInstance *csharp_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance());
- if (csharp_instance) {
- csharp_instance->connect_event_signal(*p_event_signal);
- }
-}
-
GD_PINVOKE_EXPORT int32_t godotsharp_internal_signal_awaiter_connect(Object *p_source, StringName *p_signal, Object *p_target, GCHandleIntPtr p_awaiter_handle_ptr) {
StringName signal = p_signal ? *p_signal : StringName();
return (int32_t)gd_mono_connect_signal_awaiter(p_source, signal, p_target, p_awaiter_handle_ptr);
@@ -1322,7 +1315,7 @@ GD_PINVOKE_EXPORT void godotsharp_object_to_string(Object *p_ptr, godot_string *
#endif
// We need this to prevent the functions from being stripped.
-void *godotsharp_pinvoke_funcs[186] = {
+void *godotsharp_pinvoke_funcs[185] = {
(void *)godotsharp_method_bind_get_method,
(void *)godotsharp_get_class_constructor,
(void *)godotsharp_engine_get_singleton,
@@ -1333,7 +1326,6 @@ void *godotsharp_pinvoke_funcs[186] = {
(void *)godotsharp_internal_object_get_associated_gchandle,
(void *)godotsharp_internal_object_disposed,
(void *)godotsharp_internal_refcounted_disposed,
- (void *)godotsharp_internal_object_connect_event_signal,
(void *)godotsharp_internal_signal_awaiter_connect,
(void *)godotsharp_internal_unmanaged_get_script_instance_managed,
(void *)godotsharp_internal_unmanaged_get_instance_binding_managed,
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index f5e38c2e61..b3ecee620f 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -299,16 +299,6 @@ using godot_plugins_initialize_fn = bool (*)(void *, bool, gdmono::PluginCallbac
using godot_plugins_initialize_fn = bool (*)(void *, GDMonoCache::ManagedCallbacks *);
#endif
-static String get_assembly_name() {
- String appname = ProjectSettings::get_singleton()->get("application/config/name");
- String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
- if (appname_safe.is_empty()) {
- appname_safe = "UnnamedProject";
- }
-
- return appname_safe;
-}
-
#ifdef TOOLS_ENABLED
godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
@@ -338,6 +328,16 @@ godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime
return godot_plugins_initialize;
}
#else
+static String get_assembly_name() {
+ String appname = ProjectSettings::get_singleton()->get("application/config/name");
+ String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+ if (appname_safe.is_empty()) {
+ appname_safe = "UnnamedProject";
+ }
+
+ return appname_safe;
+}
+
godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp
index 37433c59ee..bfd803f326 100644
--- a/modules/mono/mono_gd/gd_mono_cache.cpp
+++ b/modules/mono/mono_gd/gd_mono_cache.cpp
@@ -60,8 +60,6 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetScriptNativeName);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SetGodotObjectPtr);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RaiseEventSignal);
- CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetScriptSignalList);
- CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, HasScriptSignal);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, ScriptIsOrInherits);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, AddScriptBridge);
CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetOrCreateScriptBridgeForPath);
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index 92778cc2c9..3c58c251b7 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -82,14 +82,12 @@ struct ManagedCallbacks {
using FuncScriptManagerBridge_GetScriptNativeName = void(GD_CLR_STDCALL *)(const CSharpScript *, StringName *);
using FuncScriptManagerBridge_SetGodotObjectPtr = void(GD_CLR_STDCALL *)(GCHandleIntPtr, Object *);
using FuncScriptManagerBridge_RaiseEventSignal = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, bool *);
- using FuncScriptManagerBridge_GetScriptSignalList = void(GD_CLR_STDCALL *)(const CSharpScript *, Dictionary *);
- using FuncScriptManagerBridge_HasScriptSignal = bool(GD_CLR_STDCALL *)(const CSharpScript *, const String *);
using FuncScriptManagerBridge_ScriptIsOrInherits = bool(GD_CLR_STDCALL *)(const CSharpScript *, const CSharpScript *);
using FuncScriptManagerBridge_AddScriptBridge = bool(GD_CLR_STDCALL *)(const CSharpScript *, const String *);
using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *);
using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *);
using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *);
- using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Dictionary *, Ref<CSharpScript> *);
+ using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Dictionary *, Dictionary *, Ref<CSharpScript> *);
using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool);
using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add);
using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add);
@@ -117,8 +115,6 @@ struct ManagedCallbacks {
FuncScriptManagerBridge_GetScriptNativeName ScriptManagerBridge_GetScriptNativeName;
FuncScriptManagerBridge_SetGodotObjectPtr ScriptManagerBridge_SetGodotObjectPtr;
FuncScriptManagerBridge_RaiseEventSignal ScriptManagerBridge_RaiseEventSignal;
- FuncScriptManagerBridge_GetScriptSignalList ScriptManagerBridge_GetScriptSignalList;
- FuncScriptManagerBridge_HasScriptSignal ScriptManagerBridge_HasScriptSignal;
FuncScriptManagerBridge_ScriptIsOrInherits ScriptManagerBridge_ScriptIsOrInherits;
FuncScriptManagerBridge_AddScriptBridge ScriptManagerBridge_AddScriptBridge;
FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath ScriptManagerBridge_GetOrCreateScriptBridgeForPath;