瀏覽代碼

Merge pull request #32732 from neikeq/fix-release-api-assemblies-update-check

C#: Fix detection of outdated release Godot API assemblies
Rémi Verschelde 6 年之前
父節點
當前提交
8a459c03dd

+ 10 - 3
modules/mono/editor/GodotTools/GodotTools/BuildManager.cs

@@ -160,9 +160,16 @@ namespace GodotTools
             if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
                 return true; // No solution to build
 
-            // Make sure to update the API assemblies if they happen to be missing. Just in
-            // case the user decided to delete them at some point after they were loaded.
-            Internal.UpdateApiAssembliesFromPrebuilt();
+            // Make sure the API assemblies are up to date before building the project.
+            // We may not have had the chance to update the release API assemblies, and the debug ones
+            // may have been deleted by the user at some point after they were loaded by the Godot editor.
+            string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(config == "Release" ? "Release" : "Debug");
+
+            if (!string.IsNullOrEmpty(apiAssembliesUpdateError))
+            {
+                ShowBuildErrorDialog("Failed to update the Godot API assemblies");
+                return false;
+            }
 
             var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
             var buildTool = (BuildTool) editorSettings.GetSetting("mono/builds/build_tool");

+ 18 - 4
modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs

@@ -34,7 +34,7 @@ namespace GodotTools
 
         private bool CreateProjectSolution()
         {
-            using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2))
+            using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 3))
             {
                 pr.Step("Generating C# project...".TTR());
 
@@ -73,9 +73,23 @@ namespace GodotTools
                         return false;
                     }
 
-                    // Make sure to update the API assemblies if they happen to be missing. Just in
-                    // case the user decided to delete them at some point after they were loaded.
-                    Internal.UpdateApiAssembliesFromPrebuilt();
+                    pr.Step("Updating Godot API assemblies...".TTR());
+
+                    string debugApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Debug");
+
+                    if (!string.IsNullOrEmpty(debugApiAssembliesError))
+                    {
+                        ShowErrorDialog("Failed to update the Godot API assemblies: " + debugApiAssembliesError);
+                        return false;
+                    }
+
+                    string releaseApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Release");
+
+                    if (!string.IsNullOrEmpty(releaseApiAssembliesError))
+                    {
+                        ShowErrorDialog("Failed to update the Godot API assemblies: " + releaseApiAssembliesError);
+                        return false;
+                    }
 
                     pr.Step("Done".TTR());
 

+ 1 - 2
modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs

@@ -40,8 +40,7 @@ namespace GodotTools.Ides
 
         protected ILogger Logger
         {
-            get => logger ?? (logger = new ConsoleLogger());
-            set => logger = value;
+            get => logger ?? (logger = new GodotLogger());
         }
 
         private void StartServer()

+ 3 - 3
modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs

@@ -10,8 +10,8 @@ namespace GodotTools.Internals
         public const string CSharpLanguageType = "CSharpScript";
         public const string CSharpLanguageExtension = "cs";
 
-        public static string UpdateApiAssembliesFromPrebuilt() =>
-            internal_UpdateApiAssembliesFromPrebuilt();
+        public static string UpdateApiAssembliesFromPrebuilt(string config) =>
+            internal_UpdateApiAssembliesFromPrebuilt(config);
 
         public static string FullTemplatesDir =>
             internal_FullTemplatesDir();
@@ -55,7 +55,7 @@ namespace GodotTools.Internals
         // Internal Calls
 
         [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern string internal_UpdateApiAssembliesFromPrebuilt();
+        private static extern string internal_UpdateApiAssembliesFromPrebuilt(string config);
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern string internal_FullTemplatesDir();

+ 1 - 1
modules/mono/editor/csharp_project.cpp

@@ -75,7 +75,7 @@ bool generate_api_solution(const String &p_solution_dir, const String &p_core_pr
 				p_editor_proj_dir, p_editor_compile_items,
 				GDMono::get_singleton()->get_tools_project_editor_assembly());
 	} else {
-		MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.ApiSolutionGenerationDomain");
+		MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ApiSolutionGeneration");
 		CRASH_COND(temp_domain == NULL);
 		_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain);
 

+ 26 - 25
modules/mono/editor/editor_internal_calls.cpp

