Forráskód Böngészése

Merge pull request #24091 from neikeq/ii

C#: Improve tool script support and fix reloading issues
Ignacio Etcheverry 6 éve
szülő
commit
989b93d6a4

+ 20 - 8
modules/mono/SCsub

@@ -103,6 +103,16 @@ import os
 
 
 def find_nuget_unix():
+    import os
+
+    if 'NUGET_PATH' in os.environ:
+        hint_path = os.environ['NUGET_PATH']
+        if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+            return hint_path
+        hint_path = os.path.join(hint_path, 'nuget')
+        if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+            return hint_path
+
     import os.path
     import sys
 
@@ -129,6 +139,16 @@ def find_nuget_unix():
 
 
 def find_nuget_windows():
+    import os
+
+    if 'NUGET_PATH' in os.environ:
+        hint_path = os.environ['NUGET_PATH']
+        if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+            return hint_path
+        hint_path = os.path.join(hint_path, 'nuget.exe')
+        if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+            return hint_path
+
     import mono_reg_utils as monoreg
 
     mono_root = ''
@@ -160,14 +180,6 @@ def find_nuget_windows():
         if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
             return hint_path
 
-    if 'NUGET_PATH' in os.environ:
-        hint_path = os.environ['NUGET_PATH']
-        if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
-            return hint_path
-        hint_path = os.path.join(hint_path, 'nuget.exe')
-        if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
-            return hint_path
-
     return None
 
 

+ 295 - 288
modules/mono/csharp_script.cpp

@@ -50,6 +50,7 @@
 #include "mono_gd/gd_mono_marshal.h"
 #include "signal_awaiter_utils.h"
 #include "utils/macros.h"
+#include "utils/mutex_utils.h"
 #include "utils/string_utils.h"
 #include "utils/thread_local.h"
 
@@ -378,60 +379,72 @@ static String variant_type_to_managed_name(const String &p_var_type_name) {
 		return "object";
 
 	if (!ClassDB::class_exists(p_var_type_name)) {
-		Variant::Type var_types[] = {
-			Variant::BOOL,
-			Variant::INT,
-			Variant::REAL,
-			Variant::STRING,
-			Variant::VECTOR2,
-			Variant::RECT2,
-			Variant::VECTOR3,
-			Variant::TRANSFORM2D,
-			Variant::PLANE,
-			Variant::QUAT,
-			Variant::AABB,
-			Variant::BASIS,
-			Variant::TRANSFORM,
-			Variant::COLOR,
-			Variant::NODE_PATH,
-			Variant::_RID
-		};
-
-		for (int i = 0; i < sizeof(var_types) / sizeof(Variant::Type); i++) {
-			if (p_var_type_name == Variant::get_type_name(var_types[i]))
-				return p_var_type_name;
-		}
+		return p_var_type_name;
+	}
+
+	if (p_var_type_name == Variant::get_type_name(Variant::OBJECT))
+		return "Godot.Object";
 
-		if (p_var_type_name == "String")
-			return "string"; // I prefer this one >:[
+	if (p_var_type_name == Variant::get_type_name(Variant::REAL)) {
+#ifdef REAL_T_IS_DOUBLE
+		return "double";
+#else
+		return "float";
+#endif
+	}
 
-		// TODO these will be rewritten later into custom containers
+	if (p_var_type_name == Variant::get_type_name(Variant::STRING))
+		return "string"; // I prefer this one >:[
 
-		if (p_var_type_name == "Array")
-			return "object[]";
+	if (p_var_type_name == Variant::get_type_name(Variant::DICTIONARY))
+		return "Collections.Dictionary";
 
-		if (p_var_type_name == "Dictionary")
-			return "Dictionary<object, object>";
+	if (p_var_type_name == Variant::get_type_name(Variant::ARRAY))
+		return "Collections.Array";
 
-		if (p_var_type_name == "PoolByteArray")
-			return "byte[]";
-		if (p_var_type_name == "PoolIntArray")
-			return "int[]";
-		if (p_var_type_name == "PoolRealArray")
-			return "float[]";
-		if (p_var_type_name == "PoolStringArray")
-			return "string[]";
-		if (p_var_type_name == "PoolVector2Array")
-			return "Vector2[]";
-		if (p_var_type_name == "PoolVector3Array")
-			return "Vector3[]";
-		if (p_var_type_name == "PoolColorArray")
-			return "Color[]";
+	if (p_var_type_name == Variant::get_type_name(Variant::POOL_BYTE_ARRAY))
+		return "byte[]";
+	if (p_var_type_name == Variant::get_type_name(Variant::POOL_INT_ARRAY))
+		return "int[]";
+	if (p_var_type_name == Variant::get_type_name(Variant::POOL_REAL_ARRAY)) {
+#ifdef REAL_T_IS_DOUBLE
+		return "double[]";
+#else
+		return "float[]";
+#endif
+	}
+	if (p_var_type_name == Variant::get_type_name(Variant::POOL_STRING_ARRAY))
+		return "string[]";
+	if (p_var_type_name == Variant::get_type_name(Variant::POOL_VECTOR2_ARRAY))
+		return "Vector2[]";
+	if (p_var_type_name == Variant::get_type_name(Variant::POOL_VECTOR3_ARRAY))
+		return "Vector3[]";
+	if (p_var_type_name == Variant::get_type_name(Variant::POOL_COLOR_ARRAY))
+		return "Color[]";
+
+	Variant::Type var_types[] = {
+		Variant::BOOL,
+		Variant::INT,
+		Variant::VECTOR2,
+		Variant::RECT2,
+		Variant::VECTOR3,
+		Variant::TRANSFORM2D,
+		Variant::PLANE,
+		Variant::QUAT,
+		Variant::AABB,
+		Variant::BASIS,
+		Variant::TRANSFORM,
+		Variant::COLOR,
+		Variant::NODE_PATH,
+		Variant::_RID
+	};
 
-		return "object";
+	for (int i = 0; i < sizeof(var_types) / sizeof(Variant::Type); i++) {
+		if (p_var_type_name == Variant::get_type_name(var_types[i]))
+			return p_var_type_name;
 	}
 
-	return p_var_type_name;
+	return "object";
 }
 
 String CSharpLanguage::make_function(const String &, const String &p_name, const PoolStringArray &p_args) const {
@@ -507,8 +520,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec
 
 	MonoException *exc = NULL;
 
-	GDMonoUtils::StackTrace_GetFrames st_get_frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames);
-	MonoArray *frames = st_get_frames(p_stack_trace, (MonoObject **)&exc);
+	MonoArray *frames = invoke_method_thunk(CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames), p_stack_trace, (MonoObject **)&exc);
 
 	if (exc) {
 		GDMonoUtils::debug_print_unhandled_exception(exc);
@@ -532,7 +544,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec
 		MonoString *file_name;
 		int file_line_num;
 		MonoString *method_decl;
-		get_sf_info(frame, &file_name, &file_line_num, &method_decl, (MonoObject **)&exc);
+		invoke_method_thunk(get_sf_info, frame, &file_name, &file_line_num, &method_decl, (MonoObject **)&exc);
 
 		if (exc) {
 			GDMonoUtils::debug_print_unhandled_exception(exc);
@@ -561,10 +573,8 @@ void CSharpLanguage::frame() {
 			MonoObject *task_scheduler = task_scheduler_handle->get_target();
 
 			if (task_scheduler) {
-				GDMonoUtils::GodotTaskScheduler_Activate thunk = CACHED_METHOD_THUNK(GodotTaskScheduler, Activate);
-
 				MonoException *exc = NULL;
-				thunk(task_scheduler, (MonoObject **)&exc);
+				invoke_method_thunk(CACHED_METHOD_THUNK(GodotTaskScheduler, Activate), task_scheduler, (MonoObject **)&exc);
 
 				if (exc) {
 					GDMonoUtils::debug_unhandled_exception(exc);
@@ -599,24 +609,20 @@ void CSharpLanguage::reload_all_scripts() {
 
 #ifdef DEBUG_ENABLED
 
-#ifndef NO_THREADS
-	lock->lock();
-#endif
-
 	List<Ref<CSharpScript> > scripts;
 
-	SelfList<CSharpScript> *elem = script_list.first();
-	while (elem) {
-		if (elem->self()->get_path().is_resource_file()) {
-			scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to gdscript to avoid being erased by accident
+	{
+		SCOPED_MUTEX_LOCK(script_instances_mutex);
+
+		SelfList<CSharpScript> *elem = script_list.first();
+		while (elem) {
+			if (elem->self()->get_path().is_resource_file()) {
+				scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to gdscript to avoid being erased by accident
+			}
+			elem = elem->next();
 		}
-		elem = elem->next();
 	}
 
-#ifndef NO_THREADS
-	lock->unlock();
-#endif
-
 	//as scripts are going to be reloaded, must proceed without locking here
 
 	scripts.sort_custom<CSharpScriptDepSort>(); //update in inheritance dependency order
@@ -625,6 +631,7 @@ void CSharpLanguage::reload_all_scripts() {
 		E->get()->load_source_code(E->get()->get_path());
 		E->get()->reload(true);
 	}
+
 #endif
 }
 
@@ -634,15 +641,17 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft
 
 #ifdef TOOLS_ENABLED
 	MonoReloadNode::get_singleton()->restart_reload_timer();
-	reload_assemblies_if_needed(p_soft_reload);
+	if (is_assembly_reloading_needed()) {
+		reload_assemblies(p_soft_reload);
+	}
 #endif
 }
 
 #ifdef TOOLS_ENABLED
-void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) {
+bool CSharpLanguage::is_assembly_reloading_needed() {
 
 	if (!gdmono->is_runtime_initialized())
-		return;
+		return false;
 
 	GDMonoAssembly *proj_assembly = gdmono->get_project_assembly();
 
@@ -660,164 +669,208 @@ void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) {
 			// Maybe it wasn't loaded from the default path, so check this as well
 			proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name);
 			if (!FileAccess::exists(proj_asm_path))
-				return; // No assembly to load
+				return false; // No assembly to load
 		}
 
 		if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time())
-			return; // Already up to date
+			return false; // Already up to date
 	} else {
 		if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name)))
-			return; // No assembly to load
+			return false; // No assembly to load
 	}
 
 	if (!gdmono->get_core_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE))
