瀏覽代碼

C#: Re-implement assembly reloading with ALCs

Ignacio Roldán Etcheverry 3 年之前
父節點
當前提交
e235cef09f
共有 37 個文件被更改,包括 1529 次插入557 次删除
  1. 0 1
      modules/mono/SCsub
  2. 108 243
      modules/mono/csharp_script.cpp
  3. 44 31
      modules/mono/csharp_script.h
  4. 8 2
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
  5. 217 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
  6. 15 8
      modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
  7. 14 2
      modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
  8. 1 0
      modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
  9. 5 0
      modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml
  10. 135 12
      modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
  11. 5 0
      modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
  12. 1 0
      modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings
  13. 18 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs
  14. 88 16
      modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs
  15. 1 1
      modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs
  16. 67 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs
  17. 11 1
      modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
  18. 248 67
      modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
  19. 92 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs
  20. 98 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs
  21. 119 57
      modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
  22. 18 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
  23. 9 15
      modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs
  24. 1 1
      modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs
  25. 5 3
      modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs
  26. 25 3
      modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs
  27. 4 1
      modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
  28. 74 1
      modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
  29. 16 0
      modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs
  30. 5 4
      modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs
  31. 5 0
      modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
  32. 13 1
      modules/mono/glue/runtime_interop.cpp
  33. 2 0
      modules/mono/managed_callable.cpp
  34. 15 73
      modules/mono/mono_gd/gd_mono.cpp
  35. 26 10
      modules/mono/mono_gd/gd_mono.h
  36. 5 0
      modules/mono/mono_gd/gd_mono_cache.cpp
  37. 11 4
      modules/mono/mono_gd/gd_mono_cache.h

+ 0 - 1
modules/mono/SCsub

@@ -15,7 +15,6 @@ mono_configure.configure(env, env_mono)
 
 env_mono.add_source_files(env.modules_sources, "*.cpp")
 env_mono.add_source_files(env.modules_sources, "glue/*.cpp")
-env_mono.add_source_files(env.modules_sources, "glue/mono_glue.gen.cpp")
 env_mono.add_source_files(env.modules_sources, "mono_gd/*.cpp")
 env_mono.add_source_files(env.modules_sources, "utils/*.cpp")
 

+ 108 - 243
modules/mono/csharp_script.cpp

@@ -104,7 +104,7 @@ Error CSharpLanguage::execute_file(const String &p_path) {
 	return OK;
 }
 
-extern void *godotsharp_pinvoke_funcs[185];
+extern void *godotsharp_pinvoke_funcs[186];
 [[maybe_unused]] volatile void **do_not_strip_godotsharp_pinvoke_funcs;
 #ifdef TOOLS_ENABLED
 extern void *godotsharp_editor_pinvoke_funcs[30];
@@ -646,6 +646,28 @@ void CSharpLanguage::frame() {
 	}
 }
 
+struct CSharpScriptDepSort {
+	// Must support sorting so inheritance works properly (parent must be reloaded first)
+	bool operator()(const Ref<CSharpScript> &A, const Ref<CSharpScript> &B) const {
+		if (A == B) {
+			// Shouldn't happen but just in case...
+			return false;
+		}
+		const Script *I = B->get_base_script().ptr();
+		while (I) {
+			if (I == A.ptr()) {
+				// A is a base of B
+				return true;
+			}
+
+			I = I->get_base_script().ptr();
+		}
+
+		// A isn't a base of B
+		return false;
+	}
+};
+
 void CSharpLanguage::reload_all_scripts() {
 #ifdef GD_MONO_HOT_RELOAD
 	if (is_assembly_reloading_needed()) {
@@ -676,38 +698,29 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
 		return false;
 	}
 
-#warning TODO
-#if 0
-	GDMonoAssembly *proj_assembly = gdmono->get_project_assembly();
-
-	String appname_safe = ProjectSettings::get_singleton()->get_safe_project_name();
-
-	appname_safe += ".dll";
-
-	if (proj_assembly) {
-		String proj_asm_path = proj_assembly->get_path();
+	String assembly_path = gdmono->get_project_assembly_path();
 
-		if (!FileAccess::exists(proj_asm_path)) {
-			// 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(appname_safe);
-			if (!FileAccess::exists(proj_asm_path)) {
-				return false; // No assembly to load
-			}
+	if (!assembly_path.is_empty()) {
+		if (!FileAccess::exists(assembly_path)) {
+			return false; // No assembly to load
 		}
 
-		if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) {
+		if (FileAccess::get_modified_time(assembly_path) <= gdmono->get_project_assembly_modified_time()) {
 			return false; // Already up to date
 		}
 	} else {
-		if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe))) {
+		String appname_safe = ProjectSettings::get_singleton()->get_safe_project_name();
+
+		assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir()
+								.plus_file(appname_safe + ".dll");
+		assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path);
+
+		if (!FileAccess::exists(assembly_path)) {
 			return false; // No assembly to load
 		}
 	}
 
 	return true;
-#else
-	return false;
-#endif
 }
 
 void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
@@ -715,27 +728,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 		return;
 	}
 
-#warning TODO ALCs after switching to .NET 6
+	// TODO:
+	//  Currently, this reloads all scripts, including those whose class is not part of the
+	//  assembly load context being unloaded. As such, we unnecessarily reload GodotTools.
 
-	// Try to load the project assembly if it was not yet loaded
-	// (while hot-reload is not yet implemented)
-	gdmono->initialize_load_assemblies();
+	print_verbose(".NET: Reloading assemblies...");
 
-	{
-		MutexLock lock(script_instances_mutex);
-
-		for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) {
-			Ref<CSharpScript> script(elem->self());
-
-			script->exports_invalidated = true;
-
-			if (!script->get_path().is_empty()) {
-				script->reload(p_soft_reload);
-			}
-		}
-	}
-
-#if 0
 	// There is no soft reloading with Mono. It's always hard reloading.
 
 	List<Ref<CSharpScript>> scripts;
@@ -758,18 +756,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 		for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) {
 			ManagedCallable *managed_callable = elem->self();
 
-			Array serialized_data;
-			MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
+			ERR_CONTINUE(managed_callable->delegate_handle.value == nullptr);
 
-			MonoException *exc = nullptr;
-			bool success = (bool)GDMonoCache::managed_callbacks.methodthunk_DelegateUtils_TrySerializeDelegateWithGCHandle
-								   .invoke(managed_callable->delegate_handle,
-										   managed_serialized_data, &exc);
+			Array serialized_data;
 
-			if (exc) {
-				GDMonoUtils::debug_print_unhandled_exception(exc);
-				continue;
-			}
+			bool success = GDMonoCache::managed_callbacks.DelegateUtils_TrySerializeDelegateWithGCHandle(
+					managed_callable->delegate_handle, &serialized_data);
 
 			if (success) {
 				ManagedCallable::instances_pending_reload.insert(managed_callable, serialized_data);
@@ -798,17 +790,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 		// If someone removes a script from a node, deletes the script, builds, adds a script to the
 		// same node, then builds again, the script might have no path and also no script_class. In
 		// that case, we can't (and don't need to) reload it.
-		if (script->get_path().is_empty() && !script->script_class) {
+		if (script->get_path().is_empty() && !script->valid) {
 			continue;
 		}
 
 		to_reload.push_back(script);
 
-		if (script->get_path().is_empty()) {
-			script->tied_class_name_for_reload = script->script_class->get_name_for_lookup();
-			script->tied_class_namespace_for_reload = script->script_class->get_namespace();
-		}
-
 		// 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.
 
@@ -841,17 +828,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 
 			CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance());
 
-			// Call OnBeforeSerialize
-			if (csi->script->script_class->implements_interface(GDMonoCache::cached_data.class_ISerializationListener)) {
-				obj->get_script_instance()->call(string_names.on_before_serialize);
-			}
+			// Call OnBeforeSerialize and save instance info
 
-			// Save instance info
 			CSharpScript::StateBackup state;
 
-			// TODO: Proper state backup (Not only variants, serialize managed state of scripts)
-			csi->get_properties_state_for_reloading(state.properties);
-			csi->get_event_signals_state_for_reloading(state.event_signals);
+			Dictionary properties;
+
+			GDMonoCache::managed_callbacks.CSharpInstanceBridge_SerializeState(
+					csi->get_gchandle_intptr(), &properties, &state.event_signals);
+
+			for (const Variant *s = properties.next(nullptr); s != nullptr; s = properties.next(s)) {
+				StringName name = *s;
+				Variant value = properties[*s];
+				state.properties.push_back(Pair<StringName, Variant>(name, value));
+			}
 
 			owners_map[obj->get_instance_id()] = state;
 		}
@@ -868,7 +858,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 	}
 
 	// Do domain reload
-	if (gdmono->reload_scripts_domain() != OK) {
+	if (gdmono->reload_project_assemblies() != OK) {
 		// Failed to reload the scripts domain
 		// Make sure to add the scripts back to their owners before returning
 		for (Ref<CSharpScript> &scr : to_reload) {
@@ -899,6 +889,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 
 				scr->pending_reload_state.erase(obj_id);
 			}
+
+			scr->pending_reload_instances.clear();
+			scr->pending_reload_state.clear();
 		}
 
 		return;
@@ -916,46 +909,21 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 
 			if (!script->valid) {
 				script->pending_reload_instances.clear();
+				script->pending_reload_state.clear();
 				continue;
 			}
 		} else {
-			const StringName &class_namespace = script->tied_class_namespace_for_reload;
-			const StringName &class_name = script->tied_class_name_for_reload;
-			GDMonoAssembly *project_assembly = gdmono->get_project_assembly();
-
-			// Search in project and tools assemblies first as those are the most likely to have the class
-			GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : nullptr);
+			bool success = GDMonoCache::managed_callbacks.ScriptManagerBridge_TryReloadRegisteredScriptWithClass(script.ptr());
 
-#ifdef TOOLS_ENABLED
-			if (!script_class) {
-				GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly();
-				script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : nullptr);
-			}
-#endif
-
-			if (!script_class) {
-				script_class = gdmono->get_class(class_namespace, class_name);
-			}
-
-			if (!script_class) {
-				// The class was removed, can't reload
-				script->pending_reload_instances.clear();
-				continue;
-			}
-
-			bool obj_type = GDMonoCache::cached_data.class_GodotObject->is_assignable_from(script_class);
-			if (!obj_type) {
-				// The class no longer inherits Godot.Object, can't reload
+			if (!success) {
+				// Couldn't reload
 				script->pending_reload_instances.clear();
+				script->pending_reload_state.clear();
 				continue;
 			}
-
-			GDMonoClass *native = GDMonoUtils::get_class_native_base(script_class);
-
-			CSharpScript::reload_registered_script(script, script_class, native);
 		}
 
-		StringName native_name = NATIVE_GDMONOCLASS_NAME(script->native);
+		StringName native_name = script->get_instance_base_type();
 
 		{
 			for (const ObjectID &obj_id : script->pending_reload_instances) {
@@ -1020,57 +988,25 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 
 			ERR_CONTINUE(!obj->get_script_instance());
 
-			// TODO: Restore serialized state
-
 			CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id];
 
-			for (const Pair<StringName, Variant> &G : state_backup.properties) {
-				obj->get_script_instance()->set(G.first, G.second);
-			}
-
 			CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance());
 
 			if (csi) {
-				for (const Pair<StringName, Array> &G : state_backup.event_signals) {
-					const StringName &name = G.first;
-					const Array &serialized_data = G.second;
-
-					HashMap<StringName, GDMonoField *>::Iterator match = script->event_signals.find(name);
-
-					if (!match) {
-						// The event or its signal attribute were removed
-						continue;
-					}
+				Dictionary properties;
 
-					GDMonoField *event_signal_field = match->value;
-
-					MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
-					MonoDelegate *delegate = nullptr;
-
-					MonoException *exc = nullptr;
-					bool success = (bool)GDMonoCache::managed_callbacks.methodthunk_DelegateUtils_TryDeserializeDelegate.invoke(managed_serialized_data, &delegate, &exc);
-
-					if (exc) {
-						GDMonoUtils::debug_print_unhandled_exception(exc);
-						continue;
-					}
-
-					if (success) {
-						ERR_CONTINUE(delegate == nullptr);
-						event_signal_field->set_value(csi->get_mono_object(), (MonoObject *)delegate);
-					} else if (OS::get_singleton()->is_stdout_verbose()) {
-						OS::get_singleton()->print("Failed to deserialize event signal delegate\n");
-					}
+				for (const Pair<StringName, Variant> &G : state_backup.properties) {
+					properties[G.first] = G.second;
 				}
 
-				// Call OnAfterDeserialization
-				if (csi->script->script_class->implements_interface(GDMonoCache::cached_data.class_ISerializationListener)) {
-					obj->get_script_instance()->call(string_names.on_after_deserialize);
-				}
+				// Restore serialized state and call OnAfterDeserialization
+				GDMonoCache::managed_callbacks.CSharpInstanceBridge_DeserializeState(
+						csi->get_gchandle_intptr(), &properties, &state_backup.event_signals);
 			}
 		}
 
 		script->pending_reload_instances.clear();
+		script->pending_reload_state.clear();
 	}
 
 	// Deserialize managed callables
@@ -1081,20 +1017,13 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 			ManagedCallable *managed_callable = elem.key;
 			const Array &serialized_data = elem.value;
 
-			MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
-			void *delegate = nullptr;
+			GCHandleIntPtr delegate = { nullptr };
 
-			MonoException *exc = nullptr;
-			bool success = (bool)GDMonoCache::managed_callbacks.methodthunk_DelegateUtils_TryDeserializeDelegateWithGCHandle
-								   .invoke(managed_serialized_data, &delegate, &exc);
-
-			if (exc) {
-				GDMonoUtils::debug_print_unhandled_exception(exc);
-				continue;
-			}
+			bool success = GDMonoCache::managed_callbacks.DelegateUtils_TryDeserializeDelegateWithGCHandle(
+					&serialized_data, &delegate);
 
 			if (success) {
-				ERR_CONTINUE(delegate == nullptr);
+				ERR_CONTINUE(delegate.value == nullptr);
 				managed_callable->delegate_handle = delegate;
 			} else if (OS::get_singleton()->is_stdout_verbose()) {
 				OS::get_singleton()->print("Failed to deserialize delegate\n");
@@ -1111,7 +1040,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 		NodeDock::get_singleton()->update_lists();
 	}
 #endif
-#endif
 }
 #endif
 
@@ -1155,12 +1083,6 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) {
 }
 
 void CSharpLanguage::_on_scripts_domain_about_to_unload() {
-	for (KeyValue<Object *, CSharpScriptBinding> &E : script_bindings) {
-		CSharpScriptBinding &script_binding = E.value;
-		script_binding.gchandle.release();
-		script_binding.inited = false;
-	}
-
 #ifdef GD_MONO_HOT_RELOAD
 	{
 		MutexLock lock(ManagedCallable::instances_mutex);
@@ -1263,7 +1185,8 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b
 #endif
 
 	GCHandleIntPtr strong_gchandle =
-			GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectBinding(&type_name, p_object);
+			GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectBinding(
+					&type_name, p_object);
 
 	ERR_FAIL_NULL_V(strong_gchandle.value, false);
 
@@ -1604,75 +1527,6 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const {
 	return false;
 }
 
-#warning TODO
-#if 0
-void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state) {
-	List<PropertyInfo> property_list;
-	get_property_list(&property_list);
-
-	for (const PropertyInfo &prop_info : property_list) {
-		Pair<StringName, Variant> state_pair;
-		state_pair.first = prop_info.name;
-
-		ManagedType managedType;
-
-		GDMonoField *field = nullptr;
-		GDMonoClass *top = script->script_class;
-		while (top && top != script->native) {
-			field = top->get_field(state_pair.first);
-			if (field) {
-				break;
-			}
-
-			top = top->get_parent_class();
-		}
-		if (!field) {
-			continue; // Properties ignored. We get the property baking fields instead.
-		}
-
-		managedType = field->get_type();
-
-		if (GDMonoMarshal::managed_to_variant_type(managedType) != Variant::NIL) { // If we can marshal it
-			if (get(state_pair.first, state_pair.second)) {
-				r_state.push_back(state_pair);
-			}
-		}
-	}
-}
-
-void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state) {
-	MonoObject *owner_managed = get_mono_object();
-	ERR_FAIL_NULL(owner_managed);
-
-	for (const KeyValue<StringName, GDMonoField *> &E : script->event_signals) {
-		GDMonoField *event_signal_field = E.value;
-
-		MonoDelegate *delegate_field_value = (MonoDelegate *)event_signal_field->get_value(owner_managed);
-		if (!delegate_field_value) {
-			continue; // Empty
-		}
-
-		Array serialized_data;
-		MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
-
-		MonoException *exc = nullptr;
-		bool success = (bool)GDMonoCache::managed_callbacks.methodthunk_DelegateUtils_TrySerializeDelegate
-							   .invoke(delegate_field_value, managed_serialized_data, &exc);
-
-		if (exc) {
-			GDMonoUtils::debug_print_unhandled_exception(exc);
-			continue;
-		}
-
-		if (success) {
-			r_state.push_back(Pair<StringName, Array>(event_signal_field->get_name(), serialized_data));
-		} else if (OS::get_singleton()->is_stdout_verbose()) {
-			OS::get_singleton()->print("Failed to serialize event signal delegate\n");
-		}
-	}
-}
-#endif
-
 void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
 	List<PropertyInfo> props;
 	script->get_script_property_list(&props);