@@ -230,31 +230,9 @@ uint32_t godot_icall_GodotSharpExport_GetExportedAssemblyDependencies(MonoString
 	return GodotSharpExport::get_exported_assembly_dependencies(project_dll_name, project_dll_src_path, build_config, custom_lib_dir, dependencies);
 }
 
-float godot_icall_Globals_EditorScale() {
-	return EDSCALE;
-}
-
-MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
-	String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
-	Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
-	Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
-	return GDMonoMarshal::variant_to_mono_object(result);
-}
-
-MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
-	String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
-	Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
-	Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed);
-	return GDMonoMarshal::variant_to_mono_object(result);
-}
-
-MonoString *godot_icall_Globals_TTR(MonoString *p_text) {
-	String text = GDMonoMarshal::mono_string_to_godot(p_text);
-	return GDMonoMarshal::mono_string_from_godot(TTR(text));
-}
-
-MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt() {
-	String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt();
+MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) {
+	String config = GDMonoMarshal::mono_string_to_godot(p_config);
+	String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt(config);
 	return GDMonoMarshal::mono_string_from_godot(error_str);
 }
 
@@ -365,6 +343,29 @@ void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
 	}
 }
 
+float godot_icall_Globals_EditorScale() {
+	return EDSCALE;
+}
+
+MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
+	String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
+	Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
+	Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
+	return GDMonoMarshal::variant_to_mono_object(result);
+}
+
+MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
+	String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
+	Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
+	Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed);
+	return GDMonoMarshal::variant_to_mono_object(result);
+}
+
+MonoString *godot_icall_Globals_TTR(MonoString *p_text) {
+	String text = GDMonoMarshal::mono_string_to_godot(p_text);
+	return GDMonoMarshal::mono_string_from_godot(TTR(text));
+}
+
 MonoString *godot_icall_Utils_OS_GetPlatformName() {
 	String os_name = OS::get_singleton()->get_name();
 	return GDMonoMarshal::mono_string_from_godot(os_name);

+ 10 - 4
modules/mono/editor/godotsharp_export.cpp

@@ -32,9 +32,13 @@
 
 #include <mono/metadata/image.h>
 
+#include "core/os/os.h"
+
 #include "../mono_gd/gd_mono.h"
 #include "../mono_gd/gd_mono_assembly.h"
 
+namespace GodotSharpExport {
+
 String get_assemblyref_name(MonoImage *p_image, int index) {
 	const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);
 
@@ -45,7 +49,7 @@ String get_assemblyref_name(MonoImage *p_image, int index) {
 	return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
 }
 
-Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) {
+Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) {
 	MonoImage *image = p_assembly->get_image();
 
 	for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
@@ -96,8 +100,8 @@ Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, co
 	return OK;
 }
 
-Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies) {
-	MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain");
+Error get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_dependencies) {
+	MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport");
 	ERR_FAIL_NULL_V(export_domain, FAILED);
 	_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
 
@@ -110,7 +114,9 @@ Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_proje
 	ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + p_project_dll_name + "'.");
 
 	Vector<String> search_dirs;
-	GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_lib_dir);
+	GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir);
 
 	return get_assembly_dependencies(scripts_assembly, search_dirs, r_dependencies);
 }
+
+} // namespace GodotSharpExport

+ 2 - 1
modules/mono/editor/godotsharp_export.h

@@ -39,10 +39,11 @@
 
 namespace GodotSharpExport {
 
+Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies);
+
 Error get_exported_assembly_dependencies(const String &p_project_dll_name,
 		const String &p_project_dll_src_path, const String &p_build_config,
 		const String &p_custom_lib_dir, Dictionary &r_dependencies);
-Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies);
 
 } // namespace GodotSharpExport
 

+ 3 - 15
modules/mono/godotsharp_dirs.cpp

@@ -43,6 +43,8 @@
 #include "utils/android_utils.h"
 #endif
 