-		return; // The core API assembly to load is invalidated
+		return false; // The core API assembly to load is invalidated
 
 	if (!gdmono->get_editor_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR))
-		return; // The editor API assembly to load is invalidated
+		return false; // The editor API assembly to load is invalidated
 
-#ifndef NO_THREADS
-	lock->lock();
-#endif
+	return true;
+}
+
+void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
+
+	if (!gdmono->is_runtime_initialized())
+		return;
+
+	// There is no soft reloading with Mono. It's always hard reloading.
 
 	List<Ref<CSharpScript> > scripts;
 
-	SelfList<CSharpScript> *elem = script_list.first();
-	while (elem) {
-		if (elem->self()->get_path().is_resource_file()) {
+	{
+		SCOPED_MUTEX_LOCK(script_instances_mutex);
 
-			scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to CSharpScript to avoid being erased by accident
+		for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) {
+			if (elem->self()->get_path().is_resource_file()) {
+				// Cast to CSharpScript to avoid being erased by accident
+				scripts.push_back(Ref<CSharpScript>(elem->self()));
+			}
 		}
-		elem = elem->next();
 	}
 
-#ifndef NO_THREADS
-	lock->unlock();
-#endif
+	List<Ref<CSharpScript> > to_reload;
 
-	//when someone asks you why dynamically typed languages are easier to write....
+	// As scripts are going to be reloaded, must proceed without locking here
 
-	Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > > to_reload;
+	scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order
 
-	//as scripts are going to be reloaded, must proceed without locking here
+	for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) {
 
-	scripts.sort_custom<CSharpScriptDepSort>(); //update in inheritance dependency order
+		Ref<CSharpScript> &script = E->get();
 
-	for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) {
+		to_reload.push_back(script);
 
-		to_reload.insert(E->get(), Map<ObjectID, List<Pair<StringName, Variant> > >());
+		// Script::instances are deleted during managed object disposal, which happens on domain finalize.
+		// Only placeholders are kept. Therefore we need to keep a copy before that happens.
 
-		if (!p_soft_reload) {
+		for (Set<Object *>::Element *E = script->instances.front(); E; E = E->next()) {
+			script->pending_reload_instances.insert(E->get()->get_instance_id());
+		}
 
-			//save state and remove script from instances
-			Map<ObjectID, List<Pair<StringName, Variant> > > &map = to_reload[E->get()];
+#ifdef TOOLS_ENABLED
+		for (Set<PlaceHolderScriptInstance *>::Element *E = script->placeholders.front(); E; E = E->next()) {
+			script->pending_reload_instances.insert(E->get()->get_owner()->get_instance_id());
+		}
+#endif
 
-			while (E->get()->instances.front()) {
-				Object *obj = E->get()->instances.front()->get();
-				//save instance info
-				List<Pair<StringName, Variant> > state;
-				if (obj->get_script_instance()) {
+		// FIXME: What about references? Need to keep them alive if only managed code references them.
 
-					obj->get_script_instance()->get_property_state(state);
+		// Save state and remove script from instances
+		Map<ObjectID, CSharpScript::StateBackup> &owners_map = script->pending_reload_state;
 
-					Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle;
-					if (gchandle.is_valid())
-						gchandle->release();
+		while (script->instances.front()) {
+			Object *obj = script->instances.front()->get();
+			// Save instance info
+			CSharpScript::StateBackup state;
 
-					map[obj->get_instance_id()] = state;
-					obj->set_script(RefPtr());
-				}
-			}
+			ERR_CONTINUE(!obj->get_script_instance());
 
-			//same thing for placeholders
-			while (E->get()->placeholders.size()) {
-
-				Object *obj = E->get()->placeholders.front()->get()->get_owner();
-				//save instance info
-				List<Pair<StringName, Variant> > state;
-				if (obj->get_script_instance()) {
-					obj->get_script_instance()->get_property_state(state);
-					map[obj->get_instance_id()] = state;
-					obj->set_script(RefPtr());
-				} else {
-					// no instance found. Let's remove it so we don't loop forever
-					E->get()->placeholders.erase(E->get()->placeholders.front()->get());
-				}
-			}
+			// TODO: Proper state backup (Not only variants, serialize managed state of scripts)
+			obj->get_script_instance()->get_property_state(state.properties);
 
-			for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get()->pending_reload_state.front(); F; F = F->next()) {
-				map[F->key()] = F->get(); //pending to reload, use this one instead
-			}
+			Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle;
+			if (gchandle.is_valid())
+				gchandle->release();
 
-			E->get()->_clear();
+			owners_map[obj->get_instance_id()] = state;
+			obj->set_script(RefPtr()); // Remove script and existing script instances (placeholder are not removed before domain reload)
 		}
+
+		script->_clear();
 	}
 