@@ -1906,6 +1760,7 @@ void CSharpInstance::mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_f
 			// If the native instance is still alive and Dispose() was called
 			// (instead of the finalizer), then we remove the script instance.
 			r_remove_script_instance = true;
+			// TODO: Last usage of 'is_finalizing_scripts_domain'. It should be replaced with a check to determine if the load context is being unloaded.
 		} else if (!GDMono::get_singleton()->is_finalizing_scripts_domain()) {
 			// If the native instance is still alive and this is called from the finalizer,
 			// then it was referenced from another thread before the finalizer could
@@ -2156,8 +2011,8 @@ void CSharpScript::_update_exports_values(HashMap<StringName, Variant> &values,
 		propnames.push_back(prop_info);
 	}
 
-	if (base_cache.is_valid()) {
-		base_cache->_update_exports_values(values, propnames);
+	if (base_script.is_valid()) {
+		base_script->_update_exports_values(values, propnames);
 	}
 }
 #endif
@@ -2319,13 +2174,16 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
 	// only for this, so need to call the destructor manually before passing this to C#.
 	rpc_functions_dict.~Dictionary();
 
+	Ref<CSharpScript> base_script;
 	GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo(
-			p_script.ptr(), &tool, &rpc_functions_dict);
+			p_script.ptr(), &tool, &rpc_functions_dict, &base_script);
 
 	p_script->tool = tool;
 
 	p_script->rpc_config.clear();
 	p_script->rpc_config = rpc_functions_dict;
+
+	p_script->base_script = base_script;
 }
 
 bool CSharpScript::can_instantiate() const {
@@ -2586,8 +2444,8 @@ bool CSharpScript::get_property_default_value(const StringName &p_property, Vari
 		return true;
 	}
 
-	if (base_cache.is_valid()) {
-		return base_cache->get_property_default_value(p_property, r_value);
+	if (base_script.is_valid()) {
+		return base_script->get_property_default_value(p_property, r_value);
 	}
 
 #endif
@@ -2671,26 +2529,35 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const {
 }
 
 Ref<Script> CSharpScript::get_base_script() const {
-	// TODO search in metadata file once we have it, not important any way?
-	return Ref<Script>();
+	return base_script;
 }
 
 void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const {
-	List<PropertyInfo> props;
-
 #ifdef TOOLS_ENABLED
-	for (const PropertyInfo &E : exported_members_cache) {
-		props.push_back(E);
+	const CSharpScript *top = this;
+	while (top != nullptr) {
+		for (const PropertyInfo &E : top->exported_members_cache) {
+			r_list->push_back(E);
+		}
+
+		top = top->base_script.ptr();
 	}
 #else
-	for (const KeyValue<StringName, PropertyInfo> &E : member_info) {
-		props.push_front(E.value);
-	}
-#endif
+	const CSharpScript *top = this;
+	while (top != nullptr) {
+		List<PropertyInfo> props;
 
-	for (const PropertyInfo &prop : props) {
-		r_list->push_back(prop);
+		for (const KeyValue<StringName, PropertyInfo> &E : top->member_info) {
+			props.push_front(E.value);
+		}
+
+		for (const PropertyInfo &prop : props) {
+			r_list->push_back(prop);
+		}
+
+		top = top->base_script.ptr();
 	}
+#endif
 }
 
 int CSharpScript::get_member_line(const StringName &p_member) const {
@@ -2852,6 +2719,4 @@ CSharpLanguage::StringNameCache::StringNameCache() {
 	_property_can_revert = StaticCString::create("_property_can_revert");
 	_property_get_revert = StaticCString::create("_property_get_revert");
 	_script_source = StaticCString::create("script/source");
-	on_before_serialize = StaticCString::create("OnBeforeSerialize");
-	on_after_deserialize = StaticCString::create("OnAfterDeserialize");
 }

+ 44 - 31
modules/mono/csharp_script.h

@@ -68,14 +68,6 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
 class CSharpScript : public Script {
 	GDCLASS(CSharpScript, Script);
 
-public:
-	struct SignalParameter {
-		String name;
-		Variant::Type type;
-		bool nil_is_variant = false;
-	};
-
-private:
 	friend class CSharpInstance;
 	friend class CSharpLanguage;
 
@@ -83,7 +75,7 @@ private:
 	bool valid = false;
 	bool reload_invalidated = false;
 
-	Ref<CSharpScript> base_cache; // TODO what's this for?
+	Ref<CSharpScript> base_script;
 
 	HashSet<Object *> instances;
 
@@ -93,13 +85,11 @@ private:
 		// 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;
-		List<Pair<StringName, Array>> event_signals;
+		Dictionary event_signals;
 	};
 
 	HashSet<ObjectID> pending_reload_instances;
 	RBMap<ObjectID, StateBackup> pending_reload_state;
-	StringName tied_class_name_for_reload;
-	StringName tied_class_namespace_for_reload;
 #endif
 
 	String source;
@@ -174,8 +164,12 @@ public:
 
 	void get_members(HashSet<StringName> *p_members) override;
 
-	bool is_tool() const override { return tool; }
-	bool is_valid() const override { return valid; }
+	bool is_tool() const override {
+		return tool;
+	}
+	bool is_valid() const override {
+		return valid;
+	}
 
 	bool inherits_script(const Ref<Script> &p_script) const override;
 
@@ -191,7 +185,9 @@ public:
 	const Variant get_rpc_config() const override;
 
 #ifdef TOOLS_ENABLED
-	bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
+	bool is_placeholder_fallback_enabled() const override {
+		return placeholder_fallback_enabled;
+	}
 #endif
 
 	Error load_source_code(const String &p_path);
@@ -231,9 +227,6 @@ class CSharpInstance : public ScriptInstance {
 	// Do not use unless you know what you are doing
 	static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle);
 
-	void get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state);
-	void get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state);
-
 public:
 	_FORCE_INLINE_ bool is_destructing_script_instance() { return destructing_script_instance; }
 
@@ -325,8 +318,6 @@ class CSharpLanguage : public ScriptLanguage {
 		StringName _property_can_revert;
 		StringName _property_get_revert;
 		StringName _script_source;
-		StringName on_before_serialize; // OnBeforeSerialize
-		StringName on_after_deserialize; // OnAfterDeserialize
 
 		StringNameCache();
 	};
@@ -361,18 +352,30 @@ public:
 
 	StringNameCache string_names;
 
-	const Mutex &get_language_bind_mutex() { return language_bind_mutex; }
-	const Mutex &get_script_instances_mutex() { return script_instances_mutex; }
+	const Mutex &get_language_bind_mutex() {
+		return language_bind_mutex;
+	}
+	const Mutex &get_script_instances_mutex() {
+		return script_instances_mutex;
+	}
 
-	_FORCE_INLINE_ int get_language_index() { return lang_idx; }
+	_FORCE_INLINE_ int get_language_index() {
+		return lang_idx;
+	}
 	void set_language_index(int p_idx);
 
-	_FORCE_INLINE_ const StringNameCache &get_string_names() { return string_names; }
+	_FORCE_INLINE_ const StringNameCache &get_string_names() {
+		return string_names;
+	}
 
-	_FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; }
+	_FORCE_INLINE_ static CSharpLanguage *get_singleton() {
+		return singleton;
+	}
 
 #ifdef TOOLS_ENABLED
-	_FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; }
+	_FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const {
+		return godotsharp_editor;
+	}
 #endif
 
 	static void release_script_gchandle(MonoGCHandleData &p_gchandle);
@@ -387,7 +390,9 @@ public:
 	void reload_assemblies(bool p_soft_reload);
 #endif
 
-	_FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; }
+	_FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const {
+		return managed_callable_middleman;
+	}
 
 	String get_name() const override;
 
@@ -416,7 +421,9 @@ public:
 	Script *create_script() const override;
 	bool has_named_classes() const override;
 	bool supports_builtin_mode() const override;
-	/* TODO? */ int find_function(const String &p_function, const String &p_code) const override { return -1; }
+	/* TODO? */ int find_function(const String &p_function, const String &p_code) const override {
+		return -1;
+	}
 	String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const override;
 	virtual String _get_indentation() const;
 	/* TODO? */ void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override {}
@@ -431,14 +438,20 @@ public:
 	/* TODO */ void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {}
 	/* TODO */ void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {}
 	/* TODO */ void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {}
-	/* TODO */ String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) override { return ""; }
+	/* TODO */ String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) override {
+		return "";
+	}
 	Vector<StackInfo> debug_get_current_stack_info() override;
 
 	/* PROFILING FUNCTIONS */
 	/* TODO */ void profiling_start() override {}
 	/* TODO */ void profiling_stop() override {}
-	/* TODO */ int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override { return 0; }
-	/* TODO */ int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override { return 0; }
+	/* TODO */ int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override {
+		return 0;
+	}
+	/* TODO */ int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override {
+		return 0;
+	}
 
 	void frame() override;
 

+ 8 - 2
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs

@@ -224,8 +224,9 @@ namespace Godot.SourceGenerators
         {
             foreach (var property in properties)
             {
-                // Ignore properties without a getter. Godot properties must be readable.
-                if (property.IsWriteOnly)
+                // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
+                // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
+                if (property.IsWriteOnly || property.IsReadOnly)
                     continue;
 
                 var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(property.Type, typeCache);
@@ -244,6 +245,11 @@ namespace Godot.SourceGenerators
         {
             foreach (var field in fields)
             {
+                // TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload.
+                // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable.
+                if (field.IsReadOnly)
+                    continue;
+
                 var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache);
 
                 if (marshalType == null)

+ 217 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs

@@ -0,0 +1,217 @@
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Godot.SourceGenerators
+{
+    [Generator]
+    public class ScriptSerializationGenerator : ISourceGenerator
+    {
+        public void Initialize(GeneratorInitializationContext context)
+        {
+        }
+
+        public void Execute(GeneratorExecutionContext context)
+        {
+            if (context.AreGodotSourceGeneratorsDisabled())
+                return;
+
+            INamedTypeSymbol[] godotClasses = context
+                .Compilation.SyntaxTrees
+                .SelectMany(tree =>
+                    tree.GetRoot().DescendantNodes()
+                        .OfType<ClassDeclarationSyntax>()
+                        .SelectGodotScriptClasses(context.Compilation)
+                        // Report and skip non-partial classes
+                        .Where(x =>
+                        {
+                            if (x.cds.IsPartial())
+                            {
+                                if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial))
+                                {
+                                    Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!);
+                                    return false;
+                                }
+
+                                return true;
+                            }
+
+                            Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
+                            return false;
+                        })
+                        .Select(x => x.symbol)
+                )
+                .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default)
+                .ToArray();
+
+            if (godotClasses.Length > 0)
+            {
+                var typeCache = new MarshalUtils.TypeCache(context);
+
+                foreach (var godotClass in godotClasses)
+                {
+                    VisitGodotScriptClass(context, typeCache, godotClass);
+                }
+            }
+        }
+
+        private static void VisitGodotScriptClass(
+            GeneratorExecutionContext context,
+            MarshalUtils.TypeCache typeCache,
+            INamedTypeSymbol symbol
+        )
+        {
+            INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
+            string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
+                namespaceSymbol.FullQualifiedName() :
+                string.Empty;
+            bool hasNamespace = classNs.Length != 0;
+
+            bool isInnerClass = symbol.ContainingType != null;
+
+            string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+                                + "_ScriptSerialization_Generated";
+
+            var source = new StringBuilder();
+
+            source.Append("using Godot;\n");
+            source.Append("using Godot.NativeInterop;\n");
+            source.Append("\n");
+
+            if (hasNamespace)
+            {
+                source.Append("namespace ");
+                source.Append(classNs);
+                source.Append(" {\n\n");
+            }
+
+            if (isInnerClass)
+            {
+                var containingType = symbol.ContainingType;
+
+                while (containingType != null)
+                {
+                    source.Append("partial ");
+                    source.Append(containingType.GetDeclarationKeyword());
+                    source.Append(" ");
+                    source.Append(containingType.NameWithTypeParameters());
+                    source.Append("\n{\n");
+
+                    containingType = containingType.ContainingType;
+                }
+            }
+
+            source.Append("partial class ");
+            source.Append(symbol.NameWithTypeParameters());
+            source.Append("\n{\n");
+
+            var members = symbol.GetMembers();
+
+            var propertySymbols = members
+                .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
+                .Cast<IPropertySymbol>();
+
+            var fieldSymbols = members
+                .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
+                .Cast<IFieldSymbol>();
+
+            var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
+            var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
+
+            source.Append(
+                "    protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n    {\n");
+            source.Append("        base.SaveGodotObjectData(info);\n");
+
+            foreach (var property in godotClassProperties)
+            {
+                string propertyName = property.PropertySymbol.Name;
+
+                source.Append("        info.AddProperty(GodotInternal.PropName_")
+                    .Append(propertyName)
+                    .Append(", this.")
+                    .Append(propertyName)
+                    .Append(");\n");
+            }
+
+            foreach (var field in godotClassFields)
+            {
+                string fieldName = field.FieldSymbol.Name;
+
+                source.Append("        info.AddProperty(GodotInternal.PropName_")
+                    .Append(fieldName)
+                    .Append(", this.")
+                    .Append(fieldName)
+                    .Append(");\n");
+            }
+
+            source.Append("    }\n");
+
+            source.Append(
+                "    protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n    {\n");
+            source.Append("        base.RestoreGodotObjectData(info);\n");
+
+            foreach (var property in godotClassProperties)
+            {
+                string propertyName = property.PropertySymbol.Name;
+                string propertyTypeQualifiedName = property.PropertySymbol.Type.FullQualifiedName();
+
+                source.Append("        if (info.TryGetProperty<")
+                    .Append(propertyTypeQualifiedName)
+                    .Append(">(GodotInternal.PropName_")
+                    .Append(propertyName)
+                    .Append(", out var _value_")
+                    .Append(propertyName)
+                    .Append("))\n")
+                    .Append("            this.")
+                    .Append(propertyName)
+                    .Append(" = _value_")
+                    .Append(propertyName)
+                    .Append(";\n");
+            }
+
+            foreach (var field in godotClassFields)
+            {
+                string fieldName = field.FieldSymbol.Name;
+                string fieldTypeQualifiedName = field.FieldSymbol.Type.FullQualifiedName();
+
+                source.Append("        if (info.TryGetProperty<")
+                    .Append(fieldTypeQualifiedName)
+                    .Append(">(GodotInternal.PropName_")
+                    .Append(fieldName)
+                    .Append(", out var _value_")
+                    .Append(fieldName)
+                    .Append("))\n")
+                    .Append("            this.")
+                    .Append(fieldName)
+                    .Append(" = _value_")
+                    .Append(fieldName)
+                    .Append(";\n");
+            }
+
+            source.Append("    }\n");
+
+            source.Append("}\n"); // partial class
+
+            if (isInnerClass)
+            {
+                var containingType = symbol.ContainingType;
+
+                while (containingType != null)
+                {
+                    source.Append("}\n"); // outer class
+
+                    containingType = containingType.ContainingType;
+                }
+            }
+
+            if (hasNamespace)
+            {
+                source.Append("\n}\n");
+            }
+
+            context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
+        }
+    }
+}