+#include "mono_gd/gd_mono.h"
+
 namespace GodotSharpDirs {
 
 String _get_expected_build_config() {
@@ -59,20 +61,6 @@ String _get_expected_build_config() {
 #endif
 }
 
-String _get_expected_api_build_config() {
-#ifdef TOOLS_ENABLED
-	return "Debug";
-#else
-
-#ifdef DEBUG_ENABLED
-	return "Debug";
-#else
-	return "Release";
-#endif
-
-#endif
-}
-
 String _get_mono_user_dir() {
 #ifdef TOOLS_ENABLED
 	if (EditorSettings::get_singleton()) {
@@ -134,7 +122,7 @@ private:
 		res_data_dir = "res://.mono";
 		res_metadata_dir = res_data_dir.plus_file("metadata");
 		res_assemblies_base_dir = res_data_dir.plus_file("assemblies");
-		res_assemblies_dir = res_assemblies_base_dir.plus_file(_get_expected_api_build_config());
+		res_assemblies_dir = res_assemblies_base_dir.plus_file(GDMono::get_expected_api_build_config());
 		res_config_dir = res_data_dir.plus_file("etc").plus_file("mono");
 
 		// TODO use paths from csproj

+ 182 - 106
modules/mono/mono_gd/gd_mono.cpp

@@ -381,10 +381,10 @@ void GDMono::initialize_load_assemblies() {
 }
 
 bool GDMono::_are_api_assemblies_out_of_sync() {
-	bool out_of_sync = core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated);
+	bool out_of_sync = core_api_assembly.assembly && (core_api_assembly.out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated);
 #ifdef TOOLS_ENABLED
 	if (!out_of_sync)
-		out_of_sync = editor_api_assembly && editor_api_assembly_out_of_sync;
+		out_of_sync = editor_api_assembly.assembly && editor_api_assembly.out_of_sync;
 #endif
 	return out_of_sync;
 }
@@ -523,10 +523,10 @@ bool GDMono::load_assembly_from(const String &p_name, const String &p_path, GDMo
 	return true;
 }
 
-APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, APIAssembly::Type p_api_type) {
-	APIAssembly::Version api_assembly_version;
+ApiAssemblyInfo::Version ApiAssemblyInfo::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, ApiAssemblyInfo::Type p_api_type) {
+	ApiAssemblyInfo::Version api_assembly_version;
 
-	const char *nativecalls_name = p_api_type == APIAssembly::API_CORE ?
+	const char *nativecalls_name = p_api_type == ApiAssemblyInfo::API_CORE ?
 										   BINDINGS_CLASS_NATIVECALLS :
 										   BINDINGS_CLASS_NATIVECALLS_EDITOR;
 
@@ -549,8 +549,8 @@ APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssemb
 	return api_assembly_version;
 }
 
-String APIAssembly::to_string(APIAssembly::Type p_type) {
-	return p_type == APIAssembly::API_CORE ? "API_CORE" : "API_EDITOR";
+String ApiAssemblyInfo::to_string(ApiAssemblyInfo::Type p_type) {
+	return p_type == ApiAssemblyInfo::API_CORE ? "API_CORE" : "API_EDITOR";
 }
 
 bool GDMono::_load_corlib_assembly() {
@@ -567,16 +567,12 @@ bool GDMono::_load_corlib_assembly() {
 }
 
 #ifdef TOOLS_ENABLED
-bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config) {
-
-	bool &api_assembly_out_of_sync = (p_api_type == APIAssembly::API_CORE) ?
-											 GDMono::get_singleton()->core_api_assembly_out_of_sync :
-											 GDMono::get_singleton()->editor_api_assembly_out_of_sync;
+bool GDMono::copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config) {
 
 	String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
 	String dst_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config);
 
-	String assembly_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
+	String assembly_name = p_api_type == ApiAssemblyInfo::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
 
 	// Create destination directory if needed
 	if (!DirAccess::exists(dst_dir)) {
@@ -590,35 +586,102 @@ bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const Stri
 		}
 	}
 
+	DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+
+	String xml_file = assembly_name + ".xml";
+	if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK)
+		WARN_PRINTS("Failed to copy '" + xml_file + "'.");
+
+	String pdb_file = assembly_name + ".pdb";
+	if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK)
+		WARN_PRINTS("Failed to copy '" + pdb_file + "'.");
+
 	String assembly_file = assembly_name + ".dll";
-	String assembly_src = src_dir.plus_file(assembly_file);
-	String assembly_dst = dst_dir.plus_file(assembly_file);
+	if (da->copy(src_dir.plus_file(assembly_file), dst_dir.plus_file(assembly_file)) != OK) {
+		ERR_PRINTS("Failed to copy '" + assembly_file + "'.");
+		return false;
+	}
 
-	if (!FileAccess::exists(assembly_dst) || api_assembly_out_of_sync) {
-		DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+	return true;
+}
 