+	// Do domain reload
 	if (gdmono->reload_scripts_domain() != OK) {
 		// Failed to reload the scripts domain
 		// Make sure to add the scripts back to their owners before returning
-		for (Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > >::Element *E = to_reload.front(); E; E = E->next()) {
-			Ref<CSharpScript> scr = E->key();
-			for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get().front(); F; F = F->next()) {
+		for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
+			Ref<CSharpScript> scr = E->get();
+
+			for (const Map<ObjectID, CSharpScript::StateBackup>::Element *F = scr->pending_reload_state.front(); F; F = F->next()) {
 				Object *obj = ObjectDB::get_instance(F->key());
+
 				if (!obj)
 					continue;
+
+				ObjectID obj_id = obj->get_instance_id();
+
+				// Use a placeholder for now to avoid losing the state when saving a scene
+
 				obj->set_script(scr.get_ref_ptr());
-				// Save reload state for next time if not saved
-				if (!scr->pending_reload_state.has(obj->get_instance_id())) {
-					scr->pending_reload_state[obj->get_instance_id()] = F->get();
+
+				PlaceHolderScriptInstance *placeholder = scr->placeholder_instance_create(obj);
+				obj->set_script_instance(placeholder);
+
+				// Even though build didn't fail, this tells the placeholder to keep properties and
+				// it allows using property_set_fallback for restoring the state without a valid script.
+				placeholder->set_build_failed(true);
+
+				// Restore Variant properties state, it will be kept by the placeholder until the next script reloading
+				for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) {
+					placeholder->property_set_fallback(G->get().first, G->get().second, NULL);
 				}
+
+				scr->pending_reload_state.erase(obj_id);
 			}
 		}
 		return;
 	}
 
-	for (Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > >::Element *E = to_reload.front(); E; E = E->next()) {
+	for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
 
-		Ref<CSharpScript> scr = E->key();
+		Ref<CSharpScript> scr = E->get();
 		scr->exports_invalidated = true;
 		scr->signals_invalidated = true;
 		scr->reload(p_soft_reload);
 		scr->update_exports();
 
-		//restore state if saved
-		for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get().front(); F; F = F->next()) {
+		{
+#ifdef DEBUG_ENABLED
+			for (Set<ObjectID>::Element *F = scr->pending_reload_instances.front(); F; F = F->next()) {
+				ObjectID obj_id = F->get();
+				Object *obj = ObjectDB::get_instance(obj_id);
+
+				if (!obj) {
+					scr->pending_reload_state.erase(obj_id);
+					continue;
+				}
 
-			Object *obj = ObjectDB::get_instance(F->key());
-			if (!obj)
-				continue;
+				ScriptInstance *si = obj->get_script_instance();
 
-			if (!p_soft_reload) {
-				//clear it just in case (may be a pending reload state)
-				obj->set_script(RefPtr());
-			}
-			obj->set_script(scr.get_ref_ptr());
-			if (!obj->get_script_instance()) {
-				//failed, save reload state for next time if not saved
-				if (!scr->pending_reload_state.has(obj->get_instance_id())) {
-					scr->pending_reload_state[obj->get_instance_id()] = F->get();
+#ifdef TOOLS_ENABLED
+				if (si) {
+					// If the script instance is not null, then it must be a placeholder.
+					// Non-placeholder script instances are removed in godot_icall_Object_Disposed.
+					CRASH_COND(!si->is_placeholder());
+
+					if (scr->is_tool() || ScriptServer::is_scripting_enabled()) {
+						// Replace placeholder with a script instance
+
+						CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id];
+
+						// Backup placeholder script instance state before replacing it with a script instance
+						obj->get_script_instance()->get_property_state(state_backup.properties);
+
+						ScriptInstance *script_instance = scr->instance_create(obj);
+
+						if (script_instance) {
+							scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si));
+							obj->set_script_instance(script_instance);
+						}
+
+						// TODO: Restore serialized state
+
+						for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) {
+							script_instance->set(G->get().first, G->get().second);
+						}
+
+						scr->pending_reload_state.erase(obj_id);
+					}
+
+					continue;
 				}
-				continue;
-			}
+#else
+				CRASH_COND(si != NULL);
+#endif
+				// Re-create script instance
 
