From 7e745753fcfdce8949d0f0040bffba877abb4518 Mon Sep 17 00:00:00 2001 From: DE-YU_H14 Date: Mon, 14 Oct 2024 04:57:42 +0800 Subject: [PATCH] Editor: Stop loading dotnet project assembly when `TOOLS` is not defined to prevent data loss --- .../GodotPluginsInitializerGenerator.cs | 6 ++++ .../mono/glue/GodotSharp/GodotPlugins/Main.cs | 20 ++++++++++-- modules/mono/mono_gd/gd_mono.cpp | 32 +++++++++++++++---- modules/mono/mono_gd/gd_mono.h | 4 +-- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs index 467313dc28d7..027f4607d982 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs @@ -52,6 +52,12 @@ private static godot_bool InitializeFromGameProject(IntPtr godotDllHandle, IntPt return false.ToGodotBool(); } } + +#if TOOLS +#pragma warning disable CS0169 + private static bool ToolsDefined; +#pragma warning restore CS0169 +#endif } } "; diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs index 6117ae17eaaa..ad3060a47526 100644 --- a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs +++ b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs @@ -131,19 +131,20 @@ private static unsafe godot_bool InitializeFromEngine(IntPtr godotDllHandle, god [StructLayout(LayoutKind.Sequential)] private struct PluginsCallbacks { - public unsafe delegate* unmanaged LoadProjectAssemblyCallback; + public unsafe delegate* unmanaged LoadProjectAssemblyCallback; public unsafe delegate* unmanaged LoadToolsAssemblyCallback; public unsafe delegate* unmanaged UnloadProjectPluginCallback; } [UnmanagedCallersOnly] - private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath, godot_string* outLoadedAssemblyPath) + private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath, godot_string* outLoadedAssemblyPath, godot_string* outFailedReason, godot_bool* shouldRetry) { try { if (_projectLoadContext != null) return godot_bool.True; // Already loaded + *shouldRetry = godot_bool.False; string assemblyPath = new(nAssemblyPath); (var projectAssembly, _projectLoadContext) = LoadPlugin(assemblyPath, isCollectible: _editorHint); @@ -151,6 +152,20 @@ private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath, godot_ string loadedAssemblyPath = _projectLoadContext.AssemblyLoadedPath ?? assemblyPath; *outLoadedAssemblyPath = Marshaling.ConvertStringToNative(loadedAssemblyPath); + // This generated field is here to ensure that + // the developer do not drop the TOOLS compiler preprocessor + // from their csproj, which breaks the C# Editor Functionality. + var isToolsDefinedField = + projectAssembly + .GetType("GodotPlugins.Game.Main")? + .GetField("ToolsDefined", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + + if (isToolsDefinedField == null) + { + *outFailedReason = Marshaling.ConvertStringToNative("The csproj was missing the `TOOLS` compiler symbol, which is required for C# editor functionality. Please modify the csproj file and rebuild the C# project, check https://github.com/godotengine/godot/issues/98124 for more information."); + return godot_bool.False; + } + ScriptManagerBridge.LookupScriptsInAssembly(projectAssembly); return godot_bool.True; @@ -158,6 +173,7 @@ private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath, godot_ catch (Exception e) { Console.Error.WriteLine(e); + *outFailedReason = Marshaling.ConvertStringToNative(e.Message); return godot_bool.False; } } diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index c81aa91fa5f5..ad0c02e1c008 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -605,9 +605,15 @@ void GDMono::_try_load_project_assembly() { // 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()) { + String error_message; + bool should_retry; + if (!_load_project_assembly(&error_message, &should_retry)) { if (OS::get_singleton()->is_stdout_verbose()) { - print_error(".NET: Failed to load project assembly"); + if (error_message.is_empty()) { + print_error(".NET: Failed to load project assembly"); + } else { + print_error(".NET: Failed to load project assembly: " + error_message); + } } } } @@ -624,7 +630,9 @@ void GDMono::_init_godot_api_hashes() { } #ifdef TOOLS_ENABLED -bool GDMono::_load_project_assembly() { +bool GDMono::_load_project_assembly(String *p_error_message, bool *p_should_retry) { + *p_should_retry = true; + String assembly_name = path::get_csharp_project_name(); String assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir() @@ -632,11 +640,12 @@ bool GDMono::_load_project_assembly() { assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path); if (!FileAccess::exists(assembly_path)) { + *p_error_message = String("Assembly Path \"" + assembly_path + "\" does not exist"); return false; } String loaded_assembly_path; - bool success = plugin_callbacks.LoadProjectAssemblyCallback(assembly_path.utf16(), &loaded_assembly_path); + bool success = plugin_callbacks.LoadProjectAssemblyCallback(assembly_path.utf16(), &loaded_assembly_path, p_error_message, p_should_retry); if (success) { project_assembly_path = loaded_assembly_path.simplify_path(); @@ -679,10 +688,19 @@ Error GDMono::reload_project_assemblies() { // 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. - if (!_load_project_assembly()) { - ERR_PRINT_ED(".NET: Failed to load project assembly."); + String error_message; + bool should_retry; + if (!_load_project_assembly(&error_message, &should_retry)) { + if (error_message.is_empty()) { + ERR_PRINT_ED(".NET: Failed to load project assembly."); + } else { + ERR_PRINT_ED(".NET: Failed to load project assembly: " + error_message); + } + if (!should_retry) { + project_load_failure_count = (int)GLOBAL_GET("dotnet/project/assembly_reload_attempts"); + } reload_failure(); - return ERR_CANT_OPEN; + return FAILED; } if (project_load_failure_count > 0) { diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index fae3421ac9ec..9357fa306ced 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -47,7 +47,7 @@ namespace gdmono { #ifdef TOOLS_ENABLED struct PluginCallbacks { - using FuncLoadProjectAssemblyCallback = bool(GD_CLR_STDCALL *)(const char16_t *, String *); + using FuncLoadProjectAssemblyCallback = bool(GD_CLR_STDCALL *)(const char16_t *, String *, String *, bool *); using FuncLoadToolsAssemblyCallback = Object *(GD_CLR_STDCALL *)(const char16_t *, const void **, int32_t); using FuncUnloadProjectPluginCallback = bool(GD_CLR_STDCALL *)(); FuncLoadProjectAssemblyCallback LoadProjectAssemblyCallback = nullptr; @@ -73,7 +73,7 @@ class GDMono { #endif #ifdef TOOLS_ENABLED - bool _load_project_assembly(); + bool _load_project_assembly(String *p_error_message, bool *p_should_retry); void _try_load_project_assembly(); #endif