-		String xml_file = assembly_name + ".xml";
-		if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK)
-			WARN_PRINTS("Failed to copy '" + xml_file + "'.");
+static bool try_get_cached_api_hash_for(const String &p_api_assemblies_dir, bool &r_out_of_sync) {
+	String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
+	String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
 
-		String pdb_file = assembly_name + ".pdb";
-		if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK)
-			WARN_PRINTS("Failed to copy '" + pdb_file + "'.");
+	if (!FileAccess::exists(core_api_assembly_path) || !FileAccess::exists(editor_api_assembly_path))
+		return false;
 
-		Error err = da->copy(assembly_src, assembly_dst);
+	String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg");
 
-		if (err != OK) {
-			ERR_PRINTS("Failed to copy '" + assembly_file + "'.");
-			return false;
-		}
+	if (!FileAccess::exists(cached_api_hash_path))
+		return false;
+
+	Ref<ConfigFile> cfg;
+	cfg.instance();
+	Error cfg_err = cfg->load(cached_api_hash_path);
+	ERR_FAIL_COND_V(cfg_err != OK, false);
 
-		api_assembly_out_of_sync = false;
+	// Checking the modified time is good enough
+	if (FileAccess::get_modified_time(core_api_assembly_path) != (uint64_t)cfg->get_value("core", "modified_time") ||
+			FileAccess::get_modified_time(editor_api_assembly_path) != (uint64_t)cfg->get_value("editor", "modified_time")) {
+		return false;
 	}
 
+	r_out_of_sync = GodotSharpBindings::get_bindings_version() != (uint32_t)cfg->get_value("core", "bindings_version") ||
+					GodotSharpBindings::get_cs_glue_version() != (uint32_t)cfg->get_value("core", "cs_glue_version") ||
+					GodotSharpBindings::get_bindings_version() != (uint32_t)cfg->get_value("editor", "bindings_version") ||
+					GodotSharpBindings::get_cs_glue_version() != (uint32_t)cfg->get_value("editor", "cs_glue_version") ||
+					GodotSharpBindings::get_core_api_hash() != (uint64_t)cfg->get_value("core", "api_hash") ||
+					GodotSharpBindings::get_editor_api_hash() != (uint64_t)cfg->get_value("editor", "api_hash");
+
 	return true;
 }
 
-String GDMono::update_api_assemblies_from_prebuilt() {
+static void create_cached_api_hash_for(const String &p_api_assemblies_dir) {
+
+	String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
+	String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
+	String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg");
+
+	Ref<ConfigFile> cfg;
+	cfg.instance();
+
+	cfg->set_value("core", "modified_time", FileAccess::get_modified_time(core_api_assembly_path));
+	cfg->set_value("editor", "modified_time", FileAccess::get_modified_time(editor_api_assembly_path));
+
+	cfg->set_value("core", "bindings_version", GodotSharpBindings::get_bindings_version());
+	cfg->set_value("core", "cs_glue_version", GodotSharpBindings::get_cs_glue_version());
+	cfg->set_value("editor", "bindings_version", GodotSharpBindings::get_bindings_version());
+	cfg->set_value("editor", "cs_glue_version", GodotSharpBindings::get_cs_glue_version());
+
+	// This assumes the prebuilt api assemblies we copied to the project are not out of sync
+	cfg->set_value("core", "api_hash", GodotSharpBindings::get_core_api_hash());
+	cfg->set_value("editor", "api_hash", GodotSharpBindings::get_editor_api_hash());
+
+	Error err = cfg->save(cached_api_hash_path);
+	ERR_FAIL_COND(err != OK);
+}
+
+bool GDMono::_temp_domain_load_are_assemblies_out_of_sync(const String &p_config) {
+	MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.Domain.CheckApiAssemblies");
+	ERR_FAIL_NULL_V(temp_domain, "Failed to create temporary domain to check API assemblies");
+	_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain);
+
+	_GDMONO_SCOPE_DOMAIN_(temp_domain);
+
+	GDMono::LoadedApiAssembly temp_core_api_assembly;
+	GDMono::LoadedApiAssembly temp_editor_api_assembly;
+
+	if (!_try_load_api_assemblies(temp_core_api_assembly, temp_editor_api_assembly,
+				p_config, /* refonly: */ true, /* loaded_callback: */ NULL)) {
+		return temp_core_api_assembly.out_of_sync || temp_editor_api_assembly.out_of_sync;
+	}
+
+	return true; // Failed to load, assume they're outdated assemblies
+}
+
+String GDMono::update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync, const bool *p_editor_api_out_of_sync) {
 
 #define FAIL_REASON(m_out_of_sync, m_prebuilt_exists)                            \
 	(                                                                            \
@@ -629,46 +692,55 @@ String GDMono::update_api_assemblies_from_prebuilt() {
 							String("and the prebuilt assemblies are missing.") : \
 							String("and we failed to copy the prebuilt assemblies.")))
 
-	bool api_assembly_out_of_sync = core_api_assembly_out_of_sync || editor_api_assembly_out_of_sync;
+	String dst_assemblies_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config);
 