+ 15 - 8
modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs

@@ -11,16 +11,16 @@ namespace GodotTools.Build
     [Serializable]
     public sealed partial class BuildInfo : RefCounted // TODO Remove RefCounted once we have proper serialization
     {
-        public string Solution { get; }
-        public string Configuration { get; }
-        public string? RuntimeIdentifier { get; }
-        public string? PublishOutputDir { get; }
-        public bool Restore { get; }
-        public bool Rebuild { get; }
-        public bool OnlyClean { get; }
+        public string Solution { get; private set; }
+        public string Configuration { get; private set; }
+        public string? RuntimeIdentifier { get; private set; }
+        public string? PublishOutputDir { get; private set; }
+        public bool Restore { get; private set; }
+        public bool Rebuild { get; private set; }
+        public bool OnlyClean { get; private set; }
 
         // TODO Use List once we have proper serialization
-        public Array<string> CustomProperties { get; } = new Array<string>();
+        public Array<string> CustomProperties { get; private set; } = new Array<string>();
 
         public string LogsDirPath =>
             Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}");
@@ -56,6 +56,13 @@ namespace GodotTools.Build
             }
         }
 
+        // Needed for instantiation from Godot, after reloading assemblies
+        private BuildInfo()
+        {
+            Solution = string.Empty;
+            Configuration = string.Empty;
+        }
+
         public BuildInfo(string solution, string configuration, bool restore, bool rebuild, bool onlyClean)
         {
             Solution = solution;

+ 14 - 2
modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs

@@ -58,7 +58,7 @@ namespace GodotTools.Build
         }
 
         // TODO Use List once we have proper serialization.
-        private readonly Array<BuildIssue> _issues = new Array<BuildIssue>();
+        private Array<BuildIssue> _issues = new Array<BuildIssue>();
         private ItemList _issuesList;
         private PopupMenu _issuesListContextMenu;
         private TextEdit _buildLog;
@@ -133,7 +133,9 @@ namespace GodotTools.Build
             if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File))
                 return;
 
-            string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : _buildInfo.Solution.GetBaseDir();
+            string projectDir = issue.ProjectFile.Length > 0 ?
+                issue.ProjectFile.GetBaseDir() :
+                _buildInfo.Solution.GetBaseDir();
 
             string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath());
 
@@ -412,6 +414,16 @@ namespace GodotTools.Build
         {
             // In case it didn't update yet. We don't want to have to serialize any pending output.
             UpdateBuildLogText();
+
+            // NOTE:
+            // Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are.
+            // Until that changes, we need workarounds like this one because events keep strong references to disposed objects.
+            BuildManager.BuildLaunchFailed -= BuildLaunchFailed;
+            BuildManager.BuildStarted -= BuildStarted;
+            BuildManager.BuildFinished -= BuildFinished;
+            // StdOutput/Error can be received from different threads, so we need to use CallDeferred
+            BuildManager.StdOutputReceived -= StdOutputReceived;
+            BuildManager.StdErrorReceived -= StdErrorReceived;
         }
 
         public void OnAfterDeserialize()

+ 1 - 0
modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj

@@ -9,6 +9,7 @@
     <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
     <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir>
     <GodotApiAssembliesDir>$(GodotOutputDataDir)/Api/$(GodotApiConfiguration)</GodotApiAssembliesDir>
+    <ProduceReferenceAssembly>false</ProduceReferenceAssembly>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
   <!-- Needed for our source generators to work despite this not being a Godot game project -->

+ 5 - 0
modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml

@@ -0,0 +1,5 @@
+<assembly name="System.Runtime.InteropServices">
+  <member name="T:System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute">
+    <attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" />
+  </member>
+</assembly>

+ 135 - 12
modules/mono/glue/GodotSharp/GodotPlugins/Main.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Reflection;
+using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Runtime.Loader;
 using Godot.Bridge;
@@ -11,10 +12,55 @@ namespace GodotPlugins
 {
     public static class Main
     {
+        // IMPORTANT:
+        // Keeping strong references to the AssemblyLoadContext (our PluginLoadContext) prevents
+        // it from being unloaded. To avoid issues, we wrap the reference in this class, and mark
+        // all the methods that access it as non-inlineable. This way we prevent local references
+        // (either real or introduced by the JIT) to escape the scope of these methods due to
+        // inlining, which could keep the AssemblyLoadContext alive while trying to unload.
+        private sealed class PluginLoadContextWrapper
+        {
+            private PluginLoadContext? _pluginLoadContext;
+
+            public string? AssemblyLoadedPath
+            {
+                [MethodImpl(MethodImplOptions.NoInlining)]
+                get => _pluginLoadContext?.AssemblyLoadedPath;
+            }
+
+            [MethodImpl(MethodImplOptions.NoInlining)]
+            public static (Assembly, PluginLoadContextWrapper) CreateAndLoadFromAssemblyName(
+                AssemblyName assemblyName,
+                string pluginPath,
+                ICollection<string> sharedAssemblies,
+                AssemblyLoadContext mainLoadContext
+            )
+            {
+                var wrapper = new PluginLoadContextWrapper();
+                wrapper._pluginLoadContext = new PluginLoadContext(
+                    pluginPath, sharedAssemblies, mainLoadContext);
+                var assembly = wrapper._pluginLoadContext.LoadFromAssemblyName(assemblyName);
+                return (assembly, wrapper);
+            }
+
+            [MethodImpl(MethodImplOptions.NoInlining)]
+            public WeakReference CreateWeakReference()
+            {
+                return new WeakReference(_pluginLoadContext, trackResurrection: true);
+            }
+
+            [MethodImpl(MethodImplOptions.NoInlining)]
+            internal void Unload()
+            {
+                _pluginLoadContext?.Unload();
+                _pluginLoadContext = null;
+            }
+        }
+
         private static readonly List<AssemblyName> SharedAssemblies = new();
         private static readonly Assembly CoreApiAssembly = typeof(Godot.Object).Assembly;
         private static Assembly? _editorApiAssembly;
-        private static Assembly? _projectAssembly;
+        private static PluginLoadContextWrapper? _projectLoadContext;
 
         private static readonly AssemblyLoadContext MainLoadContext =
             AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ??
@@ -35,6 +81,8 @@ namespace GodotPlugins
                 SharedAssemblies.Add(CoreApiAssembly.GetName());
                 NativeLibrary.SetDllImportResolver(CoreApiAssembly, _dllImportResolver);
 
+                AlcReloadCfg.Configure(alcReloadEnabled: editorHint.ToBool());
+
                 if (editorHint.ToBool())
                 {
                     _editorApiAssembly = Assembly.Load("GodotSharpEditor");
@@ -46,6 +94,7 @@ namespace GodotPlugins
                 {
                     LoadProjectAssemblyCallback = &LoadProjectAssembly,
                     LoadToolsAssemblyCallback = &LoadToolsAssembly,
+                    UnloadProjectPluginCallback = &UnloadProjectPlugin,
                 };
 
                 *managedCallbacks = ManagedCallbacks.Create();
@@ -55,37 +104,41 @@ namespace GodotPlugins
             catch (Exception e)
             {
                 Console.Error.WriteLine(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
         }
 
         [StructLayout(LayoutKind.Sequential)]
         private struct PluginsCallbacks
         {
-            public unsafe delegate* unmanaged<char*, godot_bool> LoadProjectAssemblyCallback;
+            public unsafe delegate* unmanaged<char*, godot_string*, godot_bool> LoadProjectAssemblyCallback;
             public unsafe delegate* unmanaged<char*, IntPtr> LoadToolsAssemblyCallback;
+            public unsafe delegate* unmanaged<godot_bool> UnloadProjectPluginCallback;
         }
 
         [UnmanagedCallersOnly]
-        private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath)
+        private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath, godot_string* outLoadedAssemblyPath)
         {
             try
             {
-                if (_projectAssembly != null)
+                if (_projectLoadContext != null)
                     return godot_bool.True; // Already loaded
 
                 string assemblyPath = new(nAssemblyPath);
 
-                _projectAssembly = LoadPlugin(assemblyPath);
+                (var projectAssembly, _projectLoadContext) = LoadPlugin(assemblyPath);
+
+                string loadedAssemblyPath = _projectLoadContext.AssemblyLoadedPath ?? assemblyPath;
+                *outLoadedAssemblyPath = Marshaling.ConvertStringToNative(loadedAssemblyPath);
 
-                ScriptManagerBridge.LookupScriptsInAssembly(_projectAssembly);
+                ScriptManagerBridge.LookupScriptsInAssembly(projectAssembly);
 
                 return godot_bool.True;
             }
             catch (Exception e)
             {
                 Console.Error.WriteLine(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
         }
 
@@ -99,7 +152,7 @@ namespace GodotPlugins
                 if (_editorApiAssembly == null)
                     throw new InvalidOperationException("The Godot editor API assembly is not loaded");
 
-                var assembly = LoadPlugin(assemblyPath);
+                var (assembly, _) = LoadPlugin(assemblyPath);
 
                 NativeLibrary.SetDllImportResolver(assembly, _dllImportResolver!);
 
@@ -122,7 +175,7 @@ namespace GodotPlugins
             }
         }
 
-        private static Assembly LoadPlugin(string assemblyPath)
+        private static (Assembly, PluginLoadContextWrapper) LoadPlugin(string assemblyPath)
         {
             string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
 
@@ -135,8 +188,78 @@ namespace GodotPlugins
                     sharedAssemblies.Add(sharedAssemblyName);
             }
 
-            var loadContext = new PluginLoadContext(assemblyPath, sharedAssemblies, MainLoadContext);
-            return loadContext.LoadFromAssemblyName(new AssemblyName(assemblyName));
+            return PluginLoadContextWrapper.CreateAndLoadFromAssemblyName(
+                new AssemblyName(assemblyName), assemblyPath, sharedAssemblies, MainLoadContext);
+        }
+
+        [UnmanagedCallersOnly]
+        private static godot_bool UnloadProjectPlugin()
+        {
+            try
+            {
+                return UnloadPlugin(ref _projectLoadContext).ToGodotBool();
+            }
+            catch (Exception e)
+            {
+                Console.Error.WriteLine(e);
+                return godot_bool.False;
+            }
+        }
+
+        private static bool UnloadPlugin(ref PluginLoadContextWrapper? pluginLoadContext)
+        {
+            try
+            {
+                if (pluginLoadContext == null)
+                    return true;
+
+                Console.WriteLine("Unloading assembly load context...");
+
+                var alcWeakReference = pluginLoadContext.CreateWeakReference();
+
+                pluginLoadContext.Unload();
+                pluginLoadContext = null;
+
+                int startTimeMs = Environment.TickCount;
+                bool takingTooLong = false;
+
+                while (alcWeakReference.IsAlive)
+                {
+                    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
+                    GC.WaitForPendingFinalizers();
+
+                    if (!alcWeakReference.IsAlive)
+                        break;
+
+                    int elapsedTimeMs = Environment.TickCount - startTimeMs;
+
+                    if (!takingTooLong && elapsedTimeMs >= 2000)
+                    {
+                        takingTooLong = true;
+
+                        // TODO: How to log from GodotPlugins? (delegate pointer?)
+                        Console.Error.WriteLine("Assembly unloading is taking longer than expected...");
+                    }
+                    else if (elapsedTimeMs >= 5000)
+                    {
+                        // TODO: How to log from GodotPlugins? (delegate pointer?)
+                        Console.Error.WriteLine(
+                            "Failed to unload assemblies. Possible causes: Strong GC handles, running threads, etc.");
+
+                        return false;
+                    }
+                }
+
+                Console.WriteLine("Assembly load context unloaded successfully.");
+
+                return true;
+            }
+            catch (Exception e)
+            {
+                // TODO: How to log exceptions from GodotPlugins? (delegate pointer?)
+                Console.Error.WriteLine(e);
+                return false;
+            }
         }
     }
 }

+ 5 - 0
modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs

@@ -12,8 +12,11 @@ namespace GodotPlugins
         private readonly ICollection<string> _sharedAssemblies;
         private readonly AssemblyLoadContext _mainLoadContext;
 
+        public string? AssemblyLoadedPath { get; private set; }
+
         public PluginLoadContext(string pluginPath, ICollection<string> sharedAssemblies,
             AssemblyLoadContext mainLoadContext)
+            : base(isCollectible: true)
         {
             _resolver = new AssemblyDependencyResolver(pluginPath);
             _sharedAssemblies = sharedAssemblies;
@@ -31,6 +34,8 @@ namespace GodotPlugins
             string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
             if (assemblyPath != null)
             {
+                AssemblyLoadedPath = assemblyPath;
+
                 // Load in memory to prevent locking the file
                 using var assemblyFile = File.Open(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read);
                 string pdbPath = Path.ChangeExtension(assemblyPath, ".pdb");

+ 1 - 0
modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings

@@ -1,5 +1,6 @@
 <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
 	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GC/@EntryIndexedValue">GC</s:String>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=alcs/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=gdnative/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=godotsharp/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=icall/@EntryIndexedValue">True</s:Boolean>

+ 18 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs

@@ -0,0 +1,18 @@
+namespace Godot.Bridge;
+
+public static class AlcReloadCfg
+{
+    private static bool _configured = false;
+
+    public static void Configure(bool alcReloadEnabled)
+    {
+        if (_configured)
+            return;
+
+        _configured = true;
+
+        IsAlcReloadingEnabled = alcReloadEnabled;
+    }
+
+    internal static bool IsAlcReloadingEnabled = false;
+}

+ 88 - 16
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs

@@ -18,7 +18,7 @@ namespace Godot.Bridge
                 {
                     *ret = default;
                     (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL;
-                    return false.ToGodotBool();
+                    return godot_bool.False;
                 }
 
                 bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method),
@@ -31,17 +31,17 @@ namespace Godot.Bridge
                     // This is important, as it tells Object::call that no method was called.
                     // Otherwise, it would prevent Object::call from calling native methods.
                     (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD;
-                    return false.ToGodotBool();
+                    return godot_bool.False;
                 }
 
                 *ret = retValue;
-                return true.ToGodotBool();
+                return godot_bool.True;
             }
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
                 *ret = default;
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
         }
 
@@ -57,7 +57,7 @@ namespace Godot.Bridge
 
                 if (godotObject.SetGodotClassPropertyValue(CustomUnsafe.AsRef(name), CustomUnsafe.AsRef(value)))
                 {
-                    return true.ToGodotBool();
+                    return godot_bool.True;
                 }
 
                 var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
@@ -70,7 +70,7 @@ namespace Godot.Bridge
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
         }
 
@@ -88,7 +88,7 @@ namespace Godot.Bridge
                 if (godotObject.GetGodotClassPropertyValue(CustomUnsafe.AsRef(name), out godot_variant outRetValue))
                 {
                     *outRet = outRetValue;
-                    return true.ToGodotBool();
+                    return godot_bool.True;
                 }
 
                 var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
@@ -99,17 +99,17 @@ namespace Godot.Bridge
                 if (ret == null)
                 {
                     *outRet = default;
-                    return false.ToGodotBool();
+                    return godot_bool.False;
                 }
 
                 *outRet = Marshaling.ConvertManagedObjectToVariant(ret);
-                return true.ToGodotBool();
+                return godot_bool.True;
             }
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
                 *outRet = default;
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
         }
 
@@ -141,7 +141,7 @@ namespace Godot.Bridge
                 if (self == null)
                 {
                     *outRes = default;
-                    *outValid = false.ToGodotBool();
+                    *outValid = godot_bool.False;
                     return;
                 }
 
@@ -150,18 +150,18 @@ namespace Godot.Bridge
                 if (resultStr == null)
                 {
                     *outRes = default;
-                    *outValid = false.ToGodotBool();
+                    *outValid = godot_bool.False;
                     return;
                 }
 
                 *outRes = Marshaling.ConvertStringToNative(resultStr);
-                *outValid = true.ToGodotBool();
+                *outValid = godot_bool.True;
             }
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
                 *outRes = default;
-                *outValid = false.ToGodotBool();
+                *outValid = godot_bool.False;
             }
         }
 
@@ -173,14 +173,86 @@ namespace Godot.Bridge
                 var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
 
                 if (godotObject == null)
