Prechádzať zdrojové kódy

C#: Re-implement assembly reloading with ALCs

Ignacio Roldán Etcheverry 3 rokov pred
rodič
commit
e235cef09f
37 zmenil súbory, kde vykonal 1529 pridanie a 557 odobranie
  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, "*.cpp")
 env_mono.add_source_files(env.modules_sources, "glue/*.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, "mono_gd/*.cpp")
 env_mono.add_source_files(env.modules_sources, "utils/*.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;
 	return OK;
 }
 }
 
 
-extern void *godotsharp_pinvoke_funcs[185];
+extern void *godotsharp_pinvoke_funcs[186];
 [[maybe_unused]] volatile void **do_not_strip_godotsharp_pinvoke_funcs;
 [[maybe_unused]] volatile void **do_not_strip_godotsharp_pinvoke_funcs;
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 extern void *godotsharp_editor_pinvoke_funcs[30];
 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() {
 void CSharpLanguage::reload_all_scripts() {
 #ifdef GD_MONO_HOT_RELOAD
 #ifdef GD_MONO_HOT_RELOAD
 	if (is_assembly_reloading_needed()) {
 	if (is_assembly_reloading_needed()) {
@@ -676,38 +698,29 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
 		return false;
 		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
 			return false; // Already up to date
 		}
 		}
 	} else {
 	} 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 false; // No assembly to load
 		}
 		}
 	}
 	}
 
 
 	return true;
 	return true;
-#else
-	return false;
-#endif
 }
 }
 
 
 void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
@@ -715,27 +728,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 		return;
 		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.
 	// There is no soft reloading with Mono. It's always hard reloading.
 
 
 	List<Ref<CSharpScript>> scripts;
 	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()) {
 		for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) {
 			ManagedCallable *managed_callable = elem->self();
 			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) {
 			if (success) {
 				ManagedCallable::instances_pending_reload.insert(managed_callable, serialized_data);
 				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
 		// 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
 		// 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.
 		// 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;
 			continue;
 		}
 		}
 
 
 		to_reload.push_back(script);
 		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.
 		// 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.
 		// 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());
 			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;
 			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;
 			owners_map[obj->get_instance_id()] = state;
 		}
 		}
@@ -868,7 +858,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 	}
 	}
 
 
 	// Do domain reload
 	// Do domain reload
-	if (gdmono->reload_scripts_domain() != OK) {
+	if (gdmono->reload_project_assemblies() != OK) {
 		// Failed to reload the scripts domain
 		// Failed to reload the scripts domain
 		// Make sure to add the scripts back to their owners before returning
 		// Make sure to add the scripts back to their owners before returning
 		for (Ref<CSharpScript> &scr : to_reload) {
 		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_state.erase(obj_id);
 			}
 			}
+
+			scr->pending_reload_instances.clear();
+			scr->pending_reload_state.clear();
 		}
 		}
 
 
 		return;
 		return;
@@ -916,46 +909,21 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 
 
 			if (!script->valid) {
 			if (!script->valid) {
 				script->pending_reload_instances.clear();
 				script->pending_reload_instances.clear();
+				script->pending_reload_state.clear();
 				continue;
 				continue;
 			}
 			}
 		} else {
 		} 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_instances.clear();
+				script->pending_reload_state.clear();
 				continue;
 				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) {
 			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());
 			ERR_CONTINUE(!obj->get_script_instance());
 
 
-			// TODO: Restore serialized state
-
 			CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id];
 			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());
 			CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance());
 
 
 			if (csi) {
 			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_instances.clear();
+		script->pending_reload_state.clear();
 	}
 	}
 
 
 	// Deserialize managed callables
 	// Deserialize managed callables
@@ -1081,20 +1017,13 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
 			ManagedCallable *managed_callable = elem.key;
 			ManagedCallable *managed_callable = elem.key;
 			const Array &serialized_data = elem.value;
 			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) {
 			if (success) {
-				ERR_CONTINUE(delegate == nullptr);
+				ERR_CONTINUE(delegate.value == nullptr);
 				managed_callable->delegate_handle = delegate;
 				managed_callable->delegate_handle = delegate;
 			} else if (OS::get_singleton()->is_stdout_verbose()) {
 			} else if (OS::get_singleton()->is_stdout_verbose()) {
 				OS::get_singleton()->print("Failed to deserialize delegate\n");
 				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();
 		NodeDock::get_singleton()->update_lists();
 	}
 	}
 #endif
 #endif
-#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() {
 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
 #ifdef GD_MONO_HOT_RELOAD
 	{
 	{
 		MutexLock lock(ManagedCallable::instances_mutex);
 		MutexLock lock(ManagedCallable::instances_mutex);
@@ -1263,7 +1185,8 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b
 #endif
 #endif
 
 
 	GCHandleIntPtr strong_gchandle =
 	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);
 	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;
 	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 {
 void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
 	List<PropertyInfo> props;
 	List<PropertyInfo> props;
 	script->get_script_property_list(&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
 			// If the native instance is still alive and Dispose() was called
 			// (instead of the finalizer), then we remove the script instance.
 			// (instead of the finalizer), then we remove the script instance.
 			r_remove_script_instance = true;
 			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()) {
 		} else if (!GDMono::get_singleton()->is_finalizing_scripts_domain()) {
 			// If the native instance is still alive and this is called from the finalizer,
 			// 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
 			// 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);
 		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
 #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#.
 	// only for this, so need to call the destructor manually before passing this to C#.
 	rpc_functions_dict.~Dictionary();
 	rpc_functions_dict.~Dictionary();
 
 
+	Ref<CSharpScript> base_script;
 	GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo(
 	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->tool = tool;
 
 
 	p_script->rpc_config.clear();
 	p_script->rpc_config.clear();
 	p_script->rpc_config = rpc_functions_dict;
 	p_script->rpc_config = rpc_functions_dict;
+
+	p_script->base_script = base_script;
 }
 }
 
 
 bool CSharpScript::can_instantiate() const {
 bool CSharpScript::can_instantiate() const {
@@ -2586,8 +2444,8 @@ bool CSharpScript::get_property_default_value(const StringName &p_property, Vari
 		return true;
 		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
 #endif
@@ -2671,26 +2529,35 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const {
 }
 }
 
 
 Ref<Script> CSharpScript::get_base_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 {
 void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const {
-	List<PropertyInfo> props;
-
 #ifdef TOOLS_ENABLED
 #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
 #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 {
 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_can_revert = StaticCString::create("_property_can_revert");
 	_property_get_revert = StaticCString::create("_property_get_revert");
 	_property_get_revert = StaticCString::create("_property_get_revert");
 	_script_source = StaticCString::create("script/source");
 	_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 {
 class CSharpScript : public Script {
 	GDCLASS(CSharpScript, Script);
 	GDCLASS(CSharpScript, Script);
 
 
-public:
-	struct SignalParameter {
-		String name;
-		Variant::Type type;
-		bool nil_is_variant = false;
-	};
-
-private:
 	friend class CSharpInstance;
 	friend class CSharpInstance;
 	friend class CSharpLanguage;
 	friend class CSharpLanguage;
 
 
@@ -83,7 +75,7 @@ private:
 	bool valid = false;
 	bool valid = false;
 	bool reload_invalidated = false;
 	bool reload_invalidated = false;
 
 
-	Ref<CSharpScript> base_cache; // TODO what's this for?
+	Ref<CSharpScript> base_script;
 
 
 	HashSet<Object *> instances;
 	HashSet<Object *> instances;
 
 
@@ -93,13 +85,11 @@ private:
 		// Replace with buffer containing the serialized state of managed scripts.
 		// Replace with buffer containing the serialized state of managed scripts.
 		// Keep variant state backup to use only with script instance placeholders.
 		// Keep variant state backup to use only with script instance placeholders.
 		List<Pair<StringName, Variant>> properties;
 		List<Pair<StringName, Variant>> properties;
-		List<Pair<StringName, Array>> event_signals;
+		Dictionary event_signals;
 	};
 	};
 
 
 	HashSet<ObjectID> pending_reload_instances;
 	HashSet<ObjectID> pending_reload_instances;
 	RBMap<ObjectID, StateBackup> pending_reload_state;
 	RBMap<ObjectID, StateBackup> pending_reload_state;
-	StringName tied_class_name_for_reload;
-	StringName tied_class_namespace_for_reload;
 #endif
 #endif
 
 
 	String source;
 	String source;
@@ -174,8 +164,12 @@ public:
 
 
 	void get_members(HashSet<StringName> *p_members) override;
 	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;
 	bool inherits_script(const Ref<Script> &p_script) const override;
 
 
@@ -191,7 +185,9 @@ public:
 	const Variant get_rpc_config() const override;
 	const Variant get_rpc_config() const override;
 
 
 #ifdef TOOLS_ENABLED
 #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
 #endif
 
 
 	Error load_source_code(const String &p_path);
 	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
 	// 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);
 	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:
 public:
 	_FORCE_INLINE_ bool is_destructing_script_instance() { return destructing_script_instance; }
 	_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_can_revert;
 		StringName _property_get_revert;
 		StringName _property_get_revert;
 		StringName _script_source;
 		StringName _script_source;
-		StringName on_before_serialize; // OnBeforeSerialize
-		StringName on_after_deserialize; // OnAfterDeserialize
 
 
 		StringNameCache();
 		StringNameCache();
 	};
 	};
@@ -361,18 +352,30 @@ public:
 
 
 	StringNameCache string_names;
 	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);
 	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
 #ifdef TOOLS_ENABLED
-	_FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; }
+	_FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const {
+		return godotsharp_editor;
+	}
 #endif
 #endif
 
 
 	static void release_script_gchandle(MonoGCHandleData &p_gchandle);
 	static void release_script_gchandle(MonoGCHandleData &p_gchandle);
@@ -387,7 +390,9 @@ public:
 	void reload_assemblies(bool p_soft_reload);
 	void reload_assemblies(bool p_soft_reload);
 #endif
 #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;
 	String get_name() const override;
 
 
@@ -416,7 +421,9 @@ public:
 	Script *create_script() const override;
 	Script *create_script() const override;
 	bool has_named_classes() const override;
 	bool has_named_classes() const override;
 	bool supports_builtin_mode() 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;
 	String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const override;
 	virtual String _get_indentation() const;
 	virtual String _get_indentation() const;
 	/* TODO? */ void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override {}
 	/* 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_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_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 */ 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;
 	Vector<StackInfo> debug_get_current_stack_info() override;
 
 
 	/* PROFILING FUNCTIONS */
 	/* PROFILING FUNCTIONS */
 	/* TODO */ void profiling_start() override {}
 	/* TODO */ void profiling_start() override {}
 	/* TODO */ void profiling_stop() 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;
 	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)
             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;
                     continue;
 
 
                 var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(property.Type, typeCache);
                 var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(property.Type, typeCache);
