diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/mono/csharp_script.cpp | 21 | ||||
-rw-r--r-- | modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs | 126 | ||||
-rw-r--r-- | modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs | 87 | ||||
-rw-r--r-- | modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs | 23 | ||||
-rw-r--r-- | modules/mono/editor/editor_internal_calls.cpp | 24 | ||||
-rw-r--r-- | modules/mono/mono_gd/gd_mono.cpp | 504 | ||||
-rw-r--r-- | modules/mono/mono_gd/gd_mono.h | 15 |
7 files changed, 255 insertions, 545 deletions
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 1add697997..078a490b22 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -114,15 +114,20 @@ void CSharpLanguage::init() { gdmono = memnew(GDMono); gdmono->initialize(); -#ifndef MONO_GLUE_ENABLED - WARN_PRINT("This binary is built with `mono_glue=no` and cannot be used for scripting"); -#endif - #if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED) + // Generate bindings here, before loading assemblies. `initialize_load_assemblies` aborts + // the applications if the api assemblies or the main tools assembly is missing, but this + // is not a problem for BindingsGenerator as it only needs the tools project editor assembly. List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); BindingsGenerator::handle_cmdline_args(cmdline_args); #endif +#ifndef MONO_GLUE_ENABLED + print_line("Run this binary with `--generate-mono-glue path/to/modules/mono/glue`"); +#endif + + gdmono->initialize_load_assemblies(); + #ifdef TOOLS_ENABLED EditorNode::add_init_callback(&_editor_init_callback); @@ -710,14 +715,6 @@ bool CSharpLanguage::is_assembly_reloading_needed() { return false; // No assembly to load } -#ifdef TOOLS_ENABLED - if (!gdmono->get_core_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) - return false; // The core API assembly to load is invalidated - - if (!gdmono->get_editor_api_assembly() && gdmono->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) - return false; // The editor API assembly to load is invalidated -#endif - return true; } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs index e606781115..a884b0ead0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpBuilds.cs @@ -193,134 +193,14 @@ namespace GodotTools return false; } - private static bool CopyApiAssembly(string srcDir, string dstDir, string assemblyName, ApiAssemblyType apiType) - { - // Create destination directory if needed - if (!Directory.Exists(dstDir)) - { - try - { - Directory.CreateDirectory(dstDir); - } - catch (IOException e) - { - ShowBuildErrorDialog($"Failed to create destination directory for the API assemblies. Exception message: {e.Message}"); - return false; - } - } - - string assemblyFile = assemblyName + ".dll"; - string assemblySrc = Path.Combine(srcDir, assemblyFile); - string assemblyDst = Path.Combine(dstDir, assemblyFile); - - if (!File.Exists(assemblyDst) || File.GetLastWriteTime(assemblySrc) > File.GetLastWriteTime(assemblyDst) || - Internal.MetadataIsApiAssemblyInvalidated(apiType)) - { - string xmlFile = $"{assemblyName}.xml"; - string pdbFile = $"{assemblyName}.pdb"; - - try - { - File.Copy(Path.Combine(srcDir, xmlFile), Path.Combine(dstDir, xmlFile)); - } - catch (IOException e) - { - Godot.GD.PushWarning(e.ToString()); - } - - try - { - File.Copy(Path.Combine(srcDir, pdbFile), Path.Combine(dstDir, pdbFile)); - } - catch (IOException e) - { - Godot.GD.PushWarning(e.ToString()); - } - - try - { - File.Copy(assemblySrc, assemblyDst); - } - catch (IOException e) - { - ShowBuildErrorDialog($"Failed to copy {assemblyFile}. Exception message: {e.Message}"); - return false; - } - - Internal.MetadataSetApiAssemblyInvalidated(apiType, false); - } - - return true; - } - - public static bool MakeApiAssembly(ApiAssemblyType apiType, string config) - { - string apiName = apiType == ApiAssemblyType.Core ? ApiAssemblyNames.Core : ApiAssemblyNames.Editor; - - string editorPrebuiltApiDir = Path.Combine(GodotSharpDirs.DataEditorPrebuiltApiDir, config); - string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, config); - - if (File.Exists(Path.Combine(editorPrebuiltApiDir, $"{apiName}.dll"))) - { - using (var copyProgress = new EditorProgress("mono_copy_prebuilt_api_assembly", $"Copying prebuilt {apiName} assembly...", 1)) - { - copyProgress.Step($"Copying {apiName} assembly", 0); - return CopyApiAssembly(editorPrebuiltApiDir, resAssembliesDir, apiName, apiType); - } - } - - const string apiSolutionName = ApiAssemblyNames.SolutionName; - - using (var pr = new EditorProgress($"mono_build_release_{apiSolutionName}", $"Building {apiSolutionName} solution...", 3)) - { - pr.Step($"Generating {apiSolutionName} solution", 0); - - string apiSlnDir = Path.Combine(GodotSharpDirs.MonoSolutionsDir, _ApiFolderName(ApiAssemblyType.Core)); - string apiSlnFile = Path.Combine(apiSlnDir, $"{apiSolutionName}.sln"); - - if (!Directory.Exists(apiSlnDir) || !File.Exists(apiSlnFile)) - { - var bindingsGenerator = new BindingsGenerator(); - - if (!Godot.OS.IsStdoutVerbose()) - bindingsGenerator.LogPrintEnabled = false; - - Error err = bindingsGenerator.GenerateCsApi(apiSlnDir); - if (err != Error.Ok) - { - ShowBuildErrorDialog($"Failed to generate {apiSolutionName} solution. Error: {err}"); - return false; - } - } - - pr.Step($"Building {apiSolutionName} solution", 1); - - if (!BuildApiSolution(apiSlnDir, config)) - return false; - - pr.Step($"Copying {apiName} assembly", 2); - - // Copy the built assembly to the assemblies directory - string apiAssemblyDir = Path.Combine(apiSlnDir, apiName, "bin", config); - if (!CopyApiAssembly(apiAssemblyDir, resAssembliesDir, apiName, apiType)) - return false; - } - - return true; - } - public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines) { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return true; // No solution to build - string apiConfig = config == "Release" ? "Release" : "Debug"; - - if (!MakeApiAssembly(ApiAssemblyType.Core, apiConfig)) - return false; - - if (!MakeApiAssembly(ApiAssemblyType.Editor, apiConfig)) - 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(); using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1)) { diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 422752bb72..90dec43412 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -27,7 +27,7 @@ namespace GodotTools private MonoDevelopInstance monoDevelopInstance; private MonoDevelopInstance visualStudioForMacInstance; - private WeakReference<GodotSharpExport> exportPluginWeak; + private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization public MonoBottomPanel MonoBottomPanel { get; private set; } @@ -72,13 +72,9 @@ namespace GodotTools return false; } - string apiConfig = "Debug"; - - if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, apiConfig)) - return false; - - if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, apiConfig)) - 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("Done".TTR()); @@ -94,70 +90,6 @@ namespace GodotTools } } - private static int _makeApiSolutionsAttempts = 100; - private static bool _makeApiSolutionsRecursionGuard = false; - - private void _MakeApiSolutionsIfNeeded() - { - // I'm sick entirely of ProgressDialog - - if (Internal.IsMessageQueueFlushing() || Engine.GetMainLoop() == null) - { - if (_makeApiSolutionsAttempts == 0) // This better never happen or I swear... - throw new TimeoutException(); - - if (Engine.GetMainLoop() != null) - { - if (!Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded))) - Engine.GetMainLoop().Connect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)); - } - else - { - CallDeferred(nameof(_MakeApiSolutionsIfNeededImpl)); - } - - _makeApiSolutionsAttempts--; - return; - } - - // Recursion guard needed because signals don't play well with ProgressDialog either, but unlike - // the message queue, with signals the collateral damage should be minimal in the worst case. - if (!_makeApiSolutionsRecursionGuard) - { - _makeApiSolutionsRecursionGuard = true; - - // Oneshot signals don't play well with ProgressDialog either, so we do it this way instead - if (Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded))) - Engine.GetMainLoop().Disconnect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)); - - _MakeApiSolutionsIfNeededImpl(); - - _makeApiSolutionsRecursionGuard = false; - } - } - - private void _MakeApiSolutionsIfNeededImpl() - { - // If the project has a solution and C# project make sure the API assemblies are present and up to date - - string api_config = "Debug"; - string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, api_config); - - if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Core}.dll")) || - Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Core)) - { - if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, api_config)) - return; - } - - if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Editor}.dll")) || - Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Editor)) - { - if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, api_config)) - return; // Redundant? I don't think so! - } - } - private void _RemoveCreateSlnMenuOption() { menuPopup.RemoveItem(menuPopup.GetItemIndex((int) MenuOptions.CreateSln)); @@ -465,9 +397,6 @@ namespace GodotTools if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) { - // Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized. - CallDeferred(nameof(_MakeApiSolutionsIfNeeded)); - // Make sure the existing project has Api assembly references configured correctly CSharpProject.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath); } @@ -521,7 +450,7 @@ namespace GodotTools // Export plugin var exportPlugin = new GodotSharpExport(); AddExportPlugin(exportPlugin); - exportPluginWeak = new WeakReference<GodotSharpExport>(exportPlugin); + exportPluginWeak = WeakRef(exportPlugin); GodotSharpBuilds.Initialize(); } @@ -530,13 +459,15 @@ namespace GodotTools { base.Dispose(disposing); - if (exportPluginWeak.TryGetTarget(out var exportPlugin)) + if (exportPluginWeak != null) { // We need to dispose our export plugin before the editor destroys EditorSettings. // Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid // will be freed after EditorSettings already was, and its device polling thread // will try to access the EditorSettings singleton, resulting in null dereferencing. - exportPlugin.Dispose(); + (exportPluginWeak.GetRef() as GodotSharpExport)?.Dispose(); + + exportPluginWeak.Dispose(); } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index c33cb8b785..9526dd3c6f 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -10,6 +10,9 @@ namespace GodotTools.Internals public const string CSharpLanguageType = "CSharpScript"; public const string CSharpLanguageExtension = "cs"; + public static string UpdateApiAssembliesFromPrebuilt() => + internal_UpdateApiAssembliesFromPrebuilt(); + public static string FullTemplatesDir => internal_FullTemplatesDir(); @@ -17,14 +20,6 @@ namespace GodotTools.Internals public static bool IsOsxAppBundleInstalled(string bundleId) => internal_IsOsxAppBundleInstalled(bundleId); - public static bool MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType) => - internal_MetadataIsApiAssemblyInvalidated(apiType); - - public static void MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated) => - internal_MetadataSetApiAssemblyInvalidated(apiType, invalidated); - - public static bool IsMessageQueueFlushing() => internal_IsMessageQueueFlushing(); - public static bool GodotIs32Bits() => internal_GodotIs32Bits(); public static bool GodotIsRealTDouble() => internal_GodotIsRealTDouble(); @@ -54,6 +49,9 @@ namespace GodotTools.Internals // Internal Calls [MethodImpl(MethodImplOptions.InternalCall)] + private static extern string internal_UpdateApiAssembliesFromPrebuilt(); + + [MethodImpl(MethodImplOptions.InternalCall)] private static extern string internal_FullTemplatesDir(); [MethodImpl(MethodImplOptions.InternalCall)] @@ -63,15 +61,6 @@ namespace GodotTools.Internals private static extern bool internal_IsOsxAppBundleInstalled(string bundleId); [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_IsMessageQueueFlushing(); - - [MethodImpl(MethodImplOptions.InternalCall)] private static extern bool internal_GodotIs32Bits(); [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 3813c0ce45..0014aaca70 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -30,7 +30,6 @@ #include "editor_internal_calls.h" -#include "core/message_queue.h" #include "core/os/os.h" #include "core/version.h" #include "editor/editor_node.h" @@ -245,7 +244,7 @@ MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_d 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 = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed); + Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed); return GDMonoMarshal::variant_to_mono_object(result); } @@ -254,6 +253,11 @@ MonoString *godot_icall_Globals_TTR(MonoString *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(); + return GDMonoMarshal::mono_string_from_godot(error_str); +} + MonoString *godot_icall_Internal_FullTemplatesDir() { String full_templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); return GDMonoMarshal::mono_string_from_godot(full_templates_dir); @@ -274,18 +278,6 @@ MonoBoolean godot_icall_Internal_IsOsxAppBundleInstalled(MonoString *p_bundle_id #endif } -MonoBoolean godot_icall_Internal_MetadataIsApiAssemblyInvalidated(int32_t p_api_type) { - return GDMono::get_singleton()->metadata_is_api_assembly_invalidated((APIAssembly::Type)p_api_type); -} - -void godot_icall_Internal_MetadataSetApiAssemblyInvalidated(int32_t p_api_type, MonoBoolean p_invalidated) { - GDMono::get_singleton()->metadata_set_api_assembly_invalidated((APIAssembly::Type)p_api_type, (bool)p_invalidated); -} - -MonoBoolean godot_icall_Internal_IsMessageQueueFlushing() { - return (MonoBoolean)MessageQueue::get_singleton()->is_flushing(); -} - MonoBoolean godot_icall_Internal_GodotIs32Bits() { return sizeof(void *) == 4; } @@ -407,12 +399,10 @@ void register_editor_internal_calls() { mono_add_internal_call("GodotTools.GodotSharpExport::internal_GetExportedAssemblyDependencies", (void *)godot_icall_GodotSharpExport_GetExportedAssemblyDependencies); // Internals + mono_add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", (void *)godot_icall_Internal_UpdateApiAssembliesFromPrebuilt); mono_add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", (void *)godot_icall_Internal_FullTemplatesDir); mono_add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", (void *)godot_icall_Internal_SimplifyGodotPath); mono_add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", (void *)godot_icall_Internal_IsOsxAppBundleInstalled); - mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataIsApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataIsApiAssemblyInvalidated); - mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataSetApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataSetApiAssemblyInvalidated); - mono_add_internal_call("GodotTools.Internals.Internal::internal_IsMessageQueueFlushing", (void *)godot_icall_Internal_IsMessageQueueFlushing); mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", (void *)godot_icall_Internal_GodotIs32Bits); mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", (void *)godot_icall_Internal_GodotIsRealTDouble); mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", (void *)godot_icall_Internal_GodotMainIteration); diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 06fbae019c..096ad0f5e3 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -59,10 +59,6 @@ #include "android_mono_config.gen.h" #endif -#define OUT_OF_SYNC_ERR_MESSAGE(m_assembly_name) "The assembly '" m_assembly_name "' is out of sync. " \ - "This error is expected if you just upgraded to a newer Godot version. " \ - "Building the project will update the assembly to the correct version." - GDMono *GDMono::singleton = NULL; namespace { @@ -304,26 +300,9 @@ void GDMono::initialize() { mono_install_unhandled_exception_hook(&unhandled_exception_hook, NULL); #ifndef TOOLS_ENABLED - if (!DirAccess::exists("res://.mono")) { - // 'res://.mono/' is missing so there is nothing to load. We don't need to initialize mono, but - // we still do so unless mscorlib is missing (which is the case for projects that don't use C#). - - String mscorlib_fname("mscorlib.dll"); - - Vector<String> search_dirs; - GDMonoAssembly::fill_search_dirs(search_dirs); - - bool found = false; - for (int i = 0; i < search_dirs.size(); i++) { - if (FileAccess::exists(search_dirs[i].plus_file(mscorlib_fname))) { - found = true; - break; - } - } - - if (!found) - return; // mscorlib is missing, do not initialize mono - } + // Export templates only load the Mono runtime if the project uses it + if (!DirAccess::exists("res://.mono")) + return; #endif root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); @@ -354,63 +333,48 @@ void GDMono::initialize() { _register_internal_calls(); - // The following assemblies are not required at initialization -#ifdef MONO_GLUE_ENABLED - if (_load_api_assemblies()) { - // Everything is fine with the api assemblies, load the tools and project assemblies - -#if defined(TOOLS_ENABLED) - ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies"); - ERR_FAIL_COND(!_load_tools_assemblies()); -#endif + print_verbose("Mono: INITIALIZED"); +} - _load_project_assembly(); +void GDMono::initialize_load_assemblies() { - } else { - if ((core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated)) -#ifdef TOOLS_ENABLED - || (editor_api_assembly && editor_api_assembly_out_of_sync) +#ifndef MONO_GLUE_ENABLED + ERR_EXPLAIN("Mono: This binary was built with `mono_glue=no`; cannot load assemblies"); + CRASH_NOW(); #endif - ) { -#ifdef TOOLS_ENABLED - // The assembly was successfully loaded, but the full api could not be cached. - // This is most likely an outdated assembly loaded because of an invalid version in the - // metadata, so we invalidate the version in the metadata and unload the script domain. - - if (core_api_assembly_out_of_sync) { - ERR_PRINT(OUT_OF_SYNC_ERR_MESSAGE(CORE_API_ASSEMBLY_NAME)); - metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); - } 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"); - metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); - } - if (editor_api_assembly_out_of_sync) { - ERR_PRINT(OUT_OF_SYNC_ERR_MESSAGE(EDITOR_API_ASSEMBLY_NAME)); - metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true); - } + // Load assemblies. The API and tools assemblies are required, + // the application is aborted if these assemblies cannot be loaded. - print_line("Mono: Proceeding to unload scripts domain because of invalid API assemblies."); + _load_api_assemblies(); - Error err = _unload_scripts_domain(); - if (err != OK) { - WARN_PRINT("Mono: Failed to unload scripts domain"); - } -#else - ERR_PRINT("The loaded API assembly is invalid"); - CRASH_NOW(); -#endif // TOOLS_ENABLED - } +#if defined(TOOLS_ENABLED) + if (!_load_tools_assemblies()) { + ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies"); + CRASH_NOW(); } -#else - print_verbose("Mono: Glue disabled, ignoring script assemblies."); -#endif // MONO_GLUE_ENABLED +#endif - print_verbose("Mono: INITIALIZED"); + // Load the project's main assembly. This doesn't necessarily need to succeed. + // The game may not be using .NET at all, or if the project does use .NET and + // we're running in the editor, it may just happen to be it wasn't built yet. + if (!_load_project_assembly()) { + if (OS::get_singleton()->is_stdout_verbose()) + print_error("Mono: Failed to load project assembly"); + } +} + +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); +#ifdef TOOLS_ENABLED + if (!out_of_sync) + out_of_sync = editor_api_assembly && editor_api_assembly_out_of_sync; +#endif + return out_of_sync; } -#ifdef MONO_GLUE_ENABLED namespace GodotSharpBindings { +#ifdef MONO_GLUE_ENABLED uint64_t get_core_api_hash(); #ifdef TOOLS_ENABLED @@ -419,13 +383,33 @@ uint64_t get_editor_api_hash(); uint32_t get_bindings_version(); void register_generated_icalls(); -} // namespace GodotSharpBindings + +#else + +uint64_t get_core_api_hash() { + CRASH_NOW(); + GD_UNREACHABLE(); +} +#ifdef TOOLS_ENABLED +uint64_t get_editor_api_hash() { + CRASH_NOW(); + GD_UNREACHABLE(); +} #endif +uint32_t get_bindings_version() { + CRASH_NOW(); + GD_UNREACHABLE(); +} + +void register_generated_icalls() { + /* Fine, just do nothing */ +} + +#endif // MONO_GLUE_ENABLED +} // namespace GodotSharpBindings void GDMono::_register_internal_calls() { -#ifdef MONO_GLUE_ENABLED GodotSharpBindings::register_generated_icalls(); -#endif } void GDMono::_initialize_and_check_api_hashes() { @@ -565,12 +549,21 @@ bool GDMono::_load_corlib_assembly() { } #ifdef TOOLS_ENABLED -static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) { +bool GDMono::copy_prebuilt_api_assembly(APIAssembly::Type p_api_type) { + + 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; + + String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); + String dst_dir = GodotSharpDirs::get_res_assemblies_dir(); + + String assembly_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; // Create destination directory if needed - if (!DirAccess::exists(p_dst_dir)) { - DirAccess *da = DirAccess::create_for_path(p_dst_dir); - Error err = da->make_dir_recursive(p_dst_dir); + if (!DirAccess::exists(dst_dir)) { + DirAccess *da = DirAccess::create_for_path(dst_dir); + Error err = da->make_dir_recursive(dst_dir); memdelete(da); if (err != OK) { @@ -579,21 +572,19 @@ static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, } } - String assembly_file = p_assembly_name + ".dll"; - String assembly_src = p_src_dir.plus_file(assembly_file); - String assembly_dst = p_dst_dir.plus_file(assembly_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 (!FileAccess::exists(assembly_dst) || - FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) || - GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) { + if (!FileAccess::exists(assembly_dst) || api_assembly_out_of_sync) { DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - String xml_file = p_assembly_name + ".xml"; - if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK) + 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 = p_assembly_name + ".pdb"; - if (da->copy(p_src_dir.plus_file(pdb_file), p_dst_dir.plus_file(pdb_file)) != OK) + 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); Error err = da->copy(assembly_src, assembly_dst); @@ -603,11 +594,46 @@ static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, return false; } - GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false); + api_assembly_out_of_sync = false; } return true; } + +String GDMono::update_api_assemblies_from_prebuilt() { + +#define FAIL_REASON(m_out_of_sync, m_prebuilt_exist) \ + ( \ + (m_out_of_sync ? \ + String("The assembly is invalidated") : \ + String("The assembly was not found")) + \ + (m_prebuilt_exist ? \ + 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 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"); + + if (!api_assembly_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("Debug"); + 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"); + + if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) + return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exist: */ false); + + // Copy the prebuilt Api + if (!copy_prebuilt_api_assembly(APIAssembly::API_CORE) || !copy_prebuilt_api_assembly(APIAssembly::API_EDITOR)) + return FAIL_REASON(api_assembly_out_of_sync, /* prebuilt_exist: */ true); + + return String(); // Updated successfully + +#undef FAIL_REASON +} #endif bool GDMono::_load_core_api_assembly() { @@ -616,35 +642,15 @@ bool GDMono::_load_core_api_assembly() { return true; #ifdef TOOLS_ENABLED - if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) { - String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); - String prebuilt_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String invalidated_dll_path = get_invalidated_api_assembly_path(APIAssembly::API_CORE); - - if (!FileAccess::exists(prebuilt_dll_path) || - FileAccess::get_modified_time(invalidated_dll_path) == FileAccess::get_modified_time(prebuilt_dll_path)) { - print_verbose("Mono: Skipping loading of Core API assembly because it was invalidated"); - return false; - } else { - // Copy the prebuilt Api - String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); - if (!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, CORE_API_ASSEMBLY_NAME, APIAssembly::API_CORE) || - !copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, EDITOR_API_ASSEMBLY_NAME, APIAssembly::API_EDITOR)) { - print_verbose("Mono: Failed to copy prebuilt API. Skipping loading of Core API assembly because it was invalidated"); - return false; - } - } - } -#endif - + // 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 String assembly_path = GodotSharpDirs::get_res_assemblies_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(CORE_API_ASSEMBLY_NAME, &core_api_assembly); + bool success = FileAccess::exists(assembly_path) && + load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly); +#else + bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly); +#endif if (success) { -#ifdef MONO_GLUE_ENABLED 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 || @@ -654,9 +660,8 @@ bool GDMono::_load_core_api_assembly() { _install_trace_listener(); } -#else - GDMonoUtils::update_godot_api_cache(); -#endif + } else { + core_api_assembly_out_of_sync = false; } return success; @@ -668,44 +673,100 @@ bool GDMono::_load_editor_api_assembly() { if (editor_api_assembly) return true; - if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) { - String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"); - String prebuilt_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - String invalidated_dll_path = get_invalidated_api_assembly_path(APIAssembly::API_EDITOR); - - if (!FileAccess::exists(prebuilt_dll_path) || - FileAccess::get_modified_time(invalidated_dll_path) == FileAccess::get_modified_time(prebuilt_dll_path)) { - print_verbose("Mono: Skipping loading of Editor API assembly because it was invalidated"); - return false; - } else { - // Copy the prebuilt editor Api (no need to copy the core api if we got to this point) - String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); - if (!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, EDITOR_API_ASSEMBLY_NAME, APIAssembly::API_EDITOR)) { - print_verbose("Mono: Failed to copy prebuilt API. Skipping loading of Editor API assembly because it was invalidated"); - return false; - } - } - } - + // 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 String assembly_path = GodotSharpDirs::get_res_assemblies_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(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly); + bool success = FileAccess::exists(assembly_path) && + load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly); if (success) { -#ifdef MONO_GLUE_ENABLED 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 || CS_GLUE_VERSION != api_assembly_ver.cs_glue_version; -#endif + } else { + editor_api_assembly_out_of_sync = false; } return success; } #endif +bool GDMono::_try_load_api_assemblies() { + + if (!_load_core_api_assembly()) { + if (OS::get_singleton()->is_stdout_verbose()) + print_error("Mono: Failed to load Core API assembly"); + return false; + } + + if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated) + return false; + +#ifdef TOOLS_ENABLED + if (!_load_editor_api_assembly()) { + 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) + return false; +#endif + + return true; +} + +void GDMono::_load_api_assemblies() { + + if (!_try_load_api_assemblies()) { + // 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. + + // 1. Unload the scripts domain + if (_unload_scripts_domain() != OK) { + ERR_EXPLAIN("Mono: Failed to unload scripts domain"); + CRASH_NOW(); + } + + // 2. Update the API assemblies + String update_error = update_api_assemblies_from_prebuilt(); + if (!update_error.empty()) { + ERR_EXPLAIN(update_error); + CRASH_NOW(); + } + + // 3. Load the scripts domain again + if (_load_scripts_domain() != OK) { + ERR_EXPLAIN("Mono: Failed to load scripts domain"); + CRASH_NOW(); + } + + // 4. Try loading the updated assemblies + if (!_try_load_api_assemblies()) { + // welp... too bad + + if (_are_api_assemblies_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) { + ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync"); + } +#endif + + CRASH_NOW(); + } else { + ERR_EXPLAIN("Failed to load one of the API assemblies"); + CRASH_NOW(); + } + } + } +} + #ifdef TOOLS_ENABLED bool GDMono::_load_tools_assemblies() { @@ -734,39 +795,11 @@ bool GDMono::_load_project_assembly() { if (success) { mono_assembly_set_main(project_assembly->get_assembly()); - } else { - if (OS::get_singleton()->is_stdout_verbose()) - print_error("Mono: Failed to load project assembly"); } return success; } -bool GDMono::_load_api_assemblies() { - - if (!_load_core_api_assembly()) { - if (OS::get_singleton()->is_stdout_verbose()) - print_error("Mono: Failed to load Core API assembly"); - return false; - } - - if (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated) - return false; - -#ifdef TOOLS_ENABLED - if (!_load_editor_api_assembly()) { - 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) - return false; -#endif - - return true; -} - void GDMono::_install_trace_listener() { #ifdef DEBUG_ENABLED @@ -784,78 +817,6 @@ void GDMono::_install_trace_listener() { #endif } -#ifdef TOOLS_ENABLED -String GDMono::_get_api_assembly_metadata_path() { - - return GodotSharpDirs::get_res_metadata_dir().plus_file("api_assemblies.cfg"); -} - -void GDMono::metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated) { - - String section = APIAssembly::to_string(p_api_type); - String path = _get_api_assembly_metadata_path(); - - Ref<ConfigFile> metadata; - metadata.instance(); - metadata->load(path); - - metadata->set_value(section, "invalidated", p_invalidated); - - String assembly_path = GodotSharpDirs::get_res_assemblies_dir() - .plus_file(p_api_type == APIAssembly::API_CORE ? - CORE_API_ASSEMBLY_NAME ".dll" : - EDITOR_API_ASSEMBLY_NAME ".dll"); - - ERR_FAIL_COND(!FileAccess::exists(assembly_path)); - - uint64_t modified_time = FileAccess::get_modified_time(assembly_path); - - metadata->set_value(section, "invalidated_asm_modified_time", String::num_uint64(modified_time)); - - String dir = path.get_base_dir(); - if (!DirAccess::exists(dir)) { - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND(!da); - Error err = da->make_dir_recursive(ProjectSettings::get_singleton()->globalize_path(dir)); - ERR_FAIL_COND(err != OK); - } - - Error save_err = metadata->save(path); - ERR_FAIL_COND(save_err != OK); -} - -bool GDMono::metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type) { - - String section = APIAssembly::to_string(p_api_type); - - Ref<ConfigFile> metadata; - metadata.instance(); - metadata->load(_get_api_assembly_metadata_path()); - - String assembly_path = GodotSharpDirs::get_res_assemblies_dir() - .plus_file(p_api_type == APIAssembly::API_CORE ? - CORE_API_ASSEMBLY_NAME ".dll" : - EDITOR_API_ASSEMBLY_NAME ".dll"); - - if (!FileAccess::exists(assembly_path)) - return false; - - uint64_t modified_time = FileAccess::get_modified_time(assembly_path); - - uint64_t stored_modified_time = metadata->get_value(section, "invalidated_asm_modified_time", 0); - - return metadata->get_value(section, "invalidated", false) && modified_time <= stored_modified_time; -} - -String GDMono::get_invalidated_api_assembly_path(APIAssembly::Type p_api_type) { - - return GodotSharpDirs::get_res_assemblies_dir() - .plus_file(p_api_type == APIAssembly::API_CORE ? - CORE_API_ASSEMBLY_NAME ".dll" : - EDITOR_API_ASSEMBLY_NAME ".dll"); -} -#endif - Error GDMono::_load_scripts_domain() { ERR_FAIL_COND_V(scripts_domain != NULL, ERR_BUG); @@ -903,11 +864,6 @@ Error GDMono::_unload_scripts_domain() { tools_project_editor_assembly = NULL; #endif - core_api_assembly_out_of_sync = false; -#ifdef TOOLS_ENABLED - editor_api_assembly_out_of_sync = false; -#endif - MonoDomain *domain = scripts_domain; scripts_domain = NULL; @@ -944,57 +900,25 @@ Error GDMono::reload_scripts_domain() { return err; } -#ifdef MONO_GLUE_ENABLED - if (!_load_api_assemblies()) { - if ((core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated)) -#ifdef TOOLS_ENABLED - || (editor_api_assembly && editor_api_assembly_out_of_sync) -#endif - ) { -#ifdef TOOLS_ENABLED - // The assembly was successfully loaded, but the full api could not be cached. - // This is most likely an outdated assembly loaded because of an invalid version in the - // metadata, so we invalidate the version in the metadata and unload the script domain. - - if (core_api_assembly_out_of_sync) { - ERR_PRINT(OUT_OF_SYNC_ERR_MESSAGE(CORE_API_ASSEMBLY_NAME)); - metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); - } else if (!GDMonoUtils::mono_cache.godot_api_cache_updated) { - ERR_PRINT("The loaded Core API assembly is in sync, but the cache update failed"); - metadata_set_api_assembly_invalidated(APIAssembly::API_CORE, true); - } - - if (editor_api_assembly_out_of_sync) { - ERR_PRINT(OUT_OF_SYNC_ERR_MESSAGE(EDITOR_API_ASSEMBLY_NAME)); - metadata_set_api_assembly_invalidated(APIAssembly::API_EDITOR, true); - } + // Load assemblies. The API and tools assemblies are required, + // the application is aborted if these assemblies cannot be loaded. - err = _unload_scripts_domain(); - if (err != OK) { - WARN_PRINT("Mono: Failed to unload scripts domain"); - } + _load_api_assemblies(); - return ERR_CANT_RESOLVE; -#else - ERR_PRINT("The loaded API assembly is invalid"); - CRASH_NOW(); -#endif - } else { - return ERR_CANT_OPEN; - } +#if defined(TOOLS_ENABLED) + if (!_load_tools_assemblies()) { + ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies"); + CRASH_NOW(); } - -#ifdef TOOLS_ENABLED - ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies"); - ERR_FAIL_COND_V(!_load_tools_assemblies(), ERR_CANT_OPEN); #endif + // Load the project's main assembly. Here, during hot-reloading, we do + // consider failing to load the project's main assembly to be an error. + // However, unlike the API and tools assemblies, the application can continue working. if (!_load_project_assembly()) { + print_error("Mono: Failed to load project assembly"); return ERR_CANT_OPEN; } -#else - print_verbose("Mono: Glue disabled, ignoring script assemblies."); -#endif // MONO_GLUE_ENABLED return OK; } diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index a926bf4126..deebe5fd50 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -104,6 +104,8 @@ class GDMono { void _domain_assemblies_cleanup(uint32_t p_domain_id); + bool _are_api_assemblies_out_of_sync(); + bool _load_corlib_assembly(); bool _load_core_api_assembly(); #ifdef TOOLS_ENABLED @@ -112,11 +114,8 @@ class GDMono { #endif bool _load_project_assembly(); - bool _load_api_assemblies(); - -#ifdef TOOLS_ENABLED - String _get_api_assembly_metadata_path(); -#endif + bool _try_load_api_assemblies(); + void _load_api_assemblies(); void _install_trace_listener(); @@ -157,9 +156,8 @@ public: #endif #ifdef TOOLS_ENABLED - void metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated); - bool metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type); - String get_invalidated_api_assembly_path(APIAssembly::Type p_api_type); + bool copy_prebuilt_api_assembly(APIAssembly::Type p_api_type); + String update_api_assemblies_from_prebuilt(); #endif static GDMono *get_singleton() { return singleton; } @@ -203,6 +201,7 @@ public: Error finalize_and_unload_domain(MonoDomain *p_domain); void initialize(); + void initialize_load_assemblies(); GDMono(); ~GDMono(); |