-	String core_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll");
-	String editor_assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
+	String core_assembly_path = dst_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
+	String editor_assembly_path = dst_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
 
-	if (!api_assembly_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path))
-		return String(); // No update needed
+	bool api_assemblies_out_of_sync = false;
 
-	const int CONFIGS_LEN = 2;
-	String configs[CONFIGS_LEN] = { String("Debug"), String("Release") };
+	if (p_core_api_out_of_sync && p_editor_api_out_of_sync) {
+		api_assemblies_out_of_sync = p_core_api_out_of_sync || p_editor_api_out_of_sync;
+	} else if (FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) {
+		// Determine if they're out of sync
+		if (!try_get_cached_api_hash_for(dst_assemblies_dir, api_assemblies_out_of_sync)) {
+			api_assemblies_out_of_sync = _temp_domain_load_are_assemblies_out_of_sync(p_config);
+		}
+	}
 
-	for (int i = 0; i < CONFIGS_LEN; i++) {
-		String config = configs[i];
+	// Note: Even if only one of the assemblies if missing or out of sync, we update both
 
-		print_verbose("Updating '" + config + "' API assemblies");
+	if (!api_assemblies_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path))
+		return String(); // No update needed
 
-		String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(config);
-		String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
-		String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
+	print_verbose("Updating '" + p_config + "' API assemblies");
 
-		if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) {
-			return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ false);
-		}
+	String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
+	String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
+	String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
 
-		// Copy the prebuilt Api
-		if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE, config) ||
-				!copy_prebuilt_api_assembly(APIAssembly::API_EDITOR, config)) {
-			return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exists: */ true);
-		}
+	if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) {
+		return FAIL_REASON(api_assemblies_out_of_sync, /* prebuilt_exists: */ false);
 	}
 
+	// Copy the prebuilt Api
+	if (!copy_prebuilt_api_assembly(ApiAssemblyInfo::API_CORE, p_config) ||
+			!copy_prebuilt_api_assembly(ApiAssemblyInfo::API_EDITOR, p_config)) {
+		return FAIL_REASON(api_assemblies_out_of_sync, /* prebuilt_exists: */ true);
+	}
+
+	// Cache the api hash of the assemblies we just copied
+	create_cached_api_hash_for(dst_assemblies_dir);
+
 	return String(); // Updated successfully
 
 #undef FAIL_REASON
 }
 #endif
 
-bool GDMono::_load_core_api_assembly() {
+bool GDMono::_load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) {
 
-	if (core_api_assembly)
+	if (r_loaded_api_assembly.assembly)
 		return true;
 
 #ifdef TOOLS_ENABLED
@@ -676,101 +748,115 @@ bool GDMono::_load_core_api_assembly() {
 
 	// If running the project manager, load it from the prebuilt API directory
 	String assembly_dir = !Main::is_project_manager() ?
-								  GodotSharpDirs::get_res_assemblies_dir() :
-								  GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
+								  GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) :
+								  GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
 
 	String assembly_path = assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
 
 	bool success = FileAccess::exists(assembly_path) &&
-				   load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly);
+				   load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &r_loaded_api_assembly.assembly, p_refonly);
 #else
-	bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly);
+	bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly, p_refonly);
 #endif
 
 	if (success) {
-		APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(core_api_assembly, APIAssembly::API_CORE);
-		core_api_assembly_out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash ||
-										GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
-										GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version;
-		if (!core_api_assembly_out_of_sync) {
-			GDMonoUtils::update_godot_api_cache();
-
-			_install_trace_listener();
-		}
+		ApiAssemblyInfo::Version api_assembly_ver = ApiAssemblyInfo::Version::get_from_loaded_assembly(r_loaded_api_assembly.assembly, ApiAssemblyInfo::API_CORE);
+		r_loaded_api_assembly.out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash ||
+											GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
+											GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version;
 	} else {
-		core_api_assembly_out_of_sync = false;
+		r_loaded_api_assembly.out_of_sync = false;
 	}
 
 	return success;
 }
 
 #ifdef TOOLS_ENABLED