@@ -244,6 +245,11 @@ namespace Godot.SourceGenerators
         {
         {
             foreach (var field in fields)
             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);
                 var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache);
 
 
                 if (marshalType == null)
                 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]
     [Serializable]
     public sealed partial class BuildInfo : RefCounted // TODO Remove RefCounted once we have proper serialization
     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
         // 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 =>
         public string LogsDirPath =>
             Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}");
             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)
         public BuildInfo(string solution, string configuration, bool restore, bool rebuild, bool onlyClean)
         {
         {
             Solution = solution;
             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.
         // 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 ItemList _issuesList;
         private PopupMenu _issuesListContextMenu;
         private PopupMenu _issuesListContextMenu;
         private TextEdit _buildLog;
         private TextEdit _buildLog;
@@ -133,7 +133,9 @@ namespace GodotTools.Build
             if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File))
             if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File))
                 return;
                 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());
             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.
             // In case it didn't update yet. We don't want to have to serialize any pending output.
             UpdateBuildLogText();
             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()
         public void OnAfterDeserialize()

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

@@ -9,6 +9,7 @@
     <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
     <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
     <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir>
     <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir>
     <GodotApiAssembliesDir>$(GodotOutputDataDir)/Api/$(GodotApiConfiguration)</GodotApiAssembliesDir>
     <GodotApiAssembliesDir>$(GodotOutputDataDir)/Api/$(GodotApiConfiguration)</GodotApiAssembliesDir>
+    <ProduceReferenceAssembly>false</ProduceReferenceAssembly>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
   </PropertyGroup>
   <!-- Needed for our source generators to work despite this not being a Godot game project -->
   <!-- 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.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Reflection;
 using System.Reflection;
+using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using System.Runtime.Loader;
 using System.Runtime.Loader;
 using Godot.Bridge;
 using Godot.Bridge;
@@ -11,10 +12,55 @@ namespace GodotPlugins
 {
 {
     public static class Main
     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 List<AssemblyName> SharedAssemblies = new();
         private static readonly Assembly CoreApiAssembly = typeof(Godot.Object).Assembly;
         private static readonly Assembly CoreApiAssembly = typeof(Godot.Object).Assembly;
         private static Assembly? _editorApiAssembly;
         private static Assembly? _editorApiAssembly;
-        private static Assembly? _projectAssembly;
+        private static PluginLoadContextWrapper? _projectLoadContext;
 
 
         private static readonly AssemblyLoadContext MainLoadContext =
         private static readonly AssemblyLoadContext MainLoadContext =
             AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ??
             AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ??
@@ -35,6 +81,8 @@ namespace GodotPlugins
                 SharedAssemblies.Add(CoreApiAssembly.GetName());
                 SharedAssemblies.Add(CoreApiAssembly.GetName());
                 NativeLibrary.SetDllImportResolver(CoreApiAssembly, _dllImportResolver);
                 NativeLibrary.SetDllImportResolver(CoreApiAssembly, _dllImportResolver);
 
 
+                AlcReloadCfg.Configure(alcReloadEnabled: editorHint.ToBool());
+
                 if (editorHint.ToBool())
                 if (editorHint.ToBool())
                 {
                 {
                     _editorApiAssembly = Assembly.Load("GodotSharpEditor");
                     _editorApiAssembly = Assembly.Load("GodotSharpEditor");
@@ -46,6 +94,7 @@ namespace GodotPlugins
                 {
                 {
                     LoadProjectAssemblyCallback = &LoadProjectAssembly,
                     LoadProjectAssemblyCallback = &LoadProjectAssembly,
                     LoadToolsAssemblyCallback = &LoadToolsAssembly,
                     LoadToolsAssemblyCallback = &LoadToolsAssembly,
+                    UnloadProjectPluginCallback = &UnloadProjectPlugin,
                 };
                 };
 
 
                 *managedCallbacks = ManagedCallbacks.Create();
                 *managedCallbacks = ManagedCallbacks.Create();
@@ -55,37 +104,41 @@ namespace GodotPlugins
             catch (Exception e)
             catch (Exception e)
             {
             {
                 Console.Error.WriteLine(e);
                 Console.Error.WriteLine(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
             }
         }
         }
 
 
         [StructLayout(LayoutKind.Sequential)]
         [StructLayout(LayoutKind.Sequential)]
         private struct PluginsCallbacks
         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<char*, IntPtr> LoadToolsAssemblyCallback;
+            public unsafe delegate* unmanaged<godot_bool> UnloadProjectPluginCallback;
         }
         }
 
 
         [UnmanagedCallersOnly]
         [UnmanagedCallersOnly]
-        private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath)
+        private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath, godot_string* outLoadedAssemblyPath)
         {
         {
             try
             try
             {
             {
-                if (_projectAssembly != null)
+                if (_projectLoadContext != null)
                     return godot_bool.True; // Already loaded
                     return godot_bool.True; // Already loaded
 
 
                 string assemblyPath = new(nAssemblyPath);
                 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;
                 return godot_bool.True;
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
                 Console.Error.WriteLine(e);
                 Console.Error.WriteLine(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
             }
         }
         }
 
 
@@ -99,7 +152,7 @@ namespace GodotPlugins
                 if (_editorApiAssembly == null)
                 if (_editorApiAssembly == null)
                     throw new InvalidOperationException("The Godot editor API assembly is not loaded");
                     throw new InvalidOperationException("The Godot editor API assembly is not loaded");
 
 
-                var assembly = LoadPlugin(assemblyPath);
+                var (assembly, _) = LoadPlugin(assemblyPath);
 
 
                 NativeLibrary.SetDllImportResolver(assembly, _dllImportResolver!);
                 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);
             string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
 
 
@@ -135,8 +188,78 @@ namespace GodotPlugins
                     sharedAssemblies.Add(sharedAssemblyName);
                     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 ICollection<string> _sharedAssemblies;
         private readonly AssemblyLoadContext _mainLoadContext;
         private readonly AssemblyLoadContext _mainLoadContext;
 
 
+        public string? AssemblyLoadedPath { get; private set; }
+
         public PluginLoadContext(string pluginPath, ICollection<string> sharedAssemblies,
         public PluginLoadContext(string pluginPath, ICollection<string> sharedAssemblies,
             AssemblyLoadContext mainLoadContext)
             AssemblyLoadContext mainLoadContext)
+            : base(isCollectible: true)
         {
         {
             _resolver = new AssemblyDependencyResolver(pluginPath);
             _resolver = new AssemblyDependencyResolver(pluginPath);
             _sharedAssemblies = sharedAssemblies;
             _sharedAssemblies = sharedAssemblies;
@@ -31,6 +34,8 @@ namespace GodotPlugins
             string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
             string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
             if (assemblyPath != null)
             if (assemblyPath != null)
             {
             {
+                AssemblyLoadedPath = assemblyPath;
+
                 // Load in memory to prevent locking the file
                 // Load in memory to prevent locking the file
                 using var assemblyFile = File.Open(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read);
                 using var assemblyFile = File.Open(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read);
                 string pdbPath = Path.ChangeExtension(assemblyPath, ".pdb");
                 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">
 <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: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/=gdnative/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=godotsharp/@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>
 	<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;
                     *ret = default;
                     (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL;
                     (*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),
                 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.
                     // This is important, as it tells Object::call that no method was called.
                     // Otherwise, it would prevent Object::call from calling native methods.
                     // Otherwise, it would prevent Object::call from calling native methods.
                     (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD;
                     (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD;
-                    return false.ToGodotBool();
+                    return godot_bool.False;
                 }
                 }
 
 
                 *ret = retValue;
                 *ret = retValue;
-                return true.ToGodotBool();
+                return godot_bool.True;
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(e);
                 ExceptionUtils.LogException(e);
                 *ret = default;
                 *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)))
                 if (godotObject.SetGodotClassPropertyValue(CustomUnsafe.AsRef(name), CustomUnsafe.AsRef(value)))
                 {
                 {
-                    return true.ToGodotBool();
+                    return godot_bool.True;
                 }
                 }
 
 
                 var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
                 var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
@@ -70,7 +70,7 @@ namespace Godot.Bridge
             catch (Exception e)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(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))
                 if (godotObject.GetGodotClassPropertyValue(CustomUnsafe.AsRef(name), out godot_variant outRetValue))
                 {
                 {
                     *outRet = outRetValue;
                     *outRet = outRetValue;
-                    return true.ToGodotBool();
+                    return godot_bool.True;
                 }
                 }
 
 
                 var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
                 var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue(
@@ -99,17 +99,17 @@ namespace Godot.Bridge
                 if (ret == null)
                 if (ret == null)
                 {
                 {
                     *outRet = default;
                     *outRet = default;
-                    return false.ToGodotBool();
+                    return godot_bool.False;
                 }
                 }
 
 
                 *outRet = Marshaling.ConvertManagedObjectToVariant(ret);
                 *outRet = Marshaling.ConvertManagedObjectToVariant(ret);
-                return true.ToGodotBool();
+                return godot_bool.True;
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(e);
                 ExceptionUtils.LogException(e);
                 *outRet = default;
                 *outRet = default;
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
             }
         }
         }
 
 