-                    return false.ToGodotBool();
+                    return godot_bool.False;
 
                 return godotObject.HasGodotClassMethod(CustomUnsafe.AsRef(method)).ToGodotBool();
             }
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
+            }
+        }
+
+        [UnmanagedCallersOnly]
+        internal static unsafe void SerializeState(
+            IntPtr godotObjectGCHandle,
+            godot_dictionary* propertiesState,
+            godot_dictionary* signalEventsState
+        )
+        {
+            try
+            {
+                var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
+
+                if (godotObject == null)
+                    return;
+
+                // Call OnBeforeSerialize
+
+                // ReSharper disable once SuspiciousTypeConversion.Global
+                if (godotObject is ISerializationListener serializationListener)
+                    serializationListener.OnBeforeSerialize();
+
+                // Save instance state
+
+                var info = new GodotSerializationInfo(
+                    Collections.Dictionary<StringName, object>.CreateTakingOwnershipOfDisposableValue(
+                        NativeFuncs.godotsharp_dictionary_new_copy(*propertiesState)),
+                    Collections.Dictionary<StringName, Collections.Array>.CreateTakingOwnershipOfDisposableValue(
+                        NativeFuncs.godotsharp_dictionary_new_copy(*signalEventsState)));
+
+                godotObject.SaveGodotObjectData(info);
+            }
+            catch (Exception e)
+            {
+                ExceptionUtils.LogException(e);
+            }
+        }
+
+        [UnmanagedCallersOnly]
+        internal static unsafe void DeserializeState(
+            IntPtr godotObjectGCHandle,
+            godot_dictionary* propertiesState,
+            godot_dictionary* signalEventsState
+        )
+        {
+            try
+            {
+                var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
+
+                if (godotObject == null)
+                    return;
+
+                // Restore instance state
+
+                var info = new GodotSerializationInfo(
+                    Collections.Dictionary<StringName, object>.CreateTakingOwnershipOfDisposableValue(
+                        NativeFuncs.godotsharp_dictionary_new_copy(*propertiesState)),
+                    Collections.Dictionary<StringName, Collections.Array>.CreateTakingOwnershipOfDisposableValue(
+                        NativeFuncs.godotsharp_dictionary_new_copy(*signalEventsState)));
+
+                godotObject.RestoreGodotObjectData(info);
+
+                // Call OnAfterDeserialize
+
+                // ReSharper disable once SuspiciousTypeConversion.Global
+                if (godotObject is ISerializationListener serializationListener)
+                    serializationListener.OnAfterDeserialize();
+            }
+            catch (Exception e)
+            {
+                ExceptionUtils.LogException(e);
             }
         }
     }

+ 1 - 1
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs

@@ -11,7 +11,7 @@ namespace Godot.Bridge
         {
             try
             {
-                GCHandle.FromIntPtr(gcHandlePtr).Free();
+                CustomGCHandle.Free(GCHandle.FromIntPtr(gcHandlePtr));
             }
             catch (Exception e)
             {

+ 67 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Godot.Bridge;
+
+public class GodotSerializationInfo
+{
+    private readonly Collections.Dictionary<StringName, object> _properties = new();
+    private readonly Collections.Dictionary<StringName, Collections.Array> _signalEvents = new();
+
+    internal GodotSerializationInfo()
+    {
+    }
+
+    internal GodotSerializationInfo(
+        Collections.Dictionary<StringName, object> properties,
+        Collections.Dictionary<StringName, Collections.Array> signalEvents
+    )
+    {
+        _properties = properties;
+        _signalEvents = signalEvents;
+    }
+
+    public void AddProperty(StringName name, object value)
+    {
+        _properties[name] = value;
+    }
+
+    public bool TryGetProperty<T>(StringName name, [MaybeNullWhen(false)] out T value)
+    {
+        return _properties.TryGetValueAsType(name, out value);
+    }
+
+    public void AddSignalEventDelegate(StringName name, Delegate eventDelegate)
+    {
+        var serializedData = new Collections.Array();
+
+        if (DelegateUtils.TrySerializeDelegate(eventDelegate, serializedData))
+        {
+            _signalEvents[name] = serializedData;
+        }
+        else if (OS.IsStdoutVerbose())
+        {
+            Console.WriteLine($"Failed to serialize event signal delegate: {name}");
+        }
+    }
+
+    public Delegate GetSignalEventDelegate(StringName name)
+    {
+        if (DelegateUtils.TryDeserializeDelegate(_signalEvents[name], out var eventDelegate))
+        {
+            return eventDelegate;
+        }
+        else if (OS.IsStdoutVerbose())
+        {
+            Console.WriteLine($"Failed to deserialize event signal delegate: {name}");
+        }
+
+        return null;
+    }
+
+    public IEnumerable<StringName> GetSignalEventsList()
+    {
+        return _signalEvents.Keys;
+    }
+}

+ 11 - 1
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs

@@ -11,6 +11,8 @@ namespace Godot.Bridge
         public delegate* unmanaged<IntPtr, godot_variant**, int, godot_bool*, void> SignalAwaiter_SignalCallback;
         public delegate* unmanaged<IntPtr, godot_variant**, uint, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs;
         public delegate* unmanaged<IntPtr, IntPtr, godot_bool> DelegateUtils_DelegateEquals;
+        public delegate* unmanaged<IntPtr, godot_array*, godot_bool> DelegateUtils_TrySerializeDelegateWithGCHandle;
+        public delegate* unmanaged<godot_array*, IntPtr*, godot_bool> DelegateUtils_TryDeserializeDelegateWithGCHandle;
         public delegate* unmanaged<void> ScriptManagerBridge_FrameCallback;
         public delegate* unmanaged<godot_string_name*, IntPtr, IntPtr> ScriptManagerBridge_CreateManagedForGodotObjectBinding;
         public delegate* unmanaged<IntPtr, IntPtr, godot_variant**, int, godot_bool> ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
@@ -23,7 +25,8 @@ namespace Godot.Bridge
         public delegate* unmanaged<IntPtr, godot_string*, godot_bool> ScriptManagerBridge_AddScriptBridge;
         public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
         public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
-        public delegate* unmanaged<IntPtr, godot_bool*, godot_dictionary*, void> ScriptManagerBridge_UpdateScriptClassInfo;
+        public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
+        public delegate* unmanaged<IntPtr, godot_bool*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo;
         public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType;
         public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList;
         public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues;
@@ -33,6 +36,8 @@ namespace Godot.Bridge
         public delegate* unmanaged<IntPtr, godot_bool, void> CSharpInstanceBridge_CallDispose;
         public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, void> CSharpInstanceBridge_CallToString;
         public delegate* unmanaged<IntPtr, godot_string_name*, godot_bool> CSharpInstanceBridge_HasMethodUnknownParams;
+        public delegate* unmanaged<IntPtr, godot_dictionary*, godot_dictionary*, void> CSharpInstanceBridge_SerializeState;
+        public delegate* unmanaged<IntPtr, godot_dictionary*, godot_dictionary*, void> CSharpInstanceBridge_DeserializeState;
         public delegate* unmanaged<IntPtr, void> GCHandleBridge_FreeGCHandle;
         public delegate* unmanaged<void*, void> DebuggingUtils_GetCurrentStackInfo;
         public delegate* unmanaged<void> DisposablesTracker_OnGodotShuttingDown;
@@ -47,6 +52,8 @@ namespace Godot.Bridge
                 SignalAwaiter_SignalCallback = &SignalAwaiter.SignalCallback,
                 DelegateUtils_InvokeWithVariantArgs = &DelegateUtils.InvokeWithVariantArgs,
                 DelegateUtils_DelegateEquals = &DelegateUtils.DelegateEquals,
+                DelegateUtils_TrySerializeDelegateWithGCHandle = &DelegateUtils.TrySerializeDelegateWithGCHandle,
+                DelegateUtils_TryDeserializeDelegateWithGCHandle = &DelegateUtils.TryDeserializeDelegateWithGCHandle,
                 ScriptManagerBridge_FrameCallback = &ScriptManagerBridge.FrameCallback,
                 ScriptManagerBridge_CreateManagedForGodotObjectBinding = &ScriptManagerBridge.CreateManagedForGodotObjectBinding,
                 ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = &ScriptManagerBridge.CreateManagedForGodotObjectScriptInstance,
@@ -59,6 +66,7 @@ namespace Godot.Bridge
                 ScriptManagerBridge_AddScriptBridge = &ScriptManagerBridge.AddScriptBridge,
                 ScriptManagerBridge_GetOrCreateScriptBridgeForPath = &ScriptManagerBridge.GetOrCreateScriptBridgeForPath,
                 ScriptManagerBridge_RemoveScriptBridge = &ScriptManagerBridge.RemoveScriptBridge,
+                ScriptManagerBridge_TryReloadRegisteredScriptWithClass = &ScriptManagerBridge.TryReloadRegisteredScriptWithClass,
                 ScriptManagerBridge_UpdateScriptClassInfo = &ScriptManagerBridge.UpdateScriptClassInfo,
                 ScriptManagerBridge_SwapGCHandleForType = &ScriptManagerBridge.SwapGCHandleForType,
                 ScriptManagerBridge_GetPropertyInfoList = &ScriptManagerBridge.GetPropertyInfoList,
@@ -69,6 +77,8 @@ namespace Godot.Bridge
                 CSharpInstanceBridge_CallDispose = &CSharpInstanceBridge.CallDispose,
                 CSharpInstanceBridge_CallToString = &CSharpInstanceBridge.CallToString,
                 CSharpInstanceBridge_HasMethodUnknownParams = &CSharpInstanceBridge.HasMethodUnknownParams,
+                CSharpInstanceBridge_SerializeState = &CSharpInstanceBridge.SerializeState,
+                CSharpInstanceBridge_DeserializeState = &CSharpInstanceBridge.DeserializeState,
                 GCHandleBridge_FreeGCHandle = &GCHandleBridge.FreeGCHandle,
                 DebuggingUtils_GetCurrentStackInfo = &DebuggingUtils.GetCurrentStackInfo,
                 DisposablesTracker_OnGodotShuttingDown = &DisposablesTracker.OnGodotShuttingDown,

+ 248 - 67
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs

@@ -1,22 +1,77 @@
+#nullable enable
+
 using System;
+using System.Collections.Concurrent;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Reflection;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
+using System.Runtime.Loader;
 using System.Runtime.Serialization;
 using Godot.Collections;
 using Godot.NativeInterop;
 
 namespace Godot.Bridge
 {
-    public static class ScriptManagerBridge
+    // TODO: Make class internal once we replace LookupScriptsInAssembly (the only public member) with source generators
+    public static partial class ScriptManagerBridge
     {
-        private static System.Collections.Generic.Dictionary<string, Type> _pathScriptMap = new();
+        private static ConcurrentDictionary<AssemblyLoadContext, ConcurrentDictionary<Type, byte>>
+            _alcData = new();
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static void OnAlcUnloading(AssemblyLoadContext alc)
+        {
+            if (_alcData.TryRemove(alc, out var typesInAlc))
+            {
+                foreach (var type in typesInAlc.Keys)
+                {
+                    if (_scriptTypeBiMap.RemoveByScriptType(type, out IntPtr scriptPtr) &&
+                        !_pathTypeBiMap.TryGetScriptPath(type, out _))
+                    {
+                        // For scripts without a path, we need to keep the class qualified name for reloading
+                        _scriptDataForReload.TryAdd(scriptPtr,
+                            (type.Assembly.GetName().Name, type.FullName ?? type.ToString()));
+                    }
+
+                    _pathTypeBiMap.RemoveByScriptType(type);
+                }
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static void AddTypeForAlcReloading(Type type)
+        {
+            var alc = AssemblyLoadContext.GetLoadContext(type.Assembly);
+            if (alc == null)
+                return;
+
+            var typesInAlc = _alcData.GetOrAdd(alc,
+                static alc =>
+                {
+                    alc.Unloading += OnAlcUnloading;
+                    return new();
+                });
+            typesInAlc.TryAdd(type, 0);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        public static void TrackAlcForUnloading(AssemblyLoadContext alc)
+        {
+            _ = _alcData.GetOrAdd(alc,
+                static alc =>
+                {
+                    alc.Unloading += OnAlcUnloading;
+                    return new();
+                });
+        }
+
+        private static ScriptTypeBiMap _scriptTypeBiMap = new();
+        private static PathScriptTypeBiMap _pathTypeBiMap = new();
 
-        private static readonly object ScriptBridgeLock = new();
-        private static System.Collections.Generic.Dictionary<IntPtr, Type> _scriptTypeMap = new();
-        private static System.Collections.Generic.Dictionary<Type, IntPtr> _typeScriptMap = new();
+        private static ConcurrentDictionary<IntPtr, (string? assemblyName, string classFullName)>
+            _scriptDataForReload = new();
 
         [UnmanagedCallersOnly]
         internal static void FrameCallback()
@@ -55,7 +110,7 @@ namespace Godot.Bridge
 
                 _ = ctor!.Invoke(obj, null);
 
-                return GCHandle.ToIntPtr(GCHandle.Alloc(obj));
+                return GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(obj));
             }
             catch (Exception e)
             {
@@ -74,7 +129,7 @@ namespace Godot.Bridge
             try
             {
                 // Performance is not critical here as this will be replaced with source generators.
-                Type scriptType = _scriptTypeMap[scriptPtr];
+                Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
                 var obj = (Object)FormatterServices.GetUninitializedObject(scriptType);
 
                 var ctor = scriptType
@@ -99,7 +154,7 @@ namespace Godot.Bridge
                 var parameters = ctor.GetParameters();
                 int paramCount = parameters.Length;
 
-                object[] invokeParams = new object[paramCount];
+                var invokeParams = new object?[paramCount];
 
                 for (int i = 0; i < paramCount; i++)
                 {
@@ -112,12 +167,12 @@ namespace Godot.Bridge
                 _ = ctor.Invoke(obj, invokeParams);
 
 
-                return true.ToGodotBool();
+                return godot_bool.True;
             }
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
         }
 
@@ -127,7 +182,7 @@ namespace Godot.Bridge
             try
             {
                 // Performance is not critical here as this will be replaced with source generators.
-                if (!_scriptTypeMap.TryGetValue(scriptPtr, out var scriptType))
+                if (!_scriptTypeBiMap.TryGetScriptType(scriptPtr, out Type? scriptType))
                 {
                     *outRes = default;
                     return;
@@ -144,7 +199,7 @@ namespace Godot.Bridge
                     return;
                 }
 
-                var nativeName = (StringName)field.GetValue(null);
+                var nativeName = (StringName?)field.GetValue(null);
 
                 if (nativeName == null)
                 {
@@ -166,7 +221,7 @@ namespace Godot.Bridge
         {
             try
             {
-                var target = (Object)GCHandle.FromIntPtr(gcHandlePtr).Target;
+                var target = (Object?)GCHandle.FromIntPtr(gcHandlePtr).Target;
                 if (target != null)
                     target.NativePtr = newPtr;
             }
@@ -176,14 +231,14 @@ namespace Godot.Bridge
             }
         }
 
-        private static Type TypeGetProxyClass(string nativeTypeNameStr)
+        private static Type? TypeGetProxyClass(string nativeTypeNameStr)
         {
             // Performance is not critical here as this will be replaced with a generated dictionary.
 
             if (nativeTypeNameStr[0] == '_')
                 nativeTypeNameStr = nativeTypeNameStr.Substring(1);
 
-            Type wrapperType = typeof(Object).Assembly.GetType("Godot." + nativeTypeNameStr);
+            Type? wrapperType = typeof(Object).Assembly.GetType("Godot." + nativeTypeNameStr);
 
             if (wrapperType == null)
             {
@@ -216,7 +271,12 @@ namespace Godot.Bridge
                 if (scriptPathAttr == null)
                     return;
 
-                _pathScriptMap[scriptPathAttr.Path] = type;
+                _pathTypeBiMap.Add(scriptPathAttr.Path, type);
+
+                if (AlcReloadCfg.IsAlcReloadingEnabled)
+                {
+                    AddTypeForAlcReloading(type);
+                }
             }
 
             var assemblyHasScriptsAttr = assembly.GetCustomAttributes(inherit: false)
@@ -267,15 +327,15 @@ namespace Godot.Bridge
         {
             try
             {
-                var owner = (Object)GCHandle.FromIntPtr(ownerGCHandlePtr).Target;
+                var owner = (Object?)GCHandle.FromIntPtr(ownerGCHandlePtr).Target;
 
                 if (owner == null)
                 {
-                    *outOwnerIsNull = true.ToGodotBool();
+                    *outOwnerIsNull = godot_bool.True;
                     return;
                 }
 
-                *outOwnerIsNull = false.ToGodotBool();
+                *outOwnerIsNull = godot_bool.False;
 
                 owner.InternalRaiseEventSignal(CustomUnsafe.AsRef(eventSignalName),
                     new NativeVariantPtrArgs(args), argCount);
@@ -283,7 +343,7 @@ namespace Godot.Bridge
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
-                *outOwnerIsNull = false.ToGodotBool();
+                *outOwnerIsNull = godot_bool.False;
             }
         }
 
@@ -295,7 +355,7 @@ namespace Godot.Bridge
                 // Performance is not critical here as this will be replaced with source generators.
                 using var signals = new Dictionary();
 
-                Type top = _scriptTypeMap[scriptPtr];
+                Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr);
                 Type native = Object.InternalGetClassNativeBase(top);
 
                 while (top != null && top != native)
@@ -391,7 +451,7 @@ namespace Godot.Bridge
 
                 string signalNameStr = Marshaling.ConvertStringToManaged(*signalName);
 
-                Type top = _scriptTypeMap[scriptPtr];
+                Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr);
                 Type native = Object.InternalGetClassNativeBase(top);
 
                 while (top != null && top != native)
@@ -405,7 +465,7 @@ namespace Godot.Bridge
                         .Any(signalDelegate => signalDelegate.Name == signalNameStr)
                        )
                     {
-                        return true.ToGodotBool();
+                        return godot_bool.True;
                     }
 
                     // Event signals
@@ -417,18 +477,18 @@ namespace Godot.Bridge
                         .Any(eventSignal => eventSignal.Name == signalNameStr)
                        )
                     {
-                        return true.ToGodotBool();
+                        return godot_bool.True;
                     }
 
                     top = top.BaseType;
                 }
 
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
         }
 
@@ -437,18 +497,18 @@ namespace Godot.Bridge
         {
             try
             {
-                if (!_scriptTypeMap.TryGetValue(scriptPtr, out var scriptType))
-                    return false.ToGodotBool();
+                if (!_scriptTypeBiMap.TryGetScriptType(scriptPtr, out Type? scriptType))
+                    return godot_bool.False;
 
-                if (!_scriptTypeMap.TryGetValue(scriptPtrMaybeBase, out var maybeBaseType))
-                    return false.ToGodotBool();
+                if (!_scriptTypeBiMap.TryGetScriptType(scriptPtrMaybeBase, out Type? maybeBaseType))
+                    return godot_bool.False;
 
                 return (scriptType == maybeBaseType || maybeBaseType.IsAssignableFrom(scriptType)).ToGodotBool();
             }
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
         }
 
@@ -457,26 +517,25 @@ namespace Godot.Bridge
         {
             try
             {
-                lock (ScriptBridgeLock)
+                lock (_scriptTypeBiMap.ReadWriteLock)
                 {
-                    if (!_scriptTypeMap.ContainsKey(scriptPtr))
+                    if (!_scriptTypeBiMap.IsScriptRegistered(scriptPtr))
                     {
                         string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
 
-                        if (!_pathScriptMap.TryGetValue(scriptPathStr, out Type scriptType))
-                            return false.ToGodotBool();
+                        if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType))
+                            return godot_bool.False;
 
-                        _scriptTypeMap.Add(scriptPtr, scriptType);
-                        _typeScriptMap.Add(scriptType, scriptPtr);
+                        _scriptTypeBiMap.Add(scriptPtr, scriptType);
                     }
                 }
 
-                return true.ToGodotBool();
+                return godot_bool.True;
             }
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
         }
 
