From 97713ff77a339faa72d54bd596e3d8c2b8520ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Rold=C3=A1n=20Etcheverry?= Date: Thu, 28 Jul 2022 17:41:47 +0200 Subject: 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); ``` --- modules/mono/csharp_script.cpp | 110 ++++--- modules/mono/csharp_script.h | 11 +- .../Godot.SourceGenerators.Sample/EventSignals.cs | 7 + .../Godot.NET.Sdk/Godot.SourceGenerators/Common.cs | 49 +++ .../Godot.SourceGenerators/ExtensionMethods.cs | 64 ++-- .../Godot.SourceGenerators/GodotClasses.cs | 1 + .../Godot.SourceGenerators/GodotEnums.cs | 14 + .../Godot.SourceGenerators/GodotMemberData.cs | 14 + .../Godot.SourceGenerators/MethodInfo.cs | 24 ++ .../Godot.SourceGenerators/PropertyInfo.cs | 23 ++ .../ScriptMemberInvokerGenerator.cs | 115 +++++-- .../ScriptPropertiesGenerator.cs | 31 +- .../ScriptSerializationGenerator.cs | 70 ++++ .../ScriptSignalsGenerator.cs | 360 +++++++++++++++++++++ .../GodotTools/GodotTools/Build/BuildOutputView.cs | 3 +- .../mono/glue/GodotSharp/GodotSharp/Core/Array.cs | 6 +- .../Core/Bridge/GodotSerializationInfo.cs | 38 ++- .../GodotSharp/Core/Bridge/ManagedCallbacks.cs | 6 +- .../GodotSharp/Core/Bridge/MethodInfo.cs | 25 ++ .../GodotSharp/Core/Bridge/PropertyInfo.cs | 40 ++- .../GodotSharp/Core/Bridge/ScriptManagerBridge.cs | 289 ++++++----------- .../glue/GodotSharp/GodotSharp/Core/Dictionary.cs | 8 +- .../GodotSharp/Core/NativeInterop/NativeFuncs.cs | 4 - .../glue/GodotSharp/GodotSharp/Core/NodePath.cs | 6 +- .../glue/GodotSharp/GodotSharp/Core/Object.base.cs | 155 +-------- .../glue/GodotSharp/GodotSharp/Core/StringName.cs | 6 +- .../glue/GodotSharp/GodotSharp/GodotSharp.csproj | 1 + modules/mono/glue/runtime_interop.cpp | 10 +- modules/mono/mono_gd/gd_mono.cpp | 20 +- modules/mono/mono_gd/gd_mono_cache.cpp | 2 - modules/mono/mono_gd/gd_mono_cache.h | 6 +- 31 files changed, 996 insertions(+), 522 deletions(-) create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs create mode 100644 modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs create mode 100644 modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs 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 p_script) { // Extract information about the script using the mono class. void CSharpScript::update_script_class_info(Ref 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 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 *r_signals) const { @@ -2477,38 +2530,17 @@ void CSharpScript::get_script_signal_list(List *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::get_script_event_signals() const { + if (!valid) { + return Vector(); } + + return event_signals; } bool CSharpScript::inherits_script(const Ref