@@ -141,7 +141,7 @@ namespace Godot.Bridge
                 if (self == null)
                 if (self == null)
                 {
                 {
                     *outRes = default;
                     *outRes = default;
-                    *outValid = false.ToGodotBool();
+                    *outValid = godot_bool.False;
                     return;
                     return;
                 }
                 }
 
 
@@ -150,18 +150,18 @@ namespace Godot.Bridge
                 if (resultStr == null)
                 if (resultStr == null)
                 {
                 {
                     *outRes = default;
                     *outRes = default;
-                    *outValid = false.ToGodotBool();
+                    *outValid = godot_bool.False;
                     return;
                     return;
                 }
                 }
 
 
                 *outRes = Marshaling.ConvertStringToNative(resultStr);
                 *outRes = Marshaling.ConvertStringToNative(resultStr);
-                *outValid = true.ToGodotBool();
+                *outValid = godot_bool.True;
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(e);
                 ExceptionUtils.LogException(e);
                 *outRes = default;
                 *outRes = default;
-                *outValid = false.ToGodotBool();
+                *outValid = godot_bool.False;
             }
             }
         }
         }
 
 
@@ -173,14 +173,86 @@ namespace Godot.Bridge
                 var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
                 var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target;
 
 
                 if (godotObject == null)
                 if (godotObject == null)
-                    return false.ToGodotBool();
+                    return godot_bool.False;
 
 
                 return godotObject.HasGodotClassMethod(CustomUnsafe.AsRef(method)).ToGodotBool();
                 return godotObject.HasGodotClassMethod(CustomUnsafe.AsRef(method)).ToGodotBool();
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(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
             try
             {
             {
-                GCHandle.FromIntPtr(gcHandlePtr).Free();
+                CustomGCHandle.Free(GCHandle.FromIntPtr(gcHandlePtr));
             }
             }
             catch (Exception e)
             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**, int, godot_bool*, void> SignalAwaiter_SignalCallback;
         public delegate* unmanaged<IntPtr, godot_variant**, uint, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs;
         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, 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<void> ScriptManagerBridge_FrameCallback;
         public delegate* unmanaged<godot_string_name*, IntPtr, IntPtr> ScriptManagerBridge_CreateManagedForGodotObjectBinding;
         public delegate* unmanaged<godot_string_name*, IntPtr, IntPtr> ScriptManagerBridge_CreateManagedForGodotObjectBinding;
         public delegate* unmanaged<IntPtr, IntPtr, godot_variant**, int, godot_bool> ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
         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<IntPtr, godot_string*, godot_bool> ScriptManagerBridge_AddScriptBridge;
         public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
         public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
         public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
         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, 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, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList;
         public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues;
         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_bool, void> CSharpInstanceBridge_CallDispose;
         public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, void> CSharpInstanceBridge_CallToString;
         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_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<IntPtr, void> GCHandleBridge_FreeGCHandle;
         public delegate* unmanaged<void*, void> DebuggingUtils_GetCurrentStackInfo;
         public delegate* unmanaged<void*, void> DebuggingUtils_GetCurrentStackInfo;
         public delegate* unmanaged<void> DisposablesTracker_OnGodotShuttingDown;
         public delegate* unmanaged<void> DisposablesTracker_OnGodotShuttingDown;
@@ -47,6 +52,8 @@ namespace Godot.Bridge
                 SignalAwaiter_SignalCallback = &SignalAwaiter.SignalCallback,
                 SignalAwaiter_SignalCallback = &SignalAwaiter.SignalCallback,
                 DelegateUtils_InvokeWithVariantArgs = &DelegateUtils.InvokeWithVariantArgs,
                 DelegateUtils_InvokeWithVariantArgs = &DelegateUtils.InvokeWithVariantArgs,
                 DelegateUtils_DelegateEquals = &DelegateUtils.DelegateEquals,
                 DelegateUtils_DelegateEquals = &DelegateUtils.DelegateEquals,
+                DelegateUtils_TrySerializeDelegateWithGCHandle = &DelegateUtils.TrySerializeDelegateWithGCHandle,
+                DelegateUtils_TryDeserializeDelegateWithGCHandle = &DelegateUtils.TryDeserializeDelegateWithGCHandle,
                 ScriptManagerBridge_FrameCallback = &ScriptManagerBridge.FrameCallback,
                 ScriptManagerBridge_FrameCallback = &ScriptManagerBridge.FrameCallback,
                 ScriptManagerBridge_CreateManagedForGodotObjectBinding = &ScriptManagerBridge.CreateManagedForGodotObjectBinding,
                 ScriptManagerBridge_CreateManagedForGodotObjectBinding = &ScriptManagerBridge.CreateManagedForGodotObjectBinding,
                 ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = &ScriptManagerBridge.CreateManagedForGodotObjectScriptInstance,
                 ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = &ScriptManagerBridge.CreateManagedForGodotObjectScriptInstance,
@@ -59,6 +66,7 @@ namespace Godot.Bridge
                 ScriptManagerBridge_AddScriptBridge = &ScriptManagerBridge.AddScriptBridge,
                 ScriptManagerBridge_AddScriptBridge = &ScriptManagerBridge.AddScriptBridge,
                 ScriptManagerBridge_GetOrCreateScriptBridgeForPath = &ScriptManagerBridge.GetOrCreateScriptBridgeForPath,
                 ScriptManagerBridge_GetOrCreateScriptBridgeForPath = &ScriptManagerBridge.GetOrCreateScriptBridgeForPath,
                 ScriptManagerBridge_RemoveScriptBridge = &ScriptManagerBridge.RemoveScriptBridge,
                 ScriptManagerBridge_RemoveScriptBridge = &ScriptManagerBridge.RemoveScriptBridge,
+                ScriptManagerBridge_TryReloadRegisteredScriptWithClass = &ScriptManagerBridge.TryReloadRegisteredScriptWithClass,
                 ScriptManagerBridge_UpdateScriptClassInfo = &ScriptManagerBridge.UpdateScriptClassInfo,
                 ScriptManagerBridge_UpdateScriptClassInfo = &ScriptManagerBridge.UpdateScriptClassInfo,
                 ScriptManagerBridge_SwapGCHandleForType = &ScriptManagerBridge.SwapGCHandleForType,
                 ScriptManagerBridge_SwapGCHandleForType = &ScriptManagerBridge.SwapGCHandleForType,
                 ScriptManagerBridge_GetPropertyInfoList = &ScriptManagerBridge.GetPropertyInfoList,
                 ScriptManagerBridge_GetPropertyInfoList = &ScriptManagerBridge.GetPropertyInfoList,
@@ -69,6 +77,8 @@ namespace Godot.Bridge
                 CSharpInstanceBridge_CallDispose = &CSharpInstanceBridge.CallDispose,
                 CSharpInstanceBridge_CallDispose = &CSharpInstanceBridge.CallDispose,
                 CSharpInstanceBridge_CallToString = &CSharpInstanceBridge.CallToString,
                 CSharpInstanceBridge_CallToString = &CSharpInstanceBridge.CallToString,
                 CSharpInstanceBridge_HasMethodUnknownParams = &CSharpInstanceBridge.HasMethodUnknownParams,
                 CSharpInstanceBridge_HasMethodUnknownParams = &CSharpInstanceBridge.HasMethodUnknownParams,
+                CSharpInstanceBridge_SerializeState = &CSharpInstanceBridge.SerializeState,
+                CSharpInstanceBridge_DeserializeState = &CSharpInstanceBridge.DeserializeState,
                 GCHandleBridge_FreeGCHandle = &GCHandleBridge.FreeGCHandle,
                 GCHandleBridge_FreeGCHandle = &GCHandleBridge.FreeGCHandle,
                 DebuggingUtils_GetCurrentStackInfo = &DebuggingUtils.GetCurrentStackInfo,
                 DebuggingUtils_GetCurrentStackInfo = &DebuggingUtils.GetCurrentStackInfo,
                 DisposablesTracker_OnGodotShuttingDown = &DisposablesTracker.OnGodotShuttingDown,
                 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;
+using System.Collections.Concurrent;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
+using System.Runtime.Loader;
 using System.Runtime.Serialization;
 using System.Runtime.Serialization;
 using Godot.Collections;
 using Godot.Collections;
 using Godot.NativeInterop;
 using Godot.NativeInterop;
 
 
 namespace Godot.Bridge
 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]
         [UnmanagedCallersOnly]
         internal static void FrameCallback()
         internal static void FrameCallback()