@@ -485,7 +544,7 @@ namespace Godot.Bridge
         {
             string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
 
-            if (!_pathScriptMap.TryGetValue(scriptPathStr, out Type scriptType))
+            if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType))
             {
                 NativeFuncs.godotsharp_internal_new_csharp_script(outScript);
                 return;
@@ -494,34 +553,84 @@ namespace Godot.Bridge
             GetOrCreateScriptBridgeForType(scriptType, outScript);
         }
 
-        internal static unsafe void GetOrCreateScriptBridgeForType(Type scriptType, godot_ref* outScript)
+        private static unsafe void GetOrCreateScriptBridgeForType(Type scriptType, godot_ref* outScript)
         {
-            lock (ScriptBridgeLock)
+            lock (_scriptTypeBiMap.ReadWriteLock)
             {
-                if (_typeScriptMap.TryGetValue(scriptType, out IntPtr scriptPtr))
+                if (_scriptTypeBiMap.TryGetScriptPtr(scriptType, out IntPtr scriptPtr))
                 {
+                    // Use existing
                     NativeFuncs.godotsharp_ref_new_from_ref_counted_ptr(out *outScript, scriptPtr);
                     return;
                 }
 
-                NativeFuncs.godotsharp_internal_new_csharp_script(outScript);
-                scriptPtr = outScript->Reference;
+                // This path is slower, but it's only executed for the first instantiation of the type
+                CreateScriptBridgeForType(scriptType, outScript);
+            }
+        }
 
-                _scriptTypeMap.Add(scriptPtr, scriptType);
-                _typeScriptMap.Add(scriptType, scriptPtr);
+        internal static unsafe void GetOrLoadOrCreateScriptForType(Type scriptType, godot_ref* outScript)
+        {
+            static bool GetPathOtherwiseGetOrCreateScript(Type scriptType, godot_ref* outScript,
+                [MaybeNullWhen(false)] out string scriptPath)
+            {
+                lock (_scriptTypeBiMap.ReadWriteLock)
+                {
+                    if (_scriptTypeBiMap.TryGetScriptPtr(scriptType, out IntPtr scriptPtr))
+                    {
+                        // Use existing
+                        NativeFuncs.godotsharp_ref_new_from_ref_counted_ptr(out *outScript, scriptPtr);
+                        scriptPath = null;
+                        return false;
+                    }
 
-                NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr);
+                    // This path is slower, but it's only executed for the first instantiation of the type
+
+                    if (_pathTypeBiMap.TryGetScriptPath(scriptType, out scriptPath))
+                        return true;
+
+                    CreateScriptBridgeForType(scriptType, outScript);
+                    scriptPath = null;
+                    return false;
+                }
+            }
+
+            if (GetPathOtherwiseGetOrCreateScript(scriptType, outScript, out string? scriptPath))
+            {
+                // This path is slower, but it's only executed for the first instantiation of the type
+
+                // This must be done outside the read-write lock, as the script resource loading can lock it
+                using godot_string scriptPathIn = Marshaling.ConvertStringToNative(scriptPath);
+                if (!NativeFuncs.godotsharp_internal_script_load(scriptPathIn, outScript).ToBool())
+                {
+                    GD.PushError($"Cannot load script for type '{scriptType.FullName}'. Path: '{scriptPath}'.");
+
+                    // If loading of the script fails, best we can do create a new script
+                    // with no path, as we do for types without an associated script file.
+                    GetOrCreateScriptBridgeForType(scriptType, outScript);
+                }
             }
         }
 
+        private static unsafe void CreateScriptBridgeForType(Type scriptType, godot_ref* outScript)
+        {
+            NativeFuncs.godotsharp_internal_new_csharp_script(outScript);
+            IntPtr scriptPtr = outScript->Reference;
+
+            // Caller takes care of locking
+            _scriptTypeBiMap.Add(scriptPtr, scriptType);
+
+            NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr);
+        }
+
         [UnmanagedCallersOnly]
         internal static void RemoveScriptBridge(IntPtr scriptPtr)
         {
             try
             {
-                lock (ScriptBridgeLock)
+                lock (_scriptTypeBiMap.ReadWriteLock)
                 {
-                    _ = _scriptTypeMap.Remove(scriptPtr);
+                    _scriptTypeBiMap.Remove(scriptPtr);
                 }
             }
             catch (Exception e)
@@ -530,14 +639,75 @@ namespace Godot.Bridge
             }
         }
 
+        [UnmanagedCallersOnly]
+        internal static godot_bool TryReloadRegisteredScriptWithClass(IntPtr scriptPtr)
+        {
+            try
+            {
+                lock (_scriptTypeBiMap.ReadWriteLock)
+                {
+                    if (_scriptTypeBiMap.TryGetScriptType(scriptPtr, out _))
+                    {
+                        // NOTE:
+                        // Currently, we reload all scripts, not only the ones from the unloaded ALC.
+                        // As such, we need to handle this case instead of treating it as an error.
+                        NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr);
+                        return godot_bool.True;
+                    }
+
+                    if (!_scriptDataForReload.TryGetValue(scriptPtr, out var dataForReload))
+                    {
+                        GD.PushError("Missing class qualified name for reloading script");
+                        return godot_bool.False;
+                    }
+
+                    _ = _scriptDataForReload.TryRemove(scriptPtr, out _);
+
+                    if (dataForReload.assemblyName == null)
+                    {
+                        GD.PushError(
+                            $"Missing assembly name of class '{dataForReload.classFullName}' for reloading script");
+                        return godot_bool.False;
+                    }
+
+                    var scriptType = ReflectionUtils.FindTypeInLoadedAssemblies(dataForReload.assemblyName,
+                        dataForReload.classFullName);
+
+                    if (scriptType == null)
+                    {
+                        // The class was removed, can't reload
+                        return godot_bool.False;
+                    }
+
+                    // ReSharper disable once RedundantNameQualifier
+                    if (!typeof(Godot.Object).IsAssignableFrom(scriptType))
+                    {
+                        // The class no longer inherits Godot.Object, can't reload
+                        return godot_bool.False;
+                    }
+
+                    _scriptTypeBiMap.Add(scriptPtr, scriptType);
+
+                    NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr);
+
+                    return godot_bool.True;
+                }
+            }
+            catch (Exception e)
+            {
+                ExceptionUtils.LogException(e);
+                return godot_bool.False;
+            }
+        }
+
         [UnmanagedCallersOnly]
         internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool* outTool,
-            godot_dictionary* outRpcFunctionsDest)
+            godot_dictionary* outRpcFunctionsDest, godot_ref* outBaseScript)
         {
             try
             {
                 // Performance is not critical here as this will be replaced with source generators.
-                var scriptType = _scriptTypeMap[scriptPtr];
+                var scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
 
                 *outTool = scriptType.GetCustomAttributes(inherit: false)
                     .OfType<ToolAttribute>()
@@ -551,13 +721,13 @@ namespace Godot.Bridge
                 }
 
                 if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools")
-                    *outTool = true.ToGodotBool();
+                    *outTool = godot_bool.True;
 
                 // RPC functions
 
                 Dictionary<string, Dictionary> rpcFunctions = new();
 
-                Type top = scriptType;
+                Type? top = scriptType;
                 Type native = Object.InternalGetClassNativeBase(top);
 
                 while (top != null && top != native)
@@ -595,11 +765,21 @@ namespace Godot.Bridge
                 *outRpcFunctionsDest =
                     NativeFuncs.godotsharp_dictionary_new_copy(
                         (godot_dictionary)((Dictionary)rpcFunctions).NativeValue);
+
+                var baseType = scriptType.BaseType;
+                if (baseType != null && baseType != native)
+                {
+                    GetOrLoadOrCreateScriptForType(baseType, outBaseScript);
+                }
+                else
+                {
+                    *outBaseScript = default;
+                }
             }
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
-                *outTool = false.ToGodotBool();
+                *outTool = godot_bool.False;
                 *outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new();
             }
         }
@@ -612,28 +792,29 @@ namespace Godot.Bridge
             {
                 var oldGCHandle = GCHandle.FromIntPtr(oldGCHandlePtr);
 
-                object target = oldGCHandle.Target;
+                object? target = oldGCHandle.Target;
 
                 if (target == null)
                 {
-                    oldGCHandle.Free();
+                    CustomGCHandle.Free(oldGCHandle);
                     *outNewGCHandlePtr = IntPtr.Zero;
-                    return false.ToGodotBool(); // Called after the managed side was collected, so nothing to do here
+                    return godot_bool.False; // Called after the managed side was collected, so nothing to do here
                 }
 
                 // Release the current weak handle and replace it with a strong handle.
-                var newGCHandle = GCHandle.Alloc(target,
-                    createWeak.ToBool() ? GCHandleType.Weak : GCHandleType.Normal);
+                var newGCHandle = createWeak.ToBool() ?
+                    CustomGCHandle.AllocWeak(target) :
+                    CustomGCHandle.AllocStrong(target);
 
-                oldGCHandle.Free();
+                CustomGCHandle.Free(oldGCHandle);
                 *outNewGCHandlePtr = GCHandle.ToIntPtr(newGCHandle);
-                return true.ToGodotBool();
+                return godot_bool.True;
             }
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
                 *outNewGCHandlePtr = IntPtr.Zero;
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
         }
 