-bool GDMono::_load_editor_api_assembly() {
+bool GDMono::_load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) {
 
-	if (editor_api_assembly)
+	if (r_loaded_api_assembly.assembly)
 		return true;
 
 	// For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date
 
 	// If running the project manager, load it from the prebuilt API directory
 	String assembly_dir = !Main::is_project_manager() ?
-								  GodotSharpDirs::get_res_assemblies_dir() :
-								  GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
+								  GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) :
+								  GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
 
 	String assembly_path = assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
 
 	bool success = FileAccess::exists(assembly_path) &&
-				   load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly);
+				   load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &r_loaded_api_assembly.assembly, p_refonly);
 
 	if (success) {
-		APIAssembly::Version api_assembly_ver = APIAssembly::Version::get_from_loaded_assembly(editor_api_assembly, APIAssembly::API_EDITOR);
-		editor_api_assembly_out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash ||
-										  GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
-										  GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version;
+		ApiAssemblyInfo::Version api_assembly_ver = ApiAssemblyInfo::Version::get_from_loaded_assembly(r_loaded_api_assembly.assembly, ApiAssemblyInfo::API_EDITOR);
+		r_loaded_api_assembly.out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash ||
+											GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version ||
+											GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version;
 	} else {
-		editor_api_assembly_out_of_sync = false;
+		r_loaded_api_assembly.out_of_sync = false;
 	}
 
 	return success;
 }
 #endif
 