@@ -55,7 +110,7 @@ namespace Godot.Bridge
 
 
                 _ = ctor!.Invoke(obj, null);
                 _ = ctor!.Invoke(obj, null);
 
 
-                return GCHandle.ToIntPtr(GCHandle.Alloc(obj));
+                return GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(obj));
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
@@ -74,7 +129,7 @@ namespace Godot.Bridge
             try
             try
             {
             {
                 // Performance is not critical here as this will be replaced with source generators.
                 // 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 obj = (Object)FormatterServices.GetUninitializedObject(scriptType);
 
 
                 var ctor = scriptType
                 var ctor = scriptType
@@ -99,7 +154,7 @@ namespace Godot.Bridge
                 var parameters = ctor.GetParameters();
                 var parameters = ctor.GetParameters();
                 int paramCount = parameters.Length;
                 int paramCount = parameters.Length;
 
 
-                object[] invokeParams = new object[paramCount];
+                var invokeParams = new object?[paramCount];
 
 
                 for (int i = 0; i < paramCount; i++)
                 for (int i = 0; i < paramCount; i++)
                 {
                 {
@@ -112,12 +167,12 @@ namespace Godot.Bridge
                 _ = ctor.Invoke(obj, invokeParams);
                 _ = ctor.Invoke(obj, invokeParams);
 
 
 
 
-                return true.ToGodotBool();
+                return godot_bool.True;
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(e);
                 ExceptionUtils.LogException(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
             }
         }
         }
 
 
@@ -127,7 +182,7 @@ namespace Godot.Bridge
             try
             try
             {
             {
                 // Performance is not critical here as this will be replaced with source generators.
                 // 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;
                     *outRes = default;
                     return;
                     return;
@@ -144,7 +199,7 @@ namespace Godot.Bridge
                     return;
                     return;
                 }
                 }
 
 
-                var nativeName = (StringName)field.GetValue(null);
+                var nativeName = (StringName?)field.GetValue(null);
 
 
                 if (nativeName == null)
                 if (nativeName == null)
                 {
                 {
@@ -166,7 +221,7 @@ namespace Godot.Bridge
         {
         {
             try
             try
             {
             {
-                var target = (Object)GCHandle.FromIntPtr(gcHandlePtr).Target;
+                var target = (Object?)GCHandle.FromIntPtr(gcHandlePtr).Target;
                 if (target != null)
                 if (target != null)
                     target.NativePtr = newPtr;
                     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.
             // Performance is not critical here as this will be replaced with a generated dictionary.
 
 
             if (nativeTypeNameStr[0] == '_')
             if (nativeTypeNameStr[0] == '_')
                 nativeTypeNameStr = nativeTypeNameStr.Substring(1);
                 nativeTypeNameStr = nativeTypeNameStr.Substring(1);
 
 
-            Type wrapperType = typeof(Object).Assembly.GetType("Godot." + nativeTypeNameStr);
+            Type? wrapperType = typeof(Object).Assembly.GetType("Godot." + nativeTypeNameStr);
 
 
             if (wrapperType == null)
             if (wrapperType == null)
             {
             {
@@ -216,7 +271,12 @@ namespace Godot.Bridge
                 if (scriptPathAttr == null)
                 if (scriptPathAttr == null)
                     return;
                     return;
 
 
-                _pathScriptMap[scriptPathAttr.Path] = type;
+                _pathTypeBiMap.Add(scriptPathAttr.Path, type);
+
+                if (AlcReloadCfg.IsAlcReloadingEnabled)
+                {
+                    AddTypeForAlcReloading(type);
+                }
             }
             }
 
 
             var assemblyHasScriptsAttr = assembly.GetCustomAttributes(inherit: false)
             var assemblyHasScriptsAttr = assembly.GetCustomAttributes(inherit: false)
@@ -267,15 +327,15 @@ namespace Godot.Bridge
         {
         {
             try
             try
             {
             {
-                var owner = (Object)GCHandle.FromIntPtr(ownerGCHandlePtr).Target;
+                var owner = (Object?)GCHandle.FromIntPtr(ownerGCHandlePtr).Target;
 
 
                 if (owner == null)
                 if (owner == null)
                 {
                 {
-                    *outOwnerIsNull = true.ToGodotBool();
+                    *outOwnerIsNull = godot_bool.True;
                     return;
                     return;
                 }
                 }
 
 
-                *outOwnerIsNull = false.ToGodotBool();
+                *outOwnerIsNull = godot_bool.False;
 
 
                 owner.InternalRaiseEventSignal(CustomUnsafe.AsRef(eventSignalName),
                 owner.InternalRaiseEventSignal(CustomUnsafe.AsRef(eventSignalName),
                     new NativeVariantPtrArgs(args), argCount);
                     new NativeVariantPtrArgs(args), argCount);
@@ -283,7 +343,7 @@ namespace Godot.Bridge
             catch (Exception e)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(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.
                 // Performance is not critical here as this will be replaced with source generators.
                 using var signals = new Dictionary();
                 using var signals = new Dictionary();
 
 
-                Type top = _scriptTypeMap[scriptPtr];
+                Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr);
                 Type native = Object.InternalGetClassNativeBase(top);
                 Type native = Object.InternalGetClassNativeBase(top);
 
 
                 while (top != null && top != native)
                 while (top != null && top != native)
@@ -391,7 +451,7 @@ namespace Godot.Bridge
 
 
                 string signalNameStr = Marshaling.ConvertStringToManaged(*signalName);
                 string signalNameStr = Marshaling.ConvertStringToManaged(*signalName);
 
 
-                Type top = _scriptTypeMap[scriptPtr];
+                Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr);
                 Type native = Object.InternalGetClassNativeBase(top);
                 Type native = Object.InternalGetClassNativeBase(top);
 
 
                 while (top != null && top != native)
                 while (top != null && top != native)
@@ -405,7 +465,7 @@ namespace Godot.Bridge
                         .Any(signalDelegate => signalDelegate.Name == signalNameStr)
                         .Any(signalDelegate => signalDelegate.Name == signalNameStr)
                        )
                        )
                     {
                     {
-                        return true.ToGodotBool();
+                        return godot_bool.True;
                     }
                     }
 
 
                     // Event signals
                     // Event signals
@@ -417,18 +477,18 @@ namespace Godot.Bridge
                         .Any(eventSignal => eventSignal.Name == signalNameStr)
                         .Any(eventSignal => eventSignal.Name == signalNameStr)
                        )
                        )
                     {
                     {
-                        return true.ToGodotBool();
+                        return godot_bool.True;
                     }
                     }
 
 
                     top = top.BaseType;
                     top = top.BaseType;
                 }
                 }
 
 
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(e);
                 ExceptionUtils.LogException(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
             }
         }
         }
 
 
@@ -437,18 +497,18 @@ namespace Godot.Bridge
         {
         {
             try
             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();
                 return (scriptType == maybeBaseType || maybeBaseType.IsAssignableFrom(scriptType)).ToGodotBool();
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(e);
                 ExceptionUtils.LogException(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
             }
         }
         }
 
 
@@ -457,26 +517,25 @@ namespace Godot.Bridge
         {
         {
             try
             try
             {
             {
-                lock (ScriptBridgeLock)
+                lock (_scriptTypeBiMap.ReadWriteLock)
                 {
                 {
-                    if (!_scriptTypeMap.ContainsKey(scriptPtr))
+                    if (!_scriptTypeBiMap.IsScriptRegistered(scriptPtr))
                     {
                     {
                         string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
                         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)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(e);
                 ExceptionUtils.LogException(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
             }
         }
         }
 
 
@@ -485,7 +544,7 @@ namespace Godot.Bridge
         {
         {
             string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath);
             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);
                 NativeFuncs.godotsharp_internal_new_csharp_script(outScript);
                 return;
                 return;
@@ -494,34 +553,84 @@ namespace Godot.Bridge
             GetOrCreateScriptBridgeForType(scriptType, outScript);
             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);
                     NativeFuncs.godotsharp_ref_new_from_ref_counted_ptr(out *outScript, scriptPtr);
                     return;
                     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]
         [UnmanagedCallersOnly]
         internal static void RemoveScriptBridge(IntPtr scriptPtr)
         internal static void RemoveScriptBridge(IntPtr scriptPtr)
         {
         {
             try
             try
             {
             {
-                lock (ScriptBridgeLock)
+                lock (_scriptTypeBiMap.ReadWriteLock)
                 {
                 {
-                    _ = _scriptTypeMap.Remove(scriptPtr);
+                    _scriptTypeBiMap.Remove(scriptPtr);
                 }
                 }
             }
             }
             catch (Exception e)
             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]
         [UnmanagedCallersOnly]
         internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool* outTool,
         internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool* outTool,
-            godot_dictionary* outRpcFunctionsDest)
+            godot_dictionary* outRpcFunctionsDest, godot_ref* outBaseScript)
         {
         {
             try
             try
             {
             {
                 // Performance is not critical here as this will be replaced with source generators.
                 // 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)
                 *outTool = scriptType.GetCustomAttributes(inherit: false)
                     .OfType<ToolAttribute>()
                     .OfType<ToolAttribute>()
@@ -551,13 +721,13 @@ namespace Godot.Bridge
                 }
                 }
 
 
                 if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools")
                 if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools")
-                    *outTool = true.ToGodotBool();
+                    *outTool = godot_bool.True;
 
 
                 // RPC functions
                 // RPC functions
 
 
                 Dictionary<string, Dictionary> rpcFunctions = new();
                 Dictionary<string, Dictionary> rpcFunctions = new();
 
 
-                Type top = scriptType;
+                Type? top = scriptType;
                 Type native = Object.InternalGetClassNativeBase(top);
                 Type native = Object.InternalGetClassNativeBase(top);
 
 
                 while (top != null && top != native)
                 while (top != null && top != native)
@@ -595,11 +765,21 @@ namespace Godot.Bridge
                 *outRpcFunctionsDest =
                 *outRpcFunctionsDest =
                     NativeFuncs.godotsharp_dictionary_new_copy(
                     NativeFuncs.godotsharp_dictionary_new_copy(
                         (godot_dictionary)((Dictionary)rpcFunctions).NativeValue);
                         (godot_dictionary)((Dictionary)rpcFunctions).NativeValue);
+
+                var baseType = scriptType.BaseType;
+                if (baseType != null && baseType != native)
+                {
+                    GetOrLoadOrCreateScriptForType(baseType, outBaseScript);
+                }
+                else
+                {
+                    *outBaseScript = default;
+                }
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(e);
                 ExceptionUtils.LogException(e);
-                *outTool = false.ToGodotBool();
+                *outTool = godot_bool.False;
                 *outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new();
                 *outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new();
             }
             }
         }
         }
@@ -612,28 +792,29 @@ namespace Godot.Bridge
             {
             {
                 var oldGCHandle = GCHandle.FromIntPtr(oldGCHandlePtr);
                 var oldGCHandle = GCHandle.FromIntPtr(oldGCHandlePtr);
 
 
-                object target = oldGCHandle.Target;
+                object? target = oldGCHandle.Target;
 
 
                 if (target == null)
                 if (target == null)
                 {
                 {
-                    oldGCHandle.Free();
+                    CustomGCHandle.Free(oldGCHandle);
                     *outNewGCHandlePtr = IntPtr.Zero;
                     *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.
                 // 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);
                 *outNewGCHandlePtr = GCHandle.ToIntPtr(newGCHandle);
-                return true.ToGodotBool();
+                return godot_bool.True;
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(e);
                 ExceptionUtils.LogException(e);
                 *outNewGCHandlePtr = IntPtr.Zero;
                 *outNewGCHandlePtr = IntPtr.Zero;
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
             }
         }
         }
 
 
@@ -662,7 +843,7 @@ namespace Godot.Bridge
         {
         {
             try
             try
             {
             {
-                Type scriptType = _scriptTypeMap[scriptPtr];
+                Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr);
                 GetPropertyInfoListForType(scriptType, scriptPtr, addPropInfoFunc);
                 GetPropertyInfoListForType(scriptType, scriptPtr, addPropInfoFunc);
             }
             }
             catch (Exception e)
             catch (Exception e)
@@ -684,7 +865,7 @@ namespace Godot.Bridge
                 if (getGodotPropertiesMetadataMethod == null)
                 if (getGodotPropertiesMetadataMethod == null)
                     return;
                     return;
 
 
-                var properties = (System.Collections.Generic.List<PropertyInfo>)
+                var properties = (System.Collections.Generic.List<PropertyInfo>?)
                     getGodotPropertiesMetadataMethod.Invoke(null, null);
                     getGodotPropertiesMetadataMethod.Invoke(null, null);
 
 
                 if (properties == null || properties.Count <= 0)
                 if (properties == null || properties.Count <= 0)
@@ -774,7 +955,7 @@ namespace Godot.Bridge
         {
         {
             try
             try
             {
             {
-                Type top = _scriptTypeMap[scriptPtr];
+                Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr);
                 Type native = Object.InternalGetClassNativeBase(top);
                 Type native = Object.InternalGetClassNativeBase(top);
 
 
                 while (top != null && top != native)
                 while (top != null && top != native)
@@ -804,7 +985,7 @@ namespace Godot.Bridge
                 if (getGodotPropertyDefaultValuesMethod == null)
                 if (getGodotPropertyDefaultValuesMethod == null)
                     return;
                     return;
 
 
-                var defaultValues = (System.Collections.Generic.Dictionary<StringName, object>)
+                var defaultValues = (System.Collections.Generic.Dictionary<StringName, object>?)
                     getGodotPropertyDefaultValuesMethod.Invoke(null, null);
                     getGodotPropertyDefaultValuesMethod.Invoke(null, null);
 
 
                 if (defaultValues == null || defaultValues.Count <= 0)
                 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;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.IO;
 using System.Reflection;
 using System.Reflection;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
@@ -16,14 +18,14 @@ namespace Godot
         {
         {
             try
             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)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(e);
                 ExceptionUtils.LogException(e);
-                return false.ToGodotBool();
+                return godot_bool.False;
             }
             }
         }
         }
 
 
@@ -34,10 +36,10 @@ namespace Godot
             try
             try
             {
             {
                 // TODO: Optimize
                 // 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;
                 var paramsLength = parameterInfos.Length;
 
 
                 if (argc != paramsLength)
                 if (argc != paramsLength)
@@ -52,7 +54,7 @@ namespace Godot
                         *args[i], parameterInfos[i].ParameterType);
                         *args[i], parameterInfos[i].ParameterType);
                 }
                 }
 
 
-                object invokeRet = @delegate.DynamicInvoke(managedArgs);
+                object? invokeRet = @delegate.DynamicInvoke(managedArgs);
 
 
                 *outRet = Marshaling.ConvertManagedObjectToVariant(invokeRet);
                 *outRet = Marshaling.ConvertManagedObjectToVariant(invokeRet);
             }
             }
@@ -72,10 +74,7 @@ namespace Godot
             CompilerGenerated
             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)
             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);
                 serializedData.Add(buffer);
                 return true;
                 return true;
