diff options
author | Ignacio Roldán Etcheverry <ignalfonsore@gmail.com> | 2021-09-12 20:21:15 +0200 |
---|---|---|
committer | Ignacio Roldán Etcheverry <ignalfonsore@gmail.com> | 2022-08-22 03:35:59 +0200 |
commit | 513ee857a938c466e0f7146f66db771b9c6e2024 (patch) | |
tree | 9b05c59a6d63f8cc18460e69c1a782ef6fe2713f /modules/mono/csharp_script.cpp | |
parent | 5e37d073bb86492e8c415964ffd554a2fa08920d (diff) | |
download | redot-engine-513ee857a938c466e0f7146f66db771b9c6e2024.tar.gz |
C#: Restructure code prior move to .NET Core
The main focus here was to remove the majority of code that relied on
Mono's embedding APIs, specially the reflection APIs. The embedding
APIs we still use are the bare minimum we need for things to work.
A lot of code was moved to C#. We no longer deal with any managed
objects (`MonoObject*`, and such) in native code, and all marshaling
is done in C#.
The reason for restructuring the code and move away from embedding APIs
is that once we move to .NET Core, we will be limited by the much more
minimal .NET hosting.
PERFORMANCE REGRESSIONS
-----------------------
Some parts of the code were written with little to no concern about
performance. This includes code that calls into script methods and
accesses script fields, properties and events.
The reason for this is that all of that will be moved to source
generators, so any work prior to that would be a waste of time.
DISABLED FEATURES
-----------------
Some code was removed as it no longer makes sense (or won't make sense
in the future).
Other parts were commented out with `#if 0`s and TODO warnings because
it doesn't make much sense to work on them yet as those parts will
change heavily when we switch to .NET Core but also when we start
introducing source generators.
As such, the following features were disabled temporarily:
- Assembly-reloading (will be done with ALCs in .NET Core).
- Properties/fields exports and script method listing (will be
handled by source generators in the future).
- Exception logging in the editor and stack info for errors.
- Exporting games.
- Building of C# projects. We no longer copy the Godot API assemblies
to the project directory, so MSBuild won't be able to find them. The
idea is to turn them into NuGet packages in the future, which could
also be obtained from local NuGet sources during development.
Diffstat (limited to 'modules/mono/csharp_script.cpp')
-rw-r--r-- | modules/mono/csharp_script.cpp | 1311 |
1 files changed, 509 insertions, 802 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 4316f38bd2..b088271b18 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -59,8 +59,6 @@ #include "godotsharp_dirs.h" #include "managed_callable.h" #include "mono_gd/gd_mono_cache.h" -#include "mono_gd/gd_mono_class.h" -#include "mono_gd/gd_mono_marshal.h" #include "mono_gd/gd_mono_utils.h" #include "signal_awaiter_utils.h" #include "utils/macros.h" @@ -590,6 +588,8 @@ String CSharpLanguage::debug_get_stack_level_source(int p_level) const { return String(); } +#warning TODO +#if 0 Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() { #ifdef DEBUG_ENABLED // Printing an error here will result in endless recursion, so we must be careful @@ -682,6 +682,11 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec return si; } #endif +#else +Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() { + return Vector<StackInfo>(); +} +#endif void CSharpLanguage::post_unsafe_reference(Object *p_obj) { #ifdef DEBUG_ENABLED @@ -706,37 +711,13 @@ void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) { void CSharpLanguage::frame() { if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != nullptr) { MonoException *exc = nullptr; - gdmono->get_core_api_assembly() - ->get_class("Godot", "ScriptManager") - ->get_method("FrameCallback") - ->invoke(nullptr, &exc); - + GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_FrameCallback.invoke(&exc); if (exc) { GDMonoUtils::debug_unhandled_exception(exc); } } } -struct CSharpScriptDepSort { - // must support sorting so inheritance works properly (parent must be reloaded first) - bool operator()(const Ref<CSharpScript> &A, const Ref<CSharpScript> &B) const { - if (A == B) { - return false; // shouldn't happen but.. - } - GDMonoClass *I = B->base; - while (I) { - if (I == A->script_class) { - // A is a base of B - return true; - } - - I = I->get_parent_class(); - } - - return false; // not a base - } -}; - void CSharpLanguage::reload_all_scripts() { #ifdef GD_MONO_HOT_RELOAD if (is_assembly_reloading_needed()) { @@ -803,6 +784,8 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { return; } +#warning TODO ALCs after switching to .NET 6 +#if 0 // There is no soft reloading with Mono. It's always hard reloading. List<Ref<CSharpScript>> scripts; @@ -829,7 +812,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); MonoException *exc = nullptr; - bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TrySerializeDelegateWithGCHandle) + bool success = (bool)GDMonoCache::cached_data.methodthunk_DelegateUtils_TrySerializeDelegateWithGCHandle .invoke(managed_callable->delegate_handle, managed_serialized_data, &exc); @@ -909,7 +892,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance()); // Call OnBeforeSerialize - if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) { + if (csi->script->script_class->implements_interface(GDMonoCache::cached_data.class_ISerializationListener)) { obj->get_script_instance()->call(string_names.on_before_serialize); } @@ -977,7 +960,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #ifdef TOOLS_ENABLED script->exports_invalidated = true; #endif - script->signals_invalidated = true; if (!script->get_path().is_empty()) { script->reload(p_soft_reload); @@ -1011,7 +993,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { continue; } - bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(script_class); + bool obj_type = GDMonoCache::cached_data.class_GodotObject->is_assignable_from(script_class); if (!obj_type) { // The class no longer inherits Godot.Object, can't reload script->pending_reload_instances.clear(); @@ -1103,20 +1085,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { const StringName &name = G.first; const Array &serialized_data = G.second; - HashMap<StringName, CSharpScript::EventSignal>::Iterator match = script->event_signals.find(name); + HashMap<StringName, GDMonoField *>::Iterator match = script->event_signals.find(name); if (!match) { // The event or its signal attribute were removed continue; } - const CSharpScript::EventSignal &event_signal = match->value; + GDMonoField *event_signal_field = match->value; MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); MonoDelegate *delegate = nullptr; MonoException *exc = nullptr; - bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TryDeserializeDelegate).invoke(managed_serialized_data, &delegate, &exc); + bool success = (bool)GDMonoCache::cached_data.methodthunk_DelegateUtils_TryDeserializeDelegate.invoke(managed_serialized_data, &delegate, &exc); if (exc) { GDMonoUtils::debug_print_unhandled_exception(exc); @@ -1125,14 +1107,14 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { if (success) { ERR_CONTINUE(delegate == nullptr); - event_signal.field->set_value(csi->get_mono_object(), (MonoObject *)delegate); + event_signal_field->set_value(csi->get_mono_object(), (MonoObject *)delegate); } else if (OS::get_singleton()->is_stdout_verbose()) { OS::get_singleton()->print("Failed to deserialize event signal delegate\n"); } } // Call OnAfterDeserialization - if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) { + if (csi->script->script_class->implements_interface(GDMonoCache::cached_data.class_ISerializationListener)) { obj->get_script_instance()->call(string_names.on_after_deserialize); } } @@ -1153,7 +1135,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { void *delegate = nullptr; MonoException *exc = nullptr; - bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TryDeserializeDelegateWithGCHandle) + bool success = (bool)GDMonoCache::cached_data.methodthunk_DelegateUtils_TryDeserializeDelegateWithGCHandle .invoke(managed_serialized_data, &delegate, &exc); if (exc) { @@ -1179,62 +1161,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { NodeDock::get_singleton()->update_lists(); } #endif -} #endif - -void CSharpLanguage::lookup_script_for_class(GDMonoClass *p_class) { - if (!p_class->has_attribute(CACHED_CLASS(ScriptPathAttribute))) { - return; - } - - MonoObject *attr = p_class->get_attribute(CACHED_CLASS(ScriptPathAttribute)); - String path = CACHED_FIELD(ScriptPathAttribute, path)->get_string_value(attr); - - dotnet_script_lookup_map[path] = DotNetScriptLookupInfo( - p_class->get_namespace(), p_class->get_name(), p_class); -} - -void CSharpLanguage::lookup_scripts_in_assembly(GDMonoAssembly *p_assembly) { - if (p_assembly->has_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute))) { - MonoObject *attr = p_assembly->get_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute)); - bool requires_lookup = CACHED_FIELD(AssemblyHasScriptsAttribute, requiresLookup)->get_bool_value(attr); - - if (requires_lookup) { - // This is supported for scenarios where specifying all types would be cumbersome, - // such as when disabling C# source generators (for whatever reason) or when using a - // language other than C# that has nothing similar to source generators to automate it. - MonoImage *image = p_assembly->get_image(); - - int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF); - - for (int i = 1; i < rows; i++) { - // We don't search inner classes, only top-level. - MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF); - - if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) { - continue; - } - - GDMonoClass *current = p_assembly->get_class(mono_class); - if (current) { - lookup_script_for_class(current); - } - } - } else { - // This is the most likely scenario as we use C# source generators - MonoArray *script_types = (MonoArray *)CACHED_FIELD(AssemblyHasScriptsAttribute, scriptTypes)->get_value(attr); - - int length = mono_array_length(script_types); - - for (int i = 0; i < length; i++) { - MonoReflectionType *reftype = mono_array_get(script_types, MonoReflectionType *, i); - ManagedType type = ManagedType::from_reftype(reftype); - ERR_CONTINUE(!type.type_class); - lookup_script_for_class(type.type_class); - } - } - } } +#endif void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("cs"); @@ -1308,8 +1237,6 @@ void CSharpLanguage::_on_scripts_domain_about_to_unload() { } } #endif - - dotnet_script_lookup_map.clear(); } #ifdef TOOLS_ENABLED @@ -1318,18 +1245,20 @@ void CSharpLanguage::_editor_init_callback() { // Initialize GodotSharpEditor - GDMonoClass *editor_klass = GDMono::get_singleton()->get_tools_assembly()->get_class("GodotTools", "GodotSharpEditor"); + MonoClass *editor_klass = mono_class_from_name( + GDMono::get_singleton()->get_tools_assembly()->get_image(), + "GodotTools", "GodotSharpEditor"); CRASH_COND(editor_klass == nullptr); - MonoObject *mono_object = mono_object_new(mono_domain_get(), editor_klass->get_mono_ptr()); - CRASH_COND(mono_object == nullptr); + MonoMethod *create_instance = mono_class_get_method_from_name(editor_klass, "InternalCreateInstance", 0); + CRASH_COND(create_instance == nullptr); MonoException *exc = nullptr; - GDMonoUtils::runtime_object_init(mono_object, editor_klass, &exc); + MonoObject *ret = mono_runtime_invoke(create_instance, nullptr, nullptr, (MonoObject **)&exc); UNHANDLED_EXCEPTION(exc); - EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>( - GDMonoMarshal::mono_object_to_variant(mono_object).operator Object *()); + EditorPlugin *godotsharp_editor = *(EditorPlugin **)mono_object_unbox(ret); + CRASH_COND(godotsharp_editor == nullptr); // Enable it as a plugin @@ -1353,24 +1282,17 @@ void CSharpLanguage::release_script_gchandle(MonoGCHandleData &p_gchandle) { } } -void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, MonoGCHandleData &p_gchandle) { - uint32_t pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(p_expected_obj); // We might lock after this, so pin it +void CSharpLanguage::release_script_gchandle(void *p_expected_mono_obj_unused, MonoGCHandleData &p_gchandle) { +#warning KNOWN BUG. DO NOT USE THIS IN PRODUCTION + // KNOWN BUG: + // I removed the patch from commit e558e1ec09aa27852426bbd24dfa21e9b60cfbfc. + // This may cause data races. Re-implementing it without the Mono embedding API would be + // too painful and would make the code even more of a mess than it already was. + // We will switch from scripts to the new extension system before a release with .NET 6 support. + // The problem the old patch was working around won't be present at all with the new extension system. - if (!p_gchandle.is_released()) { // Do not lock unnecessarily - MutexLock lock(get_singleton()->script_gchandle_release_mutex); - - MonoObject *target = p_gchandle.get_target(); - - // We release the gchandle if it points to the MonoObject* we expect (otherwise it was - // already released and could have been replaced) or if we can't get its target MonoObject* - // (which doesn't necessarily mean it was released, and we want it released in order to - // avoid locking other threads unnecessarily). - if (target == p_expected_obj || target == nullptr) { - p_gchandle.release(); - } - } - - GDMonoUtils::free_gchandle(pinned_gchandle); + (void)p_expected_mono_obj_unused; + return release_script_gchandle(p_gchandle); } CSharpLanguage::CSharpLanguage() { @@ -1402,18 +1324,25 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b ERR_FAIL_NULL_V(classinfo, false); type_name = classinfo->name; - GDMonoClass *type_class = GDMonoUtils::type_get_proxy_class(type_name); + bool parent_is_object_class = ClassDB::is_parent_class(p_object->get_class_name(), type_name); + ERR_FAIL_COND_V_MSG(!parent_is_object_class, false, + "Type inherits from native type '" + type_name + "', so it can't be instantiated in object of type: '" + p_object->get_class() + "'."); - ERR_FAIL_NULL_V(type_class, false); + MonoException *exc = nullptr; + GCHandleIntPtr strong_gchandle = + GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_CreateManagedForGodotObjectBinding + .invoke(&type_name, p_object, &exc); - MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(type_class, type_name, p_object); + if (exc) { + GDMonoUtils::set_pending_exception(exc); + return false; + } - ERR_FAIL_NULL_V(mono_object, false); + ERR_FAIL_NULL_V(strong_gchandle.value, false); r_script_binding.inited = true; r_script_binding.type_name = type_name; - r_script_binding.wrapper_class = type_class; // cache - r_script_binding.gchandle = MonoGCHandleData::new_strong_handle(mono_object); + r_script_binding.gchandle = MonoGCHandleData(strong_gchandle, gdmono::GCHandleType::STRONG_HANDLE); r_script_binding.owner = p_object; // Tie managed to unmanaged @@ -1478,11 +1407,13 @@ void CSharpLanguage::_instance_binding_free_callback(void *, void *, void *p_bin if (script_binding.inited) { // Set the native instance field to IntPtr.Zero, if not yet garbage collected. // This is done to avoid trying to dispose the native instance from Dispose(bool). - MonoObject *mono_object = script_binding.gchandle.get_target(); - if (mono_object) { - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, nullptr); - } + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_SetGodotObjectPtr + .invoke(script_binding.gchandle.get_intptr(), nullptr, &exc); + UNHANDLED_EXCEPTION(exc); + script_binding.gchandle.release(); + script_binding.inited = false; } csharp_lang->script_bindings.erase(data); @@ -1517,15 +1448,23 @@ GDNativeBool CSharpLanguage::_instance_binding_reference_callback(void *p_token, // This means the owner is being referenced again by the unmanaged side, // so the owner must hold the managed side alive again to avoid it from being GCed. - MonoObject *target = gchandle.get_target(); - if (!target) { + // Release the current weak handle and replace it with a strong handle. + + GCHandleIntPtr old_gchandle = gchandle.get_intptr(); + gchandle.handle = GCHandleIntPtr(); // No longer owns the handle (released by swap function) + + GCHandleIntPtr new_gchandle; + bool create_weak = false; + MonoException *exc = nullptr; + bool target_alive = GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_SwapGCHandleForType + .invoke(old_gchandle, &new_gchandle, create_weak, &exc); + UNHANDLED_EXCEPTION(exc); + + if (!target_alive) { return 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. - MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(target); - gchandle.release(); - gchandle = strong_gchandle; + gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::STRONG_HANDLE); } return false; @@ -1537,15 +1476,23 @@ GDNativeBool CSharpLanguage::_instance_binding_reference_callback(void *p_token, // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. - MonoObject *target = gchandle.get_target(); - if (!target) { + // Release the current strong handle and replace it with a weak handle. + + GCHandleIntPtr old_gchandle = gchandle.get_intptr(); + gchandle.handle = GCHandleIntPtr(); // No longer owns the handle (released by swap function) + + GCHandleIntPtr new_gchandle; + bool create_weak = true; + MonoException *exc = nullptr; + bool target_alive = GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_SwapGCHandleForType + .invoke(old_gchandle, &new_gchandle, create_weak, &exc); + UNHANDLED_EXCEPTION(exc); + + if (!target_alive) { return refcount == 0; // Called after the managed side was collected, so nothing to do here } - // Release the current strong handle and replace it with a weak handle. - MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(target); - gchandle.release(); - gchandle = weak_gchandle; + gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::WEAK_HANDLE); return false; } @@ -1590,151 +1537,175 @@ void CSharpLanguage::set_instance_binding(Object *p_object, void *p_binding) { bool CSharpLanguage::has_instance_binding(Object *p_object) { return p_object->has_instance_binding(get_singleton()); } +void CSharpLanguage::tie_native_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, const StringName *p_native_name, bool p_ref_counted) { + // This method should not fail -CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) { - CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script))); + CRASH_COND(!p_unmanaged); - RefCounted *rc = Object::cast_to<RefCounted>(p_owner); + // All mono objects created from the managed world (e.g.: 'new Player()') + // need to have a CSharpScript in order for their methods to be callable from the unmanaged side - instance->base_ref_counted = rc != nullptr; - instance->owner = p_owner; - instance->gchandle = p_gchandle; + RefCounted *rc = Object::cast_to<RefCounted>(p_unmanaged); - if (instance->base_ref_counted) { - instance->_reference_owner_unsafe(); + CRASH_COND(p_ref_counted != (bool)rc); + + MonoGCHandleData gchandle = MonoGCHandleData(p_gchandle_intptr, + p_ref_counted ? gdmono::GCHandleType::WEAK_HANDLE : gdmono::GCHandleType::STRONG_HANDLE); + + // If it's just a wrapper Godot class and not a custom inheriting class, then attach a + // script binding instead. One of the advantages of this is that if a script is attached + // later and it's not a C# script, then the managed object won't have to be disposed. + // Another reason for doing this is that this instance could outlive CSharpLanguage, which would + // be problematic when using a script. See: https://github.com/godotengine/godot/issues/25621 + + CSharpScriptBinding script_binding; + + script_binding.inited = true; + script_binding.type_name = *p_native_name; + script_binding.gchandle = gchandle; + script_binding.owner = p_unmanaged; + + if (p_ref_counted) { + // Unsafe refcount increment. The managed instance also counts as a reference. + // This way if the unmanaged world has no references to our owner + // but the managed instance is alive, the refcount will be 1 instead of 0. + // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr) + + // May not me referenced yet, so we must use init_ref() instead of reference() + if (rc->init_ref()) { + CSharpLanguage::get_singleton()->post_unsafe_reference(rc); + } } - p_script->instances.insert(p_owner); + // The object was just created, no script instance binding should have been attached + CRASH_COND(CSharpLanguage::has_instance_binding(p_unmanaged)); - return instance; -} + void *data; + { + MutexLock lock(CSharpLanguage::get_singleton()->get_language_bind_mutex()); + data = (void *)CSharpLanguage::get_singleton()->insert_script_binding(p_unmanaged, script_binding); + } -MonoObject *CSharpInstance::get_mono_object() const { - ERR_FAIL_COND_V(gchandle.is_released(), nullptr); - return gchandle.get_target(); + // Should be thread safe because the object was just created and nothing else should be referencing it + CSharpLanguage::set_instance_binding(p_unmanaged, data); } -Object *CSharpInstance::get_owner() { - return owner; -} +void CSharpLanguage::tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, CSharpScript *p_script, bool p_ref_counted) { + // This method should not fail -bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { - ERR_FAIL_COND_V(!script.is_valid(), false); + CRASH_COND(!p_unmanaged); - GD_MONO_SCOPE_THREAD_ATTACH; + // All mono objects created from the managed world (e.g.: 'new Player()') + // need to have a CSharpScript in order for their methods to be callable from the unmanaged side - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL_V(mono_object, false); + RefCounted *rc = Object::cast_to<RefCounted>(p_unmanaged); - GDMonoClass *top = script->script_class; + CRASH_COND(p_ref_counted != (bool)rc); - while (top && top != script->native) { - GDMonoField *field = top->get_field(p_name); + MonoGCHandleData gchandle = MonoGCHandleData(p_gchandle_intptr, + p_ref_counted ? gdmono::GCHandleType::WEAK_HANDLE : gdmono::GCHandleType::STRONG_HANDLE); - if (field) { - field->set_value_from_variant(mono_object, p_value); - return true; - } + Ref<CSharpScript> script = p_script; - GDMonoProperty *property = top->get_property(p_name); + CSharpScript::initialize_for_managed_type(script); - if (property) { - property->set_value_from_variant(mono_object, p_value); - return true; - } + CRASH_COND(script.is_null()); - top = top->get_parent_class(); - } + CSharpInstance *csharp_instance = CSharpInstance::create_for_managed_type(p_unmanaged, script.ptr(), gchandle); - // Call _set + p_unmanaged->set_script_and_instance(script, csharp_instance); +} - top = script->script_class; +void CSharpLanguage::tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged) { + // This method should not fail - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_set), 2); + CRASH_COND(!p_unmanaged); - if (method) { - Variant name = p_name; - const Variant *args[2] = { &name, &p_value }; + CSharpInstance *instance = CAST_CSHARP_INSTANCE(p_unmanaged->get_script_instance()); - MonoObject *ret = method->invoke(mono_object, args); + if (!instance) { + return; + } - if (ret && GDMonoMarshal::unbox<MonoBoolean>(ret)) { - return true; - } + CRASH_COND(!instance->gchandle.is_released()); - break; - } + // Tie managed to unmanaged + instance->gchandle = MonoGCHandleData(p_gchandle_intptr, gdmono::GCHandleType::STRONG_HANDLE); - top = top->get_parent_class(); + if (instance->base_ref_counted) { + instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) } - return false; + { + MutexLock lock(CSharpLanguage::get_singleton()->get_script_instances_mutex()); + // instances is a set, so it's safe to insert multiple times (e.g.: from _internal_new_managed) + instance->script->instances.insert(instance->owner); + } } -bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { - ERR_FAIL_COND_V(!script.is_valid(), false); +CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) { + CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script))); - GD_MONO_SCOPE_THREAD_ATTACH; + RefCounted *rc = Object::cast_to<RefCounted>(p_owner); - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL_V(mono_object, false); + instance->base_ref_counted = rc != nullptr; + instance->owner = p_owner; + instance->gchandle = p_gchandle; - GDMonoClass *top = script->script_class; + if (instance->base_ref_counted) { + instance->_reference_owner_unsafe(); + } - while (top && top != script->native) { - GDMonoField *field = top->get_field(p_name); + p_script->instances.insert(p_owner); - if (field) { - MonoObject *value = field->get_value(mono_object); - r_ret = GDMonoMarshal::mono_object_to_variant(value); - return true; - } + return instance; +} - GDMonoProperty *property = top->get_property(p_name); +Object *CSharpInstance::get_owner() { + return owner; +} - if (property) { - MonoException *exc = nullptr; - MonoObject *value = property->get_value(mono_object, &exc); - if (exc) { - r_ret = Variant(); - GDMonoUtils::set_pending_exception(exc); - } else { - r_ret = GDMonoMarshal::mono_object_to_variant(value); - } - return true; - } +bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { + ERR_FAIL_COND_V(!script.is_valid(), false); - top = top->get_parent_class(); - } + GD_MONO_SCOPE_THREAD_ATTACH; - // Call _get + MonoException *exc = nullptr; + bool ret = GDMonoCache::cached_data.methodthunk_CSharpInstanceBridge_Set.invoke( + gchandle.get_intptr(), &p_name, &p_value, &exc); - top = script->script_class; + if (exc) { + GDMonoUtils::set_pending_exception(exc); + } else if (ret) { + return true; + } - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_get), 1); + return false; +} - if (method) { - Variant name = p_name; - const Variant *args[1] = { &name }; +bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { + ERR_FAIL_COND_V(!script.is_valid(), false); - MonoObject *ret = method->invoke(mono_object, args); + GD_MONO_SCOPE_THREAD_ATTACH; - if (ret) { - r_ret = GDMonoMarshal::mono_object_to_variant(ret); - return true; - } + Variant ret_value; - break; - } + MonoException *exc = nullptr; + bool ret = GDMonoCache::cached_data.methodthunk_CSharpInstanceBridge_Get.invoke( + gchandle.get_intptr(), &p_name, &ret_value, &exc); - top = top->get_parent_class(); + if (exc) { + GDMonoUtils::set_pending_exception(exc); + } else if (ret) { + r_ret = ret_value; + return true; } return false; } +#warning TODO +#if 0 void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state) { List<PropertyInfo> property_list; get_property_list(&property_list); @@ -1773,10 +1744,10 @@ void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName, MonoObject *owner_managed = get_mono_object(); ERR_FAIL_NULL(owner_managed); - for (const KeyValue<StringName, CSharpScript::EventSignal> &E : script->event_signals) { - const CSharpScript::EventSignal &event_signal = E.value; + for (const KeyValue<StringName, GDMonoField *> &E : script->event_signals) { + GDMonoField *event_signal_field = E.value; - MonoDelegate *delegate_field_value = (MonoDelegate *)event_signal.field->get_value(owner_managed); + MonoDelegate *delegate_field_value = (MonoDelegate *)event_signal_field->get_value(owner_managed); if (!delegate_field_value) { continue; // Empty } @@ -1785,7 +1756,7 @@ void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName, MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); MonoException *exc = nullptr; - bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TrySerializeDelegate) + bool success = (bool)GDMonoCache::cached_data.methodthunk_DelegateUtils_TrySerializeDelegate .invoke(delegate_field_value, managed_serialized_data, &exc); if (exc) { @@ -1794,12 +1765,13 @@ void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName, } if (success) { - r_state.push_back(Pair<StringName, Array>(event_signal.field->get_name(), serialized_data)); + r_state.push_back(Pair<StringName, Array>(event_signal_field->get_name(), serialized_data)); } else if (OS::get_singleton()->is_stdout_verbose()) { OS::get_singleton()->print("Failed to serialize event signal delegate\n"); } } } +#endif void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { List<PropertyInfo> props; @@ -1811,28 +1783,24 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { GD_MONO_SCOPE_THREAD_ATTACH; - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL(mono_object); + StringName method = SNAME("_get_property_list"); - GDMonoClass *top = script->script_class; - - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_get_property_list), 0); - - if (method) { - MonoObject *ret = method->invoke(mono_object); + Variant ret; + Callable::CallError call_error; + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_CSharpInstanceBridge_Call.invoke( + gchandle.get_intptr(), &method, nullptr, 0, &call_error, &ret, &exc); - if (ret) { - Array array = Array(GDMonoMarshal::mono_object_to_variant(ret)); - for (int i = 0, size = array.size(); i < size; i++) { - props.push_back(PropertyInfo::from_dict(array.get(i))); - } - } + if (exc) { + GDMonoUtils::set_pending_exception(exc); + } - break; - } + ERR_FAIL_COND_MSG(call_error.error != Callable::CallError::CALL_OK, + "Error calling '_get_property_list': " + Variant::get_call_error_text(method, nullptr, 0, call_error)); - top = top->get_parent_class(); + Array array = ret; + for (int i = 0, size = array.size(); i < size; i++) { + p_properties->push_back(PropertyInfo::from_dict(array.get(i))); } for (const PropertyInfo &prop : props) { @@ -1860,34 +1828,26 @@ bool CSharpInstance::property_can_revert(const StringName &p_name) const { GD_MONO_SCOPE_THREAD_ATTACH; - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL_V(mono_object, false); - - GDMonoClass *top = script->script_class; - - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_property_can_revert), 1); - - if (method) { - Variant name = p_name; - const Variant *args[1] = { &name }; + Callable::CallError call_error; - MonoObject *ret = method->invoke(mono_object, args); + Variant name_arg = p_name; + const Variant *args[1] = { &name_arg }; - if (ret) { - bool can_revert = GDMonoMarshal::mono_object_to_variant(ret); - if (can_revert) { - return true; - } - } + Variant ret; + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_CSharpInstanceBridge_Call.invoke( + gchandle.get_intptr(), &CACHED_STRING_NAME(_property_can_revert), args, 1, &call_error, &ret, &exc); - break; - } + if (exc) { + GDMonoUtils::set_pending_exception(exc); + return false; + } - top = top->get_parent_class(); + if (call_error.error != Callable::CallError::CALL_OK) { + return false; } - return false; + return (bool)ret; } bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const { @@ -1895,35 +1855,32 @@ bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_re GD_MONO_SCOPE_THREAD_ATTACH; - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL_V(mono_object, false); + Callable::CallError call_error; - GDMonoClass *top = script->script_class; + Variant name_arg = p_name; + const Variant *args[1] = { &name_arg }; - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_property_get_revert), 1); - - if (method) { - Variant name = p_name; - const Variant *args[1] = { &name }; - - MonoObject *ret = method->invoke(mono_object, args); - - if (ret) { - r_ret = GDMonoMarshal::mono_object_to_variant(ret); - return true; - } + Variant ret; + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_CSharpInstanceBridge_Call.invoke( + gchandle.get_intptr(), &CACHED_STRING_NAME(_property_get_revert), args, 1, &call_error, &ret, &exc); - break; - } + if (exc) { + GDMonoUtils::set_pending_exception(exc); + return false; + } - top = top->get_parent_class(); + if (call_error.error != Callable::CallError::CALL_OK) { + return false; } - return false; + r_ret = ret; + return true; } void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const { +#warning TODO +#if 0 if (!script->is_valid() || !script->script_class) { return; } @@ -1944,6 +1901,7 @@ void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const { top = top->get_parent_class(); } +#endif } bool CSharpInstance::has_method(const StringName &p_method) const { @@ -1953,17 +1911,19 @@ bool CSharpInstance::has_method(const StringName &p_method) const { GD_MONO_SCOPE_THREAD_ATTACH; - GDMonoClass *top = script->script_class; + if (!GDMonoCache::cached_data.godot_api_cache_updated) { + return false; + } - while (top && top != script->native) { - if (top->has_fetched_method_unknown_params(p_method)) { - return true; - } + String method = p_method; + bool deep = true; - top = top->get_parent_class(); - } + MonoException *exc = nullptr; + bool found = GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_HasMethodUnknownParams + .invoke(script.ptr(), &method, deep, &exc); + UNHANDLED_EXCEPTION(exc); - return false; + return found; } Variant CSharpInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { @@ -1971,36 +1931,16 @@ Variant CSharpInstance::callp(const StringName &p_method, const Variant **p_args GD_MONO_SCOPE_THREAD_ATTACH; - MonoObject *mono_object = get_mono_object(); - - if (!mono_object) { - r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; - ERR_FAIL_V(Variant()); - } - - GDMonoClass *top = script->script_class; - - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(p_method, p_argcount); - - if (method) { - MonoObject *return_value = method->invoke(mono_object, p_args); - - r_error.error = Callable::CallError::CALL_OK; - - if (return_value) { - return GDMonoMarshal::mono_object_to_variant(return_value); - } else { - return Variant(); - } - } + Variant ret; + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_CSharpInstanceBridge_Call.invoke( + gchandle.get_intptr(), &p_method, p_args, p_argcount, &r_error, &ret, &exc); - top = top->get_parent_class(); + if (exc) { + GDMonoUtils::set_pending_exception(exc); } - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - - return Variant(); + return ret; } bool CSharpInstance::_reference_owner_unsafe() { @@ -2046,48 +1986,32 @@ bool CSharpInstance::_unreference_owner_unsafe() { return static_cast<RefCounted *>(owner)->unreference(); } -MonoObject *CSharpInstance::_internal_new_managed() { - // Search the constructor first, to fail with an error if it's not found before allocating anything else. - GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - ERR_FAIL_NULL_V_MSG(ctor, nullptr, - "Cannot create script instance because the class does not define a parameterless constructor: '" + script->get_path() + "'."); - +bool CSharpInstance::_internal_new_managed() { CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); - ERR_FAIL_NULL_V(owner, nullptr); - ERR_FAIL_COND_V(script.is_null(), nullptr); + ERR_FAIL_NULL_V(owner, false); + ERR_FAIL_COND_V(script.is_null(), false); - MonoObject *mono_object = mono_object_new(mono_domain_get(), script->script_class->get_mono_ptr()); + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance + .invoke(script.ptr(), owner, nullptr, 0, &exc); + + if (exc) { + GDMonoUtils::set_pending_exception(exc); - if (!mono_object) { // Important to clear this before destroying the script instance here script = Ref<CSharpScript>(); - - bool die = _unreference_owner_unsafe(); - // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug. - CRASH_COND(die); - owner = nullptr; - ERR_FAIL_V_MSG(nullptr, "Failed to allocate memory for the object."); - } - - // Tie managed to unmanaged - gchandle = MonoGCHandleData::new_strong_handle(mono_object); - - if (base_ref_counted) { - _reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) + return false; } - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, owner); - - // Construct - ctor->invoke_raw(mono_object, nullptr); + CRASH_COND(gchandle.is_released()); - return mono_object; + return true; } -void CSharpInstance::mono_object_disposed(MonoObject *p_obj) { +void CSharpInstance::mono_object_disposed() { // Must make sure event signals are not left dangling disconnect_event_signals(); @@ -2095,10 +2019,10 @@ void CSharpInstance::mono_object_disposed(MonoObject *p_obj) { CRASH_COND(base_ref_counted); CRASH_COND(gchandle.is_released()); #endif - CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); + CSharpLanguage::get_singleton()->release_script_gchandle(nullptr, gchandle); } -void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance) { +void CSharpInstance::mono_object_disposed_baseref(bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance) { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref_counted); CRASH_COND(gchandle.is_released()); @@ -2114,7 +2038,7 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f r_delete_owner = true; } else { r_delete_owner = false; - CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); + CSharpLanguage::get_singleton()->release_script_gchandle(nullptr, gchandle); if (!p_is_finalizer) { // If the native instance is still alive and Dispose() was called @@ -2126,27 +2050,20 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f // unreference and delete it, so we want to keep it. // GC.ReRegisterForFinalize(this) is not safe because the objects referenced by 'this' // could have already been collected. Instead we will create a new managed instance here. - MonoObject *new_managed = _internal_new_managed(); - if (!new_managed) { + if (!_internal_new_managed()) { r_remove_script_instance = true; } } } } -void CSharpInstance::connect_event_signals() { - for (const KeyValue<StringName, CSharpScript::EventSignal> &E : script->event_signals) { - const CSharpScript::EventSignal &event_signal = E.value; - - StringName signal_name = event_signal.field->get_name(); - - // TODO: Use pooling for ManagedCallable instances. - EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, &event_signal)); +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)); - Callable callable(event_signal_callable); - connected_event_signals.push_back(callable); - owner->connect(signal_name, callable); - } + Callable callable(event_signal_callable); + connected_event_signals.push_back(callable); + owner->connect(p_event_signal, callable); } void CSharpInstance::disconnect_event_signals() { @@ -2174,9 +2091,22 @@ void CSharpInstance::refcount_incremented() { // so the owner must hold the managed side alive again to avoid it from being GCed. // Release the current weak handle and replace it with a strong handle. - MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(gchandle.get_target()); - gchandle.release(); - gchandle = strong_gchandle; + + GCHandleIntPtr old_gchandle = gchandle.get_intptr(); + gchandle.handle = GCHandleIntPtr(); // No longer owns the handle (released by swap function) + + GCHandleIntPtr new_gchandle; + bool create_weak = false; + MonoException *exc = nullptr; + bool target_alive = GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_SwapGCHandleForType + .invoke(old_gchandle, &new_gchandle, create_weak, &exc); + UNHANDLED_EXCEPTION(exc); + + if (!target_alive) { + return; // Called after the managed side was collected, so nothing to do here + } + + gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::STRONG_HANDLE); } } @@ -2197,9 +2127,22 @@ bool CSharpInstance::refcount_decremented() { // the managed instance takes responsibility of deleting the owner when GCed. // Release the current strong handle and replace it with a weak handle. - MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(gchandle.get_target()); - gchandle.release(); - gchandle = weak_gchandle; + + GCHandleIntPtr old_gchandle = gchandle.get_intptr(); + gchandle.handle = GCHandleIntPtr(); // No longer owns the handle (released by swap function) + + GCHandleIntPtr new_gchandle; + bool create_weak = true; + MonoException *exc = nullptr; + bool target_alive = GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_SwapGCHandleForType + .invoke(old_gchandle, &new_gchandle, create_weak, &exc); + UNHANDLED_EXCEPTION(exc); + + if (!target_alive) { + return refcount == 0; // Called after the managed side was collected, so nothing to do here + } + + gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::WEAK_HANDLE); return false; } @@ -2235,11 +2178,9 @@ void CSharpInstance::notification(int p_notification) { _call_notification(p_notification); - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL(mono_object); - MonoException *exc = nullptr; - GDMonoUtils::dispose(mono_object, &exc); + GDMonoCache::cached_data.methodthunk_CSharpInstanceBridge_CallDispose + .invoke(gchandle.get_intptr(), /* okIfNull */ false, &exc); if (exc) { GDMonoUtils::set_pending_exception(exc); @@ -2254,43 +2195,31 @@ void CSharpInstance::notification(int p_notification) { void CSharpInstance::_call_notification(int p_notification) { GD_MONO_ASSERT_THREAD_ATTACHED; - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL(mono_object); - - // Custom version of _call_multilevel, optimized for _notification + Variant arg = p_notification; + const Variant *args[1] = { &arg }; + StringName method_name = SNAME("_notification"); - int32_t arg = p_notification; - void *args[1] = { &arg }; - StringName method_name = CACHED_STRING_NAME(_notification); - - GDMonoClass *top = script->script_class; + Callable::CallError call_error; - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(method_name, 1); - - if (method) { - method->invoke_raw(mono_object, args); - return; - } + Variant ret; + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_CSharpInstanceBridge_Call.invoke( + gchandle.get_intptr(), &method_name, args, 1, &call_error, &ret, &exc); - top = top->get_parent_class(); + if (exc) { + GDMonoUtils::set_pending_exception(exc); } } String CSharpInstance::to_string(bool *r_valid) { GD_MONO_SCOPE_THREAD_ATTACH; - MonoObject *mono_object = get_mono_object(); - - if (mono_object == nullptr) { - if (r_valid) { - *r_valid = false; - } - return String(); - } + String res; + bool valid; MonoException *exc = nullptr; - MonoString *result = GDMonoUtils::object_to_string(mono_object, &exc); + GDMonoCache::cached_data.methodthunk_CSharpInstanceBridge_CallToString + .invoke(gchandle.get_intptr(), &res, &valid, &exc); if (exc) { GDMonoUtils::set_pending_exception(exc); @@ -2300,14 +2229,11 @@ String CSharpInstance::to_string(bool *r_valid) { return String(); } - if (result == nullptr) { - if (r_valid) { - *r_valid = false; - } - return String(); + if (r_valid) { + *r_valid = valid; } - return GDMonoMarshal::mono_string_to_godot(result); + return res; } Ref<Script> CSharpInstance::get_script() const { @@ -2338,15 +2264,12 @@ CSharpInstance::~CSharpInstance() { // we must call Dispose here, because Dispose calls owner->set_script_instance(nullptr) // and that would mess up with the new script instance if called later. - MonoObject *mono_object = gchandle.get_target(); - - if (mono_object) { - MonoException *exc = nullptr; - GDMonoUtils::dispose(mono_object, &exc); + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_CSharpInstanceBridge_CallDispose + .invoke(gchandle.get_intptr(), /* okIfNull */ true, &exc); - if (exc) { - GDMonoUtils::set_pending_exception(exc); - } + if (exc) { + GDMonoUtils::set_pending_exception(exc); } } @@ -2424,6 +2347,8 @@ void CSharpScript::_update_member_info_no_exports() { member_info.clear(); +#warning TODO +#if 0 GDMonoClass *top = script_class; List<PropertyInfo> props; @@ -2468,6 +2393,7 @@ void CSharpScript::_update_member_info_no_exports() { top = top->get_parent_class(); } +#endif } } #endif @@ -2489,6 +2415,8 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda if (exports_invalidated) #endif { +#warning TODO +#if 0 GD_MONO_SCOPE_THREAD_ATTACH; changed = true; @@ -2524,7 +2452,7 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda MonoException *ctor_exc = nullptr; ctor->invoke(tmp_object, nullptr, &ctor_exc); - tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object)); + tmp_native = GDMonoMarshal::unbox<Object *>(GDMonoCache::cached_data.field_GodotObject_ptr->get_value(tmp_object)); if (ctor_exc) { // TODO: Should we free 'tmp_native' if the exception was thrown after its creation? @@ -2649,6 +2577,8 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda } } #endif + +#endif // #if 0 } #ifdef TOOLS_ENABLED @@ -2675,111 +2605,8 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda return changed; } -void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class) { - // no need to load the script's signals more than once - if (!signals_invalidated) { - return; - } - - // make sure this classes signals are empty when loading for the first time - _signals.clear(); - event_signals.clear(); - - GD_MONO_SCOPE_THREAD_ATTACH; - - GDMonoClass *top = p_class; - while (top && top != p_native_class) { - const Vector<GDMonoClass *> &delegates = top->get_all_delegates(); - for (int i = delegates.size() - 1; i >= 0; --i) { - GDMonoClass *delegate = delegates[i]; - - if (!delegate->has_attribute(CACHED_CLASS(SignalAttribute))) { - continue; - } - - // Arguments are accessibles as arguments of .Invoke method - GDMonoMethod *invoke_method = delegate->get_method(mono_get_delegate_invoke(delegate->get_mono_ptr())); - - Vector<SignalParameter> parameters; - if (_get_signal(top, invoke_method, parameters)) { - _signals[delegate->get_name()] = parameters; - } - } - - List<StringName> found_event_signals; - - void *iter = nullptr; - MonoEvent *raw_event = nullptr; - while ((raw_event = mono_class_get_events(top->get_mono_ptr(), &iter)) != nullptr) { - MonoCustomAttrInfo *event_attrs = mono_custom_attrs_from_event(top->get_mono_ptr(), raw_event); - if (event_attrs) { - if (mono_custom_attrs_has_attr(event_attrs, CACHED_CLASS(SignalAttribute)->get_mono_ptr())) { - String event_name = String::utf8(mono_event_get_name(raw_event)); - found_event_signals.push_back(StringName(event_name)); - } - - mono_custom_attrs_free(event_attrs); - } - } - - const Vector<GDMonoField *> &fields = top->get_all_fields(); - for (int i = 0; i < fields.size(); i++) { - GDMonoField *field = fields[i]; - - GDMonoClass *field_class = field->get_type().type_class; - - if (!mono_class_is_delegate(field_class->get_mono_ptr())) { - continue; - } - - if (!found_event_signals.find(field->get_name())) { - continue; - } - - GDMonoMethod *invoke_method = field_class->get_method(mono_get_delegate_invoke(field_class->get_mono_ptr())); - - Vector<SignalParameter> parameters; - if (_get_signal(top, invoke_method, parameters)) { - event_signals[field->get_name()] = { field, invoke_method, parameters }; - } - } - - top = top->get_parent_class(); - } - - signals_invalidated = false; -} - -bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> ¶ms) { - GD_MONO_ASSERT_THREAD_ATTACHED; - - Vector<StringName> names; - Vector<ManagedType> types; - p_delegate_invoke->get_parameter_names(names); - p_delegate_invoke->get_parameter_types(types); - - for (int i = 0; i < names.size(); ++i) { - SignalParameter arg; - arg.name = names[i]; - - bool nil_is_variant = false; - arg.type = GDMonoMarshal::managed_to_variant_type(types[i], &nil_is_variant); - - if (arg.type == Variant::NIL) { - if (nil_is_variant) { - arg.nil_is_variant = true; - } else { - ERR_PRINT("Unknown type of signal parameter: '" + arg.name + "' in '" + p_class->get_full_name() + "'."); - return false; - } - } - - params.push_back(arg); - } - - return true; -} - +#warning TODO +#if 0 /** * Returns false if there was an error, otherwise true. * If there was an error, r_prop_info and r_exported are not assigned any value. @@ -2793,7 +2620,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect if (p_member->is_static()) { #ifdef TOOLS_ENABLED - if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) { + if (p_member->has_attribute(GDMonoCache::cached_data.class_ExportAttribute)) { ERR_PRINT("Cannot export member because it is static: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); } #endif @@ -2814,7 +2641,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect CRASH_NOW(); } - bool exported = p_member->has_attribute(CACHED_CLASS(ExportAttribute)); + bool exported = p_member->has_attribute(GDMonoCache::cached_data.class_ExportAttribute); if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) { GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member); @@ -2846,7 +2673,7 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect } #ifdef TOOLS_ENABLED - MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute)); + MonoObject *attr = p_member->get_attribute(GDMonoCache::cached_data.class_ExportAttribute); #endif PropertyHint hint = PROPERTY_HINT_NONE; @@ -2867,8 +2694,8 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); if (hint_res == 0) { - hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)); - hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr); + hint = PropertyHint(GDMonoCache::cached_data.field_ExportAttribute_hint->get_int_value(attr)); + hint_string = GDMonoCache::cached_data.field_ExportAttribute_hintString->get_string_value(attr); } #endif @@ -2950,7 +2777,7 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage // This may not be needed in the future if the editor is changed to not display values. r_hint_string = name_only_hint_string; } - } else if (p_variant_type == Variant::OBJECT && CACHED_CLASS(GodotResource)->is_assignable_from(p_type.type_class)) { + } else if (p_variant_type == Variant::OBJECT && GDMonoCache::cached_data.class_GodotResource->is_assignable_from(p_type.type_class)) { GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(p_type.type_class); CRASH_COND(field_native_class == nullptr); @@ -3007,41 +2834,7 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage return 1; } #endif - -Variant CSharpScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - if (unlikely(GDMono::get_singleton() == nullptr)) { - // Probably not the best error but eh. - r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; - return Variant(); - } - - GD_MONO_SCOPE_THREAD_ATTACH; - - GDMonoClass *top = script_class; - - while (top && top != native) { - GDMonoMethod *method = top->get_method(p_method, p_argcount); - - if (method && method->is_static()) { - MonoObject *result = method->invoke(nullptr, p_args); - - if (result) { - return GDMonoMarshal::mono_object_to_variant(result); - } else { - return Variant(); - } - } - - top = top->get_parent_class(); - } - - // No static method found. Try regular instance calls - return Script::callp(p_method, p_args, p_argcount, r_error); -} - -void CSharpScript::_resource_path_changed() { - _update_name(); -} +#endif bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const { if (p_name == CSharpLanguage::singleton->string_names._script_source) { @@ -3070,30 +2863,13 @@ void CSharpScript::_bind_methods() { ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo("new")); } -Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) { - // This method should not fail, only assertions allowed - - CRASH_COND(p_class == nullptr); - - // TODO OPTIMIZE: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time - Ref<CSharpScript> script = memnew(CSharpScript); - - initialize_for_managed_type(script, p_class, p_native); +void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script) { + // IMPORTANT: + // This method must be called only after the CSharpScript and its associated type + // have been added to the script bridge map in the ScriptManagerBridge C# class. - return script; -} - -void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native) { // This method should not fail, only assertions allowed - CRASH_COND(p_class == nullptr); - - p_script->name = p_class->get_name(); - p_script->script_class = p_class; - p_script->native = p_native; - - CRASH_COND(p_script->native == nullptr); - p_script->valid = true; p_script->reload_invalidated = false; @@ -3106,71 +2882,21 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon // Extract information about the script using the mono class. void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { - GDMonoClass *base = p_script->script_class->get_parent_class(); - - // `base` should only be set if the script is a user defined type. - if (base != p_script->native) { - p_script->base = base; - } + 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(); - p_script->tool = p_script->script_class->has_attribute(CACHED_CLASS(ToolAttribute)); - - if (!p_script->tool) { - GDMonoClass *nesting_class = p_script->script_class->get_nesting_class(); - p_script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute)); - } - -#ifdef TOOLS_ENABLED - if (!p_script->tool) { - p_script->tool = p_script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly(); - } -#endif - -#ifdef DEBUG_ENABLED - // For debug builds, we must fetch from all native base methods as well. - // Native base methods must be fetched before the current class. - // Not needed if the script class itself is a native class. - - if (p_script->script_class != p_script->native) { - GDMonoClass *native_top = p_script->native; - while (native_top) { - native_top->fetch_methods_with_godot_api_checks(p_script->native); - - if (native_top == CACHED_CLASS(GodotObject)) { - break; - } - - native_top = native_top->get_parent_class(); - } - } -#endif + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_UpdateScriptClassInfo + .invoke(p_script.ptr(), &tool, &rpc_functions_dict, &exc); + UNHANDLED_EXCEPTION(exc); - p_script->script_class->fetch_methods_with_godot_api_checks(p_script->native); + p_script->tool = tool; p_script->rpc_config.clear(); - - GDMonoClass *top = p_script->script_class; - while (top && top != p_script->native) { - // Fetch methods from base classes as well - top->fetch_methods_with_godot_api_checks(p_script->native); - - // Update RPC info - { - Vector<GDMonoMethod *> methods = top->get_all_methods(); - for (int i = 0; i < methods.size(); i++) { - if (!methods[i]->is_static()) { - const Variant rpc_config = p_script->_member_get_rpc_config(methods[i]); - if (rpc_config.get_type() != Variant::NIL) { - p_script->rpc_config[methods[i]->get_name()] = rpc_config; - } - } - } - } - - top = top->get_parent_class(); - } - - p_script->load_script_signals(p_script->script_class, p_script->native); + p_script->rpc_config = rpc_functions_dict; } bool CSharpScript::can_instantiate() const { @@ -3183,13 +2909,13 @@ bool CSharpScript::can_instantiate() const { // FIXME Need to think this through better. // For tool scripts, this will never fire if the class is not found. That's because we // don't know if it's a tool script if we can't find the class to access the attributes. - if (extra_cond && !script_class) { + if (extra_cond && !valid) { if (GDMono::get_singleton()->get_project_assembly() == nullptr) { // The project assembly is not loaded ERR_FAIL_V_MSG(false, "Cannot instance script because the project assembly is not loaded. Script: '" + get_path() + "'."); } else { // The project assembly is loaded, but the class could not found - ERR_FAIL_V_MSG(false, "Cannot instance script because the class '" + name + "' could not be found. Script: '" + get_path() + "'."); + ERR_FAIL_V_MSG(false, "Cannot instance script because the associated class could not be found. Script: '" + get_path() + "'."); } } @@ -3197,11 +2923,12 @@ bool CSharpScript::can_instantiate() const { } StringName CSharpScript::get_instance_base_type() const { - if (native) { - return native->get_name(); - } else { - return StringName(); - } + StringName native_name; + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_GetScriptNativeName + .invoke(this, &native_name, &exc); + UNHANDLED_EXCEPTION(exc); + return native_name; } CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) { @@ -3209,17 +2936,6 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg /* STEP 1, CREATE */ - // Search the constructor first, to fail with an error if it's not found before allocating anything else. - GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); - if (ctor == nullptr) { - ERR_FAIL_COND_V_MSG(p_argcount == 0, nullptr, - "Cannot create script instance. The class '" + script_class->get_full_name() + - "' does not define a parameterless constructor." + - (get_path().is_empty() ? String() : " Path: '" + get_path() + "'.")); - - ERR_FAIL_V_MSG(nullptr, "Constructor not found."); - } - Ref<RefCounted> ref; if (p_is_ref_counted) { // Hold it alive. Important if we have to dispose a script instance binding before creating the CSharpInstance. @@ -3233,14 +2949,12 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); if (script_binding.inited && !script_binding.gchandle.is_released()) { - MonoObject *mono_object = script_binding.gchandle.get_target(); - if (mono_object) { - MonoException *exc = nullptr; - GDMonoUtils::dispose(mono_object, &exc); + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_CSharpInstanceBridge_CallDispose + .invoke(script_binding.gchandle.get_intptr(), /* okIfNull */ true, &exc); - if (exc) { - GDMonoUtils::set_pending_exception(exc); - } + if (exc) { + GDMonoUtils::set_pending_exception(exc); } script_binding.gchandle.release(); // Just in case @@ -3255,38 +2969,22 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg /* STEP 2, INITIALIZE AND CONSTRUCT */ - MonoObject *mono_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance + .invoke(this, p_owner, p_args, p_argcount, &exc); + + if (exc) { + GDMonoUtils::set_pending_exception(exc); - if (!mono_object) { // Important to clear this before destroying the script instance here instance->script = Ref<CSharpScript>(); instance->owner = nullptr; - - bool die = instance->_unreference_owner_unsafe(); - // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug. - CRASH_COND(die); - p_owner->set_script_instance(nullptr); - r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; - ERR_FAIL_V_MSG(nullptr, "Failed to allocate memory for the object."); - } - - // Tie managed to unmanaged - instance->gchandle = MonoGCHandleData::new_strong_handle(mono_object); - if (instance->base_ref_counted) { - instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) - } - - { - MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); - instances.insert(instance->owner); + return nullptr; } - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, instance->owner); - - // Construct - ctor->invoke(mono_object, p_args); + CRASH_COND(instance->gchandle.is_released()); /* STEP 3, PARTY */ @@ -3302,11 +3000,17 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::Cal r_error.error = Callable::CallError::CALL_OK; - ERR_FAIL_NULL_V(native, Variant()); + StringName native_name; + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_GetScriptNativeName + .invoke(this, &native_name, &exc); + UNHANDLED_EXCEPTION(exc); + + ERR_FAIL_COND_V(native_name == StringName(), Variant()); GD_MONO_SCOPE_THREAD_ATTACH; - Object *owner = ClassDB::instantiate(NATIVE_GDMONOCLASS_NAME(native)); + Object *owner = ClassDB::instantiate(native_name); Ref<RefCounted> ref; RefCounted *r = Object::cast_to<RefCounted>(owner); @@ -3336,16 +3040,21 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { GD_MONO_SCOPE_THREAD_ATTACH; - if (native) { - StringName native_name = NATIVE_GDMONOCLASS_NAME(native); - if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { - if (EngineDebugger::is_active()) { - CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, - "Script inherits from native type '" + String(native_name) + - "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'"); - } - ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'."); + StringName native_name; + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_GetScriptNativeName + .invoke(this, &native_name, &exc); + UNHANDLED_EXCEPTION(exc); + + ERR_FAIL_COND_V(native_name == StringName(), nullptr); + + if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { + if (EngineDebugger::is_active()) { + CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, + "Script inherits from native type '" + String(native_name) + + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'"); } + ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'."); } Callable::CallError unchecked_error; @@ -3387,12 +3096,14 @@ void CSharpScript::set_source_code(const String &p_code) { } void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { - if (!script_class) { + if (!valid) { return; } GD_MONO_SCOPE_THREAD_ATTACH; +#warning TODO +#if 0 // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. GDMonoClass *top = script_class; @@ -3407,35 +3118,51 @@ void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { top = top->get_parent_class(); } +#endif } bool CSharpScript::has_method(const StringName &p_method) const { - if (!script_class) { + if (!valid) { return false; } GD_MONO_SCOPE_THREAD_ATTACH; - return script_class->has_fetched_method_unknown_params(p_method); + if (!GDMonoCache::cached_data.godot_api_cache_updated) { + return false; + } + + String method = p_method; + bool deep = false; + + MonoException *exc = nullptr; + bool found = GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_HasMethodUnknownParams + .invoke(this, &method, deep, &exc); + UNHANDLED_EXCEPTION(exc); + + return found; } MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { - if (!script_class) { + if (!valid) { return MethodInfo(); } GD_MONO_SCOPE_THREAD_ATTACH; +#warning TODO +#if 0 GDMonoClass *top = script_class; while (top && top != native) { - GDMonoMethod *params = top->get_fetched_method_unknown_params(p_method); + GDMonoMethod *params = top->get_method_unknown_params(p_method); if (params) { return params->get_method_info(); } top = top->get_parent_class(); } +#endif return MethodInfo(); } @@ -3451,28 +3178,18 @@ Error CSharpScript::reload(bool p_keep_state) { GD_MONO_SCOPE_THREAD_ATTACH; - const DotNetScriptLookupInfo *lookup_info = - CSharpLanguage::get_singleton()->lookup_dotnet_script(get_path()); - - if (lookup_info) { - GDMonoClass *klass = lookup_info->script_class; - if (klass) { - ERR_FAIL_COND_V(!CACHED_CLASS(GodotObject)->is_assignable_from(klass), FAILED); - script_class = klass; - } - } + String script_path = get_path(); - valid = script_class != nullptr; + MonoException *exc = nullptr; + valid = GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_AddScriptBridge + .invoke(this, &script_path, &exc); + UNHANDLED_EXCEPTION(exc); - if (script_class) { + if (valid) { #ifdef DEBUG_ENABLED - print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path()); + print_verbose("Found class for script " + get_path()); #endif - native = GDMonoUtils::get_class_native_base(script_class); - - CRASH_COND(native == nullptr); - update_script_class_info(this); _update_exports(); @@ -3509,43 +3226,59 @@ void CSharpScript::update_exports() { } bool CSharpScript::has_script_signal(const StringName &p_signal) const { - return event_signals.has(p_signal) || _signals.has(p_signal); + if (!valid) { + return false; + } + + if (!GDMonoCache::cached_data.godot_api_cache_updated) { + return false; + } + + String signal = p_signal; + + MonoException *exc = nullptr; + bool res = GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_HasScriptSignal + .invoke(this, &signal, &exc); + UNHANDLED_EXCEPTION(exc); + + return res; } void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { - for (const KeyValue<StringName, Vector<SignalParameter>> &E : _signals) { - MethodInfo mi; - mi.name = E.key; + if (!valid) { + return; + } - const Vector<SignalParameter> ¶ms = E.value; - for (int i = 0; i < params.size(); i++) { - const SignalParameter ¶m = params[i]; + // Performance is not critical here as this will be replaced with source generators. - PropertyInfo arg_info = PropertyInfo(param.type, param.name); - if (param.type == Variant::NIL && param.nil_is_variant) { - arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - } + if (!GDMonoCache::cached_data.godot_api_cache_updated) { + return; + } - mi.arguments.push_back(arg_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(); - r_signals->push_back(mi); - } + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_GetScriptSignalList + .invoke(this, &signals_dict, &exc); + UNHANDLED_EXCEPTION(exc); - for (const KeyValue<StringName, EventSignal> &E : event_signals) { + for (const Variant *s = signals_dict.next(nullptr); s != nullptr; s = signals_dict.next(s)) { MethodInfo mi; - mi.name = E.key; + mi.name = *s; + + Array params = signals_dict[*s]; - const EventSignal &event_signal = E.value; - const Vector<SignalParameter> ¶ms = event_signal.parameters; for (int i = 0; i < params.size(); i++) { - const SignalParameter ¶m = params[i]; + Dictionary param = params[i]; - PropertyInfo arg_info = PropertyInfo(param.type, param.name); - if (param.type == Variant::NIL && param.nil_is_variant) { + 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); } @@ -3559,15 +3292,20 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const { return false; } - if (script_class == nullptr || cs->script_class == nullptr) { + if (!valid || !cs->valid) { return false; } - if (script_class == cs->script_class) { - return true; + if (!GDMonoCache::cached_data.godot_api_cache_updated) { + return false; } - return cs->script_class->is_assignable_from(script_class); + MonoException *exc = nullptr; + bool res = GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_ScriptIsOrInherits + .invoke(this, cs.ptr(), &exc); + UNHANDLED_EXCEPTION(exc); + + return res; } Ref<Script> CSharpScript::get_base_script() const { @@ -3598,22 +3336,6 @@ int CSharpScript::get_member_line(const StringName &p_member) const { return -1; } -Variant CSharpScript::_member_get_rpc_config(IMonoClassMember *p_member) const { - Variant out; - - MonoObject *rpc_attribute = p_member->get_attribute(CACHED_CLASS(RPCAttribute)); - if (rpc_attribute != nullptr) { - Dictionary rpc_config; - rpc_config["rpc_mode"] = CACHED_PROPERTY(RPCAttribute, Mode)->get_int_value(rpc_attribute); - rpc_config["call_local"] = CACHED_PROPERTY(RPCAttribute, CallLocal)->get_bool_value(rpc_attribute); - rpc_config["transfer_mode"] = CACHED_PROPERTY(RPCAttribute, TransferMode)->get_int_value(rpc_attribute); - rpc_config["channel"] = CACHED_PROPERTY(RPCAttribute, TransferChannel)->get_int_value(rpc_attribute); - out = rpc_config; - } - - return out; -} - const Variant CSharpScript::get_rpc_config() const { return rpc_config; } @@ -3634,29 +3356,15 @@ Error CSharpScript::load_source_code(const String &p_path) { return OK; } -void CSharpScript::_update_name() { - String path = get_path(); - - if (!path.is_empty()) { - name = get_path().get_file().get_basename(); - } -} - void CSharpScript::_clear() { tool = false; valid = false; reload_invalidated = true; - - base = nullptr; - native = nullptr; - script_class = nullptr; } CSharpScript::CSharpScript() { _clear(); - _update_name(); - #ifdef DEBUG_ENABLED { MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); @@ -3670,6 +3378,12 @@ CSharpScript::~CSharpScript() { MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); CSharpLanguage::get_singleton()->script_list.remove(&this->script_list); #endif + + if (GDMonoCache::cached_data.godot_api_cache_updated) { + MonoException *exc = nullptr; + GDMonoCache::cached_data.methodthunk_ScriptManagerBridge_RemoveScriptBridge.invoke(this, &exc); + UNHANDLED_EXCEPTION(exc); + } } void CSharpScript::get_members(HashSet<StringName> *p_members) { @@ -3771,16 +3485,9 @@ bool ResourceFormatSaverCSharpScript::recognize(const Ref<Resource> &p_resource) } CSharpLanguage::StringNameCache::StringNameCache() { - _signal_callback = StaticCString::create("_signal_callback"); - _set = StaticCString::create("_set"); - _get = StaticCString::create("_get"); - _get_property_list = StaticCString::create("_get_property_list"); _property_can_revert = StaticCString::create("_property_can_revert"); _property_get_revert = StaticCString::create("_property_get_revert"); - _notification = StaticCString::create("_notification"); _script_source = StaticCString::create("script/source"); on_before_serialize = StaticCString::create("OnBeforeSerialize"); on_after_deserialize = StaticCString::create("OnAfterDeserialize"); - dotctor = StaticCString::create(".ctor"); - delegate_invoke_method_name = StaticCString::create("Invoke"); } |