diff options
-rw-r--r-- | modules/mono/csharp_script.cpp | 342 | ||||
-rw-r--r-- | modules/mono/csharp_script.h | 32 | ||||
-rw-r--r-- | modules/mono/editor/mono_bottom_panel.cpp | 6 | ||||
-rw-r--r-- | modules/mono/glue/base_object_glue.cpp | 70 | ||||
-rw-r--r-- | modules/mono/glue/base_object_glue.h | 6 | ||||
-rw-r--r-- | modules/mono/glue/cs_files/Array.cs | 5 | ||||
-rw-r--r-- | modules/mono/glue/cs_files/Dictionary.cs | 5 | ||||
-rw-r--r-- | modules/mono/glue/cs_files/Object.base.cs | 11 | ||||
-rw-r--r-- | modules/mono/mono_gc_handle.cpp | 20 | ||||
-rw-r--r-- | modules/mono/mono_gc_handle.h | 7 | ||||
-rw-r--r-- | modules/mono/mono_gd/gd_mono.cpp | 120 | ||||
-rw-r--r-- | modules/mono/mono_gd/gd_mono.h | 22 | ||||
-rw-r--r-- | modules/mono/mono_gd/gd_mono_utils.cpp | 46 | ||||
-rw-r--r-- | modules/mono/mono_gd/gd_mono_utils.h | 2 | ||||
-rw-r--r-- | modules/mono/signal_awaiter_utils.cpp | 2 |
15 files changed, 506 insertions, 190 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 0efdf0ee1d..a7899257b5 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -138,7 +138,7 @@ void CSharpLanguage::finish() { #endif // Release gchandle bindings before finalizing mono runtime - gchandle_bindings.clear(); + script_bindings.clear(); if (gdmono) { memdelete(gdmono); @@ -551,22 +551,22 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec void CSharpLanguage::frame() { - const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoUtils::mono_cache.task_scheduler_handle; + if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != NULL) { + const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoUtils::mono_cache.task_scheduler_handle; - if (task_scheduler_handle.is_valid()) { - MonoObject *task_scheduler = task_scheduler_handle->get_target(); + if (task_scheduler_handle.is_valid()) { + MonoObject *task_scheduler = task_scheduler_handle->get_target(); - if (task_scheduler) { - GDMonoUtils::GodotTaskScheduler_Activate thunk = CACHED_METHOD_THUNK(GodotTaskScheduler, Activate); + if (task_scheduler) { + GDMonoUtils::GodotTaskScheduler_Activate thunk = CACHED_METHOD_THUNK(GodotTaskScheduler, Activate); - ERR_FAIL_NULL(thunk); + MonoException *exc = NULL; + thunk(task_scheduler, (MonoObject **)&exc); - MonoException *exc = NULL; - thunk(task_scheduler, (MonoObject **)&exc); - - if (exc) { - GDMonoUtils::debug_unhandled_exception(exc); - _UNREACHABLE_(); + if (exc) { + GDMonoUtils::debug_unhandled_exception(exc); + _UNREACHABLE_(); + } } } } @@ -892,6 +892,48 @@ void CSharpLanguage::set_language_index(int p_idx) { lang_idx = p_idx; } +void CSharpLanguage::release_script_gchandle(Ref<MonoGCHandle> &p_gchandle) { + + if (!p_gchandle->is_released()) { // Do not locking unnecessarily +#ifndef NO_THREADS + get_singleton()->script_gchandle_release_lock->lock(); +#endif + + p_gchandle->release(); + +#ifndef NO_THREADS + get_singleton()->script_gchandle_release_lock->unlock(); +#endif + } +} + +void CSharpLanguage::release_script_gchandle(MonoObject *p_pinned_expected_obj, Ref<MonoGCHandle> &p_gchandle) { + + uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_pinned_expected_obj); // we might lock after this, so pin it + + if (!p_gchandle->is_released()) { // Do not locking unnecessarily +#ifndef NO_THREADS + get_singleton()->script_gchandle_release_lock->lock(); +#endif + + 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_pinned_expected_obj || target == NULL) { + p_gchandle->release(); + } + +#ifndef NO_THREADS + get_singleton()->script_gchandle_release_lock->unlock(); +#endif + } + + MonoGCHandle::free_handle(pinned_gchandle); +} + CSharpLanguage::CSharpLanguage() { ERR_FAIL_COND(singleton); @@ -904,9 +946,11 @@ CSharpLanguage::CSharpLanguage() { #ifdef NO_THREADS lock = NULL; gchandle_bind_lock = NULL; + script_gchandle_release_lock = NULL; #else lock = Mutex::create(); script_bind_lock = Mutex::create(); + script_gchandle_release_lock = Mutex::create(); #endif lang_idx = -1; @@ -926,6 +970,11 @@ CSharpLanguage::~CSharpLanguage() { script_bind_lock = NULL; } + if (script_gchandle_release_lock) { + memdelete(script_gchandle_release_lock); + script_gchandle_release_lock = NULL; + } + singleton = NULL; } @@ -954,30 +1003,34 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) { ERR_FAIL_NULL_V(mono_object, NULL); - // Tie managed to unmanaged - Reference *ref = Object::cast_to<Reference>(p_object); - - if (ref) { - // 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: _GodotSharp::_dispose_object(Object *p_object) - - ref->reference(); - } + CSharpScriptBinding script_binding; - Ref<MonoGCHandle> gchandle = MonoGCHandle::create_strong(mono_object); + script_binding.type_name = type_name; + script_binding.wrapper_class = type_class; // cache + script_binding.gchandle = MonoGCHandle::create_strong(mono_object); #ifndef NO_THREADS script_bind_lock->lock(); #endif - void *data = (void *)gchandle_bindings.insert(p_object, gchandle); + void *data = (void *)script_bindings.insert(p_object, script_binding); #ifndef NO_THREADS script_bind_lock->unlock(); #endif + // Tie managed to unmanaged + Reference *ref = Object::cast_to<Reference>(p_object); + + if (ref) { + // 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_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) + + ref->reference(); + } + return data; } @@ -985,7 +1038,7 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) { if (GDMono::get_singleton() == NULL) { #ifdef DEBUG_ENABLED - CRASH_COND(!gchandle_bindings.empty()); + CRASH_COND(!script_bindings.empty()); #endif // Mono runtime finalized, all the gchandle bindings were already released return; @@ -998,15 +1051,15 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) { script_bind_lock->lock(); #endif - Map<Object *, Ref<MonoGCHandle> >::Element *data = (Map<Object *, Ref<MonoGCHandle> >::Element *)p_data; + Map<Object *, CSharpScriptBinding>::Element *data = (Map<Object *, CSharpScriptBinding>::Element *)p_data; // Set the native instance field to IntPtr.Zero, if not yet garbage collected - MonoObject *mono_object = data->value()->get_target(); + MonoObject *mono_object = data->value().gchandle->get_target(); if (mono_object) { CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL); } - gchandle_bindings.erase(data); + script_bindings.erase(data); #ifndef NO_THREADS script_bind_lock->unlock(); @@ -1024,7 +1077,7 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { void *data = p_object->get_script_instance_binding(get_language_index()); if (!data) return; - Ref<MonoGCHandle> &gchandle = ((Map<Object *, Ref<MonoGCHandle> >::Element *)data)->get(); + Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle; if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 // The reference count was increased after the managed side was the only one referencing our owner. @@ -1036,7 +1089,7 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) { return; // Called after the managed side was collected, so nothing to do here // Release the current weak handle and replace it with a strong handle. - uint32_t strong_gchandle = MonoGCHandle::make_strong_handle(target); + uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(target); gchandle->release(); gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE); } @@ -1055,7 +1108,7 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { void *data = p_object->get_script_instance_binding(get_language_index()); if (!data) return refcount == 0; - Ref<MonoGCHandle> &gchandle = ((Map<Object *, Ref<MonoGCHandle> >::Element *)data)->get(); + Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle; if (refcount == 1 && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 // If owner owner is no longer referenced by the unmanaged side, @@ -1066,7 +1119,7 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) { 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. - uint32_t weak_gchandle = MonoGCHandle::make_weak_handle(target); + uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(target); gchandle->release(); gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE); @@ -1096,9 +1149,8 @@ CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpS } MonoObject *CSharpInstance::get_mono_object() const { -#ifdef DEBUG_ENABLED - CRASH_COND(gchandle.is_null()); -#endif + + ERR_FAIL_COND_V(gchandle.is_null(), NULL); return gchandle->get_target(); } @@ -1326,10 +1378,12 @@ void CSharpInstance::call_multilevel_reversed(const StringName &p_method, const call_multilevel(p_method, p_args, p_argcount); } -void CSharpInstance::_reference_owner_unsafe() { +bool CSharpInstance::_reference_owner_unsafe() { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); + CRASH_COND(owner == NULL); + CRASH_COND(unsafe_referenced); // already referenced #endif // Unsafe refcount increment. The managed instance also counts as a reference. @@ -1338,36 +1392,107 @@ void CSharpInstance::_reference_owner_unsafe() { // See: _unreference_owner_unsafe() // May not me referenced yet, so we must use init_ref() instead of reference() - Object::cast_to<Reference>(owner)->init_ref(); + bool success = Object::cast_to<Reference>(owner)->init_ref(); + unsafe_referenced = success; + return success; } -void CSharpInstance::_unreference_owner_unsafe() { +bool CSharpInstance::_unreference_owner_unsafe() { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); + CRASH_COND(owner == NULL); #endif + if (!unsafe_referenced) + return false; // Already unreferenced + // Called from CSharpInstance::mono_object_disposed() or ~CSharpInstance() // Unsafe refcount decrement. The managed instance also counts as a reference. // See: _reference_owner_unsafe() - if (Object::cast_to<Reference>(owner)->unreference()) { + bool die = static_cast<Reference *>(owner)->unreference(); + + if (die) { memdelete(owner); owner = NULL; } + + return die; } -void CSharpInstance::mono_object_disposed() { +MonoObject *CSharpInstance::_internal_new_managed() { +#ifdef DEBUG_ENABLED + CRASH_COND(!gchandle.is_valid()); +#endif + + CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); + + ERR_FAIL_NULL_V(owner, NULL); + ERR_FAIL_COND_V(script.is_null(), NULL); if (base_ref) - _unreference_owner_unsafe(); + _reference_owner_unsafe(); + + MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script->script_class->get_mono_ptr()); + + if (!mono_object) { + script = Ref<CSharpScript>(); + owner->set_script_instance(NULL); + ERR_EXPLAIN("Failed to allocate memory for the object"); + ERR_FAIL_V(NULL); + } + + CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, owner); + + // Construct + GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0); + ctor->invoke_raw(mono_object, NULL); + + // Tie managed to unmanaged + gchandle = MonoGCHandle::create_strong(mono_object); + + return mono_object; +} + +bool CSharpInstance::mono_object_disposed(MonoObject *p_obj) { + +#ifdef DEBUG_ENABLED + CRASH_COND(base_ref == true); + CRASH_COND(gchandle.is_null()); +#endif + CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); +} + +bool CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_owner_deleted) { + +#ifdef DEBUG_ENABLED + CRASH_COND(base_ref == false); + CRASH_COND(gchandle.is_null()); +#endif + if (_unreference_owner_unsafe()) { + r_owner_deleted = true; + } else { + r_owner_deleted = false; + CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); + if (p_is_finalizer) { + // If the native instance is still alive, then it was + // referenced from another thread before the finalizer could + // unreference it 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. + _internal_new_managed(); + } + } } void CSharpInstance::refcount_incremented() { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); + CRASH_COND(owner == NULL); #endif Reference *ref_owner = Object::cast_to<Reference>(owner); @@ -1378,7 +1503,7 @@ 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. - uint32_t strong_gchandle = MonoGCHandle::make_strong_handle(gchandle->get_target()); + uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(gchandle->get_target()); gchandle->release(); gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE); } @@ -1388,6 +1513,7 @@ bool CSharpInstance::refcount_decremented() { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref); + CRASH_COND(owner == NULL); #endif Reference *ref_owner = Object::cast_to<Reference>(owner); @@ -1399,7 +1525,7 @@ 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. - uint32_t weak_gchandle = MonoGCHandle::make_weak_handle(gchandle->get_target()); + uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(gchandle->get_target()); gchandle->release(); gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE); @@ -1470,25 +1596,64 @@ MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variab void CSharpInstance::notification(int p_notification) { - MonoObject *mono_object = get_mono_object(); - if (p_notification == Object::NOTIFICATION_PREDELETE) { - if (mono_object != NULL) { // otherwise it was collected, and the finalizer already called NOTIFICATION_PREDELETE - call_notification_no_check(mono_object, p_notification); - // Set the native instance field to IntPtr.Zero - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL); + // When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose(). + // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed + // to be sent at least once, which happens right before the call to the destructor. + + if (base_ref) { + // It's not safe to proceed if the owner derives Reference and the refcount reached 0. + // At this point, Dispose() was already called (manually or from the finalizer) so + // that's not a problem. The refcount wouldn't have reached 0 otherwise, since the + // managed side references it and Dispose() needs to be called to release it. + // However, this means C# Reference scripts can't receive NOTIFICATION_PREDELETE, but + // this is likely the case with GDScript as well: https://github.com/godotengine/godot/issues/6784 + return; } + + _call_notification(p_notification); + + MonoObject *mono_object = get_mono_object(); + ERR_FAIL_NULL(mono_object); + + GDMonoUtils::GodotObject_Dispose thunk = CACHED_METHOD_THUNK(GodotObject, Dispose); + + MonoException *exc = NULL; + thunk(mono_object, (MonoObject **)&exc); + + if (exc) { + GDMonoUtils::set_pending_exception(exc); + } + return; } - call_notification_no_check(mono_object, p_notification); + _call_notification(p_notification); } -void CSharpInstance::call_notification_no_check(MonoObject *p_mono_object, int p_notification) { - Variant value = p_notification; - const Variant *args[1] = { &value }; +void CSharpInstance::_call_notification(int p_notification) { + + MonoObject *mono_object = get_mono_object(); + ERR_FAIL_NULL(mono_object); + + // Custom version of _call_multilevel, optimized for _notification + + uint32_t arg = p_notification; + void *args[1] = { &arg }; + StringName method_name = CACHED_STRING_NAME(_notification); + + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + GDMonoMethod *method = top->get_method(method_name, 1); + + if (method) { + method->invoke_raw(mono_object, args); + return; + } - _call_multilevel(p_mono_object, CACHED_STRING_NAME(_notification), args, 1); + top = top->get_parent_class(); + } } Ref<Script> CSharpInstance::get_script() const { @@ -1501,11 +1666,11 @@ ScriptLanguage *CSharpInstance::get_language() { return CSharpLanguage::get_singleton(); } -CSharpInstance::CSharpInstance() { - - owner = NULL; - base_ref = false; - ref_dying = false; +CSharpInstance::CSharpInstance() : + owner(NULL), + base_ref(false), + ref_dying(false), + unsafe_referenced(false) { } CSharpInstance::~CSharpInstance() { @@ -1514,10 +1679,7 @@ CSharpInstance::~CSharpInstance() { gchandle->release(); // Make sure it's released } - if (base_ref && !ref_dying) { // it may be called from the owner's destructor -#ifdef DEBUG_ENABLED - CRASH_COND(!owner); // dunno, just in case -#endif + if (base_ref && !ref_dying && owner) { // it may be called from the owner's destructor _unreference_owner_unsafe(); } @@ -1586,26 +1748,27 @@ bool CSharpScript::_update_exports() { exported_members_cache.clear(); exported_members_defval_cache.clear(); - // We are creating a temporary new instance of the class here to get the default value - // TODO Workaround. Should be replaced with IL opcodes analysis + // Here we create a temporary managed instance of the class to get the initial values MonoObject *tmp_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr()); - if (tmp_object) { - CACHED_FIELD(GodotObject, ptr)->set_value_raw(tmp_object, tmp_object); // FIXME WTF is this workaround + if (!tmp_object) { + ERR_PRINT("Failed to create temporary MonoObject"); + return false; + } - GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - MonoException *exc = NULL; - ctor->invoke(tmp_object, NULL, &exc); + uint32_t tmp_pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed) - if (exc) { - ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(exc); - tmp_object = NULL; - ERR_FAIL_V(false); - } - } else { - ERR_PRINT("Failed to create temporary MonoObject"); + GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); + MonoException *ctor_exc = NULL; + ctor->invoke(tmp_object, NULL, &ctor_exc); + + if (ctor_exc) { + MonoGCHandle::free_handle(tmp_pinned_gchandle); + tmp_object = NULL; + + ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); + GDMonoUtils::debug_print_unhandled_exception(ctor_exc); return false; } @@ -1666,6 +1829,21 @@ bool CSharpScript::_update_exports() { top = top->get_parent_class(); } + + // Dispose the temporary managed instance + + GDMonoUtils::GodotObject_Dispose thunk = CACHED_METHOD_THUNK(GodotObject, Dispose); + + MonoException *exc = NULL; + thunk(tmp_object, (MonoObject **)&exc); + + if (exc) { + ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:"); + GDMonoUtils::debug_print_unhandled_exception(exc); + } + + MonoGCHandle::free_handle(tmp_pinned_gchandle); + tmp_object = NULL; } if (placeholders.size()) { @@ -2012,6 +2190,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg ERR_FAIL_V(NULL); } + uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(mono_object); // we might lock after this, so pin it + #ifndef NO_THREADS CSharpLanguage::singleton->lock->lock(); #endif @@ -2033,6 +2213,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg /* STEP 3, PARTY */ + MonoGCHandle::free_handle(pinned_gchandle); + //@TODO make thread safe return instance; } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 1a5d0c8a69..d152e802de 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -48,6 +48,8 @@ class CSharpLanguage; #ifdef NO_SAFE_CAST template <typename TScriptInstance, typename TScriptLanguage> TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { + if (!p_inst) + return NULL; return p_inst->get_language() == TScriptLanguage::get_singleton() ? static_cast<TScriptInstance *>(p_inst) : NULL; } #else @@ -177,14 +179,19 @@ class CSharpInstance : public ScriptInstance { friend class CSharpScript; friend class CSharpLanguage; + Object *owner; - Ref<CSharpScript> script; - Ref<MonoGCHandle> gchandle; bool base_ref; bool ref_dying; + bool unsafe_referenced; + + Ref<CSharpScript> script; + Ref<MonoGCHandle> gchandle; - void _reference_owner_unsafe(); - void _unreference_owner_unsafe(); + bool _reference_owner_unsafe(); + bool _unreference_owner_unsafe(); + + MonoObject *_internal_new_managed(); // Do not use unless you know what you are doing friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *); @@ -208,7 +215,8 @@ public: virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount); virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount); - void mono_object_disposed(); + bool mono_object_disposed(MonoObject *p_obj); + bool mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_owner_deleted); virtual void refcount_incremented(); virtual bool refcount_decremented(); @@ -217,7 +225,7 @@ public: virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const; virtual void notification(int p_notification); - void call_notification_no_check(MonoObject *p_mono_object, int p_notification); + void _call_notification(int p_notification); virtual Ref<Script> get_script() const; @@ -227,6 +235,12 @@ public: ~CSharpInstance(); }; +struct CSharpScriptBinding { + StringName type_name; + GDMonoClass *wrapper_class; + Ref<MonoGCHandle> gchandle; +}; + class CSharpLanguage : public ScriptLanguage { friend class CSharpScript; @@ -241,10 +255,11 @@ class CSharpLanguage : public ScriptLanguage { Mutex *lock; Mutex *script_bind_lock; + Mutex *script_gchandle_release_lock; Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > > to_reload; - Map<Object *, Ref<MonoGCHandle> > gchandle_bindings; + Map<Object *, CSharpScriptBinding> script_bindings; struct StringNameCache { @@ -270,6 +285,9 @@ public: _FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; } + static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle); + static void release_script_gchandle(MonoObject *p_pinned_expected_obj, Ref<MonoGCHandle> &p_gchandle); + bool debug_break(const String &p_error, bool p_allow_continue = true); bool debug_break_parse(const String &p_file, int p_line, const String &p_error); diff --git a/modules/mono/editor/mono_bottom_panel.cpp b/modules/mono/editor/mono_bottom_panel.cpp index 9317550d28..ecc3e4c59e 100644 --- a/modules/mono/editor/mono_bottom_panel.cpp +++ b/modules/mono/editor/mono_bottom_panel.cpp @@ -53,9 +53,9 @@ void MonoBottomPanel::_update_build_tabs_list() { build_tabs_list->add_item(item_name, tab->get_icon_texture()); - String item_tooltip = String("Solution: ") + tab->build_info.solution; - item_tooltip += String("\nConfiguration: ") + tab->build_info.configuration; - item_tooltip += String("\nStatus: "); + String item_tooltip = "Solution: " + tab->build_info.solution; + item_tooltip += "\nConfiguration: " + tab->build_info.configuration; + item_tooltip += "\nStatus: "; if (tab->build_exited) { item_tooltip += tab->build_result == MonoBuildTab::RESULT_SUCCESS ? "Succeeded" : "Errored"; diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp index 8ee2fae475..d718c3cc61 100644 --- a/modules/mono/glue/base_object_glue.cpp +++ b/modules/mono/glue/base_object_glue.cpp @@ -35,21 +35,78 @@ #include "core/reference.h" #include "core/string_db.h" +#include "../csharp_script.h" #include "../mono_gd/gd_mono_internals.h" #include "../mono_gd/gd_mono_utils.h" #include "../signal_awaiter_utils.h" -Object *godot_icall_Object_Ctor(MonoObject *obj) { +Object *godot_icall_Object_Ctor(MonoObject *p_obj) { Object *instance = memnew(Object); - GDMonoInternals::tie_managed_to_unmanaged(obj, instance); + GDMonoInternals::tie_managed_to_unmanaged(p_obj, instance); return instance; } -void godot_icall_Object_Dtor(MonoObject *obj, Object *ptr) { +void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) { #ifdef DEBUG_ENABLED - CRASH_COND(ptr == NULL); + CRASH_COND(p_ptr == NULL); #endif - _GodotSharp::get_singleton()->queue_dispose(obj, ptr); + + if (p_ptr->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance()); + if (cs_instance) { + cs_instance->mono_object_disposed(p_obj); + p_ptr->set_script_instance(NULL); + return; + } + } + + void *data = p_ptr->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); + + if (data) { + Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle; + if (gchandle.is_valid()) { + CSharpLanguage::release_script_gchandle(p_obj, gchandle); + } + } +} + +void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, bool p_is_finalizer) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_ptr == NULL); + // This is only called with Reference derived classes + CRASH_COND(!Object::cast_to<Reference>(p_ptr)); +#endif + + Reference *ref = static_cast<Reference *>(p_ptr); + + if (ref->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(ref->get_script_instance()); + if (cs_instance) { + bool r_owner_deleted; + cs_instance->mono_object_disposed_baseref(p_obj, p_is_finalizer, r_owner_deleted); + if (!r_owner_deleted && !p_is_finalizer) { + // If the native instance is still alive and Dispose() was called + // (instead of the finalizer), then we remove the script instance. + ref->set_script_instance(NULL); + } + return; + } + } + + // Unsafe refcount decrement. The managed instance also counts as a reference. + // See: CSharpLanguage::alloc_instance_binding_data(Object *p_object) + if (ref->unreference()) { + memdelete(ref); + } else { + void *data = ref->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); + + if (data) { + Ref<MonoGCHandle> &gchandle = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get().gchandle; + if (gchandle.is_valid()) { + CSharpLanguage::release_script_gchandle(p_obj, gchandle); + } + } + } } MethodBind *godot_icall_Object_ClassDB_get_method(MonoString *p_type, MonoString *p_method) { @@ -87,7 +144,8 @@ Error godot_icall_SignalAwaiter_connect(Object *p_source, MonoString *p_signal, void godot_register_object_icalls() { mono_add_internal_call("Godot.Object::godot_icall_Object_Ctor", (void *)godot_icall_Object_Ctor); - mono_add_internal_call("Godot.Object::godot_icall_Object_Dtor", (void *)godot_icall_Object_Dtor); + mono_add_internal_call("Godot.Object::godot_icall_Object_Disposed", (void *)godot_icall_Object_Disposed); + mono_add_internal_call("Godot.Object::godot_icall_Reference_Disposed", (void *)godot_icall_Reference_Disposed); mono_add_internal_call("Godot.Object::godot_icall_Object_ClassDB_get_method", (void *)godot_icall_Object_ClassDB_get_method); mono_add_internal_call("Godot.Object::godot_icall_Object_weakref", (void *)godot_icall_Object_weakref); mono_add_internal_call("Godot.SignalAwaiter::godot_icall_SignalAwaiter_connect", (void *)godot_icall_SignalAwaiter_connect); diff --git a/modules/mono/glue/base_object_glue.h b/modules/mono/glue/base_object_glue.h index 08cc380429..2d4d66ebb8 100644 --- a/modules/mono/glue/base_object_glue.h +++ b/modules/mono/glue/base_object_glue.h @@ -38,9 +38,11 @@ #include "../mono_gd/gd_mono_marshal.h" -Object *godot_icall_Object_Ctor(MonoObject *obj); +Object *godot_icall_Object_Ctor(MonoObject *p_obj); -void godot_icall_Object_Dtor(MonoObject *obj, Object *ptr); +void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr); + +void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, bool p_is_finalizer); MethodBind *godot_icall_Object_ClassDB_get_method(MonoString *p_type, MonoString *p_method); diff --git a/modules/mono/glue/cs_files/Array.cs b/modules/mono/glue/cs_files/Array.cs index 8e337dd9b1..c80cb7cc83 100644 --- a/modules/mono/glue/cs_files/Array.cs +++ b/modules/mono/glue/cs_files/Array.cs @@ -55,11 +55,6 @@ namespace Godot.Collections public void Dispose() { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { if (disposed) return; diff --git a/modules/mono/glue/cs_files/Dictionary.cs b/modules/mono/glue/cs_files/Dictionary.cs index 57a476cb23..523e48c31a 100644 --- a/modules/mono/glue/cs_files/Dictionary.cs +++ b/modules/mono/glue/cs_files/Dictionary.cs @@ -59,11 +59,6 @@ namespace Godot.Collections public void Dispose() { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { if (disposed) return; diff --git a/modules/mono/glue/cs_files/Object.base.cs b/modules/mono/glue/cs_files/Object.base.cs index 5d62d0b335..30490a715f 100644 --- a/modules/mono/glue/cs_files/Object.base.cs +++ b/modules/mono/glue/cs_files/Object.base.cs @@ -54,7 +54,11 @@ namespace Godot if (memoryOwn) { memoryOwn = false; - godot_icall_Object_Dtor(this, ptr); + godot_icall_Reference_Disposed(this, ptr, !disposing); + } + else + { + godot_icall_Object_Disposed(this, ptr); } this.ptr = IntPtr.Zero; @@ -72,7 +76,10 @@ namespace Godot internal extern static IntPtr godot_icall_Object_Ctor(Object obj); [MethodImpl(MethodImplOptions.InternalCall)] - internal extern static void godot_icall_Object_Dtor(object obj, IntPtr ptr); + internal extern static void godot_icall_Object_Disposed(Object obj, IntPtr ptr); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_Reference_Disposed(Object obj, IntPtr ptr, bool isFinalizer); // Used by the generated API [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/modules/mono/mono_gc_handle.cpp b/modules/mono/mono_gc_handle.cpp index 9f0e933a8c..f695bddfeb 100644 --- a/modules/mono/mono_gc_handle.cpp +++ b/modules/mono/mono_gc_handle.cpp @@ -32,24 +32,34 @@ #include "mono_gd/gd_mono.h" -uint32_t MonoGCHandle::make_strong_handle(MonoObject *p_object) { +uint32_t MonoGCHandle::new_strong_handle(MonoObject *p_object) { return mono_gchandle_new(p_object, /* pinned: */ false); } -uint32_t MonoGCHandle::make_weak_handle(MonoObject *p_object) { +uint32_t MonoGCHandle::new_strong_handle_pinned(MonoObject *p_object) { + + return mono_gchandle_new(p_object, /* pinned: */ true); +} + +uint32_t MonoGCHandle::new_weak_handle(MonoObject *p_object) { return mono_gchandle_new_weakref(p_object, /* track_resurrection: */ false); } +void MonoGCHandle::free_handle(uint32_t p_gchandle) { + + mono_gchandle_free(p_gchandle); +} + Ref<MonoGCHandle> MonoGCHandle::create_strong(MonoObject *p_object) { - return memnew(MonoGCHandle(make_strong_handle(p_object), STRONG_HANDLE)); + return memnew(MonoGCHandle(new_strong_handle(p_object), STRONG_HANDLE)); } Ref<MonoGCHandle> MonoGCHandle::create_weak(MonoObject *p_object) { - return memnew(MonoGCHandle(make_weak_handle(p_object), WEAK_HANDLE)); + return memnew(MonoGCHandle(new_weak_handle(p_object), WEAK_HANDLE)); } void MonoGCHandle::release() { @@ -59,7 +69,7 @@ void MonoGCHandle::release() { #endif if (!released && GDMono::get_singleton()->is_runtime_initialized()) { - mono_gchandle_free(handle); + free_handle(handle); released = true; } } diff --git a/modules/mono/mono_gc_handle.h b/modules/mono/mono_gc_handle.h index 7eeaba30e0..e145c1e1e6 100644 --- a/modules/mono/mono_gc_handle.h +++ b/modules/mono/mono_gc_handle.h @@ -49,12 +49,15 @@ public: WEAK_HANDLE }; - static uint32_t make_strong_handle(MonoObject *p_object); - static uint32_t make_weak_handle(MonoObject *p_object); + static uint32_t new_strong_handle(MonoObject *p_object); + static uint32_t new_strong_handle_pinned(MonoObject *p_object); + static uint32_t new_weak_handle(MonoObject *p_object); + static void free_handle(uint32_t p_gchandle); static Ref<MonoGCHandle> create_strong(MonoObject *p_object); static Ref<MonoGCHandle> create_weak(MonoObject *p_object); + _FORCE_INLINE_ bool is_released() { return released; } _FORCE_INLINE_ bool is_weak() { return weak; } _FORCE_INLINE_ MonoObject *get_target() const { return released ? NULL : mono_gchandle_get_target(handle); } diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index f1dbb5c514..833aa23820 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -639,9 +639,7 @@ Error GDMono::_unload_scripts_domain() { mono_gc_collect(mono_gc_max_generation()); - finalizing_scripts_domain = true; mono_domain_finalize(scripts_domain, 2000); - finalizing_scripts_domain = false; mono_gc_collect(mono_gc_max_generation()); @@ -837,7 +835,6 @@ GDMono::GDMono() { gdmono_log = memnew(GDMonoLog); runtime_initialized = false; - finalizing_scripts_domain = false; root_domain = NULL; scripts_domain = NULL; @@ -866,7 +863,7 @@ GDMono::GDMono() { GDMono::~GDMono() { - if (runtime_initialized) { + if (is_runtime_initialized()) { if (scripts_domain) { @@ -891,8 +888,9 @@ GDMono::~GDMono() { print_verbose("Mono: Runtime cleanup..."); - runtime_initialized = false; mono_jit_cleanup(root_domain); + + runtime_initialized = false; } if (gdmono_log) @@ -903,33 +901,12 @@ GDMono::~GDMono() { _GodotSharp *_GodotSharp::singleton = NULL; -void _GodotSharp::_dispose_object(Object *p_object) { - - if (p_object->get_script_instance()) { - CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_object->get_script_instance()); - if (cs_instance) { - cs_instance->mono_object_disposed(); - return; - } - } - - // Unsafe refcount decrement. The managed instance also counts as a reference. - // See: CSharpLanguage::alloc_instance_binding_data(Object *p_object) - if (Object::cast_to<Reference>(p_object)->unreference()) { - memdelete(p_object); - } -} - void _GodotSharp::_dispose_callback() { #ifndef NO_THREADS queue_mutex->lock(); #endif - for (List<Object *>::Element *E = obj_delete_queue.front(); E; E = E->next()) { - _dispose_object(E->get()); - } - for (List<NodePath *>::Element *E = np_delete_queue.front(); E; E = E->next()) { memdelete(E->get()); } @@ -938,7 +915,6 @@ void _GodotSharp::_dispose_callback() { memdelete(E->get()); } - obj_delete_queue.clear(); np_delete_queue.clear(); rid_delete_queue.clear(); queue_empty = true; @@ -958,52 +934,69 @@ void _GodotSharp::detach_thread() { GDMonoUtils::detach_current_thread(); } -bool _GodotSharp::is_finalizing_domain() { +int32_t _GodotSharp::get_domain_id() { - return GDMono::get_singleton()->is_finalizing_scripts_domain(); + MonoDomain *domain = mono_domain_get(); + CRASH_COND(!domain); // User must check if runtime is initialized before calling this method + return mono_domain_get_id(domain); } -bool _GodotSharp::is_domain_loaded() { +int32_t _GodotSharp::get_scripts_domain_id() { - return GDMono::get_singleton()->get_scripts_domain() != NULL; + MonoDomain *domain = SCRIPTS_DOMAIN; + CRASH_COND(!domain); // User must check if scripts domain is loaded before calling this method + return mono_domain_get_id(domain); } -#define ENQUEUE_FOR_DISPOSAL(m_queue, m_inst) \ - m_queue.push_back(m_inst); \ - if (queue_empty) { \ - queue_empty = false; \ - if (!is_finalizing_domain()) { /* call_deferred may not be safe here */ \ - call_deferred("_dispose_callback"); \ - } \ - } +bool _GodotSharp::is_scripts_domain_loaded() { -void _GodotSharp::queue_dispose(MonoObject *p_mono_object, Object *p_object) { + return GDMono::get_singleton()->is_runtime_initialized() && SCRIPTS_DOMAIN != NULL; +} - if (GDMonoUtils::is_main_thread() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { - _dispose_object(p_object); - } else { -#ifndef NO_THREADS - queue_mutex->lock(); -#endif +bool _GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) { - // This is our last chance to invoke notification predelete (this is being called from the finalizer) - // We must use the MonoObject* passed by the finalizer, because the weak GC handle target returns NULL at this point - CSharpInstance *si = CAST_CSHARP_INSTANCE(p_object->get_script_instance()); - if (si) { - si->call_notification_no_check(p_mono_object, Object::NOTIFICATION_PREDELETE); - } + return is_domain_finalizing_for_unload(p_domain_id); +} - ENQUEUE_FOR_DISPOSAL(obj_delete_queue, p_object); +bool _GodotSharp::is_domain_finalizing_for_unload() { -#ifndef NO_THREADS - queue_mutex->unlock(); -#endif - } + return is_domain_finalizing_for_unload(mono_domain_get()); +} + +bool _GodotSharp::is_domain_finalizing_for_unload(int32_t p_domain_id) { + + return is_domain_finalizing_for_unload(mono_domain_get_by_id(p_domain_id)); +} + +bool _GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) { + + if (!p_domain) + return true; + return mono_domain_is_unloading(p_domain); +} + +bool _GodotSharp::is_runtime_shutting_down() { + + return mono_runtime_is_shutting_down(); +} + +bool _GodotSharp::is_runtime_initialized() { + + return GDMono::get_singleton()->is_runtime_initialized(); } +#define ENQUEUE_FOR_DISPOSAL(m_queue, m_inst) \ + m_queue.push_back(m_inst); \ + if (queue_empty) { \ + queue_empty = false; \ + if (!is_domain_finalizing_for_unload(SCRIPTS_DOMAIN)) { /* call_deferred may not be safe here */ \ + call_deferred("_dispose_callback"); \ + } \ + } + void _GodotSharp::queue_dispose(NodePath *p_node_path) { - if (GDMonoUtils::is_main_thread() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { + if (GDMonoUtils::is_main_thread() && !is_domain_finalizing_for_unload(SCRIPTS_DOMAIN)) { memdelete(p_node_path); } else { #ifndef NO_THREADS @@ -1020,7 +1013,7 @@ void _GodotSharp::queue_dispose(NodePath *p_node_path) { void _GodotSharp::queue_dispose(RID *p_rid) { - if (GDMonoUtils::is_main_thread() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { + if (GDMonoUtils::is_main_thread() && !is_domain_finalizing_for_unload(SCRIPTS_DOMAIN)) { memdelete(p_rid); } else { #ifndef NO_THREADS @@ -1040,8 +1033,13 @@ void _GodotSharp::_bind_methods() { ClassDB::bind_method(D_METHOD("attach_thread"), &_GodotSharp::attach_thread); ClassDB::bind_method(D_METHOD("detach_thread"), &_GodotSharp::detach_thread); - ClassDB::bind_method(D_METHOD("is_finalizing_domain"), &_GodotSharp::is_finalizing_domain); - ClassDB::bind_method(D_METHOD("is_domain_loaded"), &_GodotSharp::is_domain_loaded); + ClassDB::bind_method(D_METHOD("get_domain_id"), &_GodotSharp::get_domain_id); + ClassDB::bind_method(D_METHOD("get_scripts_domain_id"), &_GodotSharp::get_scripts_domain_id); + ClassDB::bind_method(D_METHOD("is_scripts_domain_loaded"), &_GodotSharp::is_scripts_domain_loaded); + ClassDB::bind_method(D_METHOD("is_domain_finalizing_for_unload", "domain_id"), &_GodotSharp::_is_domain_finalizing_for_unload); + + ClassDB::bind_method(D_METHOD("is_runtime_shutting_down"), &_GodotSharp::is_runtime_shutting_down); + ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &_GodotSharp::is_runtime_initialized); ClassDB::bind_method(D_METHOD("_dispose_callback"), &_GodotSharp::_dispose_callback); } diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index e0ec6ced5e..0c5503d28e 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -170,8 +170,7 @@ public: void add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly); GDMonoAssembly **get_loaded_assembly(const String &p_name); - _FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized; } - _FORCE_INLINE_ bool is_finalizing_scripts_domain() const { return finalizing_scripts_domain; } + _FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized && !mono_runtime_is_shutting_down() /* stays true after shutdown finished */; } _FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; } #ifdef TOOLS_ENABLED @@ -236,11 +235,10 @@ class _GodotSharp : public Object { friend class GDMono; - void _dispose_object(Object *p_object); - void _dispose_callback(); - List<Object *> obj_delete_queue; + bool _is_domain_finalizing_for_unload(int32_t p_domain_id); + List<NodePath *> np_delete_queue; List<RID *> rid_delete_queue; @@ -260,10 +258,18 @@ public: void attach_thread(); void detach_thread(); - bool is_finalizing_domain(); - bool is_domain_loaded(); + int32_t get_domain_id(); + int32_t get_scripts_domain_id(); + + bool is_scripts_domain_loaded(); + + bool is_domain_finalizing_for_unload(); + bool is_domain_finalizing_for_unload(int32_t p_domain_id); + bool is_domain_finalizing_for_unload(MonoDomain *p_domain); + + bool is_runtime_shutting_down(); + bool is_runtime_initialized(); - void queue_dispose(MonoObject *p_mono_object, Object *p_object); void queue_dispose(NodePath *p_node_path); void queue_dispose(RID *p_rid); diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp index c1f56bc3d2..5e02ebf430 100644 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -138,6 +138,7 @@ void MonoCache::clear_members() { field_Image_ptr = NULL; field_RID_ptr = NULL; + methodthunk_GodotObject_Dispose = NULL; methodthunk_Array_GetPtr = NULL; methodthunk_Dictionary_GetPtr = NULL; methodthunk_MarshalUtils_IsArrayGenericType = NULL; @@ -235,6 +236,7 @@ void update_godot_api_cache() { CACHE_FIELD_AND_CHECK(NodePath, ptr, CACHED_CLASS(NodePath)->get_field(BINDINGS_PTR_FIELD)); CACHE_FIELD_AND_CHECK(RID, ptr, CACHED_CLASS(RID)->get_field(BINDINGS_PTR_FIELD)); + CACHE_METHOD_THUNK_AND_CHECK(GodotObject, Dispose, (GodotObject_Dispose)CACHED_CLASS(GodotObject)->get_method_thunk("Dispose", 0)); CACHE_METHOD_THUNK_AND_CHECK(Array, GetPtr, (Array_GetPtr)GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Array)->get_method_thunk("GetPtr", 0)); CACHE_METHOD_THUNK_AND_CHECK(Dictionary, GetPtr, (Dictionary_GetPtr)GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)->get_method_thunk("GetPtr", 0)); CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IsArrayGenericType, (IsArrayGenericType)GODOT_API_CLASS(MarshalUtils)->get_method_thunk("IsArrayGenericType", 1)); @@ -247,7 +249,7 @@ void update_godot_api_cache() { CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, (DebugUtils_StackFrameInfo)GODOT_API_CLASS(DebuggingUtils)->get_method_thunk("GetStackFrameInfo", 4)); #endif - // TODO Move to CSharpLanguage::init() + // TODO Move to CSharpLanguage::init() and do handle disposal MonoObject *task_scheduler = mono_object_new(SCRIPTS_DOMAIN, GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr()); GDMonoUtils::runtime_object_init(task_scheduler); mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler); @@ -270,11 +272,48 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) { } } - // Only called if the owner does not have a CSharpInstance + // If the owner does not have a CSharpInstance... + void *data = unmanaged->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); if (data) { - return ((Map<Object *, Ref<MonoGCHandle> >::Element *)data)->value()->get_target(); + CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->value(); + + Ref<MonoGCHandle> &gchandle = script_binding.gchandle; + ERR_FAIL_COND_V(gchandle.is_null(), NULL); + + MonoObject *target = gchandle->get_target(); + + if (target) + return target; + + CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); + + // Create a new one + +#ifdef DEBUG_ENABLED + CRASH_COND(script_binding.type_name == StringName()); + CRASH_COND(script_binding.wrapper_class == NULL); +#endif + + MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(script_binding.wrapper_class, script_binding.type_name, unmanaged); + ERR_FAIL_NULL_V(mono_object, NULL); + + gchandle->set_handle(MonoGCHandle::new_strong_handle(mono_object), MonoGCHandle::STRONG_HANDLE); + + // Tie managed to unmanaged + Reference *ref = Object::cast_to<Reference>(unmanaged); + + if (ref) { + // 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_Reference_Dtor(MonoObject *p_obj, Object *p_ptr) + + ref->reference(); + } + + return mono_object; } } @@ -304,6 +343,7 @@ MonoThread *get_current_thread() { void runtime_object_init(MonoObject *p_this_obj) { GD_MONO_BEGIN_RUNTIME_INVOKE; + // FIXME: Do not use mono_runtime_object_init, it aborts if an exception is thrown mono_runtime_object_init(p_this_obj); GD_MONO_END_RUNTIME_INVOKE; } diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h index bf8860c85a..c6de824d5a 100644 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -49,6 +49,7 @@ namespace GDMonoUtils { +typedef void (*GodotObject_Dispose)(MonoObject *, MonoObject **); typedef Array *(*Array_GetPtr)(MonoObject *, MonoObject **); typedef Dictionary *(*Dictionary_GetPtr)(MonoObject *, MonoObject **); typedef MonoObject *(*SignalAwaiter_SignalCallback)(MonoObject *, MonoArray *, MonoObject **); @@ -141,6 +142,7 @@ struct MonoCache { GDMonoField *field_Image_ptr; GDMonoField *field_RID_ptr; + GodotObject_Dispose methodthunk_GodotObject_Dispose; Array_GetPtr methodthunk_Array_GetPtr; Dictionary_GetPtr methodthunk_Dictionary_GetPtr; IsArrayGenericType methodthunk_MarshalUtils_IsArrayGenericType; diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp index add1e506ea..b4c78df538 100644 --- a/modules/mono/signal_awaiter_utils.cpp +++ b/modules/mono/signal_awaiter_utils.cpp @@ -119,7 +119,7 @@ void SignalAwaiterHandle::_bind_methods() { } SignalAwaiterHandle::SignalAwaiterHandle(MonoObject *p_managed) : - MonoGCHandle(MonoGCHandle::make_strong_handle(p_managed), STRONG_HANDLE) { + MonoGCHandle(MonoGCHandle::new_strong_handle(p_managed), STRONG_HANDLE) { #ifdef DEBUG_ENABLED conn_target_id = 0; |