@@ -107,11 +106,11 @@ namespace Godot
             return false;
             return false;
         }
         }
 
 
-        private static bool TrySerializeSingleDelegate(Delegate @delegate, out byte[] buffer)
+        private static bool TrySerializeSingleDelegate(Delegate @delegate, [MaybeNullWhen(false)] out byte[] buffer)
         {
         {
             buffer = null;
             buffer = null;
 
 
-            object target = @delegate.Target;
+            object? target = @delegate.Target;
 
 
             switch (target)
             switch (target)
             {
             {
@@ -200,9 +199,6 @@ namespace Godot
 
 
         private static bool TrySerializeMethodInfo(BinaryWriter writer, MethodInfo methodInfo)
         private static bool TrySerializeMethodInfo(BinaryWriter writer, MethodInfo methodInfo)
         {
         {
-            if (methodInfo == null)
-                return false;
-
             SerializeType(writer, methodInfo.DeclaringType);
             SerializeType(writer, methodInfo.DeclaringType);
 
 
             writer.Write(methodInfo.Name);
             writer.Write(methodInfo.Name);
@@ -241,7 +237,7 @@ namespace Godot
             return true;
             return true;
         }
         }
 
 
-        private static void SerializeType(BinaryWriter writer, Type type)
+        private static void SerializeType(BinaryWriter writer, Type? type)
         {
         {
             if (type == null)
             if (type == null)
             {
             {
@@ -256,9 +252,8 @@ namespace Godot
                 int genericArgumentsCount = genericArgs.Length;
                 int genericArgumentsCount = genericArgs.Length;
                 writer.Write(genericArgumentsCount);
                 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++)
                 for (int i = 0; i < genericArgs.Length; i++)
                     SerializeType(writer, genericArgs[i]);
                     SerializeType(writer, genericArgs[i]);
@@ -268,21 +263,62 @@ namespace Godot
                 int genericArgumentsCount = 0;
                 int genericArgumentsCount = 0;
                 writer.Write(genericArgumentsCount);
                 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)
             if (serializedData.Count == 1)
             {
             {
@@ -302,12 +338,12 @@ namespace Godot
             {
             {
                 if (elem is Collections.Array multiCastData)
                 if (elem is Collections.Array multiCastData)
                 {
                 {
-                    if (TryDeserializeDelegate(multiCastData, out Delegate oneDelegate))
+                    if (TryDeserializeDelegate(multiCastData, out Delegate? oneDelegate))
                         delegates.Add(oneDelegate);
                         delegates.Add(oneDelegate);
                 }
                 }
                 else
                 else
                 {
                 {
-                    if (TryDeserializeSingleDelegate((byte[])elem, out Delegate oneDelegate))
+                    if (TryDeserializeSingleDelegate((byte[])elem, out Delegate? oneDelegate))
                         delegates.Add(oneDelegate);
                         delegates.Add(oneDelegate);
                 }
                 }
             }
             }
@@ -315,11 +351,11 @@ namespace Godot
             if (delegates.Count <= 0)
             if (delegates.Count <= 0)
                 return false;
                 return false;
 
 
-            @delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray());
+            @delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray())!;
             return true;
             return true;
         }
         }
 
 
-        private static bool TryDeserializeSingleDelegate(byte[] buffer, out Delegate @delegate)
+        private static bool TryDeserializeSingleDelegate(byte[] buffer, [MaybeNullWhen(false)] out Delegate @delegate)
         {
         {
             @delegate = null;
             @delegate = null;
 
 
@@ -332,14 +368,18 @@ namespace Godot
                 {
                 {
                     case TargetKind.Static:
                     case TargetKind.Static:
                     {
                     {
-                        Type delegateType = DeserializeType(reader);
+                        Type? delegateType = DeserializeType(reader);
                         if (delegateType == null)
                         if (delegateType == null)
                             return false;
                             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;
                             return false;
 
 
-                        @delegate = Delegate.CreateDelegate(delegateType, null, methodInfo);
                         return true;
                         return true;
                     }
                     }
                     case TargetKind.GodotObject:
                     case TargetKind.GodotObject:
@@ -350,32 +390,37 @@ namespace Godot
                         if (godotObject == null)
                         if (godotObject == null)
                             return false;
                             return false;
 
 
-                        Type delegateType = DeserializeType(reader);
+                        Type? delegateType = DeserializeType(reader);
                         if (delegateType == null)
                         if (delegateType == null)
                             return false;
                             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;
                             return false;
 
 
-                        @delegate = Delegate.CreateDelegate(delegateType, godotObject, methodInfo);
                         return true;
                         return true;
                     }
                     }
                     case TargetKind.CompilerGenerated:
                     case TargetKind.CompilerGenerated:
                     {
                     {
-                        Type targetType = DeserializeType(reader);
+                        Type? targetType = DeserializeType(reader);
                         if (targetType == null)
                         if (targetType == null)
                             return false;
                             return false;
 
 
-                        Type delegateType = DeserializeType(reader);
+                        Type? delegateType = DeserializeType(reader);
                         if (delegateType == null)
                         if (delegateType == null)
                             return false;
                             return false;
 
 
-                        if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo))
+                        if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo))
                             return false;
                             return false;
 
 
                         int fieldCount = reader.ReadInt32();
                         int fieldCount = reader.ReadInt32();
 
 
-                        object recreatedTarget = Activator.CreateInstance(targetType);
+                        object recreatedTarget = Activator.CreateInstance(targetType)!;
 
 
                         for (int i = 0; i < fieldCount; i++)
                         for (int i = 0; i < fieldCount; i++)
                         {
                         {
@@ -383,12 +428,17 @@ namespace Godot
                             int valueBufferLength = reader.ReadInt32();
                             int valueBufferLength = reader.ReadInt32();
                             byte[] valueBuffer = reader.ReadBytes(valueBufferLength);
                             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));
                             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;
                         return true;
                     }
                     }
                     default:
                     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;
             methodInfo = null;
 
 
-            Type declaringType = DeserializeType(reader);
+            Type? declaringType = DeserializeType(reader);
+
+            if (declaringType == null)
+                return false;
 
 
             string methodName = reader.ReadString();
             string methodName = reader.ReadString();
 
 
             int flags = reader.ReadInt32();
             int flags = reader.ReadInt32();
 
 
             bool hasReturn = reader.ReadBoolean();
             bool hasReturn = reader.ReadBoolean();
-            Type returnType = hasReturn ? DeserializeType(reader) : typeof(void);
+            Type? returnType = hasReturn ? DeserializeType(reader) : typeof(void);
 
 
             int parametersCount = reader.ReadInt32();
             int parametersCount = reader.ReadInt32();
 
 
@@ -418,7 +472,7 @@ namespace Godot
 
 
                 for (int i = 0; i < parametersCount; i++)
                 for (int i = 0; i < parametersCount; i++)
                 {
                 {
-                    Type parameterType = DeserializeType(reader);
+                    Type? parameterType = DeserializeType(reader);
                     if (parameterType == null)
                     if (parameterType == null)
                         return false;
                         return false;
                     parameterTypes[i] = parameterType;
                     parameterTypes[i] = parameterType;
@@ -432,15 +486,23 @@ namespace Godot
             return methodInfo != null && methodInfo.ReturnType == returnType;
             return methodInfo != null && methodInfo.ReturnType == returnType;
         }
         }
 
 
-        private static Type DeserializeType(BinaryReader reader)
+        private static Type? DeserializeType(BinaryReader reader)
         {
         {
             int genericArgumentsCount = reader.ReadInt32();
             int genericArgumentsCount = reader.ReadInt32();
 
 
             if (genericArgumentsCount == -1)
             if (genericArgumentsCount == -1)
                 return null;
                 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)
             if (type == null)
                 return null; // Type not found
                 return null; // Type not found
@@ -451,7 +513,7 @@ namespace Godot
 
 
                 for (int i = 0; i < genericArgumentsCount; i++)
                 for (int i = 0; i < genericArgumentsCount; i++)
                 {
                 {
-                    Type genericArgumentType = DeserializeType(reader);
+                    Type? genericArgumentType = DeserializeType(reader);
                     if (genericArgumentType == null)
                     if (genericArgumentType == null)
                         return null;
                         return null;
                     genericArgumentTypes[i] = genericArgumentType;
                     genericArgumentTypes[i] = genericArgumentType;

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

@@ -578,6 +578,24 @@ namespace Godot.Collections
             return found;
             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>>
         // ICollection<KeyValuePair<TKey, TValue>>
 
 
         /// <summary>
         /// <summary>

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

@@ -1,7 +1,6 @@
 using System;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
-using System.Runtime.Loader;
 using Godot.NativeInterop;
 using Godot.NativeInterop;
 
 
 #nullable enable
 #nullable enable
@@ -10,17 +9,12 @@ namespace Godot
 {
 {
     internal static class DisposablesTracker
     internal static class DisposablesTracker
     {
     {
-        static DisposablesTracker()
-        {
-            AssemblyLoadContext.Default.Unloading += _ => OnUnloading();
-        }
-
         [UnmanagedCallersOnly]
         [UnmanagedCallersOnly]
         internal static void OnGodotShuttingDown()
         internal static void OnGodotShuttingDown()
         {
         {
             try
             try
             {
             {
-                OnUnloading();
+                OnGodotShuttingDownImpl();
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
@@ -28,7 +22,7 @@ namespace Godot
             }
             }
         }
         }
 
 
-        private static void OnUnloading()
+        private static void OnGodotShuttingDownImpl()
         {
         {
             bool isStdoutVerbose;
             bool isStdoutVerbose;
 
 
@@ -66,30 +60,30 @@ namespace Godot
         }
         }
 
 
         // ReSharper disable once RedundantNameQualifier
         // ReSharper disable once RedundantNameQualifier
-        private static ConcurrentDictionary<WeakReference<Godot.Object>, object?> GodotObjectInstances { get; } =
+        private static ConcurrentDictionary<WeakReference<Godot.Object>, byte> GodotObjectInstances { get; } =
             new();
             new();
 
 
-        private static ConcurrentDictionary<WeakReference<IDisposable>, object?> OtherInstances { get; } =
+        private static ConcurrentDictionary<WeakReference<IDisposable>, byte> OtherInstances { get; } =
             new();
             new();
 
 
         public static WeakReference<Object> RegisterGodotObject(Object godotObject)
         public static WeakReference<Object> RegisterGodotObject(Object godotObject)
         {
         {
             var weakReferenceToSelf = new WeakReference<Object>(godotObject);
             var weakReferenceToSelf = new WeakReference<Object>(godotObject);
-            GodotObjectInstances.TryAdd(weakReferenceToSelf, null);
+            GodotObjectInstances.TryAdd(weakReferenceToSelf, 0);
             return weakReferenceToSelf;
             return weakReferenceToSelf;
         }
         }
 
 
         public static WeakReference<IDisposable> RegisterDisposable(IDisposable disposable)
         public static WeakReference<IDisposable> RegisterDisposable(IDisposable disposable)
         {
         {
             var weakReferenceToSelf = new WeakReference<IDisposable>(disposable);
             var weakReferenceToSelf = new WeakReference<IDisposable>(disposable);
-            OtherInstances.TryAdd(weakReferenceToSelf, null);
+            OtherInstances.TryAdd(weakReferenceToSelf, 0);
             return weakReferenceToSelf;
             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)
         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,
                 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,
         public static void TieManagedToUnmanaged(Object managed, IntPtr unmanaged,
             StringName nativeName, bool refCounted, Type type, Type nativeType)
             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)
             if (type == nativeType)
             {
             {
@@ -65,7 +67,7 @@ namespace Godot.NativeInterop
                     // We don't dispose `script` ourselves here.
                     // We don't dispose `script` ourselves here.
                     // `tie_user_managed_to_unmanaged` does it for us to avoid another P/Invoke call.
                     // `tie_user_managed_to_unmanaged` does it for us to avoid another P/Invoke call.
                     godot_ref script;
                     godot_ref script;
-                    ScriptManagerBridge.GetOrCreateScriptBridgeForType(type, &script);
+                    ScriptManagerBridge.GetOrLoadOrCreateScriptForType(type, &script);
 
 
                     // IMPORTANT: This must be called after GetOrCreateScriptBridgeForType
                     // IMPORTANT: This must be called after GetOrCreateScriptBridgeForType
                     NativeFuncs.godotsharp_internal_tie_user_managed_to_unmanaged(
                     NativeFuncs.godotsharp_internal_tie_user_managed_to_unmanaged(
@@ -80,7 +82,7 @@ namespace Godot.NativeInterop
             if (type == nativeType)
             if (type == nativeType)
                 return;
                 return;
 
 
-            var strongGCHandle = GCHandle.Alloc(managed, GCHandleType.Normal);
+            var strongGCHandle = CustomGCHandle.AllocStrong(managed);
             NativeFuncs.godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup(
             NativeFuncs.godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup(
                 GCHandle.ToIntPtr(strongGCHandle), unmanaged);
                 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))
             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))
                 if (!type.IsInstanceOfType(godotObject))
                 {
                 {
@@ -864,9 +886,9 @@ namespace Godot.NativeInterop
         {
         {
             if (p_managed_callable.Delegate != null)
             if (p_managed_callable.Delegate != null)
             {
             {
+                var gcHandle = CustomGCHandle.AllocStrong(p_managed_callable.Delegate);
                 NativeFuncs.godotsharp_callable_new_with_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;
                 return callable;
             }
             }
             else
             else

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

@@ -95,7 +95,10 @@ namespace Godot.NativeInterop
             IntPtr oldGCHandlePtr);
             IntPtr oldGCHandlePtr);
 
 
         [DllImport(GodotDllName)]
         [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)]
         [DllImport(GodotDllName)]
         internal static extern void godotsharp_internal_reload_registered_script(IntPtr scriptPtr);
         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.Linq;
 using System.Reflection;
 using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
+using Godot.Bridge;
 using Godot.NativeInterop;
 using Godot.NativeInterop;
 
 
 namespace Godot
 namespace Godot
@@ -150,7 +151,7 @@ namespace Godot
                 NativePtr = IntPtr.Zero;
                 NativePtr = IntPtr.Zero;
             }
             }
 
 
-            DisposablesTracker.UnregisterGodotObject(_weakReferenceToSelf);
+            DisposablesTracker.UnregisterGodotObject(this, _weakReferenceToSelf);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -328,5 +329,77 @@ namespace Godot
 
 
             return nativeConstructor;
             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)
         public SignalAwaiter(Object source, StringName signal, Object target)
         {
         {
+            var awaiterGcHandle = CustomGCHandle.AllocStrong(this);
             using godot_string_name signalSrc = NativeFuncs.godotsharp_string_name_new_copy(
             using godot_string_name signalSrc = NativeFuncs.godotsharp_string_name_new_copy(
                 (godot_string_name)(signal?.NativeValue ?? default));
                 (godot_string_name)(signal?.NativeValue ?? default));
             NativeFuncs.godotsharp_internal_signal_awaiter_connect(Object.GetPtr(source), in signalSrc,
             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;
         public bool IsCompleted => _completed;
@@ -39,11 +40,11 @@ namespace Godot
 
 
                 if (awaiter == null)
                 if (awaiter == null)
                 {
                 {
-                    *outAwaiterIsNull = true.ToGodotBool();
+                    *outAwaiterIsNull = godot_bool.True;
                     return;
                     return;
                 }
                 }
 
 
-                *outAwaiterIsNull = false.ToGodotBool();
+                *outAwaiterIsNull = godot_bool.False;
 
 
                 awaiter._completed = true;
                 awaiter._completed = true;
 
 
@@ -59,7 +60,7 @@ namespace Godot
             catch (Exception e)
             catch (Exception e)
             {
             {
                 ExceptionUtils.LogException(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 -->
   <!-- Sources -->
   <ItemGroup>
   <ItemGroup>
     <Compile Include="Core\AABB.cs" />
     <Compile Include="Core\AABB.cs" />
+    <Compile Include="Core\Bridge\GodotSerializationInfo.cs" />
+    <Compile Include="Core\CustomGCHandle.cs" />
     <Compile Include="Core\Array.cs" />
     <Compile Include="Core\Array.cs" />
     <Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
     <Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
     <Compile Include="Core\Attributes\ExportAttribute.cs" />
     <Compile Include="Core\Attributes\ExportAttribute.cs" />
@@ -59,9 +61,11 @@
     <Compile Include="Core\Basis.cs" />
     <Compile Include="Core\Basis.cs" />
     <Compile Include="Core\Bridge\CSharpInstanceBridge.cs" />
     <Compile Include="Core\Bridge\CSharpInstanceBridge.cs" />
     <Compile Include="Core\Bridge\GCHandleBridge.cs" />
     <Compile Include="Core\Bridge\GCHandleBridge.cs" />
+    <Compile Include="Core\Bridge\AlcReloadCfg.cs" />
     <Compile Include="Core\Bridge\ManagedCallbacks.cs" />
     <Compile Include="Core\Bridge\ManagedCallbacks.cs" />
     <Compile Include="Core\Bridge\PropertyInfo.cs" />
     <Compile Include="Core\Bridge\PropertyInfo.cs" />
     <Compile Include="Core\Bridge\ScriptManagerBridge.cs" />
     <Compile Include="Core\Bridge\ScriptManagerBridge.cs" />
+    <Compile Include="Core\Bridge\ScriptManagerBridge.types.cs" />
     <Compile Include="Core\Callable.cs" />
     <Compile Include="Core\Callable.cs" />
     <Compile Include="Core\Color.cs" />
     <Compile Include="Core\Color.cs" />
     <Compile Include="Core\Colors.cs" />
     <Compile Include="Core\Colors.cs" />
@@ -100,6 +104,7 @@
     <Compile Include="Core\Quaternion.cs" />
     <Compile Include="Core\Quaternion.cs" />
     <Compile Include="Core\Rect2.cs" />
     <Compile Include="Core\Rect2.cs" />
     <Compile Include="Core\Rect2i.cs" />
     <Compile Include="Core\Rect2i.cs" />
+    <Compile Include="Core\ReflectionUtils.cs" />
     <Compile Include="Core\RID.cs" />
     <Compile Include="Core\RID.cs" />
     <Compile Include="Core\NativeInterop\NativeFuncs.cs" />
     <Compile Include="Core\NativeInterop\NativeFuncs.cs" />
     <Compile Include="Core\NativeInterop\InteropStructs.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)));
 	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) {
 GD_PINVOKE_EXPORT void godotsharp_internal_reload_registered_script(CSharpScript *p_script) {
 	CRASH_COND(!p_script);
 	CRASH_COND(!p_script);
 	CSharpScript::reload_registered_script(Ref<CSharpScript>(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
 #endif
 
 
 // We need this to prevent the functions from being stripped.
 // 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_method_bind_get_method,
 	(void *)godotsharp_get_class_constructor,
 	(void *)godotsharp_get_class_constructor,
 	(void *)godotsharp_engine_get_singleton,
 	(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_user_managed_to_unmanaged,
 	(void *)godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup,
 	(void *)godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup,
 	(void *)godotsharp_internal_new_csharp_script,
 	(void *)godotsharp_internal_new_csharp_script,
+	(void *)godotsharp_internal_script_load,
 	(void *)godotsharp_internal_reload_registered_script,
 	(void *)godotsharp_internal_reload_registered_script,
 	(void *)godotsharp_array_filter_godot_objects_by_native,
 	(void *)godotsharp_array_filter_godot_objects_by_native,
 	(void *)godotsharp_array_filter_godot_objects_by_non_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_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better
 	r_return_value = Variant();
 	r_return_value = Variant();
 
 
+	ERR_FAIL_COND(delegate_handle.value == nullptr);
+
 	GDMonoCache::managed_callbacks.DelegateUtils_InvokeWithVariantArgs(
 	GDMonoCache::managed_callbacks.DelegateUtils_InvokeWithVariantArgs(
 			delegate_handle, p_arguments, p_argcount, &r_return_value);
 			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
 #ifdef TOOLS_ENABLED
 bool GDMono::_load_project_assembly() {
 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()
 	String assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir()
 								   .plus_file(appname_safe + ".dll");
 								   .plus_file(appname_safe + ".dll");
 	assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path);
 	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
 #ifdef GD_MONO_HOT_RELOAD
-Error GDMono::reload_scripts_domain() {
+Error GDMono::reload_project_assemblies() {
 	ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG);
 	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
 	// 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.
 	// 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()) {
 	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 ERR_CANT_OPEN;
 	}
 	}
 
 
 	return OK;
 	return OK;
 }
 }
 #endif
 #endif
-#endif
 
 
 GDMono::GDMono() {
 GDMono::GDMono() {
 	singleton = this;
 	singleton = this;

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

@@ -45,10 +45,12 @@ namespace gdmono {
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 struct PluginCallbacks {
 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 FuncLoadToolsAssemblyCallback = Object *(GD_CLR_STDCALL *)(const char16_t *);
+	using FuncUnloadProjectPluginCallback = bool(GD_CLR_STDCALL *)();
 	FuncLoadProjectAssemblyCallback LoadProjectAssemblyCallback = nullptr;
 	FuncLoadProjectAssemblyCallback LoadProjectAssemblyCallback = nullptr;
 	FuncLoadToolsAssemblyCallback LoadToolsAssemblyCallback = nullptr;
 	FuncLoadToolsAssemblyCallback LoadToolsAssemblyCallback = nullptr;
+	FuncUnloadProjectPluginCallback UnloadProjectPluginCallback = nullptr;
 };
 };
 #endif
 #endif
 
 
@@ -63,14 +65,13 @@ class GDMono {
 	void *hostfxr_dll_handle = nullptr;
 	void *hostfxr_dll_handle = nullptr;
 	bool is_native_aot = false;
 	bool is_native_aot = false;
 
 
+	String project_assembly_path;
+	uint64_t project_assembly_modified_time = 0;
+
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 	bool _load_project_assembly();
 	bool _load_project_assembly();
 #endif
 #endif
 
 
-	bool _try_load_api_assemblies();
-
-	Error _unload_scripts_domain();
-
 	uint64_t api_core_hash;
 	uint64_t api_core_hash;
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 	uint64_t api_editor_hash;
 	uint64_t api_editor_hash;
@@ -114,17 +115,32 @@ public:
 #endif
 #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
 #ifdef TOOLS_ENABLED
-	const gdmono::PluginCallbacks &get_plugin_callbacks() { return plugin_callbacks; }
+	const gdmono::PluginCallbacks &get_plugin_callbacks() {
+		return plugin_callbacks;
+	}
 #endif
 #endif
 
 
 #ifdef GD_MONO_HOT_RELOAD
 #ifdef GD_MONO_HOT_RELOAD
-	Error reload_scripts_domain();
+	Error reload_project_assemblies();
 #endif
 #endif
 
 
 	void initialize();
 	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(SignalAwaiter, SignalCallback);
 	CHECK_CALLBACK_NOT_NULL(DelegateUtils, InvokeWithVariantArgs);
 	CHECK_CALLBACK_NOT_NULL(DelegateUtils, InvokeWithVariantArgs);
 	CHECK_CALLBACK_NOT_NULL(DelegateUtils, DelegateEquals);
 	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, FrameCallback);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectBinding);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectBinding);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectScriptInstance);
 	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, AddScriptBridge);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetOrCreateScriptBridgeForPath);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetOrCreateScriptBridgeForPath);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RemoveScriptBridge);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RemoveScriptBridge);
+	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, TryReloadRegisteredScriptWithClass);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, UpdateScriptClassInfo);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, UpdateScriptClassInfo);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SwapGCHandleForType);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SwapGCHandleForType);
 	CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyInfoList);
 	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, CallDispose);
 	CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallToString);
 	CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallToString);
 	CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, HasMethodUnknownParams);
 	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(GCHandleBridge, FreeGCHandle);
 	CHECK_CALLBACK_NOT_NULL(DebuggingUtils, GetCurrentStackInfo);
 	CHECK_CALLBACK_NOT_NULL(DebuggingUtils, GetCurrentStackInfo);
 	CHECK_CALLBACK_NOT_NULL(DisposablesTracker, OnGodotShuttingDown);
 	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 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_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, uint32_t, const Variant *);
 	using FuncDelegateUtils_DelegateEquals = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr);
 	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_FrameCallback = void(GD_CLR_STDCALL *)();
 	using FuncScriptManagerBridge_CreateManagedForGodotObjectBinding = GCHandleIntPtr(GD_CLR_STDCALL *)(const StringName *, Object *);
 	using FuncScriptManagerBridge_CreateManagedForGodotObjectBinding = GCHandleIntPtr(GD_CLR_STDCALL *)(const StringName *, Object *);
 	using FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = bool(GD_CLR_STDCALL *)(const CSharpScript *, Object *, const Variant **, int32_t);
 	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_AddScriptBridge = bool(GD_CLR_STDCALL *)(const CSharpScript *, const String *);
 	using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *);
 	using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *);
 	using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const 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_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool);
 	using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add);
 	using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add);
 	using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_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_CallDispose = void(GD_CLR_STDCALL *)(GCHandleIntPtr, bool);
 	using FuncCSharpInstanceBridge_CallToString = void(GD_CLR_STDCALL *)(GCHandleIntPtr, String *, bool *);
 	using FuncCSharpInstanceBridge_CallToString = void(GD_CLR_STDCALL *)(GCHandleIntPtr, String *, bool *);
 	using FuncCSharpInstanceBridge_HasMethodUnknownParams = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *);
 	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 FuncGCHandleBridge_FreeGCHandle = void(GD_CLR_STDCALL *)(GCHandleIntPtr);
 	using FuncDebuggingUtils_GetCurrentStackInfo = void(GD_CLR_STDCALL *)(Vector<ScriptLanguage::StackInfo> *);
 	using FuncDebuggingUtils_GetCurrentStackInfo = void(GD_CLR_STDCALL *)(Vector<ScriptLanguage::StackInfo> *);
 	using FuncDisposablesTracker_OnGodotShuttingDown = void(GD_CLR_STDCALL *)();
 	using FuncDisposablesTracker_OnGodotShuttingDown = void(GD_CLR_STDCALL *)();