@@ -662,7 +843,7 @@ namespace Godot.Bridge
         {
             try
             {
-                Type scriptType = _scriptTypeMap[scriptPtr];
+                Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
                 GetPropertyInfoListForType(scriptType, scriptPtr, addPropInfoFunc);
             }
             catch (Exception e)
@@ -684,7 +865,7 @@ namespace Godot.Bridge
                 if (getGodotPropertiesMetadataMethod == null)
                     return;
 
-                var properties = (System.Collections.Generic.List<PropertyInfo>)
+                var properties = (System.Collections.Generic.List<PropertyInfo>?)
                     getGodotPropertiesMetadataMethod.Invoke(null, null);
 
                 if (properties == null || properties.Count <= 0)
@@ -774,7 +955,7 @@ namespace Godot.Bridge
         {
             try
             {
-                Type top = _scriptTypeMap[scriptPtr];
+                Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr);
                 Type native = Object.InternalGetClassNativeBase(top);
 
                 while (top != null && top != native)
@@ -804,7 +985,7 @@ namespace Godot.Bridge
                 if (getGodotPropertyDefaultValuesMethod == null)
                     return;
 
-                var defaultValues = (System.Collections.Generic.Dictionary<StringName, object>)
+                var defaultValues = (System.Collections.Generic.Dictionary<StringName, object>?)
                     getGodotPropertyDefaultValuesMethod.Invoke(null, null);
 
                 if (defaultValues == null || defaultValues.Count <= 0)

+ 92 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs

@@ -0,0 +1,92 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace Godot.Bridge;
+
+#nullable enable
+
+public static partial class ScriptManagerBridge
+{
+    private class ScriptTypeBiMap
+    {
+        public readonly object ReadWriteLock = new();
+        private System.Collections.Generic.Dictionary<IntPtr, Type> _scriptTypeMap = new();
+        private System.Collections.Generic.Dictionary<Type, IntPtr> _typeScriptMap = new();
+
+        public void Add(IntPtr scriptPtr, Type scriptType)
+        {
+            // TODO: What if this is called while unloading a load context, but after we already did cleanup in preparation for unloading?
+
+            _scriptTypeMap.Add(scriptPtr, scriptType);
+            _typeScriptMap.Add(scriptType, scriptPtr);
+
+            if (AlcReloadCfg.IsAlcReloadingEnabled)
+            {
+                AddTypeForAlcReloading(scriptType);
+            }
+        }
+
+        public void Remove(IntPtr scriptPtr)
+        {
+            if (_scriptTypeMap.Remove(scriptPtr, out Type? scriptType))
+                _ = _typeScriptMap.Remove(scriptType);
+        }
+
+        public bool RemoveByScriptType(Type scriptType, out IntPtr scriptPtr)
+        {
+            if (_typeScriptMap.Remove(scriptType, out scriptPtr))
+                return _scriptTypeMap.Remove(scriptPtr);
+            return false;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public Type GetScriptType(IntPtr scriptPtr) => _scriptTypeMap[scriptPtr];
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool TryGetScriptType(IntPtr scriptPtr, [MaybeNullWhen(false)] out Type scriptType) =>
+            _scriptTypeMap.TryGetValue(scriptPtr, out scriptType);
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool TryGetScriptPtr(Type scriptType, out IntPtr scriptPtr) =>
+            _typeScriptMap.TryGetValue(scriptType, out scriptPtr);
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool IsScriptRegistered(IntPtr scriptPtr) => _scriptTypeMap.ContainsKey(scriptPtr);
+    }
+
+    private class PathScriptTypeBiMap
+    {
+        private System.Collections.Generic.Dictionary<string, Type> _pathTypeMap = new();
+        private System.Collections.Generic.Dictionary<Type, string> _typePathMap = new();
+
+        public void Add(string scriptPath, Type scriptType)
+        {
+            _pathTypeMap.Add(scriptPath, scriptType);
+
+            // Due to partial classes, more than one file can point to the same type, so
+            // there could be duplicate keys in this case. We only add a type as key once.
+            _typePathMap.TryAdd(scriptType, scriptPath);
+        }
+
+        public void RemoveByScriptType(Type scriptType)
+        {
+            foreach (var pair in _pathTypeMap
+                         .Where(p => p.Value == scriptType).ToArray())
+            {
+                _pathTypeMap.Remove(pair.Key);
+            }
+
+            _typePathMap.Remove(scriptType);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool TryGetScriptType(string scriptPath, [MaybeNullWhen(false)] out Type scriptType) =>
+            _pathTypeMap.TryGetValue(scriptPath, out scriptType);
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool TryGetScriptPath(Type scriptType, [MaybeNullWhen(false)] out string scriptPath) =>
+            _typePathMap.TryGetValue(scriptType, out scriptPath);
+    }
+}

+ 98 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs

@@ -0,0 +1,98 @@
+#nullable enable
+
+using System;
+using System.Collections.Concurrent;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Loader;
+using Godot.Bridge;
+
+namespace Godot;
+
+/// <summary>
+/// Provides a GCHandle that becomes weak when unloading the assembly load context, without having
+/// to manually replace the GCHandle. This hides all the complexity of releasing strong GC handles
+/// to allow the assembly load context to unload properly.
+///
+/// Internally, a strong CustomGCHandle actually contains a weak GCHandle, while the actual strong
+/// reference is stored in a static table.
+/// </summary>
+public static class CustomGCHandle
+{
+    // ConditionalWeakTable uses DependentHandle, so it stores weak references.
+    // Having the assembly load context as key won't prevent it from unloading.
+    private static ConditionalWeakTable<AssemblyLoadContext, object?> _alcsBeingUnloaded = new();
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    public static bool IsAlcBeingUnloaded(AssemblyLoadContext alc) => _alcsBeingUnloaded.TryGetValue(alc, out _);
+
+    // ReSharper disable once RedundantNameQualifier
+    private static ConcurrentDictionary<
+        AssemblyLoadContext,
+        ConcurrentDictionary<GCHandle, object>
+    > _strongReferencesByAlc = new();
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    private static void OnAlcUnloading(AssemblyLoadContext alc)
+    {
+        _alcsBeingUnloaded.Add(alc, null);
+
+        if (_strongReferencesByAlc.TryRemove(alc, out var strongReferences))
+        {
+            strongReferences.Clear();
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static GCHandle AllocStrong(object value)
+        => AllocStrong(value, value.GetType());
+
+    public static GCHandle AllocStrong(object value, Type valueType)
+    {
+        if (AlcReloadCfg.IsAlcReloadingEnabled)
+        {
+            var alc = AssemblyLoadContext.GetLoadContext(valueType.Assembly);
+
+            if (alc != null)
+            {
+                var weakHandle = GCHandle.Alloc(value, GCHandleType.Weak);
+
+                if (!IsAlcBeingUnloaded(alc))
+                {
+                    var strongReferences = _strongReferencesByAlc.GetOrAdd(alc,
+                        static alc =>
+                        {
+                            alc.Unloading += OnAlcUnloading;
+                            return new();
+                        });
+                    strongReferences.TryAdd(weakHandle, value);
+                }
+
+                return weakHandle;
+            }
+        }
+
+        return GCHandle.Alloc(value, GCHandleType.Normal);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static GCHandle AllocWeak(object value) => GCHandle.Alloc(value, GCHandleType.Weak);
+
+    public static void Free(GCHandle handle)
+    {
+        if (AlcReloadCfg.IsAlcReloadingEnabled)
+        {
+            var target = handle.Target;
+
+            if (target != null)
+            {
+                var alc = AssemblyLoadContext.GetLoadContext(target.GetType().Assembly);
+
+                if (alc != null && _strongReferencesByAlc.TryGetValue(alc, out var strongReferences))
+                    _ = strongReferences.TryRemove(handle, out _);
+            }
+        }
+
+        handle.Free();
+    }
+}

+ 119 - 57
modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs

@@ -1,6 +1,8 @@
+#nullable enable
+
 using System;
 using System.Collections.Generic;
-using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.Reflection;
 using System.Runtime.CompilerServices;
@@ -16,14 +18,14 @@ namespace Godot
         {
             try
             {
-                var @delegateA = (Delegate)GCHandle.FromIntPtr(delegateGCHandleA).Target;
-                var @delegateB = (Delegate)GCHandle.FromIntPtr(delegateGCHandleB).Target;
-                return (@delegateA == @delegateB).ToGodotBool();
+                var @delegateA = (Delegate?)GCHandle.FromIntPtr(delegateGCHandleA).Target;
+                var @delegateB = (Delegate?)GCHandle.FromIntPtr(delegateGCHandleB).Target;
+                return (@delegateA! == @delegateB!).ToGodotBool();
             }
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
         }
 
@@ -34,10 +36,10 @@ namespace Godot
             try
             {
                 // TODO: Optimize
-                var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target;
-                var managedArgs = new object[argc];
+                var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!;
+                var managedArgs = new object?[argc];
 
-                var parameterInfos = @delegate!.Method.GetParameters();
+                var parameterInfos = @delegate.Method.GetParameters();
                 var paramsLength = parameterInfos.Length;
 
                 if (argc != paramsLength)
@@ -52,7 +54,7 @@ namespace Godot
                         *args[i], parameterInfos[i].ParameterType);
                 }
 
-                object invokeRet = @delegate.DynamicInvoke(managedArgs);
+                object? invokeRet = @delegate.DynamicInvoke(managedArgs);
 
                 *outRet = Marshaling.ConvertManagedObjectToVariant(invokeRet);
             }
@@ -72,10 +74,7 @@ namespace Godot
             CompilerGenerated
         }
 
-        internal static bool TrySerializeDelegateWithGCHandle(IntPtr delegateGCHandle, Collections.Array serializedData)
-            => TrySerializeDelegate((Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target, serializedData);
-
-        private static bool TrySerializeDelegate(Delegate @delegate, Collections.Array serializedData)
+        internal static bool TrySerializeDelegate(Delegate @delegate, Collections.Array serializedData)
         {
             if (@delegate is MulticastDelegate multicastDelegate)
             {
@@ -98,7 +97,7 @@ namespace Godot
                 }
             }
 
-            if (TrySerializeSingleDelegate(@delegate, out byte[] buffer))
+            if (TrySerializeSingleDelegate(@delegate, out byte[]? buffer))
             {
                 serializedData.Add(buffer);
                 return true;
@@ -107,11 +106,11 @@ namespace Godot
             return false;
         }
 
-        private static bool TrySerializeSingleDelegate(Delegate @delegate, out byte[] buffer)
+        private static bool TrySerializeSingleDelegate(Delegate @delegate, [MaybeNullWhen(false)] out byte[] buffer)
         {
             buffer = null;
 
-            object target = @delegate.Target;
+            object? target = @delegate.Target;
 
             switch (target)
             {
@@ -200,9 +199,6 @@ namespace Godot
 
         private static bool TrySerializeMethodInfo(BinaryWriter writer, MethodInfo methodInfo)
         {
-            if (methodInfo == null)
-                return false;
-
             SerializeType(writer, methodInfo.DeclaringType);
 
             writer.Write(methodInfo.Name);
@@ -241,7 +237,7 @@ namespace Godot
             return true;
         }
 
-        private static void SerializeType(BinaryWriter writer, Type type)
+        private static void SerializeType(BinaryWriter writer, Type? type)
         {
             if (type == null)
             {
@@ -256,9 +252,8 @@ namespace Godot
                 int genericArgumentsCount = genericArgs.Length;
                 writer.Write(genericArgumentsCount);
 
-                string assemblyQualifiedName = genericTypeDef.AssemblyQualifiedName;
-                Debug.Assert(assemblyQualifiedName != null);
-                writer.Write(assemblyQualifiedName);
+                writer.Write(genericTypeDef.Assembly.GetName().Name ?? "");
+                writer.Write(genericTypeDef.FullName ?? genericTypeDef.ToString());
 
                 for (int i = 0; i < genericArgs.Length; i++)
                     SerializeType(writer, genericArgs[i]);
@@ -268,21 +263,62 @@ namespace Godot
                 int genericArgumentsCount = 0;
                 writer.Write(genericArgumentsCount);
 
-                string assemblyQualifiedName = type.AssemblyQualifiedName;
-                Debug.Assert(assemblyQualifiedName != null);
-                writer.Write(assemblyQualifiedName);
+                writer.Write(type.Assembly.GetName().Name ?? "");
+                writer.Write(type.FullName ?? type.ToString());
             }
         }
 
-        private static bool TryDeserializeDelegateWithGCHandle(Collections.Array serializedData,
-            out IntPtr delegateGCHandle)
+        [UnmanagedCallersOnly]
+        internal static unsafe godot_bool TrySerializeDelegateWithGCHandle(IntPtr delegateGCHandle,
+            godot_array* nSerializedData)
         {
-            bool res = TryDeserializeDelegate(serializedData, out Delegate @delegate);
-            delegateGCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(@delegate));
-            return res;
+            try
+            {
+                var serializedData = Collections.Array.CreateTakingOwnershipOfDisposableValue(
+                    NativeFuncs.godotsharp_array_new_copy(*nSerializedData));
+
+                var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!;
+
+                return TrySerializeDelegate(@delegate, serializedData)
+                    .ToGodotBool();
+            }
+            catch (Exception e)
+            {
+                ExceptionUtils.LogException(e);
+                return godot_bool.False;
+            }
         }
 
-        private static bool TryDeserializeDelegate(Collections.Array serializedData, out Delegate @delegate)
+        [UnmanagedCallersOnly]
+        internal static unsafe godot_bool TryDeserializeDelegateWithGCHandle(godot_array* nSerializedData,
+            IntPtr* delegateGCHandle)
+        {
+            try
+            {
+                var serializedData = Collections.Array.CreateTakingOwnershipOfDisposableValue(
+                    NativeFuncs.godotsharp_array_new_copy(*nSerializedData));
+
+                if (TryDeserializeDelegate(serializedData, out Delegate? @delegate))
+                {
+                    *delegateGCHandle = GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(@delegate));
+                    return godot_bool.True;
+                }
+                else
+                {
+                    *delegateGCHandle = IntPtr.Zero;
+                    return godot_bool.False;
+                }
+            }
+            catch (Exception e)
+            {
+                ExceptionUtils.LogException(e);
+                *delegateGCHandle = default;
+                return godot_bool.False;
+            }
+        }
+
+        internal static bool TryDeserializeDelegate(Collections.Array serializedData,
+            [MaybeNullWhen(false)] out Delegate @delegate)
         {
             if (serializedData.Count == 1)
             {
@@ -302,12 +338,12 @@ namespace Godot
             {
                 if (elem is Collections.Array multiCastData)
                 {
-                    if (TryDeserializeDelegate(multiCastData, out Delegate oneDelegate))
+                    if (TryDeserializeDelegate(multiCastData, out Delegate? oneDelegate))
                         delegates.Add(oneDelegate);
                 }
                 else
                 {
-                    if (TryDeserializeSingleDelegate((byte[])elem, out Delegate oneDelegate))
+                    if (TryDeserializeSingleDelegate((byte[])elem, out Delegate? oneDelegate))
                         delegates.Add(oneDelegate);
                 }
             }
@@ -315,11 +351,11 @@ namespace Godot
             if (delegates.Count <= 0)
                 return false;
 
-            @delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray());
+            @delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray())!;
             return true;
         }
 
-        private static bool TryDeserializeSingleDelegate(byte[] buffer, out Delegate @delegate)
+        private static bool TryDeserializeSingleDelegate(byte[] buffer, [MaybeNullWhen(false)] out Delegate @delegate)
         {
             @delegate = null;
 
@@ -332,14 +368,18 @@ namespace Godot
                 {
                     case TargetKind.Static:
                     {
-                        Type delegateType = DeserializeType(reader);
+                        Type? delegateType = DeserializeType(reader);
                         if (delegateType == null)
                             return false;
 
-                        if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo))
+                        if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo))
+                            return false;
+
+                        @delegate = Delegate.CreateDelegate(delegateType, null, methodInfo, throwOnBindFailure: false);
+
+                        if (@delegate == null)
                             return false;
 
-                        @delegate = Delegate.CreateDelegate(delegateType, null, methodInfo);
                         return true;
                     }
                     case TargetKind.GodotObject:
@@ -350,32 +390,37 @@ namespace Godot
                         if (godotObject == null)
                             return false;
 
-                        Type delegateType = DeserializeType(reader);
+                        Type? delegateType = DeserializeType(reader);
                         if (delegateType == null)
                             return false;
 
-                        if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo))
+                        if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo))
+                            return false;
+
+                        @delegate = Delegate.CreateDelegate(delegateType, godotObject, methodInfo,
+                            throwOnBindFailure: false);
+
+                        if (@delegate == null)
                             return false;
 
-                        @delegate = Delegate.CreateDelegate(delegateType, godotObject, methodInfo);
                         return true;
                     }
                     case TargetKind.CompilerGenerated:
                     {
-                        Type targetType = DeserializeType(reader);
+                        Type? targetType = DeserializeType(reader);
                         if (targetType == null)
                             return false;
 
-                        Type delegateType = DeserializeType(reader);
+                        Type? delegateType = DeserializeType(reader);
                         if (delegateType == null)
                             return false;
 
-                        if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo))
+                        if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo))
                             return false;
 
                         int fieldCount = reader.ReadInt32();
 
-                        object recreatedTarget = Activator.CreateInstance(targetType);
+                        object recreatedTarget = Activator.CreateInstance(targetType)!;
 
                         for (int i = 0; i < fieldCount; i++)
                         {
@@ -383,12 +428,17 @@ namespace Godot
                             int valueBufferLength = reader.ReadInt32();
                             byte[] valueBuffer = reader.ReadBytes(valueBufferLength);
 
-                            FieldInfo fieldInfo =
-                                targetType.GetField(name, BindingFlags.Instance | BindingFlags.Public);
+                            FieldInfo? fieldInfo = targetType.GetField(name,
+                                BindingFlags.Instance | BindingFlags.Public);
                             fieldInfo?.SetValue(recreatedTarget, GD.Bytes2Var(valueBuffer));
                         }
 
-                        @delegate = Delegate.CreateDelegate(delegateType, recreatedTarget, methodInfo);
+                        @delegate = Delegate.CreateDelegate(delegateType, recreatedTarget, methodInfo,
+                            throwOnBindFailure: false);
+
+                        if (@delegate == null)
+                            return false;
+
                         return true;
                     }
                     default:
@@ -397,18 +447,22 @@ namespace Godot
             }
         }
 
