diff options
author | Ignacio Roldán Etcheverry <ignalfonsore@gmail.com> | 2022-07-28 17:41:47 +0200 |
---|---|---|
committer | Ignacio Roldán Etcheverry <ignalfonsore@gmail.com> | 2022-08-22 03:36:52 +0200 |
commit | 97713ff77a339faa72d54bd596e3d8c2b8520ce0 (patch) | |
tree | 22e97aa97c7ff55d5e3cd92c3cc4130a041aa6ef | |
parent | f033764ffe5892f963a9416e8cbcfd0fb5225103 (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);
```
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; |