Skip to content

Commit

Permalink
Editor: Stop loading dotnet project assembly when TOOLS is not defi…
Browse files Browse the repository at this point in the history
…ned to prevent data loss
  • Loading branch information
Delsin-Yu committed Oct 13, 2024
1 parent 92e51fc commit 7e74575
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
";
Expand Down
20 changes: 18 additions & 2 deletions modules/mono/glue/GodotSharp/GodotPlugins/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,33 +131,49 @@ private static unsafe godot_bool InitializeFromEngine(IntPtr godotDllHandle, god
[StructLayout(LayoutKind.Sequential)]
private struct PluginsCallbacks
{
public unsafe delegate* unmanaged<char*, godot_string*, godot_bool> LoadProjectAssemblyCallback;
public unsafe delegate* unmanaged<char*, godot_string*, godot_string*, godot_bool*, godot_bool> LoadProjectAssemblyCallback;
public unsafe delegate* unmanaged<char*, IntPtr, int, IntPtr> LoadToolsAssemblyCallback;
public unsafe delegate* unmanaged<godot_bool> 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);

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;
}
catch (Exception e)
{
Console.Error.WriteLine(e);
*outFailedReason = Marshaling.ConvertStringToNative(e.Message);
return godot_bool.False;
}
}
Expand Down
32 changes: 25 additions & 7 deletions modules/mono/mono_gd/gd_mono.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
}
Expand All @@ -624,19 +630,22 @@ 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()
.path_join(assembly_name + ".dll");
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();
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions modules/mono/mono_gd/gd_mono.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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

Expand Down

0 comments on commit 7e74575

Please sign in to comment.