-        private static bool TryDeserializeMethodInfo(BinaryReader reader, out MethodInfo methodInfo)
+        private static bool TryDeserializeMethodInfo(BinaryReader reader,
+            [MaybeNullWhen(false)] out MethodInfo methodInfo)
         {
             methodInfo = null;
 
-            Type declaringType = DeserializeType(reader);
+            Type? declaringType = DeserializeType(reader);
+
+            if (declaringType == null)
+                return false;
 
             string methodName = reader.ReadString();
 
             int flags = reader.ReadInt32();
 
             bool hasReturn = reader.ReadBoolean();
-            Type returnType = hasReturn ? DeserializeType(reader) : typeof(void);
+            Type? returnType = hasReturn ? DeserializeType(reader) : typeof(void);
 
             int parametersCount = reader.ReadInt32();
 
@@ -418,7 +472,7 @@ namespace Godot
 
                 for (int i = 0; i < parametersCount; i++)
                 {
-                    Type parameterType = DeserializeType(reader);
+                    Type? parameterType = DeserializeType(reader);
                     if (parameterType == null)
                         return false;
                     parameterTypes[i] = parameterType;
@@ -432,15 +486,23 @@ namespace Godot
             return methodInfo != null && methodInfo.ReturnType == returnType;
         }
 
-        private static Type DeserializeType(BinaryReader reader)
+        private static Type? DeserializeType(BinaryReader reader)
         {
             int genericArgumentsCount = reader.ReadInt32();
 
             if (genericArgumentsCount == -1)
                 return null;
 
-            string assemblyQualifiedName = reader.ReadString();
-            var type = Type.GetType(assemblyQualifiedName);
+            string assemblyName = reader.ReadString();
+
+            if (assemblyName.Length == 0)
+            {
+                GD.PushError($"Missing assembly name of type when attempting to deserialize delegate");
+                return null;
+            }
+
+            string typeFullName = reader.ReadString();
+            var type = ReflectionUtils.FindTypeInLoadedAssemblies(assemblyName, typeFullName);
 
             if (type == null)
                 return null; // Type not found
@@ -451,7 +513,7 @@ namespace Godot
 
                 for (int i = 0; i < genericArgumentsCount; i++)
                 {
-                    Type genericArgumentType = DeserializeType(reader);
+                    Type? genericArgumentType = DeserializeType(reader);
                     if (genericArgumentType == null)
                         return null;
                     genericArgumentTypes[i] = genericArgumentType;

+ 18 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs

@@ -578,6 +578,24 @@ namespace Godot.Collections
             return found;
         }
 
+        // TODO: This is temporary. It's needed for the serialization generator. It won't be needed once we replace Sysme.Object with a Variant type.
+        internal bool TryGetValueAsType<TValueCustom>(TKey key, [MaybeNullWhen(false)] out TValueCustom value)
+        {
+            using godot_variant variantKey = Marshaling.ConvertManagedObjectToVariant(key);
+            var self = (godot_dictionary)_underlyingDict.NativeValue;
+            bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self,
+                variantKey, out godot_variant retValue).ToBool();
+
+            using (retValue)
+            {
+                value = found ?
+                    (TValueCustom)Marshaling.ConvertVariantToManagedObjectOfType(retValue, typeof(TValueCustom)) :
+                    default;
+            }
+
+            return found;
+        }
+
         // ICollection<KeyValuePair<TKey, TValue>>
 
         /// <summary>

+ 9 - 15
modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Concurrent;
 using System.Runtime.InteropServices;
-using System.Runtime.Loader;
 using Godot.NativeInterop;
 
 #nullable enable
@@ -10,17 +9,12 @@ namespace Godot
 {
     internal static class DisposablesTracker
     {
-        static DisposablesTracker()
-        {
-            AssemblyLoadContext.Default.Unloading += _ => OnUnloading();
-        }
-
         [UnmanagedCallersOnly]
         internal static void OnGodotShuttingDown()
         {
             try
             {
-                OnUnloading();
+                OnGodotShuttingDownImpl();
             }
             catch (Exception e)
             {
@@ -28,7 +22,7 @@ namespace Godot
             }
         }
 
-        private static void OnUnloading()
+        private static void OnGodotShuttingDownImpl()
         {
             bool isStdoutVerbose;
 
@@ -66,30 +60,30 @@ namespace Godot
         }
 
         // ReSharper disable once RedundantNameQualifier
-        private static ConcurrentDictionary<WeakReference<Godot.Object>, object?> GodotObjectInstances { get; } =
+        private static ConcurrentDictionary<WeakReference<Godot.Object>, byte> GodotObjectInstances { get; } =
             new();
 
-        private static ConcurrentDictionary<WeakReference<IDisposable>, object?> OtherInstances { get; } =
+        private static ConcurrentDictionary<WeakReference<IDisposable>, byte> OtherInstances { get; } =
             new();
 
         public static WeakReference<Object> RegisterGodotObject(Object godotObject)
         {
             var weakReferenceToSelf = new WeakReference<Object>(godotObject);
-            GodotObjectInstances.TryAdd(weakReferenceToSelf, null);
+            GodotObjectInstances.TryAdd(weakReferenceToSelf, 0);
             return weakReferenceToSelf;
         }
 
         public static WeakReference<IDisposable> RegisterDisposable(IDisposable disposable)
         {
             var weakReferenceToSelf = new WeakReference<IDisposable>(disposable);
-            OtherInstances.TryAdd(weakReferenceToSelf, null);
+            OtherInstances.TryAdd(weakReferenceToSelf, 0);
             return weakReferenceToSelf;
         }
 
-        public static void UnregisterGodotObject(WeakReference<Object> weakReference)
+        public static void UnregisterGodotObject(Object godotObject, WeakReference<Object> weakReferenceToSelf)
         {
-            if (!GodotObjectInstances.TryRemove(weakReference, out _))
-                throw new ArgumentException("Godot Object not registered", nameof(weakReference));
+            if (!GodotObjectInstances.TryRemove(weakReferenceToSelf, out _))
+                throw new ArgumentException("Godot Object not registered", nameof(weakReferenceToSelf));
         }
 
         public static void UnregisterDisposable(WeakReference<IDisposable> weakReference)

+ 1 - 1
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs

@@ -95,7 +95,7 @@ namespace Godot.NativeInterop
                 }
 
                 NativeFuncs.godotsharp_internal_script_debugger_send_error(nFunc, nFile, line,
-                    nErrorMsg, nExcMsg, p_warning: false.ToGodotBool(), stackInfoVector);
+                    nErrorMsg, nExcMsg, p_warning: godot_bool.False, stackInfoVector);
             }
         }
 

+ 5 - 3
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs

@@ -50,7 +50,9 @@ namespace Godot.NativeInterop
         public static void TieManagedToUnmanaged(Object managed, IntPtr unmanaged,
             StringName nativeName, bool refCounted, Type type, Type nativeType)
         {
-            var gcHandle = GCHandle.Alloc(managed, refCounted ? GCHandleType.Weak : GCHandleType.Normal);
+            var gcHandle = refCounted ?
+                CustomGCHandle.AllocWeak(managed) :
+                CustomGCHandle.AllocStrong(managed, type);
 
             if (type == nativeType)
             {
@@ -65,7 +67,7 @@ namespace Godot.NativeInterop
                     // We don't dispose `script` ourselves here.
                     // `tie_user_managed_to_unmanaged` does it for us to avoid another P/Invoke call.
                     godot_ref script;
-                    ScriptManagerBridge.GetOrCreateScriptBridgeForType(type, &script);
+                    ScriptManagerBridge.GetOrLoadOrCreateScriptForType(type, &script);
 
                     // IMPORTANT: This must be called after GetOrCreateScriptBridgeForType
                     NativeFuncs.godotsharp_internal_tie_user_managed_to_unmanaged(
@@ -80,7 +82,7 @@ namespace Godot.NativeInterop
             if (type == nativeType)
                 return;
 
-            var strongGCHandle = GCHandle.Alloc(managed, GCHandleType.Normal);
+            var strongGCHandle = CustomGCHandle.AllocStrong(managed);
             NativeFuncs.godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup(
                 GCHandle.ToIntPtr(strongGCHandle), unmanaged);
         }

+ 25 - 3
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs

@@ -577,7 +577,29 @@ namespace Godot.NativeInterop
         {
             if (typeof(Godot.Object).IsAssignableFrom(type))
             {
-                var godotObject = VariantUtils.ConvertToGodotObject(p_var);
+                if (p_var.Type == Variant.Type.Nil)
+                {
+                    res = null;
+                    return true;
+                }
+
+                if (p_var.Type != Variant.Type.Object)
+                {
+                    GD.PushError("Invalid cast when marshaling Godot.Object type." +
+                                 $" Variant type is `{p_var.Type}`; expected `{p_var.Object}`.");
+                    res = null;
+                    return true;
+                }
+
+                var godotObjectPtr = VariantUtils.ConvertToGodotObjectPtr(p_var);
+
+                if (godotObjectPtr == IntPtr.Zero)
+                {
+                    res = null;
+                    return true;
+                }
+
+                var godotObject = InteropUtils.UnmanagedGetManaged(godotObjectPtr);
 
                 if (!type.IsInstanceOfType(godotObject))
                 {
@@ -864,9 +886,9 @@ namespace Godot.NativeInterop
         {
             if (p_managed_callable.Delegate != null)
             {
+                var gcHandle = CustomGCHandle.AllocStrong(p_managed_callable.Delegate);
                 NativeFuncs.godotsharp_callable_new_with_delegate(
-                    GCHandle.ToIntPtr(GCHandle.Alloc(p_managed_callable.Delegate)),
-                    out godot_callable callable);
+                    GCHandle.ToIntPtr(gcHandle), out godot_callable callable);
                 return callable;
             }
             else

+ 4 - 1
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs

@@ -95,7 +95,10 @@ namespace Godot.NativeInterop
             IntPtr oldGCHandlePtr);
 
         [DllImport(GodotDllName)]
-        internal static extern void godotsharp_internal_new_csharp_script(godot_ref* r_script);
+        internal static extern void godotsharp_internal_new_csharp_script(godot_ref* r_dest);
+
+        [DllImport(GodotDllName)]
+        internal static extern godot_bool godotsharp_internal_script_load(in godot_string p_path, godot_ref* r_dest);
 
         [DllImport(GodotDllName)]
         internal static extern void godotsharp_internal_reload_registered_script(IntPtr scriptPtr);

+ 74 - 1
modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs

@@ -2,6 +2,7 @@ using System;
 using System.Linq;
 using System.Reflection;
 using System.Runtime.InteropServices;
+using Godot.Bridge;
 using Godot.NativeInterop;
 
 namespace Godot
@@ -150,7 +151,7 @@ namespace Godot
                 NativePtr = IntPtr.Zero;
             }
 
-            DisposablesTracker.UnregisterGodotObject(_weakReferenceToSelf);
+            DisposablesTracker.UnregisterGodotObject(this, _weakReferenceToSelf);
         }
 
         /// <summary>
@@ -328,5 +329,77 @@ namespace Godot
 
             return nativeConstructor;
         }
+
+        protected internal virtual void SaveGodotObjectData(GodotSerializationInfo info)
+        {
+            // Temporary solution via reflection until we add a signals events source generator
+
+            Type top = GetType();
+            Type native = InternalGetClassNativeBase(top);
+
+            while (top != null && top != native)
+            {
+                var foundEventSignals = top.GetEvents(
+                        BindingFlags.DeclaredOnly | BindingFlags.Instance |
+                        BindingFlags.NonPublic | BindingFlags.Public)
+                    .Where(ev => ev.GetCustomAttributes().OfType<SignalAttribute>().Any())
+                    .Select(ev => ev.Name);
+
+                var fields = top.GetFields(
+                    BindingFlags.DeclaredOnly | BindingFlags.Instance |
+                    BindingFlags.NonPublic | BindingFlags.Public);
+
+                foreach (var eventSignalField in fields
+                             .Where(f => typeof(Delegate).IsAssignableFrom(f.FieldType))
+                             .Where(f => foundEventSignals.Contains(f.Name)))
+                {
+                    var eventSignalDelegate = (Delegate)eventSignalField.GetValue(this);
+                    info.AddSignalEventDelegate(eventSignalField.Name, eventSignalDelegate);
+                }
+
+                top = top.BaseType;
+            }
+        }
+
+        // TODO: Should this be a constructor overload?
+        protected internal virtual void RestoreGodotObjectData(GodotSerializationInfo info)
+        {
+            // Temporary solution via reflection until we add a signals events source generator
+
+            void RestoreSignalEvent(StringName signalEventName)
+            {
+                Type top = GetType();
+                Type native = InternalGetClassNativeBase(top);
+
+                while (top != null && top != native)
+                {
+                    var foundEventSignal = top.GetEvent(signalEventName,
+                        BindingFlags.DeclaredOnly | BindingFlags.Instance |
+                        BindingFlags.NonPublic | BindingFlags.Public);
+
+                    if (foundEventSignal != null &&
+                        foundEventSignal.GetCustomAttributes().OfType<SignalAttribute>().Any())
+                    {
+                        var field = top.GetField(foundEventSignal.Name,
+                            BindingFlags.DeclaredOnly | BindingFlags.Instance |
+                            BindingFlags.NonPublic | BindingFlags.Public);
+
+                        if (field != null && typeof(Delegate).IsAssignableFrom(field.FieldType))
+                        {
+                            var eventSignalDelegate = info.GetSignalEventDelegate(signalEventName);
+                            field.SetValue(this, eventSignalDelegate);
+                            return;
+                        }
+                    }
+
+                    top = top.BaseType;
+                }
+            }
+
+            foreach (var signalEventName in info.GetSignalEventsList())
+            {
+                RestoreSignalEvent(signalEventName);
+            }
+        }
     }
 }

+ 16 - 0
modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Linq;
+
+#nullable enable
+
+namespace Godot;
+
+internal class ReflectionUtils
+{
+    public static Type? FindTypeInLoadedAssemblies(string assemblyName, string typeFullName)
+    {
+        return AppDomain.CurrentDomain.GetAssemblies()
+            .FirstOrDefault(a => a.GetName().Name == assemblyName)?
+            .GetType(typeFullName);
+    }
+}

+ 5 - 4
modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs

@@ -12,10 +12,11 @@ namespace Godot
 
         public SignalAwaiter(Object source, StringName signal, Object target)
         {
+            var awaiterGcHandle = CustomGCHandle.AllocStrong(this);
             using godot_string_name signalSrc = NativeFuncs.godotsharp_string_name_new_copy(
                 (godot_string_name)(signal?.NativeValue ?? default));
             NativeFuncs.godotsharp_internal_signal_awaiter_connect(Object.GetPtr(source), in signalSrc,
-                Object.GetPtr(target), GCHandle.ToIntPtr(GCHandle.Alloc(this)));
+                Object.GetPtr(target), GCHandle.ToIntPtr(awaiterGcHandle));
         }
 
         public bool IsCompleted => _completed;
@@ -39,11 +40,11 @@ namespace Godot
 
                 if (awaiter == null)
                 {
-                    *outAwaiterIsNull = true.ToGodotBool();
+                    *outAwaiterIsNull = godot_bool.True;
                     return;
                 }
 
-                *outAwaiterIsNull = false.ToGodotBool();
+                *outAwaiterIsNull = godot_bool.False;
 
                 awaiter._completed = true;
 
@@ -59,7 +60,7 @@ namespace Godot
             catch (Exception e)
             {
                 ExceptionUtils.LogException(e);
-                *outAwaiterIsNull = false.ToGodotBool();
+                *outAwaiterIsNull = godot_bool.False;
             }
         }
     }

+ 5 - 0
modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj

@@ -49,6 +49,8 @@
   <!-- Sources -->
   <ItemGroup>
     <Compile Include="Core\AABB.cs" />
+    <Compile Include="Core\Bridge\GodotSerializationInfo.cs" />
+    <Compile Include="Core\CustomGCHandle.cs" />
     <Compile Include="Core\Array.cs" />
     <Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
     <Compile Include="Core\Attributes\ExportAttribute.cs" />
@@ -59,9 +61,11 @@
     <Compile Include="Core\Basis.cs" />
     <Compile Include="Core\Bridge\CSharpInstanceBridge.cs" />
     <Compile Include="Core\Bridge\GCHandleBridge.cs" />
+    <Compile Include="Core\Bridge\AlcReloadCfg.cs" />
     <Compile Include="Core\Bridge\ManagedCallbacks.cs" />
     <Compile Include="Core\Bridge\PropertyInfo.cs" />
     <Compile Include="Core\Bridge\ScriptManagerBridge.cs" />
+    <Compile Include="Core\Bridge\ScriptManagerBridge.types.cs" />
     <Compile Include="Core\Callable.cs" />
     <Compile Include="Core\Color.cs" />
     <Compile Include="Core\Colors.cs" />
@@ -100,6 +104,7 @@
     <Compile Include="Core\Quaternion.cs" />
     <Compile Include="Core\Rect2.cs" />
     <Compile Include="Core\Rect2i.cs" />
+    <Compile Include="Core\ReflectionUtils.cs" />
     <Compile Include="Core\RID.cs" />
     <Compile Include="Core\NativeInterop\NativeFuncs.cs" />
     <Compile Include="Core\NativeInterop\InteropStructs.cs" />

+ 13 - 1
modules/mono/glue/runtime_interop.cpp