-			if (scr->valid && scr->is_tool() && obj->get_script_instance()->is_placeholder()) {
-				// Script instance was a placeholder, but now the script was built successfully and is a tool script.
-				// We have to replace the placeholder with an actual C# script instance.
-				scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(obj->get_script_instance()));
-				ScriptInstance *script_instance = scr->instance_create(obj);
-				obj->set_script_instance(script_instance); // Not necessary as it's already done in instance_create, but just in case...
-			}
+				obj->set_script(scr.get_ref_ptr()); // will create the script instance as well
+
+				// TODO: Restore serialized state
+
+				for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) {
+					obj->get_script_instance()->set(G->get().first, G->get().second);
+				}
 
-			for (List<Pair<StringName, Variant> >::Element *G = F->get().front(); G; G = G->next()) {
-				obj->get_script_instance()->set(G->get().first, G->get().second);
+				scr->pending_reload_state.erase(obj_id);
 			}
+#endif
 
-			scr->pending_reload_state.erase(obj->get_instance_id()); //as it reloaded, remove pending state
+			scr->pending_reload_instances.clear();
 		}
-
-		//if instance states were saved, set them!
 	}
 
+	// FIXME: Hack to refresh editor in order to display new properties and signals. See if there is a better alternative.
 	if (Engine::get_singleton()->is_editor_hint()) {
 		EditorNode::get_singleton()->get_inspector()->update_tree();
 		NodeDock::singleton->update_lists();
@@ -941,26 +994,17 @@ void CSharpLanguage::set_language_index(int 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
-
+		SCOPED_MUTEX_LOCK(get_singleton()->script_gchandle_release_mutex);
 		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) {
+void CSharpLanguage::release_script_gchandle(MonoObject *p_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
+	uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_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
+		SCOPED_MUTEX_LOCK(get_singleton()->script_gchandle_release_mutex);
 
 		MonoObject *target = p_gchandle->get_target();
 
@@ -968,13 +1012,9 @@ void CSharpLanguage::release_script_gchandle(MonoObject *p_pinned_expected_obj,
 		// 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) {
+		if (target == p_expected_obj || target == NULL) {
 			p_gchandle->release();
 		}
-
-#ifndef NO_THREADS
-		get_singleton()->script_gchandle_release_lock->unlock();
-#endif
 	}
 
 	MonoGCHandle::free_handle(pinned_gchandle);
@@ -990,13 +1030,13 @@ CSharpLanguage::CSharpLanguage() {
 	gdmono = NULL;
 
 #ifdef NO_THREADS
-	lock = NULL;
-	gchandle_bind_lock = NULL;
-	script_gchandle_release_lock = NULL;
+	script_instances_mutex = NULL;
+	script_gchandle_release_mutex = NULL;
+	language_bind_mutex = NULL;
 #else
-	lock = Mutex::create();
-	script_bind_lock = Mutex::create();
-	script_gchandle_release_lock = Mutex::create();
+	script_instances_mutex = Mutex::create();
+	script_gchandle_release_mutex = Mutex::create();
+	language_bind_mutex = Mutex::create();
 #endif
 
 	lang_idx = -1;
@@ -1006,19 +1046,19 @@ CSharpLanguage::~CSharpLanguage() {
 
 	finish();
 
-	if (lock) {
-		memdelete(lock);
-		lock = NULL;
+	if (script_instances_mutex) {
+		memdelete(script_instances_mutex);
+		script_instances_mutex = NULL;
 	}
 
-	if (script_bind_lock) {
-		memdelete(script_bind_lock);
-		script_bind_lock = NULL;
+	if (language_bind_mutex) {
+		memdelete(language_bind_mutex);
+		language_bind_mutex = NULL;
 	}
 
-	if (script_gchandle_release_lock) {
-		memdelete(script_gchandle_release_lock);
-		script_gchandle_release_lock = NULL;
+	if (script_gchandle_release_mutex) {
+		memdelete(script_gchandle_release_mutex);
+		script_gchandle_release_mutex = NULL;
 	}
 
 	singleton = NULL;
@@ -1055,15 +1095,12 @@ void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
 	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 *)script_bindings.insert(p_object, script_binding);
+	void *data;
 
-#ifndef NO_THREADS
-	script_bind_lock->unlock();
-#endif
+	{
+		SCOPED_MUTEX_LOCK(language_bind_mutex);
+		data = (void *)script_bindings.insert(p_object, script_binding);
+	}
 
 	// Tie managed to unmanaged
 	Reference *ref = Object::cast_to<Reference>(p_object);
@@ -1093,23 +1130,19 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) {
 	if (finalizing)
 		return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there
 
-#ifndef NO_THREADS
-	script_bind_lock->lock();
-#endif
-
-	Map<Object *, CSharpScriptBinding>::Element *data = (Map<Object *, CSharpScriptBinding>::Element *)p_data;
+	{
+		SCOPED_MUTEX_LOCK(language_bind_mutex);
 
-	// Set the native instance field to IntPtr.Zero, if not yet garbage collected
-	MonoObject *mono_object = data->value().gchandle->get_target();
-	if (mono_object) {
-		CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
-	}
+		Map<Object *, CSharpScriptBinding>::Element *data = (Map<Object *, CSharpScriptBinding>::Element *)p_data;
 
-	script_bindings.erase(data);
+		// Set the native instance field to IntPtr.Zero, if not yet garbage collected
+		MonoObject *mono_object = data->value().gchandle->get_target();
+		if (mono_object) {
+			CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
+		}
 
-#ifndef NO_THREADS
-	script_bind_lock->unlock();
-#endif
+		script_bindings.erase(data);
+	}
 }
 
 void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
@@ -1524,7 +1557,7 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f
 	} else {
 		r_owner_deleted = false;
 		CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle);
-		if (p_is_finalizer) {
+		if (p_is_finalizer && !GDMono::get_singleton()->is_finalizing_scripts_domain()) {
 			// 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.
@@ -1651,6 +1684,8 @@ void CSharpInstance::notification(int p_notification) {
 		// 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.
 
+		predelete_notified = true;
+
 		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
@@ -1666,10 +1701,8 @@ void CSharpInstance::notification(int 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);
+		GDMonoUtils::dispose(mono_object, &exc);
 
 		if (exc) {
 			GDMonoUtils::set_pending_exception(exc);
@@ -1720,12 +1753,32 @@ CSharpInstance::CSharpInstance() :
 		owner(NULL),
 		base_ref(false),
 		ref_dying(false),
-		unsafe_referenced(false) {
+		unsafe_referenced(false),
+		predelete_notified(false) {
 }
 
 CSharpInstance::~CSharpInstance() {
 
 	if (gchandle.is_valid()) {
+		if (!predelete_notified && !ref_dying) {
+			// This destructor is not called from the owners destructor.
+			// This could be being called from the owner's set_script_instance method,
+			// meaning this script is being replaced with another one. If this is the case,
+			// we must call Dispose here, because Dispose calls owner->set_script_instance(NULL)
+			// and that would mess up with the new script instance if called later.
+
+			MonoObject *mono_object = gchandle->get_target();
+
+			if (mono_object) {
+				MonoException *exc = NULL;
+				GDMonoUtils::dispose(mono_object, &exc);
+
+				if (exc) {
+					GDMonoUtils::set_pending_exception(exc);
+				}
+			}
+		}
+
 		gchandle->release(); // Make sure it's released
 	}
 
@@ -1734,9 +1787,7 @@ CSharpInstance::~CSharpInstance() {
 	}
 
 	if (script.is_valid() && owner) {
-#ifndef NO_THREADS
-		CSharpLanguage::singleton->lock->lock();
-#endif
+		SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
 
 #ifdef DEBUG_ENABLED
 		// CSharpInstance must not be created unless it's going to be added to the list for sure
@@ -1746,10 +1797,6 @@ CSharpInstance::~CSharpInstance() {
 #else
 		script->instances.erase(owner);
 #endif
-
-#ifndef NO_THREADS
-		CSharpLanguage::singleton->lock->unlock();
-#endif
 	}
 }
 
@@ -1882,10 +1929,8 @@ bool CSharpScript::_update_exports() {
 
 		// Dispose the temporary managed instance
 
-		GDMonoUtils::GodotObject_Dispose thunk = CACHED_METHOD_THUNK(GodotObject, Dispose);
-
 		MonoException *exc = NULL;
-		thunk(tmp_object, (MonoObject **)&exc);
+		GDMonoUtils::dispose(tmp_object, &exc);
 
 		if (exc) {
 			ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:");
@@ -2312,17 +2357,13 @@ 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
-
-	instances.insert(instance->owner);
+	// Tie managed to unmanaged
+	instance->gchandle = MonoGCHandle::create_strong(mono_object);
 
-#ifndef NO_THREADS
-	CSharpLanguage::singleton->lock->unlock();
-#endif
+	{
+		SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+		instances.insert(instance->owner);
+	}
 
 	CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, instance->owner);
 
@@ -2330,13 +2371,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
 	GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount);
 	ctor->invoke(mono_object, p_args);
 
-	// Tie managed to unmanaged
-	instance->gchandle = MonoGCHandle::create_strong(mono_object);
-
 	/* STEP 3, PARTY */
 
-	MonoGCHandle::free_handle(pinned_gchandle);
-
 	//@TODO make thread safe
 	return instance;
 }
@@ -2411,17 +2447,8 @@ PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_t
 
 bool CSharpScript::instance_has(const Object *p_this) const {
 
-#ifndef NO_THREADS
-	CSharpLanguage::singleton->lock->lock();
-#endif
-
-	bool ret = instances.has((Object *)p_this);
-
-#ifndef NO_THREADS
-	CSharpLanguage::singleton->lock->unlock();
-#endif
-
-	return ret;
+	SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+	return instances.has((Object *)p_this);
 }
 
 bool CSharpScript::has_source_code() const {
@@ -2454,15 +2481,11 @@ bool CSharpScript::has_method(const StringName &p_method) const {
 
 Error CSharpScript::reload(bool p_keep_state) {
 
-#ifndef NO_THREADS
-	CSharpLanguage::singleton->lock->lock();
-#endif
-
-	bool has_instances = instances.size();
-
-#ifndef NO_THREADS
-	CSharpLanguage::singleton->lock->unlock();
-#endif
+	bool has_instances;
+	{
+		SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+		has_instances = instances.size();
+	}
 
 	ERR_FAIL_COND_V(!p_keep_state && has_instances, ERR_ALREADY_IN_USE);
 
@@ -2652,35 +2675,19 @@ CSharpScript::CSharpScript() :
 	_resource_path_changed();
 
 #ifdef DEBUG_ENABLED
-
-#ifndef NO_THREADS
-	CSharpLanguage::get_singleton()->lock->lock();
-#endif
-
-	CSharpLanguage::get_singleton()->script_list.add(&script_list);
-
-#ifndef NO_THREADS
-	CSharpLanguage::get_singleton()->lock->unlock();
+	{
+		SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+		CSharpLanguage::get_singleton()->script_list.add(&this->script_list);
+	}
 #endif
-
-#endif // DEBUG_ENABLED
 }
 
 CSharpScript::~CSharpScript() {
 
 #ifdef DEBUG_ENABLED
-
-#ifndef NO_THREADS
-	CSharpLanguage::get_singleton()->lock->lock();
+	SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+	CSharpLanguage::get_singleton()->script_list.remove(&this->script_list);
 #endif
-
-	CSharpLanguage::get_singleton()->script_list.remove(&script_list);
-
-#ifndef NO_THREADS
-	CSharpLanguage::get_singleton()->lock->unlock();
-#endif
-
-#endif // DEBUG_ENABLED
 }
 
 /*************** RESOURCE ***************/

+ 21 - 10
modules/mono/csharp_script.h

@@ -82,6 +82,21 @@ class CSharpScript : public Script {
 
 	Set<Object *> instances;
 
+#ifdef DEBUG_ENABLED
+	Set<ObjectID> pending_reload_instances;
+#endif
+
+	struct StateBackup {
+		// TODO
+		// Replace with buffer containing the serialized state of managed scripts.
+		// Keep variant state backup to use only with script instance placeholders.
+		List<Pair<StringName, Variant> > properties;
+	};
+
+#ifdef TOOLS_ENABLED
+	Map<ObjectID, CSharpScript::StateBackup> pending_reload_state;
+#endif
+
 	String source;
 	StringName name;
 
@@ -105,10 +120,6 @@ class CSharpScript : public Script {
 	virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder);
 #endif
 
-#ifdef DEBUG_ENABLED
-	Map<ObjectID, List<Pair<StringName, Variant> > > pending_reload_state;
-#endif
-
 	Map<StringName, PropertyInfo> member_info;
 
 	void _clear();
@@ -186,6 +197,7 @@ class CSharpInstance : public ScriptInstance {
 	bool base_ref;
 	bool ref_dying;
 	bool unsafe_referenced;
+	bool predelete_notified;
 
 	Ref<CSharpScript> script;
 	Ref<MonoGCHandle> gchandle;
@@ -255,11 +267,9 @@ class CSharpLanguage : public ScriptLanguage {
 	GDMono *gdmono;
 	SelfList<CSharpScript>::List script_list;
 
-	Mutex *lock;
-	Mutex *script_bind_lock;
-	Mutex *script_gchandle_release_lock;
-
-	Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > > to_reload;
+	Mutex *script_instances_mutex;
+	Mutex *script_gchandle_release_mutex;
+	Mutex *language_bind_mutex;
 
 	Map<Object *, CSharpScriptBinding> script_bindings;
 
@@ -296,7 +306,8 @@ public:
 	bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
 
 #ifdef TOOLS_ENABLED
-	void reload_assemblies_if_needed(bool p_soft_reload);
+	bool is_assembly_reloading_needed();
+	void reload_assemblies(bool p_soft_reload);
 #endif
 
 	void project_assembly_loaded();

+ 6 - 2
modules/mono/editor/godotsharp_editor.cpp

@@ -475,7 +475,9 @@ MonoReloadNode *MonoReloadNode::singleton = NULL;
 
 void MonoReloadNode::_reload_timer_timeout() {
 
-	CSharpLanguage::get_singleton()->reload_assemblies_if_needed(false);
+	if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
+		CSharpLanguage::get_singleton()->reload_assemblies(false);
+	}
 }
 
 void MonoReloadNode::restart_reload_timer() {
@@ -493,7 +495,9 @@ void MonoReloadNode::_notification(int p_what) {
 	switch (p_what) {
 		case MainLoop::NOTIFICATION_WM_FOCUS_IN: {
 			restart_reload_timer();
-			CSharpLanguage::get_singleton()->reload_assemblies_if_needed(true);
+			if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
+				CSharpLanguage::get_singleton()->reload_assemblies(false);
+			}
 		} break;
 		default: {
 		} break;

+ 7 - 3
modules/mono/editor/mono_bottom_panel.cpp

@@ -154,10 +154,14 @@ void MonoBottomPanel::_build_project_pressed() {
 	Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path);
 	ERR_FAIL_COND(metadata_err != OK);
 
-	GodotSharpBuilds::get_singleton()->build_project_blocking("Tools");
+	bool build_success = GodotSharpBuilds::get_singleton()->build_project_blocking("Tools");
 
-	MonoReloadNode::get_singleton()->restart_reload_timer();
-	CSharpLanguage::get_singleton()->reload_assemblies_if_needed(true);
+	if (build_success) {
+		MonoReloadNode::get_singleton()->restart_reload_timer();
+		if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
+			CSharpLanguage::get_singleton()->reload_assemblies(false);
+		}
+	}
 }
 
 void MonoBottomPanel::_view_log_pressed() {

+ 2 - 2
modules/mono/mono_gd/gd_mono_field.cpp

@@ -423,7 +423,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
 			MonoException *exc = NULL;
 
 			GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
-			MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc);
+			MonoBoolean is_dict = invoke_method_thunk(type_is_dict, (MonoObject *)reftype, (MonoObject **)&exc);
 			UNLIKELY_UNHANDLED_EXCEPTION(exc);
 
 			if (is_dict) {
@@ -435,7 +435,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
 			exc = NULL;
 
 			GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
-			MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc);
+			MonoBoolean is_array = invoke_method_thunk(type_is_array, (MonoObject *)reftype, (MonoObject **)&exc);
 			UNLIKELY_UNHANDLED_EXCEPTION(exc);
 
 			if (is_array) {

+ 13 - 12
modules/mono/mono_gd/gd_mono_marshal.cpp

@@ -163,7 +163,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
 
 			MonoException *exc = NULL;
 			GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
-			MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc);
+			MonoBoolean is_dict = invoke_method_thunk(type_is_dict, (MonoObject *)reftype, (MonoObject **)&exc);
 			UNLIKELY_UNHANDLED_EXCEPTION(exc);
 
 			if (is_dict) {
@@ -172,7 +172,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
 
 			exc = NULL;
 			GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
-			MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc);
+			MonoBoolean is_array = invoke_method_thunk(type_is_array, (MonoObject *)reftype, (MonoObject **)&exc);
 			UNLIKELY_UNHANDLED_EXCEPTION(exc);
 
 			if (is_array) {
@@ -192,8 +192,11 @@ String mono_to_utf8_string(MonoString *p_mono_string) {
 	MonoError error;
 	char *utf8 = mono_string_to_utf8_checked(p_mono_string, &error);
 
-	ERR_EXPLAIN("Conversion of MonoString to UTF8 failed.");
-	ERR_FAIL_COND_V(!mono_error_ok(&error), String());
+	if (!mono_error_ok(&error)) {
+		ERR_PRINTS(String("Failed to convert MonoString* to UTF-8: ") + mono_error_get_message(&error));
+		mono_error_cleanup(&error);
+		return String();
+	}
 
 	String ret = String::utf8(utf8);
 
@@ -546,7 +549,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
 
 				MonoException *exc = NULL;
 				GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
-				MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc);
+				MonoBoolean is_dict = invoke_method_thunk(type_is_dict, (MonoObject *)reftype, (MonoObject **)&exc);
 				UNLIKELY_UNHANDLED_EXCEPTION(exc);
 
 				if (is_dict) {
@@ -555,7 +558,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
 
 				exc = NULL;
 				GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
-				MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc);
+				MonoBoolean is_array = invoke_method_thunk(type_is_array, (MonoObject *)reftype, (MonoObject **)&exc);
 				UNLIKELY_UNHANDLED_EXCEPTION(exc);
 
 				if (is_array) {
@@ -710,16 +713,14 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
 
 			if (CACHED_CLASS(Array) == type_class) {
 				MonoException *exc = NULL;
-				GDMonoUtils::Array_GetPtr get_ptr = CACHED_METHOD_THUNK(Array, GetPtr);
-				Array *ptr = get_ptr(p_obj, (MonoObject **)&exc);
+				Array *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Array, GetPtr), p_obj, (MonoObject **)&exc);
 				UNLIKELY_UNHANDLED_EXCEPTION(exc);
 				return ptr ? Variant(*ptr) : Variant();
 			}
 
 			if (CACHED_CLASS(Dictionary) == type_class) {
 				MonoException *exc = NULL;
-				GDMonoUtils::Dictionary_GetPtr get_ptr = CACHED_METHOD_THUNK(Dictionary, GetPtr);
-				Dictionary *ptr = get_ptr(p_obj, (MonoObject **)&exc);
+				Dictionary *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Dictionary, GetPtr), p_obj, (MonoObject **)&exc);
 				UNLIKELY_UNHANDLED_EXCEPTION(exc);
 				return ptr ? Variant(*ptr) : Variant();
 			}
@@ -731,7 +732,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
 			MonoException *exc = NULL;
 
 			GDMonoUtils::IsDictionaryGenericType type_is_dict = CACHED_METHOD_THUNK(MarshalUtils, IsDictionaryGenericType);
-			MonoBoolean is_dict = type_is_dict((MonoObject *)reftype, (MonoObject **)&exc);
+			MonoBoolean is_dict = invoke_method_thunk(type_is_dict, (MonoObject *)reftype, (MonoObject **)&exc);
 			UNLIKELY_UNHANDLED_EXCEPTION(exc);
 
 			if (is_dict) {
@@ -744,7 +745,7 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
 			exc = NULL;
 
 			GDMonoUtils::IsArrayGenericType type_is_array = CACHED_METHOD_THUNK(MarshalUtils, IsArrayGenericType);
-			MonoBoolean is_array = type_is_array((MonoObject *)reftype, (MonoObject **)&exc);
+			MonoBoolean is_array = invoke_method_thunk(type_is_array, (MonoObject *)reftype, (MonoObject **)&exc);
 			UNLIKELY_UNHANDLED_EXCEPTION(exc);
 
 			if (is_array) {

+ 4 - 0
modules/mono/mono_gd/gd_mono_utils.cpp

@@ -694,4 +694,8 @@ uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &
 	}
 }
 
+void dispose(MonoObject *p_mono_object, MonoException **r_exc) {
+	invoke_method_thunk(CACHED_METHOD_THUNK(GodotObject, Dispose), p_mono_object, (MonoObject **)r_exc);
+}
+
 } // namespace GDMonoUtils

+ 91 - 0
modules/mono/mono_gd/gd_mono_utils.h

@@ -243,6 +243,8 @@ MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_param
 
 uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &r_error);
 
+void dispose(MonoObject *p_mono_object, MonoException **r_exc);
+
 } // namespace GDMonoUtils
 
 #define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoMarshal::mono_string_to_godot((MonoString *)m_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(NULL)))
@@ -267,4 +269,93 @@ uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &
 #define GD_MONO_END_RUNTIME_INVOKE \
 	_runtime_invoke_count_ref -= 1;
 
+inline void invoke_method_thunk(void (*p_method_thunk)()) {
+	GD_MONO_BEGIN_RUNTIME_INVOKE;
+	p_method_thunk();
+	GD_MONO_END_RUNTIME_INVOKE;
+}
+
+template <class R>
+R invoke_method_thunk(R (*p_method_thunk)()) {
+	GD_MONO_BEGIN_RUNTIME_INVOKE;
+	R r = p_method_thunk();
+	GD_MONO_END_RUNTIME_INVOKE;
+	return r;
+}
+
+template <class P1>
+void invoke_method_thunk(void (*p_method_thunk)(P1), P1 p_arg1) {
+	GD_MONO_BEGIN_RUNTIME_INVOKE;
+	p_method_thunk(p_arg1);
+	GD_MONO_END_RUNTIME_INVOKE;
+}
+
+template <class R, class P1>
+R invoke_method_thunk(R (*p_method_thunk)(P1), P1 p_arg1) {
+	GD_MONO_BEGIN_RUNTIME_INVOKE;
+	R r = p_method_thunk(p_arg1);
+	GD_MONO_END_RUNTIME_INVOKE;
+	return r;
+}
+
+template <class P1, class P2>
+void invoke_method_thunk(void (*p_method_thunk)(P1, P2), P1 p_arg1, P2 p_arg2) {
+	GD_MONO_BEGIN_RUNTIME_INVOKE;
+	p_method_thunk(p_arg1, p_arg2);
+	GD_MONO_END_RUNTIME_INVOKE;
+}
+
+template <class R, class P1, class P2>
+R invoke_method_thunk(R (*p_method_thunk)(P1, P2), P1 p_arg1, P2 p_arg2) {
+	GD_MONO_BEGIN_RUNTIME_INVOKE;
+	R r = p_method_thunk(p_arg1, p_arg2);
+	GD_MONO_END_RUNTIME_INVOKE;
+	return r;
+}
+
+template <class P1, class P2, class P3>
+void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3), P1 p_arg1, P2 p_arg2, P3 p_arg3) {
+	GD_MONO_BEGIN_RUNTIME_INVOKE;
+	p_method_thunk(p_arg1, p_arg2, p_arg3);
+	GD_MONO_END_RUNTIME_INVOKE;
+}
+
+template <class R, class P1, class P2, class P3>
+R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3), P1 p_arg1, P2 p_arg2, P3 p_arg3) {
+	GD_MONO_BEGIN_RUNTIME_INVOKE;
+	R r = p_method_thunk(p_arg1, p_arg2, p_arg3);
+	GD_MONO_END_RUNTIME_INVOKE;
+	return r;
+}
+
+template <class P1, class P2, class P3, class P4>
+void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3, P4), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4) {
+	GD_MONO_BEGIN_RUNTIME_INVOKE;
+	p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4);
+	GD_MONO_END_RUNTIME_INVOKE;
+}
+
+template <class R, class P1, class P2, class P3, class P4>
+R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3, P4), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4) {
+	GD_MONO_BEGIN_RUNTIME_INVOKE;
+	R r = p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4);
+	GD_MONO_END_RUNTIME_INVOKE;
+	return r;
+}
+
+template <class P1, class P2, class P3, class P4, class P5>
+void invoke_method_thunk(void (*p_method_thunk)(P1, P2, P3, P4, P5), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4, P5 p_arg5) {
+	GD_MONO_BEGIN_RUNTIME_INVOKE;
+	p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);
+	GD_MONO_END_RUNTIME_INVOKE;
+}
+
+template <class R, class P1, class P2, class P3, class P4, class P5>
+R invoke_method_thunk(R (*p_method_thunk)(P1, P2, P3, P4, P5), P1 p_arg1, P2 p_arg2, P3 p_arg3, P4 p_arg4, P5 p_arg5) {
+	GD_MONO_BEGIN_RUNTIME_INVOKE;
+	R r = p_method_thunk(p_arg1, p_arg2, p_arg3, p_arg4, p_arg5);
+	GD_MONO_END_RUNTIME_INVOKE;
+	return r;
+}
+
 #endif // GD_MONOUTILS_H