-bool GDMono::_try_load_api_assemblies() {
-
-	if (!_load_core_api_assembly()) {
+bool GDMono::_try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly,
+		const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback) {
+	if (!_load_core_api_assembly(r_core_api_assembly, p_config, p_refonly)) {
 		if (OS::get_singleton()->is_stdout_verbose())
 			print_error("Mono: Failed to load Core API assembly");
 		return false;
 	}
 
 #ifdef TOOLS_ENABLED
-	if (!_load_editor_api_assembly()) {
+	if (!_load_editor_api_assembly(r_editor_api_assembly, p_config, p_refonly)) {
 		if (OS::get_singleton()->is_stdout_verbose())
 			print_error("Mono: Failed to load Editor API assembly");
 		return false;
 	}
 
-	if (editor_api_assembly_out_of_sync)
+	if (r_editor_api_assembly.out_of_sync)
 		return false;
 #endif
 
 	// Check if the core API assembly is out of sync only after trying to load the
 	// editor API assembly. Otherwise, if both assemblies are out of sync, we would
 	// only update the former as we won't know the latter also needs to be updated.
-	if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated)
+	if (r_core_api_assembly.out_of_sync)
 		return false;
 
+	if (p_callback)
+		return p_callback();
+
 	return true;
 }
 
+bool GDMono::_on_core_api_assembly_loaded() {
+	GDMonoUtils::update_godot_api_cache();
+
+	if (!GDMonoUtils::mono_cache.godot_api_cache_updated)
+		return false;
+
+	get_singleton()->_install_trace_listener();
+
+	return true;
+}
+
+bool GDMono::_try_load_api_assemblies_preset() {
+	return _try_load_api_assemblies(core_api_assembly, editor_api_assembly,
+			get_expected_api_build_config(), /* refonly: */ false, _on_core_api_assembly_loaded);
+}
+
 void GDMono::_load_api_assemblies() {
 
-	bool api_assemblies_loaded = _try_load_api_assemblies();
+	bool api_assemblies_loaded = _try_load_api_assemblies_preset();
 
 	if (!api_assemblies_loaded) {
 #ifdef TOOLS_ENABLED
-		// The API assemblies are out of sync. Fine, try one more time, but this time
-		// update them from the prebuilt assemblies directory before trying to load them.
+		// The API assemblies are out of sync or some other error happened. Fine, try one more time, but
+		// this time update them from the prebuilt assemblies directory before trying to load them again.
 
 		// Shouldn't happen. The project manager loads the prebuilt API assemblies
 		CRASH_COND_MSG(Main::is_project_manager(), "Failed to load one of the prebuilt API assemblies.");
@@ -780,7 +866,7 @@ void GDMono::_load_api_assemblies() {
 		CRASH_COND_MSG(domain_unload_err != OK, "Mono: Failed to unload scripts domain.");
 
 		// 2. Update the API assemblies
-		String update_error = update_api_assemblies_from_prebuilt();
+		String update_error = update_api_assemblies_from_prebuilt("Debug", &core_api_assembly.out_of_sync, &editor_api_assembly.out_of_sync);
 		CRASH_COND_MSG(!update_error.empty(), update_error);
 
 		// 3. Load the scripts domain again
@@ -788,7 +874,7 @@ void GDMono::_load_api_assemblies() {
 		CRASH_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain.");
 
 		// 4. Try loading the updated assemblies
-		api_assemblies_loaded = _try_load_api_assemblies();
+		api_assemblies_loaded = _try_load_api_assemblies_preset();
 #endif
 	}
 
@@ -796,14 +882,14 @@ void GDMono::_load_api_assemblies() {
 		// welp... too bad
 
 		if (_are_api_assemblies_out_of_sync()) {
-			if (core_api_assembly_out_of_sync) {
+			if (core_api_assembly.out_of_sync) {
 				ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync.");
 			} else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) {
 				ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed.");
 			}
 
 #ifdef TOOLS_ENABLED
-			if (editor_api_assembly_out_of_sync) {
+			if (editor_api_assembly.out_of_sync) {
 				ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync.");
 			}
 #endif
@@ -852,15 +938,14 @@ void GDMono::_install_trace_listener() {
 
 #ifdef DEBUG_ENABLED
 	// Install the trace listener now before the project assembly is loaded
-	typedef void (*DebuggingUtils_InstallTraceListener)(MonoObject **);
+	GDMonoClass *debug_utils = get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, "DebuggingUtils");
+	GDMonoMethod *install_func = debug_utils->get_method("InstallTraceListener");
+
 	MonoException *exc = NULL;
-	GDMonoClass *debug_utils = core_api_assembly->get_class(BINDINGS_NAMESPACE, "DebuggingUtils");
-	DebuggingUtils_InstallTraceListener install_func =
-			(DebuggingUtils_InstallTraceListener)debug_utils->get_method_thunk("InstallTraceListener");
-	install_func((MonoObject **)&exc);
+	install_func->invoke_raw(NULL, NULL, &exc);
 	if (exc) {
-		ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener.");
 		GDMonoUtils::debug_print_unhandled_exception(exc);
+		ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener.");
 	}
 #endif
 }
@@ -871,7 +956,7 @@ Error GDMono::_load_scripts_domain() {
 
 	print_verbose("Mono: Loading scripts domain...");
 
-	scripts_domain = GDMonoUtils::create_domain("GodotEngine.ScriptsDomain");
+	scripts_domain = GDMonoUtils::create_domain("GodotEngine.Domain.Scripts");
 
 	ERR_FAIL_NULL_V_MSG(scripts_domain, ERR_CANT_CREATE, "Mono: Could not create scripts app domain.");
 
@@ -903,10 +988,8 @@ Error GDMono::_unload_scripts_domain() {
 
 	_domain_assemblies_cleanup(mono_domain_get_id(scripts_domain));
 
-	core_api_assembly = NULL;
 	project_assembly = NULL;
 #ifdef TOOLS_ENABLED
-	editor_api_assembly = NULL;
 	tools_assembly = NULL;
 	tools_project_editor_assembly = NULL;
 #endif
@@ -1076,16 +1159,9 @@ GDMono::GDMono() {
 	root_domain = NULL;
 	scripts_domain = NULL;
 
-	core_api_assembly_out_of_sync = false;
-#ifdef TOOLS_ENABLED
-	editor_api_assembly_out_of_sync = false;
-#endif
-
 	corlib_assembly = NULL;
-	core_api_assembly = NULL;
 	project_assembly = NULL;
 #ifdef TOOLS_ENABLED
-	editor_api_assembly = NULL;
 	tools_assembly = NULL;
 	tools_project_editor_assembly = NULL;
 #endif

+ 47 - 19
modules/mono/mono_gd/gd_mono.h

@@ -41,7 +41,7 @@
 #include "../utils/mono_reg_utils.h"
 #endif
 
-namespace APIAssembly {
+namespace ApiAssemblyInfo {
 enum Type {
 	API_CORE,
 	API_EDITOR
@@ -76,7 +76,7 @@ struct Version {
 };
 
 String to_string(Type p_type);
-} // namespace APIAssembly
+} // namespace ApiAssemblyInfo
 
 class GDMono {
 
@@ -86,44 +86,58 @@ public:
 		POLICY_LOG_ERROR
 	};
 
+	struct LoadedApiAssembly {
+		GDMonoAssembly *assembly;
+		bool out_of_sync;
+
+		LoadedApiAssembly() :
+				assembly(NULL),
+				out_of_sync(false) {
+		}
+	};
+
 private:
 	bool runtime_initialized;
 	bool finalizing_scripts_domain;
 
+	UnhandledExceptionPolicy unhandled_exception_policy;
+
 	MonoDomain *root_domain;
 	MonoDomain *scripts_domain;
 
-	bool core_api_assembly_out_of_sync;
-#ifdef TOOLS_ENABLED
-	bool editor_api_assembly_out_of_sync;
-#endif
+	HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies;
 
 	GDMonoAssembly *corlib_assembly;
-	GDMonoAssembly *core_api_assembly;
 	GDMonoAssembly *project_assembly;
 #ifdef TOOLS_ENABLED
-	GDMonoAssembly *editor_api_assembly;
 	GDMonoAssembly *tools_assembly;
 	GDMonoAssembly *tools_project_editor_assembly;
 #endif
 
-	HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies;
-
-	UnhandledExceptionPolicy unhandled_exception_policy;
+	LoadedApiAssembly core_api_assembly;
+	LoadedApiAssembly editor_api_assembly;
 
-	void _domain_assemblies_cleanup(uint32_t p_domain_id);
+	typedef bool (*CoreApiAssemblyLoadedCallback)();
 
 	bool _are_api_assemblies_out_of_sync();
+	bool _temp_domain_load_are_assemblies_out_of_sync(const String &p_config);
+
+	bool _load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly);
+#ifdef TOOLS_ENABLED
+	bool _load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly);
+#endif
+
+	static bool _on_core_api_assembly_loaded();
 
 	bool _load_corlib_assembly();
-	bool _load_core_api_assembly();
 #ifdef TOOLS_ENABLED
-	bool _load_editor_api_assembly();
 	bool _load_tools_assemblies();
 #endif
 	bool _load_project_assembly();
 
-	bool _try_load_api_assemblies();
+	bool _try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly,
+			const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback);
+	bool _try_load_api_assemblies_preset();
 	void _load_api_assemblies();
 
 	void _install_trace_listener();
@@ -133,6 +147,8 @@ private:
 	Error _load_scripts_domain();
 	Error _unload_scripts_domain();
 
+	void _domain_assemblies_cleanup(uint32_t p_domain_id);
+
 	uint64_t api_core_hash;
 #ifdef TOOLS_ENABLED
 	uint64_t api_editor_hash;
@@ -166,9 +182,21 @@ public:
 #endif // TOOLS_ENABLED
 #endif // DEBUG_METHODS_ENABLED
 
+	_FORCE_INLINE_ static String get_expected_api_build_config() {
+#ifdef TOOLS_ENABLED
+		return "Debug";
+#else
+#ifdef DEBUG_ENABLED
+		return "Debug";
+#else
+		return "Release";
+#endif
+#endif
+	}
+
 #ifdef TOOLS_ENABLED
-	bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type, const String &p_config);
-	String update_api_assemblies_from_prebuilt();
+	bool copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config);
+	String update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync = NULL, const bool *p_editor_api_out_of_sync = NULL);
 #endif
 
 	static GDMono *get_singleton() { return singleton; }
@@ -188,10 +216,10 @@ public:
 	_FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; }
 
 	_FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; }
-	_FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly; }
+	_FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly.assembly; }
 	_FORCE_INLINE_ GDMonoAssembly *get_project_assembly() const { return project_assembly; }
 #ifdef TOOLS_ENABLED
-	_FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly; }
+	_FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly.assembly; }
 	_FORCE_INLINE_ GDMonoAssembly *get_tools_assembly() const { return tools_assembly; }
 	_FORCE_INLINE_ GDMonoAssembly *get_tools_project_editor_assembly() const { return tools_project_editor_assembly; }
 #endif

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

@@ -550,6 +550,8 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class)
 }
 
 MonoDomain *create_domain(const String &p_friendly_name) {
+	print_verbose("Mono: Creating domain '" + p_friendly_name + "'...");
+
 	MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), NULL);
 
 	if (domain) {