@@ -320,6 +320,17 @@ GD_PINVOKE_EXPORT void godotsharp_internal_new_csharp_script(Ref<CSharpScript> *
 	memnew_placement(r_dest, Ref<CSharpScript>(memnew(CSharpScript)));
 }
 
+GD_PINVOKE_EXPORT bool godotsharp_internal_script_load(const String *p_path, Ref<CSharpScript> *r_dest) {
+	Ref<Resource> res = ResourceLoader::load(*p_path);
+	if (res.is_valid()) {
+		memnew_placement(r_dest, Ref<CSharpScript>(res));
+		return true;
+	} else {
+		memnew_placement(r_dest, Ref<CSharpScript>());
+		return false;
+	}
+}
+
 GD_PINVOKE_EXPORT void godotsharp_internal_reload_registered_script(CSharpScript *p_script) {
 	CRASH_COND(!p_script);
 	CSharpScript::reload_registered_script(Ref<CSharpScript>(p_script));
@@ -1311,7 +1322,7 @@ GD_PINVOKE_EXPORT void godotsharp_object_to_string(Object *p_ptr, godot_string *
 #endif
 
 // We need this to prevent the functions from being stripped.
-void *godotsharp_pinvoke_funcs[185] = {
+void *godotsharp_pinvoke_funcs[186] = {
 	(void *)godotsharp_method_bind_get_method,
 	(void *)godotsharp_get_class_constructor,
 	(void *)godotsharp_engine_get_singleton,
@@ -1331,6 +1342,7 @@ void *godotsharp_pinvoke_funcs[185] = {
 	(void *)godotsharp_internal_tie_user_managed_to_unmanaged,
 	(void *)godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup,
 	(void *)godotsharp_internal_new_csharp_script,
+	(void *)godotsharp_internal_script_load,
 	(void *)godotsharp_internal_reload_registered_script,
 	(void *)godotsharp_array_filter_godot_objects_by_native,
 	(void *)godotsharp_array_filter_godot_objects_by_non_native,

+ 2 - 0
modules/mono/managed_callable.cpp

@@ -87,6 +87,8 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant
 	r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better
 	r_return_value = Variant();
 
+	ERR_FAIL_COND(delegate_handle.value == nullptr);
+
 	GDMonoCache::managed_callbacks.DelegateUtils_InvokeWithVariantArgs(
 			delegate_handle, p_arguments, p_argcount, &r_return_value);
 

+ 15 - 73
modules/mono/mono_gd/gd_mono.cpp

@@ -501,106 +501,48 @@ void GDMono::_init_godot_api_hashes() {
 
 #ifdef TOOLS_ENABLED
 bool GDMono::_load_project_assembly() {
-	String appname = ProjectSettings::get_singleton()->get("application/config/name");
-	String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
-	if (appname_safe.is_empty()) {
-		appname_safe = "UnnamedProject";
-	}
+	String appname_safe = ProjectSettings::get_singleton()->get_safe_project_name();
 
 	String assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir()
 								   .plus_file(appname_safe + ".dll");
 	assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path);
 
-	return plugin_callbacks.LoadProjectAssemblyCallback(assembly_path.utf16());
-}
-#endif
-
-#warning TODO hot-reload
-#if 0
-Error GDMono::_unload_scripts_domain() {
-	ERR_FAIL_NULL_V(scripts_domain, ERR_BUG);
-
-	CSharpLanguage::get_singleton()->_on_scripts_domain_about_to_unload();
-
-	print_verbose("Mono: Finalizing scripts domain...");
-
-	if (mono_domain_get() != root_domain) {
-		mono_domain_set(root_domain, true);
-	}
-
-	finalizing_scripts_domain = true;
+	String loaded_assembly_path;
+	bool success = plugin_callbacks.LoadProjectAssemblyCallback(assembly_path.utf16(), &loaded_assembly_path);
 
-	if (!mono_domain_finalize(scripts_domain, 2000)) {
-		ERR_PRINT("Mono: Domain finalization timeout.");
+	if (success) {
+		project_assembly_path = loaded_assembly_path.simplify_path();
+		project_assembly_modified_time = FileAccess::get_modified_time(loaded_assembly_path);
 	}
 
-	finalizing_scripts_domain = false;
-
-	mono_gc_collect(mono_gc_max_generation());
-
-	core_api_assembly = nullptr;
-#ifdef TOOLS_ENABLED
-	editor_api_assembly = nullptr;
-#endif
-
-	project_assembly = nullptr;
-#ifdef TOOLS_ENABLED
-	tools_assembly = nullptr;
-#endif
-
-	MonoDomain *domain = scripts_domain;
-	scripts_domain = nullptr;
-
-	print_verbose("Mono: Unloading scripts domain...");
-
-	MonoException *exc = nullptr;
-	mono_domain_try_unload(domain, (MonoObject **)&exc);
-
-	if (exc) {
-		ERR_PRINT("Exception thrown when unloading scripts domain.");
-		GDMonoUtils::debug_unhandled_exception(exc);
-		return FAILED;
-	}
-
-	return OK;
+	return success;
 }
+#endif
 
 #ifdef GD_MONO_HOT_RELOAD
-Error GDMono::reload_scripts_domain() {
+Error GDMono::reload_project_assemblies() {
 	ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG);
 
-	if (scripts_domain) {
-		Error domain_unload_err = _unload_scripts_domain();
-		ERR_FAIL_COND_V_MSG(domain_unload_err != OK, domain_unload_err, "Mono: Failed to unload scripts domain.");
-	}
-
-	Error domain_load_err = _load_scripts_domain();
-	ERR_FAIL_COND_V_MSG(domain_load_err != OK, domain_load_err, "Mono: Failed to load scripts domain.");
+	finalizing_scripts_domain = true;
 
-	// Load assemblies. The API and tools assemblies are required,
-	// the application is aborted if these assemblies cannot be loaded.
+	CSharpLanguage::get_singleton()->_on_scripts_domain_about_to_unload();
 
-	if (!_try_load_api_assemblies()) {
-		CRASH_NOW_MSG("Failed to load one of the API assemblies.");
+	if (!get_plugin_callbacks().UnloadProjectPluginCallback()) {
+		ERR_FAIL_V_MSG(Error::FAILED, ".NET: Failed to unload assemblies.");
 	}
 
-#if defined(TOOLS_ENABLED)
-	bool tools_assemblies_loaded = _load_tools_assemblies();
-	CRASH_COND_MSG(!tools_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies.");
-#endif
+	finalizing_scripts_domain = false;
 
 	// Load the project's main assembly. Here, during hot-reloading, we do
 	// consider failing to load the project's main assembly to be an error.
-	// However, unlike the API and tools assemblies, the application can continue working.
 	if (!_load_project_assembly()) {
-		print_error("Mono: Failed to load project assembly");
+		print_error(".NET: Failed to load project assembly.");
 		return ERR_CANT_OPEN;
 	}
 
 	return OK;
 }
 #endif
-#endif
 
 GDMono::GDMono() {
 	singleton = this;

+ 26 - 10
modules/mono/mono_gd/gd_mono.h

@@ -45,10 +45,12 @@ namespace gdmono {
 
 #ifdef TOOLS_ENABLED
 struct PluginCallbacks {
-	using FuncLoadProjectAssemblyCallback = bool(GD_CLR_STDCALL *)(const char16_t *);
+	using FuncLoadProjectAssemblyCallback = bool(GD_CLR_STDCALL *)(const char16_t *, String *);
 	using FuncLoadToolsAssemblyCallback = Object *(GD_CLR_STDCALL *)(const char16_t *);
+	using FuncUnloadProjectPluginCallback = bool(GD_CLR_STDCALL *)();
 	FuncLoadProjectAssemblyCallback LoadProjectAssemblyCallback = nullptr;
 	FuncLoadToolsAssemblyCallback LoadToolsAssemblyCallback = nullptr;
+	FuncUnloadProjectPluginCallback UnloadProjectPluginCallback = nullptr;
 };
 #endif
 
@@ -63,14 +65,13 @@ class GDMono {
 	void *hostfxr_dll_handle = nullptr;
 	bool is_native_aot = false;
 
+	String project_assembly_path;
+	uint64_t project_assembly_modified_time = 0;
+
 #ifdef TOOLS_ENABLED
 	bool _load_project_assembly();
 #endif
 
-	bool _try_load_api_assemblies();
-
-	Error _unload_scripts_domain();
-
 	uint64_t api_core_hash;
 #ifdef TOOLS_ENABLED
 	uint64_t api_editor_hash;
@@ -114,17 +115,32 @@ public:
 #endif
 	}
 
-	static GDMono *get_singleton() { return singleton; }
+	static GDMono *get_singleton() {
+		return singleton;
+	}
+
+	_FORCE_INLINE_ bool is_runtime_initialized() const {
+		return runtime_initialized;
+	}
+	_FORCE_INLINE_ bool is_finalizing_scripts_domain() {
+		return finalizing_scripts_domain;
+	}
 
-	_FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized; }
-	_FORCE_INLINE_ bool is_finalizing_scripts_domain() { return finalizing_scripts_domain; }
+	_FORCE_INLINE_ const String &get_project_assembly_path() const {
+		return project_assembly_path;
+	}
+	_FORCE_INLINE_ uint64_t get_project_assembly_modified_time() const {
+		return project_assembly_modified_time;
+	}
 
 #ifdef TOOLS_ENABLED
-	const gdmono::PluginCallbacks &get_plugin_callbacks() { return plugin_callbacks; }
+	const gdmono::PluginCallbacks &get_plugin_callbacks() {
+		return plugin_callbacks;
+	}
 #endif
 
 #ifdef GD_MONO_HOT_RELOAD
-	Error reload_scripts_domain();
+	Error reload_project_assemblies();
 #endif
 
 	void initialize();

+ 5 - 0
modules/mono/mono_gd/gd_mono_cache.cpp

@@ -52,6 +52,8 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
 	CHECK_CALLBACK_NOT_NULL(SignalAwaiter, SignalCallback);
 	CHECK_CALLBACK_NOT_NULL(DelegateUtils, InvokeWithVariantArgs);
 	CHECK_CALLBACK_NOT_NULL(DelegateUtils, DelegateEquals);
+	CHECK_CALLBACK_NOT_NULL(DelegateUtils, TrySerializeDelegateWithGCHandle);
+	CHECK_CALLBACK_NOT_NULL(DelegateUtils, TryDeserializeDelegateWithGCHandle);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, FrameCallback);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectBinding);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectScriptInstance);
@@ -64,6 +66,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, AddScriptBridge);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetOrCreateScriptBridgeForPath);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RemoveScriptBridge);
+	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, TryReloadRegisteredScriptWithClass);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, UpdateScriptClassInfo);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SwapGCHandleForType);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyInfoList);
@@ -74,6 +77,8 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) {
 	CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallDispose);
 	CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallToString);
 	CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, HasMethodUnknownParams);
+	CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, SerializeState);
+	CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, DeserializeState);
 	CHECK_CALLBACK_NOT_NULL(GCHandleBridge, FreeGCHandle);
 	CHECK_CALLBACK_NOT_NULL(DebuggingUtils, GetCurrentStackInfo);
 	CHECK_CALLBACK_NOT_NULL(DisposablesTracker, OnGodotShuttingDown);

+ 11 - 4
modules/mono/mono_gd/gd_mono_cache.h

@@ -74,6 +74,8 @@ struct ManagedCallbacks {
 	using FuncSignalAwaiter_SignalCallback = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, int32_t, bool *);
 	using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, uint32_t, const Variant *);
 	using FuncDelegateUtils_DelegateEquals = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr);
+	using FuncDelegateUtils_TrySerializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const Array *);
+	using FuncDelegateUtils_TryDeserializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(const Array *, GCHandleIntPtr *);
 	using FuncScriptManagerBridge_FrameCallback = void(GD_CLR_STDCALL *)();
 	using FuncScriptManagerBridge_CreateManagedForGodotObjectBinding = GCHandleIntPtr(GD_CLR_STDCALL *)(const StringName *, Object *);
 	using FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = bool(GD_CLR_STDCALL *)(const CSharpScript *, Object *, const Variant **, int32_t);
@@ -86,7 +88,8 @@ struct ManagedCallbacks {
 	using FuncScriptManagerBridge_AddScriptBridge = bool(GD_CLR_STDCALL *)(const CSharpScript *, const String *);
 	using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *);
 	using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *);
-	using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Dictionary *);
+	using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *);
+	using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Dictionary *, Ref<CSharpScript> *);
 	using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool);
 	using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add);
 	using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add);
@@ -96,6 +99,8 @@ struct ManagedCallbacks {
 	using FuncCSharpInstanceBridge_CallDispose = void(GD_CLR_STDCALL *)(GCHandleIntPtr, bool);
 	using FuncCSharpInstanceBridge_CallToString = void(GD_CLR_STDCALL *)(GCHandleIntPtr, String *, bool *);
 	using FuncCSharpInstanceBridge_HasMethodUnknownParams = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *);
+	using FuncCSharpInstanceBridge_SerializeState = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Dictionary *, const Dictionary *);
+	using FuncCSharpInstanceBridge_DeserializeState = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Dictionary *, const Dictionary *);
 	using FuncGCHandleBridge_FreeGCHandle = void(GD_CLR_STDCALL *)(GCHandleIntPtr);
 	using FuncDebuggingUtils_GetCurrentStackInfo = void(GD_CLR_STDCALL *)(Vector<ScriptLanguage::StackInfo> *);
 	using FuncDisposablesTracker_OnGodotShuttingDown = void(GD_CLR_STDCALL *)();
@@ -104,6 +109,8 @@ struct ManagedCallbacks {
 	FuncSignalAwaiter_SignalCallback SignalAwaiter_SignalCallback;
 	FuncDelegateUtils_InvokeWithVariantArgs DelegateUtils_InvokeWithVariantArgs;
 	FuncDelegateUtils_DelegateEquals DelegateUtils_DelegateEquals;
+	FuncDelegateUtils_TrySerializeDelegateWithGCHandle DelegateUtils_TrySerializeDelegateWithGCHandle;
+	FuncDelegateUtils_TryDeserializeDelegateWithGCHandle DelegateUtils_TryDeserializeDelegateWithGCHandle;
 	FuncScriptManagerBridge_FrameCallback ScriptManagerBridge_FrameCallback;
 	FuncScriptManagerBridge_CreateManagedForGodotObjectBinding ScriptManagerBridge_CreateManagedForGodotObjectBinding;
 	FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
@@ -116,6 +123,7 @@ struct ManagedCallbacks {
 	FuncScriptManagerBridge_AddScriptBridge ScriptManagerBridge_AddScriptBridge;
 	FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
 	FuncScriptManagerBridge_RemoveScriptBridge ScriptManagerBridge_RemoveScriptBridge;
+	FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
 	FuncScriptManagerBridge_UpdateScriptClassInfo ScriptManagerBridge_UpdateScriptClassInfo;
 	FuncScriptManagerBridge_SwapGCHandleForType ScriptManagerBridge_SwapGCHandleForType;
 	FuncScriptManagerBridge_GetPropertyInfoList ScriptManagerBridge_GetPropertyInfoList;
@@ -126,6 +134,8 @@ struct ManagedCallbacks {
 	FuncCSharpInstanceBridge_CallDispose CSharpInstanceBridge_CallDispose;
 	FuncCSharpInstanceBridge_CallToString CSharpInstanceBridge_CallToString;
 	FuncCSharpInstanceBridge_HasMethodUnknownParams CSharpInstanceBridge_HasMethodUnknownParams;
+	FuncCSharpInstanceBridge_SerializeState CSharpInstanceBridge_SerializeState;
+	FuncCSharpInstanceBridge_DeserializeState CSharpInstanceBridge_DeserializeState;
 	FuncGCHandleBridge_FreeGCHandle GCHandleBridge_FreeGCHandle;
 	FuncDebuggingUtils_GetCurrentStackInfo DebuggingUtils_GetCurrentStackInfo;
 	FuncDisposablesTracker_OnGodotShuttingDown DisposablesTracker_OnGodotShuttingDown;
@@ -137,9 +147,6 @@ extern bool godot_api_cache_updated;
 
 void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks);
 
-inline void clear_godot_api_cache() {
-	managed_callbacks = ManagedCallbacks();
-}
 } // namespace GDMonoCache
 
 #undef GD_CLR_STDCALL