+ 2 - 6
modules/mono/signal_awaiter_utils.cpp

@@ -98,11 +98,9 @@ Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argc
 		mono_array_set(signal_args, MonoObject *, i, boxed);
 	}
 
-	GDMonoUtils::SignalAwaiter_SignalCallback thunk = CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback);
-
 	MonoException *exc = NULL;
 	GD_MONO_BEGIN_RUNTIME_INVOKE;
-	thunk(get_target(), signal_args, (MonoObject **)&exc);
+	invoke_method_thunk(CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback), get_target(), signal_args, (MonoObject **)&exc);
 	GD_MONO_END_RUNTIME_INVOKE;
 
 	if (exc) {
@@ -129,14 +127,12 @@ SignalAwaiterHandle::SignalAwaiterHandle(MonoObject *p_managed) :
 SignalAwaiterHandle::~SignalAwaiterHandle() {
 
 	if (!completed) {
-		GDMonoUtils::SignalAwaiter_FailureCallback thunk = CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback);
-
 		MonoObject *awaiter = get_target();
 
 		if (awaiter) {
 			MonoException *exc = NULL;
 			GD_MONO_BEGIN_RUNTIME_INVOKE;
-			thunk(awaiter, (MonoObject **)&exc);
+			invoke_method_thunk(CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback), awaiter, (MonoObject **)&exc);
 			GD_MONO_END_RUNTIME_INVOKE;
 
 			if (exc) {

+ 6 - 4
modules/mono/utils/macros.h

@@ -31,15 +31,17 @@
 #ifndef UTIL_MACROS_H
 #define UTIL_MACROS_H
 
+#define _GD_VARNAME_CONCAT_B(m_ignore, m_name) m_name
+#define _GD_VARNAME_CONCAT_A(m_a, m_b, m_c) _GD_VARNAME_CONCAT_B(hello there, m_a##m_b##m_c)
+#define _GD_VARNAME_CONCAT(m_a, m_b, m_c) _GD_VARNAME_CONCAT_A(m_a, m_b, m_c)
+#define GD_UNIQUE_NAME(m_name) _GD_VARNAME_CONCAT(m_name, _, __COUNTER__)
+
 // noreturn
 
 #if __cpp_static_assert
 #define GD_STATIC_ASSERT(m_cond) static_assert((m_cond), "Condition '" #m_cond "' failed")
 #else
-#define _GD_STATIC_ASSERT_VARNAME_CONCAT_B(m_ignore, m_name) m_name
-#define _GD_STATIC_ASSERT_VARNAME_CONCAT_A(m_a, m_b) GD_STATIC_ASSERT_VARNAME_CONCAT_B(hello there, m_a##m_b)
-#define _GD_STATIC_ASSERT_VARNAME_CONCAT(m_a, m_b) GD_STATIC_ASSERT_VARNAME_CONCAT_A(m_a, m_b)
-#define GD_STATIC_ASSERT(m_cond) typedef int GD_STATIC_ASSERT_VARNAME_CONCAT(godot_static_assert_, __COUNTER__)[((m_cond) ? 1 : -1)]
+#define GD_STATIC_ASSERT(m_cond) typedef int GD_UNIQUE_NAME(godot_static_assert)[((m_cond) ? 1 : -1)]
 #endif
 
 #undef _NO_RETURN_

+ 67 - 0
modules/mono/utils/mutex_utils.h

@@ -0,0 +1,67 @@
+/*************************************************************************/
+/*  mutex_utils.h                                                        */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef MUTEX_UTILS_H
+#define MUTEX_UTILS_H
+
+#include "core/error_macros.h"
+#include "core/os/mutex.h"
+
+#include "macros.h"
+
+class ScopedMutexLock {
+	Mutex *mutex;
+
+public:
+	ScopedMutexLock(Mutex *mutex) {
+		this->mutex = mutex;
+#ifndef NO_THREADS
+#ifdef DEBUG_ENABLED
+		CRASH_COND(!mutex);
+#endif
+		this->mutex->lock();
+#endif
+	}
+
+	~ScopedMutexLock() {
+#ifndef NO_THREADS
+#ifdef DEBUG_ENABLED
+		CRASH_COND(!mutex);
+#endif
+		mutex->unlock();
+#endif
+	}
+};
+
+#define SCOPED_MUTEX_LOCK(m_mutex) ScopedMutexLock GD_UNIQUE_NAME(__scoped_mutex_lock__)(m_mutex);
+
+// TODO: Add version that receives a lambda instead, once C++11 is allowed
+
+#endif // MUTEX_UTILS_H