@@ -104,6 +109,8 @@ struct ManagedCallbacks {
 	FuncSignalAwaiter_SignalCallback SignalAwaiter_SignalCallback;
 	FuncSignalAwaiter_SignalCallback SignalAwaiter_SignalCallback;
 	FuncDelegateUtils_InvokeWithVariantArgs DelegateUtils_InvokeWithVariantArgs;
 	FuncDelegateUtils_InvokeWithVariantArgs DelegateUtils_InvokeWithVariantArgs;
 	FuncDelegateUtils_DelegateEquals DelegateUtils_DelegateEquals;
 	FuncDelegateUtils_DelegateEquals DelegateUtils_DelegateEquals;
+	FuncDelegateUtils_TrySerializeDelegateWithGCHandle DelegateUtils_TrySerializeDelegateWithGCHandle;
+	FuncDelegateUtils_TryDeserializeDelegateWithGCHandle DelegateUtils_TryDeserializeDelegateWithGCHandle;
 	FuncScriptManagerBridge_FrameCallback ScriptManagerBridge_FrameCallback;
 	FuncScriptManagerBridge_FrameCallback ScriptManagerBridge_FrameCallback;
 	FuncScriptManagerBridge_CreateManagedForGodotObjectBinding ScriptManagerBridge_CreateManagedForGodotObjectBinding;
 	FuncScriptManagerBridge_CreateManagedForGodotObjectBinding ScriptManagerBridge_CreateManagedForGodotObjectBinding;
 	FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
 	FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance;
@@ -116,6 +123,7 @@ struct ManagedCallbacks {
 	FuncScriptManagerBridge_AddScriptBridge ScriptManagerBridge_AddScriptBridge;
 	FuncScriptManagerBridge_AddScriptBridge ScriptManagerBridge_AddScriptBridge;
 	FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
 	FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath ScriptManagerBridge_GetOrCreateScriptBridgeForPath;
 	FuncScriptManagerBridge_RemoveScriptBridge ScriptManagerBridge_RemoveScriptBridge;
 	FuncScriptManagerBridge_RemoveScriptBridge ScriptManagerBridge_RemoveScriptBridge;
+	FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass ScriptManagerBridge_TryReloadRegisteredScriptWithClass;
 	FuncScriptManagerBridge_UpdateScriptClassInfo ScriptManagerBridge_UpdateScriptClassInfo;
 	FuncScriptManagerBridge_UpdateScriptClassInfo ScriptManagerBridge_UpdateScriptClassInfo;
 	FuncScriptManagerBridge_SwapGCHandleForType ScriptManagerBridge_SwapGCHandleForType;
 	FuncScriptManagerBridge_SwapGCHandleForType ScriptManagerBridge_SwapGCHandleForType;
 	FuncScriptManagerBridge_GetPropertyInfoList ScriptManagerBridge_GetPropertyInfoList;
 	FuncScriptManagerBridge_GetPropertyInfoList ScriptManagerBridge_GetPropertyInfoList;
@@ -126,6 +134,8 @@ struct ManagedCallbacks {
 	FuncCSharpInstanceBridge_CallDispose CSharpInstanceBridge_CallDispose;
 	FuncCSharpInstanceBridge_CallDispose CSharpInstanceBridge_CallDispose;
 	FuncCSharpInstanceBridge_CallToString CSharpInstanceBridge_CallToString;
 	FuncCSharpInstanceBridge_CallToString CSharpInstanceBridge_CallToString;
 	FuncCSharpInstanceBridge_HasMethodUnknownParams CSharpInstanceBridge_HasMethodUnknownParams;
 	FuncCSharpInstanceBridge_HasMethodUnknownParams CSharpInstanceBridge_HasMethodUnknownParams;
+	FuncCSharpInstanceBridge_SerializeState CSharpInstanceBridge_SerializeState;
+	FuncCSharpInstanceBridge_DeserializeState CSharpInstanceBridge_DeserializeState;
 	FuncGCHandleBridge_FreeGCHandle GCHandleBridge_FreeGCHandle;
 	FuncGCHandleBridge_FreeGCHandle GCHandleBridge_FreeGCHandle;
 	FuncDebuggingUtils_GetCurrentStackInfo DebuggingUtils_GetCurrentStackInfo;
 	FuncDebuggingUtils_GetCurrentStackInfo DebuggingUtils_GetCurrentStackInfo;
 	FuncDisposablesTracker_OnGodotShuttingDown DisposablesTracker_OnGodotShuttingDown;
 	FuncDisposablesTracker_OnGodotShuttingDown DisposablesTracker_OnGodotShuttingDown;
@@ -137,9 +147,6 @@ extern bool godot_api_cache_updated;
 
 
 void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks);
 void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks);
 
 
-inline void clear_godot_api_cache() {
-	managed_callbacks = ManagedCallbacks();
-}
 } // namespace GDMonoCache
 } // namespace GDMonoCache
 
 
 #undef GD_CLR_STDCALL
 #undef GD_CLR_STDCALL