diff options
Diffstat (limited to 'modules/mono')
35 files changed, 470 insertions, 619 deletions
diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index 0b91cda9b8..580f51c973 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -286,15 +286,29 @@ def generate_sdk_package_versions(): version_status = version_status[:pos] + "." + version_status[pos:] version_str += "-" + version_status + import version + + version_defines = ( + [ + f"GODOT{version.major}", + f"GODOT{version.major}_{version.minor}", + f"GODOT{version.major}_{version.minor}_{version.patch}", + ] + + [f"GODOT{v}_OR_GREATER" for v in range(4, version.major + 1)] + + [f"GODOT{version.major}_{v}_OR_GREATER" for v in range(0, version.minor + 1)] + + [f"GODOT{version.major}_{version.minor}_{v}_OR_GREATER" for v in range(0, version.patch + 1)] + ) + props = """<Project> <PropertyGroup> <PackageVersion_GodotSharp>{0}</PackageVersion_GodotSharp> <PackageVersion_Godot_NET_Sdk>{0}</PackageVersion_Godot_NET_Sdk> <PackageVersion_Godot_SourceGenerators>{0}</PackageVersion_Godot_SourceGenerators> + <GodotVersionConstants>{1}</GodotVersionConstants> </PropertyGroup> </Project> """.format( - version_str + version_str, ";".join(version_defines) ) # We write in ../SdkPackageVersions.props. diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index bc26352e9c..1ed495943f 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -120,6 +120,7 @@ void CSharpLanguage::init() { GLOBAL_DEF("dotnet/project/assembly_name", ""); #ifdef TOOLS_ENABLED GLOBAL_DEF("dotnet/project/solution_directory", ""); + GLOBAL_DEF(PropertyInfo(Variant::INT, "dotnet/project/assembly_reload_attempts", PROPERTY_HINT_RANGE, "1,16,1,or_greater"), 3); #endif gdmono = memnew(GDMono); @@ -770,10 +771,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { return; } - // TODO: - // Currently, this reloads all scripts, including those whose class is not part of the - // assembly load context being unloaded. As such, we unnecessarily reload GodotTools. - print_verbose(".NET: Reloading assemblies..."); // There is no soft reloading with Mono. It's always hard reloading. @@ -784,8 +781,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { MutexLock lock(script_instances_mutex); for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) { - // Cast to CSharpScript to avoid being erased by accident - scripts.push_back(Ref<CSharpScript>(elem->self())); + // Do not reload scripts with only non-collectible instances to avoid disrupting event subscriptions and such. + bool is_reloadable = elem->self()->instances.size() == 0; + for (Object *obj : elem->self()->instances) { + ERR_CONTINUE(!obj->get_script_instance()); + CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance()); + if (GDMonoCache::managed_callbacks.GCHandleBridge_GCHandleIsTargetCollectible(csi->get_gchandle_intptr())) { + is_reloadable = true; + break; + } + } + if (is_reloadable) { + // Cast to CSharpScript to avoid being erased by accident. + scripts.push_back(Ref<CSharpScript>(elem->self())); + } } } @@ -800,6 +809,10 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { ERR_CONTINUE(managed_callable->delegate_handle.value == nullptr); + if (!GDMonoCache::managed_callbacks.GCHandleBridge_GCHandleIsTargetCollectible(managed_callable->delegate_handle)) { + continue; + } + Array serialized_data; bool success = GDMonoCache::managed_callbacks.DelegateUtils_TrySerializeDelegateWithGCHandle( @@ -907,6 +920,15 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { scr->_clear(); } + // Release the delegates that were serialized earlier. + { + MutexLock lock(ManagedCallable::instances_mutex); + + for (KeyValue<ManagedCallable *, Array> &kv : ManagedCallable::instances_pending_reload) { + kv.key->release_delegate_handle(); + } + } + // Do domain reload if (gdmono->reload_project_assemblies() != OK) { // Failed to reload the scripts domain @@ -1158,19 +1180,6 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) { } } -void CSharpLanguage::_on_scripts_domain_about_to_unload() { -#ifdef GD_MONO_HOT_RELOAD - { - MutexLock lock(ManagedCallable::instances_mutex); - - for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) { - ManagedCallable *managed_callable = elem->self(); - managed_callable->release_delegate_handle(); - } - } -#endif -} - #ifdef TOOLS_ENABLED void CSharpLanguage::_editor_init_callback() { // Load GodotTools and initialize GodotSharpEditor @@ -2263,7 +2272,7 @@ void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) { // If the EditorFileSystem singleton is available, update the file; // otherwise, the file will be updated when the singleton becomes available. EditorFileSystem *efs = EditorFileSystem::get_singleton(); - if (efs) { + if (efs && !p_script->get_path().is_empty()) { efs->update_file(p_script->get_path()); } #endif diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index cfdf8ae6f8..9802067b46 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -347,7 +347,6 @@ class CSharpLanguage : public ScriptLanguage { String _debug_error; friend class GDMono; - void _on_scripts_domain_about_to_unload(); #ifdef TOOLS_ENABLED EditorPlugin *godotsharp_editor = nullptr; diff --git a/modules/mono/doc_classes/CSharpScript.xml b/modules/mono/doc_classes/CSharpScript.xml index e8da9d8465..b559ca20b2 100644 --- a/modules/mono/doc_classes/CSharpScript.xml +++ b/modules/mono/doc_classes/CSharpScript.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="CSharpScript" inherits="Script" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="CSharpScript" inherits="Script" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - A script implemented in the C# programming language (Mono-enabled builds only). + A script implemented in the C# programming language, saved with the [code].cs[/code] extension (Mono-enabled builds only). </brief_description> <description> This class represents a C# script. It is the C# equivalent of the [GDScript] class and is only available in Mono-enabled Godot builds. diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml index 76eefc4925..969ca14350 100644 --- a/modules/mono/doc_classes/GodotSharp.xml +++ b/modules/mono/doc_classes/GodotSharp.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GodotSharp" inherits="Object" version="4.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="GodotSharp" inherits="Object" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> Bridge between Godot and the Mono runtime (Mono-enabled builds only). </brief_description> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj index e8ad6a77ea..663eb14f07 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -7,7 +7,7 @@ <Authors>Godot Engine contributors</Authors> <PackageId>Godot.NET.Sdk</PackageId> - <Version>4.1.0</Version> + <Version>4.2.0</Version> <PackageVersion>$(PackageVersion_Godot_NET_Sdk)</PackageVersion> <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</RepositoryUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 45f930fdf7..b0bee795f8 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -74,15 +74,8 @@ <!-- Godot DefineConstants. --> <PropertyGroup> - <!-- Define constants to identify Godot builds and versions. --> - <GodotDefineConstants> - GODOT; - GODOT4;GODOT4_OR_GREATER; - GODOT4_1;GODOT4_1_OR_GREATER;GODOT4_0_OR_GREATER; - GODOT4_1_0;GODOT4_1_0_OR_GREATER; - </GodotDefineConstants> - <!-- Ensure the define constants don't contain whitespace (see https://github.com/dotnet/roslyn/issues/58391). --> - <GodotDefineConstants>$(GodotDefineConstants.Replace('%0A','').Replace('%0D','').Replace('%09','').Replace(' ',''))</GodotDefineConstants> + <!-- Define constants to identify Godot builds. --> + <GodotDefineConstants>GODOT</GodotDefineConstants> <!-- Define constant to determine the target Godot platform. This includes the @@ -97,7 +90,7 @@ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'ios' ">GODOT_IPHONE;GODOT_IOS;GODOT_MOBILE</GodotPlatformConstants> <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'web' ">GODOT_JAVASCRIPT;GODOT_HTML5;GODOT_WASM;GODOT_WEB</GodotPlatformConstants> - <GodotDefineConstants>$(GodotDefineConstants);$(GodotPlatformConstants)</GodotDefineConstants> + <GodotDefineConstants>$(GodotDefineConstants);$(GodotPlatformConstants);$(GodotVersionConstants)</GodotDefineConstants> </PropertyGroup> <PropertyGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index 8be1151142..72614dd7e0 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -384,5 +384,65 @@ namespace Godot.SourceGenerators typeArgumentSyntax.GetLocation(), typeArgumentSyntax.SyntaxTree.FilePath)); } + + public static readonly DiagnosticDescriptor GlobalClassMustDeriveFromGodotObjectRule = + new DiagnosticDescriptor(id: "GD0401", + title: "The class must derive from GodotObject or a derived class", + messageFormat: "The class '{0}' must derive from GodotObject or a derived class.", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The class must derive from GodotObject or a derived class. Change the base class or remove the '[GlobalClass]' attribute."); + + public static void ReportGlobalClassMustDeriveFromGodotObject( + SyntaxNodeAnalysisContext context, + SyntaxNode classSyntax, + ISymbol typeSymbol) + { + string message = $"The class '{typeSymbol.ToDisplayString()}' must derive from GodotObject or a derived class"; + + string description = $"{message}. Change the base class or remove the '[GlobalClass]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0401", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + classSyntax.GetLocation(), + classSyntax.SyntaxTree.FilePath)); + } + + public static readonly DiagnosticDescriptor GlobalClassMustNotBeGenericRule = + new DiagnosticDescriptor(id: "GD0402", + title: "The class must not contain generic arguments", + messageFormat: "The class '{0}' must not contain generic arguments", + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + "The class must be a non-generic type. Remove the generic arguments or the '[GlobalClass]' attribute."); + + public static void ReportGlobalClassMustNotBeGeneric( + SyntaxNodeAnalysisContext context, + SyntaxNode classSyntax, + ISymbol typeSymbol) + { + string message = $"The class '{typeSymbol.ToDisplayString()}' must not contain generic arguments"; + + string description = $"{message}. Remove the generic arguments or the '[GlobalClass]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GD0402", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + classSyntax.GetLocation(), + classSyntax.SyntaxTree.FilePath)); + } } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index 38af1cbade..b6ea4b8e88 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -37,7 +37,7 @@ namespace Godot.SourceGenerators while (symbol != null) { if (symbol.ContainingAssembly?.Name == assemblyName && - symbol.ToString() == typeFullName) + symbol.FullQualifiedNameOmitGlobal() == typeFullName) { return true; } @@ -81,7 +81,7 @@ namespace Godot.SourceGenerators return godotClassName ?? nativeType.Name; } - private static bool IsGodotScriptClass( + private static bool TryGetGodotScriptClass( this ClassDeclarationSyntax cds, Compilation compilation, out INamedTypeSymbol? symbol ) @@ -108,7 +108,7 @@ namespace Godot.SourceGenerators { foreach (var cds in source) { - if (cds.IsGodotScriptClass(compilation, out var symbol)) + if (cds.TryGetGodotScriptClass(compilation, out var symbol)) yield return (cds, symbol!); } } @@ -230,22 +230,22 @@ namespace Godot.SourceGenerators .Replace(">", ")"); public static bool IsGodotExportAttribute(this INamedTypeSymbol symbol) - => symbol.ToString() == GodotClasses.ExportAttr; + => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.ExportAttr; public static bool IsGodotSignalAttribute(this INamedTypeSymbol symbol) - => symbol.ToString() == GodotClasses.SignalAttr; + => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.SignalAttr; public static bool IsGodotMustBeVariantAttribute(this INamedTypeSymbol symbol) - => symbol.ToString() == GodotClasses.MustBeVariantAttr; + => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.MustBeVariantAttr; public static bool IsGodotClassNameAttribute(this INamedTypeSymbol symbol) - => symbol.ToString() == GodotClasses.GodotClassNameAttr; + => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.GodotClassNameAttr; public static bool IsGodotGlobalClassAttribute(this INamedTypeSymbol symbol) - => symbol.ToString() == GodotClasses.GlobalClassAttr; + => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.GlobalClassAttr; public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol) - => symbol.ToString() == GodotClasses.SystemFlagsAttr; + => symbol.FullQualifiedNameOmitGlobal() == GodotClasses.SystemFlagsAttr; public static GodotMethodData? HasGodotCompatibleSignature( this IMethodSymbol method, diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs new file mode 100644 index 0000000000..bcb35dae8a --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GlobalClassAnalyzer.cs @@ -0,0 +1,42 @@ +using System.Collections.Immutable; +using System.Linq; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Godot.SourceGenerators +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class GlobalClassAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics + => ImmutableArray.Create( + Common.GlobalClassMustDeriveFromGodotObjectRule, + Common.GlobalClassMustNotBeGenericRule); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration); + } + + private void AnalyzeNode(SyntaxNodeAnalysisContext context) + { + var typeClassDecl = (ClassDeclarationSyntax)context.Node; + + // Return if not a type symbol or the type is not a global class. + if (context.ContainingSymbol is not INamedTypeSymbol typeSymbol || + !typeSymbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotGlobalClassAttribute() ?? false)) + return; + + if (typeSymbol.IsGenericType) + Common.ReportGlobalClassMustNotBeGeneric(context, typeClassDecl, typeSymbol); + + if (!typeSymbol.InheritsFrom("GodotSharp", GodotClasses.GodotObject)) + Common.ReportGlobalClassMustDeriveFromGodotObject(context, typeClassDecl, typeSymbol); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj index 2557b70e75..a03c9bc06c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj @@ -9,7 +9,7 @@ <Authors>Godot Engine contributors</Authors> <PackageId>Godot.SourceGenerators</PackageId> - <Version>4.1.0</Version> + <Version>4.2.0</Version> <PackageVersion>$(PackageVersion_Godot_SourceGenerators)</PackageVersion> <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators</RepositoryUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index e856ad5c13..09a4ab538f 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -362,7 +362,7 @@ namespace Godot.SourceGenerators { foreach (var attr in memberSymbol.GetAttributes()) { - PropertyUsageFlags? propertyUsage = attr.AttributeClass?.ToString() switch + PropertyUsageFlags? propertyUsage = attr.AttributeClass?.FullQualifiedNameOmitGlobal() switch { GodotClasses.ExportCategoryAttr => PropertyUsageFlags.Category, GodotClasses.ExportGroupAttr => PropertyUsageFlags.Group, @@ -620,7 +620,7 @@ namespace Godot.SourceGenerators bool isPresetHint = false; - if (elementVariantType == VariantType.String) + if (elementVariantType == VariantType.String || elementVariantType == VariantType.StringName) isPresetHint = GetStringArrayEnumHint(elementVariantType, exportAttr, out hintString); if (!isPresetHint) diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 27963be00f..312c65e364 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -206,16 +206,16 @@ namespace GodotTools.Build private static bool BuildProjectBlocking(BuildInfo buildInfo) { - if (!File.Exists(buildInfo.Solution)) - return true; // No solution to build + if (!File.Exists(buildInfo.Project)) + return true; // No project to build. using var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1); - pr.Step("Building project solution", 0); + pr.Step("Building project", 0); if (!Build(buildInfo)) { - ShowBuildErrorDialog("Failed to build project solution"); + ShowBuildErrorDialog("Failed to build project"); return false; } @@ -224,16 +224,16 @@ namespace GodotTools.Build private static bool CleanProjectBlocking(BuildInfo buildInfo) { - if (!File.Exists(buildInfo.Solution)) - return true; // No solution to clean + if (!File.Exists(buildInfo.Project)) + return true; // No project to clean. using var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1); - pr.Step("Cleaning project solution", 0); + pr.Step("Cleaning project", 0); if (!Build(buildInfo)) { - ShowBuildErrorDialog("Failed to clean project solution"); + ShowBuildErrorDialog("Failed to clean project"); return false; } @@ -322,11 +322,11 @@ namespace GodotTools.Build public static bool EditorBuildCallback() { - if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) - return true; // No solution to build + if (!File.Exists(GodotSharpDirs.ProjectCsProjPath)) + return true; // No project to build. if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) - return true; // Requested play from an external editor/IDE which already built the project + return true; // Requested play from an external editor/IDE which already built the project. return BuildProjectBlocking("Debug"); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 8fe7d3c2d7..1bb1b3227e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -29,46 +29,46 @@ namespace GodotTools.Build BuildOutputView.UpdateIssuesList(); } - public void BuildSolution() + public void BuildProject() { - if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) - return; // No solution to build + if (!File.Exists(GodotSharpDirs.ProjectCsProjPath)) + return; // No project to build. if (!BuildManager.BuildProjectBlocking("Debug")) - return; // Build failed + return; // Build failed. - // Notify running game for hot-reload + // Notify running game for hot-reload. Internal.EditorDebuggerNodeReloadScripts(); - // Hot-reload in the editor + // Hot-reload in the editor. GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer(); if (Internal.IsAssembliesReloadingNeeded()) Internal.ReloadAssemblies(softReload: false); } - private void RebuildSolution() + private void RebuildProject() { - if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) - return; // No solution to build + if (!File.Exists(GodotSharpDirs.ProjectCsProjPath)) + return; // No project to build. if (!BuildManager.BuildProjectBlocking("Debug", rebuild: true)) - return; // Build failed + return; // Build failed. - // Notify running game for hot-reload + // Notify running game for hot-reload. Internal.EditorDebuggerNodeReloadScripts(); - // Hot-reload in the editor + // Hot-reload in the editor. GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer(); if (Internal.IsAssembliesReloadingNeeded()) Internal.ReloadAssemblies(softReload: false); } - private void CleanSolution() + private void CleanProject() { - if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) - return; // No solution to build + if (!File.Exists(GodotSharpDirs.ProjectCsProjPath)) + return; // No project to build. _ = BuildManager.CleanProjectBlocking("Debug"); } @@ -83,14 +83,14 @@ namespace GodotTools.Build { switch ((BuildMenuOptions)id) { - case BuildMenuOptions.BuildSolution: - BuildSolution(); + case BuildMenuOptions.BuildProject: + BuildProject(); break; - case BuildMenuOptions.RebuildSolution: - RebuildSolution(); + case BuildMenuOptions.RebuildProject: + RebuildProject(); break; - case BuildMenuOptions.CleanSolution: - CleanSolution(); + case BuildMenuOptions.CleanProject: + CleanProject(); break; default: throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid build menu option"); @@ -99,9 +99,9 @@ namespace GodotTools.Build private enum BuildMenuOptions { - BuildSolution, - RebuildSolution, - CleanSolution + BuildProject, + RebuildProject, + CleanProject } public override void _Ready() @@ -118,9 +118,9 @@ namespace GodotTools.Build toolBarHBox.AddChild(_buildMenuBtn); var buildMenu = _buildMenuBtn.GetPopup(); - buildMenu.AddItem("Build Solution".TTR(), (int)BuildMenuOptions.BuildSolution); - buildMenu.AddItem("Rebuild Solution".TTR(), (int)BuildMenuOptions.RebuildSolution); - buildMenu.AddItem("Clean Solution".TTR(), (int)BuildMenuOptions.CleanSolution); + buildMenu.AddItem("Build Project".TTR(), (int)BuildMenuOptions.BuildProject); + buildMenu.AddItem("Rebuild Project".TTR(), (int)BuildMenuOptions.RebuildProject); + buildMenu.AddItem("Clean Project".TTR(), (int)BuildMenuOptions.CleanProject); buildMenu.IdPressed += BuildMenuOptionPressed; _errorsBtn = new Button diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 4e33b38ac2..622a155d37 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -140,15 +140,15 @@ namespace GodotTools } } - private void BuildSolutionPressed() + private void BuildProjectPressed() { - if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) + if (!File.Exists(GodotSharpDirs.ProjectCsProjPath)) { if (!CreateProjectSolution()) - return; // Failed to create solution + return; // Failed to create project. } - Instance.MSBuildPanel.BuildSolution(); + Instance.MSBuildPanel.BuildProject(); } private enum MenuOptions @@ -507,10 +507,10 @@ namespace GodotTools Shortcut = buildSolutionShortcut, ShortcutInTooltip = true }; - _toolBarBuildButton.Pressed += BuildSolutionPressed; + _toolBarBuildButton.Pressed += BuildProjectPressed; AddControlToContainer(CustomControlContainer.Toolbar, _toolBarBuildButton); - if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath)) + if (File.Exists(GodotSharpDirs.ProjectCsProjPath)) { ApplyNecessaryChangesToSolution(); } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index 30525ba04a..4a0b7f9bed 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -28,10 +28,9 @@ </PropertyGroup> <ItemGroup> <PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" /> + <PackageReference Include="JetBrains.Rider.PathLocator" Version="1.0.1" /> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> - <!-- For RiderPathLocator --> - <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <Reference Include="GodotSharp"> <HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath> <Private>False</Private> diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs index 62db6e3af5..51c7a8aa22 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs @@ -385,9 +385,12 @@ namespace GodotTools.Ides // However, it doesn't fix resource loading if the rest of the path is also case insensitive. string scriptFileLocalized = FsPathUtils.LocalizePathWithCaseChecked(request.ScriptFile); + // The node API can only be called from the main thread. + await Godot.Engine.GetMainLoop().ToSignal(Godot.Engine.GetMainLoop(), "process_frame"); + var response = new CodeCompletionResponse { Kind = request.Kind, ScriptFile = request.ScriptFile }; - response.Suggestions = await Task.Run(() => - Internal.CodeCompletionRequest(response.Kind, scriptFileLocalized ?? request.ScriptFile)); + response.Suggestions = Internal.CodeCompletionRequest(response.Kind, + scriptFileLocalized ?? request.ScriptFile); return response; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs new file mode 100644 index 0000000000..7e08d8c01d --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderLocatorEnvironment.cs @@ -0,0 +1,51 @@ +using System; +using Godot; +using JetBrains.Rider.PathLocator; +using Newtonsoft.Json; +using OS = GodotTools.Utils.OS; + +namespace GodotTools.Ides.Rider; + +public class RiderLocatorEnvironment : IRiderLocatorEnvironment +{ + public JetBrains.Rider.PathLocator.OS CurrentOS + { + get + { + if (OS.IsWindows) + return JetBrains.Rider.PathLocator.OS.Windows; + if (OS.IsMacOS) return JetBrains.Rider.PathLocator.OS.MacOSX; + if (OS.IsUnixLike) return JetBrains.Rider.PathLocator.OS.Linux; + return JetBrains.Rider.PathLocator.OS.Other; + } + } + + public T FromJson<T>(string json) + { + return JsonConvert.DeserializeObject<T>(json); + } + + public void Info(string message, Exception e = null) + { + if (e == null) + GD.Print(message); + else + GD.Print(message, e); + } + + public void Warn(string message, Exception e = null) + { + if (e == null) + GD.PushWarning(message); + else + GD.PushWarning(message, e); + } + + public void Error(string message, Exception e = null) + { + if (e == null) + GD.PushError(message); + else + GD.PushError(message, e); + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs deleted file mode 100644 index dad6e35344..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs +++ /dev/null @@ -1,474 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Runtime.Versioning; -using Godot; -using Microsoft.Win32; -using Newtonsoft.Json; -using Directory = System.IO.Directory; -using Environment = System.Environment; -using File = System.IO.File; -using Path = System.IO.Path; -using OS = GodotTools.Utils.OS; - -// ReSharper disable UnassignedField.Local -// ReSharper disable InconsistentNaming -// ReSharper disable UnassignedField.Global -// ReSharper disable MemberHidesStaticFromOuterClass - -namespace GodotTools.Ides.Rider -{ - /// <summary> - /// This code is a modified version of the JetBrains resharper-unity plugin listed under Apache License 2.0 license: - /// https://github.com/JetBrains/resharper-unity/blob/master/unity/JetBrains.Rider.Unity.Editor/EditorPlugin/RiderPathLocator.cs - /// </summary> - public static class RiderPathLocator - { - public static RiderInfo[] GetAllRiderPaths() - { - try - { - if (OS.IsWindows) - { - return CollectRiderInfosWindows(); - } - if (OS.IsMacOS) - { - return CollectRiderInfosMac(); - } - if (OS.IsUnixLike) - { - return CollectAllRiderPathsLinux(); - } - throw new InvalidOperationException("Unexpected OS."); - } - catch (Exception e) - { - GD.PushWarning(e.Message); - } - - return Array.Empty<RiderInfo>(); - } - - private static RiderInfo[] CollectAllRiderPathsLinux() - { - var installInfos = new List<RiderInfo>(); - string home = Environment.GetEnvironmentVariable("HOME"); - if (!string.IsNullOrEmpty(home)) - { - string toolboxRiderRootPath = GetToolboxBaseDir(); - installInfos.AddRange(CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider.sh", false) - .Select(a => new RiderInfo(a, true)).ToList()); - - //$Home/.local/share/applications/jetbrains-rider.desktop - var shortcut = new FileInfo(Path.Combine(home, @".local/share/applications/jetbrains-rider.desktop")); - - if (shortcut.Exists) - { - string[] lines = File.ReadAllLines(shortcut.FullName); - foreach (string line in lines) - { - if (!line.StartsWith("Exec=\"")) - continue; - string path = line.Split('"').Where((item, index) => index == 1).SingleOrDefault(); - if (string.IsNullOrEmpty(path)) - continue; - - if (installInfos.Any(a => a.Path == path)) // avoid adding similar build as from toolbox - continue; - installInfos.Add(new RiderInfo(path, false)); - } - } - } - - // snap install - string snapInstallPath = "/snap/rider/current/bin/rider.sh"; - if (new FileInfo(snapInstallPath).Exists) - installInfos.Add(new RiderInfo(snapInstallPath, false)); - - return installInfos.ToArray(); - } - - private static RiderInfo[] CollectRiderInfosMac() - { - var installInfos = new List<RiderInfo>(); - // "/Applications/*Rider*.app" - // should be combined with "Contents/MacOS/rider" - var folder = new DirectoryInfo("/Applications"); - if (folder.Exists) - { - installInfos.AddRange(folder.GetDirectories("*Rider*.app") - .Select(a => new RiderInfo(Path.Combine(a.FullName, "Contents/MacOS/rider"), false)) - .ToList()); - } - - // /Users/user/Library/Application Support/JetBrains/Toolbox/apps/Rider/ch-1/181.3870.267/Rider EAP.app - // should be combined with "Contents/MacOS/rider" - string toolboxRiderRootPath = GetToolboxBaseDir(); - var paths = CollectPathsFromToolbox(toolboxRiderRootPath, "", "Rider*.app", true) - .Select(a => new RiderInfo(Path.Combine(a, "Contents/MacOS/rider"), true)); - installInfos.AddRange(paths); - - return installInfos.ToArray(); - } - - [SupportedOSPlatform("windows")] - private static RiderInfo[] CollectRiderInfosWindows() - { - var installInfos = new List<RiderInfo>(); - var toolboxRiderRootPath = GetToolboxBaseDir(); - var installPathsToolbox = CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider64.exe", false).ToList(); - installInfos.AddRange(installPathsToolbox.Select(a => new RiderInfo(a, true)).ToList()); - - var installPaths = new List<string>(); - const string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"; - CollectPathsFromRegistry(registryKey, installPaths); - const string wowRegistryKey = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"; - CollectPathsFromRegistry(wowRegistryKey, installPaths); - - installInfos.AddRange(installPaths.Select(a => new RiderInfo(a, false)).ToList()); - - return installInfos.ToArray(); - } - - private static string GetToolboxBaseDir() - { - if (OS.IsWindows) - { - string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - return GetToolboxRiderRootPath(localAppData); - } - - if (OS.IsMacOS) - { - var home = Environment.GetEnvironmentVariable("HOME"); - if (string.IsNullOrEmpty(home)) - return string.Empty; - var localAppData = Path.Combine(home, @"Library/Application Support"); - return GetToolboxRiderRootPath(localAppData); - } - - if (OS.IsUnixLike) - { - var home = Environment.GetEnvironmentVariable("HOME"); - if (string.IsNullOrEmpty(home)) - return string.Empty; - var localAppData = Path.Combine(home, @".local/share"); - return GetToolboxRiderRootPath(localAppData); - } - - return string.Empty; - } - - - private static string GetToolboxRiderRootPath(string localAppData) - { - var toolboxPath = Path.Combine(localAppData, @"JetBrains/Toolbox"); - var settingsJson = Path.Combine(toolboxPath, ".settings.json"); - - if (File.Exists(settingsJson)) - { - var path = SettingsJson.GetInstallLocationFromJson(File.ReadAllText(settingsJson)); - if (!string.IsNullOrEmpty(path)) - toolboxPath = path; - } - - var toolboxRiderRootPath = Path.Combine(toolboxPath, @"apps/Rider"); - return toolboxRiderRootPath; - } - - internal static ProductInfo GetBuildVersion(string path) - { - var buildTxtFileInfo = new FileInfo(Path.Combine(path, GetRelativePathToBuildTxt())); - var dir = buildTxtFileInfo.DirectoryName; - if (!Directory.Exists(dir)) - return null; - var buildVersionFile = new FileInfo(Path.Combine(dir, "product-info.json")); - if (!buildVersionFile.Exists) - return null; - var json = File.ReadAllText(buildVersionFile.FullName); - return ProductInfo.GetProductInfo(json); - } - - internal static Version GetBuildNumber(string path) - { - var file = new FileInfo(Path.Combine(path, GetRelativePathToBuildTxt())); - if (!file.Exists) - return null; - var text = File.ReadAllText(file.FullName); - if (text.Length <= 3) - return null; - - var versionText = text.Substring(3); - return Version.TryParse(versionText, out var v) ? v : null; - } - - internal static bool IsToolbox(string path) - { - return path.StartsWith(GetToolboxBaseDir()); - } - - private static string GetRelativePathToBuildTxt() - { - if (OS.IsWindows || OS.IsUnixLike) - return "../../build.txt"; - if (OS.IsMacOS) - return "Contents/Resources/build.txt"; - throw new InvalidOperationException("Unknown OS."); - } - - [SupportedOSPlatform("windows")] - private static void CollectPathsFromRegistry(string registryKey, List<string> installPaths) - { - using (var key = Registry.CurrentUser.OpenSubKey(registryKey)) - { - CollectPathsFromRegistry(installPaths, key); - } - using (var key = Registry.LocalMachine.OpenSubKey(registryKey)) - { - CollectPathsFromRegistry(installPaths, key); - } - } - - [SupportedOSPlatform("windows")] - private static void CollectPathsFromRegistry(List<string> installPaths, RegistryKey key) - { - if (key == null) return; - foreach (var subkeyName in key.GetSubKeyNames().Where(a => a.Contains("Rider"))) - { - using (var subkey = key.OpenSubKey(subkeyName)) - { - var folderObject = subkey?.GetValue("InstallLocation"); - if (folderObject == null) continue; - var folder = folderObject.ToString(); - var possiblePath = Path.Combine(folder, @"bin\rider64.exe"); - if (File.Exists(possiblePath)) - installPaths.Add(possiblePath); - } - } - } - - private static string[] CollectPathsFromToolbox(string toolboxRiderRootPath, string dirName, string searchPattern, - bool isMac) - { - if (!Directory.Exists(toolboxRiderRootPath)) - return Array.Empty<string>(); - - var channelDirs = Directory.GetDirectories(toolboxRiderRootPath); - var paths = channelDirs.SelectMany(channelDir => - { - try - { - // use history.json - last entry stands for the active build https://jetbrains.slack.com/archives/C07KNP99D/p1547807024066500?thread_ts=1547731708.057700&cid=C07KNP99D - var historyFile = Path.Combine(channelDir, ".history.json"); - if (File.Exists(historyFile)) - { - var json = File.ReadAllText(historyFile); - var build = ToolboxHistory.GetLatestBuildFromJson(json); - if (build != null) - { - var buildDir = Path.Combine(channelDir, build); - var executablePaths = GetExecutablePaths(dirName, searchPattern, isMac, buildDir); - if (executablePaths.Any()) - return executablePaths; - } - } - - var channelFile = Path.Combine(channelDir, ".channel.settings.json"); - if (File.Exists(channelFile)) - { - var json = File.ReadAllText(channelFile).Replace("active-application", "active_application"); - var build = ToolboxInstallData.GetLatestBuildFromJson(json); - if (build != null) - { - var buildDir = Path.Combine(channelDir, build); - var executablePaths = GetExecutablePaths(dirName, searchPattern, isMac, buildDir); - if (executablePaths.Any()) - return executablePaths; - } - } - - // changes in toolbox json files format may brake the logic above, so return all found Rider installations - return Directory.GetDirectories(channelDir) - .SelectMany(buildDir => GetExecutablePaths(dirName, searchPattern, isMac, buildDir)); - } - catch (Exception e) - { - // do not write to Debug.Log, just log it. - Logger.Warn($"Failed to get RiderPath from {channelDir}", e); - } - - return Array.Empty<string>(); - }) - .Where(c => !string.IsNullOrEmpty(c)) - .ToArray(); - return paths; - } - - private static string[] GetExecutablePaths(string dirName, string searchPattern, bool isMac, string buildDir) - { - var folder = new DirectoryInfo(Path.Combine(buildDir, dirName)); - if (!folder.Exists) - return Array.Empty<string>(); - - if (!isMac) - return new[] { Path.Combine(folder.FullName, searchPattern) }.Where(File.Exists).ToArray(); - return folder.GetDirectories(searchPattern).Select(f => f.FullName) - .Where(Directory.Exists).ToArray(); - } - - // Disable the "field is never assigned" compiler warning. We never assign it, but Unity does. - // Note that Unity disable this warning in the generated C# projects -#pragma warning disable 0649 - - [Serializable] - class SettingsJson - { - public string install_location; - - [return: MaybeNull] - public static string GetInstallLocationFromJson(string json) - { - try - { - return JsonConvert.DeserializeObject<SettingsJson>(json).install_location; - } - catch (Exception) - { - Logger.Warn($"Failed to get install_location from json {json}"); - } - - return null; - } - } - - [Serializable] - class ToolboxHistory - { - public List<ItemNode> history; - - public static string GetLatestBuildFromJson(string json) - { - try - { - return JsonConvert.DeserializeObject<ToolboxHistory>(json).history.LastOrDefault()?.item.build; - } - catch (Exception) - { - Logger.Warn($"Failed to get latest build from json {json}"); - } - - return null; - } - } - - [Serializable] - class ItemNode - { - public BuildNode item; - } - - [Serializable] - class BuildNode - { - public string build; - } - - [Serializable] - public class ProductInfo - { - public string version; - public string versionSuffix; - - [return: MaybeNull] - internal static ProductInfo GetProductInfo(string json) - { - try - { - var productInfo = JsonConvert.DeserializeObject<ProductInfo>(json); - return productInfo; - } - catch (Exception) - { - Logger.Warn($"Failed to get version from json {json}"); - } - - return null; - } - } - - // ReSharper disable once ClassNeverInstantiated.Global - [Serializable] - class ToolboxInstallData - { - // ReSharper disable once InconsistentNaming - public ActiveApplication active_application; - - [return: MaybeNull] - public static string GetLatestBuildFromJson(string json) - { - try - { - var toolbox = JsonConvert.DeserializeObject<ToolboxInstallData>(json); - var builds = toolbox.active_application.builds; - if (builds != null && builds.Any()) - return builds.First(); - } - catch (Exception) - { - Logger.Warn($"Failed to get latest build from json {json}"); - } - - return null; - } - } - - [Serializable] - class ActiveApplication - { - public List<string> builds; - } - -#pragma warning restore 0649 - - public struct RiderInfo - { - // ReSharper disable once NotAccessedField.Global - public bool IsToolbox; - public string Presentation; - public Version BuildNumber; - public ProductInfo ProductInfo; - public string Path; - - public RiderInfo(string path, bool isToolbox) - { - BuildNumber = GetBuildNumber(path); - ProductInfo = GetBuildVersion(path); - Path = new FileInfo(path).FullName; // normalize separators - var presentation = $"Rider {BuildNumber}"; - - if (ProductInfo != null && !string.IsNullOrEmpty(ProductInfo.version)) - { - var suffix = string.IsNullOrEmpty(ProductInfo.versionSuffix) ? "" : $" {ProductInfo.versionSuffix}"; - presentation = $"Rider {ProductInfo.version}{suffix}"; - } - - if (isToolbox) - presentation += " (JetBrains Toolbox)"; - - Presentation = presentation; - IsToolbox = isToolbox; - } - } - - private static class Logger - { - internal static void Warn(string message, Exception e = null) - { - throw new Exception(message, e); - } - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index f55ca4c7d7..5c09f1f83a 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -4,11 +4,19 @@ using System.IO; using System.Linq; using Godot; using GodotTools.Internals; +using JetBrains.Rider.PathLocator; namespace GodotTools.Ides.Rider { public static class RiderPathManager { + private static readonly RiderPathLocator RiderPathLocator; + + static RiderPathManager() + { + RiderPathLocator = new RiderPathLocator(new RiderLocatorEnvironment()); + } + public static readonly string EditorPathSettingName = "dotnet/editor/editor_path_optional"; private static string GetRiderPathFromSettings() diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs index 37f7005d01..a0bd96412a 100644 --- a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs @@ -38,7 +38,7 @@ internal static class ExtensionMethods } private static bool IsGenerateUnmanagedCallbacksAttribute(this INamedTypeSymbol symbol) - => symbol.ToString() == GeneratorClasses.GenerateUnmanagedCallbacksAttr; + => symbol.FullQualifiedNameOmitGlobal() == GeneratorClasses.GenerateUnmanagedCallbacksAttr; public static IEnumerable<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol)> SelectUnmanagedCallbacksClasses( this IEnumerable<ClassDeclarationSyntax> source, diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs index 2a72b7c53e..6117ae17ea 100644 --- a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs +++ b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs @@ -21,6 +21,13 @@ namespace GodotPlugins private sealed class PluginLoadContextWrapper { private PluginLoadContext? _pluginLoadContext; + private readonly WeakReference _weakReference; + + private PluginLoadContextWrapper(PluginLoadContext pluginLoadContext, WeakReference weakReference) + { + _pluginLoadContext = pluginLoadContext; + _weakReference = weakReference; + } public string? AssemblyLoadedPath { @@ -31,7 +38,14 @@ namespace GodotPlugins public bool IsCollectible { [MethodImpl(MethodImplOptions.NoInlining)] - get => _pluginLoadContext?.IsCollectible ?? false; + // if _pluginLoadContext is null we already started unloading, so it was collectible + get => _pluginLoadContext?.IsCollectible ?? true; + } + + public bool IsAlive + { + [MethodImpl(MethodImplOptions.NoInlining)] + get => _weakReference.IsAlive; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -43,20 +57,14 @@ namespace GodotPlugins bool isCollectible ) { - var wrapper = new PluginLoadContextWrapper(); - wrapper._pluginLoadContext = new PluginLoadContext( - pluginPath, sharedAssemblies, mainLoadContext, isCollectible); - var assembly = wrapper._pluginLoadContext.LoadFromAssemblyName(assemblyName); + var context = new PluginLoadContext(pluginPath, sharedAssemblies, mainLoadContext, isCollectible); + var reference = new WeakReference(context, trackResurrection: true); + var wrapper = new PluginLoadContextWrapper(context, reference); + var assembly = context.LoadFromAssemblyName(assemblyName); return (assembly, wrapper); } [MethodImpl(MethodImplOptions.NoInlining)] - public WeakReference CreateWeakReference() - { - return new WeakReference(_pluginLoadContext, trackResurrection: true); - } - - [MethodImpl(MethodImplOptions.NoInlining)] internal void Unload() { _pluginLoadContext?.Unload(); @@ -165,7 +173,7 @@ namespace GodotPlugins if (_editorApiAssembly == null) throw new InvalidOperationException("The Godot editor API assembly is not loaded."); - var (assembly, _) = LoadPlugin(assemblyPath, isCollectible: _editorHint); + var (assembly, _) = LoadPlugin(assemblyPath, isCollectible: false); NativeLibrary.SetDllImportResolver(assembly, _dllImportResolver!); @@ -236,32 +244,29 @@ namespace GodotPlugins Console.WriteLine("Unloading assembly load context..."); - var alcWeakReference = pluginLoadContext.CreateWeakReference(); - pluginLoadContext.Unload(); - pluginLoadContext = null; int startTimeMs = Environment.TickCount; bool takingTooLong = false; - while (alcWeakReference.IsAlive) + while (pluginLoadContext.IsAlive) { GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); - if (!alcWeakReference.IsAlive) + if (!pluginLoadContext.IsAlive) break; int elapsedTimeMs = Environment.TickCount - startTimeMs; - if (!takingTooLong && elapsedTimeMs >= 2000) + if (!takingTooLong && elapsedTimeMs >= 200) { takingTooLong = true; // TODO: How to log from GodotPlugins? (delegate pointer?) Console.Error.WriteLine("Assembly unloading is taking longer than expected..."); } - else if (elapsedTimeMs >= 5000) + else if (elapsedTimeMs >= 1000) { // TODO: How to log from GodotPlugins? (delegate pointer?) Console.Error.WriteLine( @@ -273,6 +278,7 @@ namespace GodotPlugins Console.WriteLine("Assembly load context unloaded successfully."); + pluginLoadContext = null; return true; } catch (Exception e) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index 36f5d8e2ab..74425c9835 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.ComponentModel; namespace Godot { @@ -623,21 +624,31 @@ namespace Godot /// </summary> /// <param name="target">The position to look at.</param> /// <param name="up">The relative up direction.</param> + /// <param name="useModelFront"> + /// If true, then the model is oriented in reverse, + /// towards the model front axis (+Z, Vector3.ModelFront), + /// which is more useful for orienting 3D models. + /// </param> /// <returns>The resulting basis matrix.</returns> - public static Basis LookingAt(Vector3 target, Vector3 up) + public static Basis LookingAt(Vector3 target, Vector3? up = null, bool useModelFront = false) { + up ??= Vector3.Up; #if DEBUG if (target.IsZeroApprox()) { throw new ArgumentException("The vector can't be zero.", nameof(target)); } - if (up.IsZeroApprox()) + if (up.Value.IsZeroApprox()) { throw new ArgumentException("The vector can't be zero.", nameof(up)); } #endif - Vector3 column2 = -target.Normalized(); - Vector3 column0 = up.Cross(column2); + Vector3 column2 = target.Normalized(); + if (!useModelFront) + { + column2 = -column2; + } + Vector3 column0 = up.Value.Cross(column2); #if DEBUG if (column0.IsZeroApprox()) { @@ -649,6 +660,13 @@ namespace Godot return new Basis(column0, column1, column2); } + /// <inheritdoc cref="LookingAt(Vector3, Nullable{Vector3}, bool)"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public static Basis LookingAt(Vector3 target, Vector3 up) + { + return LookingAt(target, up, false); + } + /// <summary> /// Returns the orthonormalized version of the basis matrix (useful to /// call occasionally to avoid rounding errors for orthogonal matrices). diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs index 456a118b90..8217572648 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs @@ -18,5 +18,26 @@ namespace Godot.Bridge ExceptionUtils.LogException(e); } } + + // Returns true, if releasing the provided handle is necessary for assembly unloading to succeed. + // This check is not perfect and only intended to prevent things in GodotTools from being reloaded. + [UnmanagedCallersOnly] + internal static godot_bool GCHandleIsTargetCollectible(IntPtr gcHandlePtr) + { + try + { + var target = GCHandle.FromIntPtr(gcHandlePtr).Target; + + if (target is Delegate @delegate) + return DelegateUtils.IsDelegateCollectible(@delegate).ToGodotBool(); + + return target.GetType().IsCollectible.ToGodotBool(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.True; + } + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs index 0571515e61..109643c2d4 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs @@ -38,6 +38,7 @@ namespace Godot.Bridge public delegate* unmanaged<IntPtr, godot_dictionary*, godot_dictionary*, void> CSharpInstanceBridge_SerializeState; public delegate* unmanaged<IntPtr, godot_dictionary*, godot_dictionary*, void> CSharpInstanceBridge_DeserializeState; public delegate* unmanaged<IntPtr, void> GCHandleBridge_FreeGCHandle; + public delegate* unmanaged<IntPtr, godot_bool> GCHandleBridge_GCHandleIsTargetCollectible; public delegate* unmanaged<void*, void> DebuggingUtils_GetCurrentStackInfo; public delegate* unmanaged<void> DisposablesTracker_OnGodotShuttingDown; public delegate* unmanaged<godot_bool, void> GD_OnCoreApiAssemblyLoaded; @@ -78,6 +79,7 @@ namespace Godot.Bridge CSharpInstanceBridge_SerializeState = &CSharpInstanceBridge.SerializeState, CSharpInstanceBridge_DeserializeState = &CSharpInstanceBridge.DeserializeState, GCHandleBridge_FreeGCHandle = &GCHandleBridge.FreeGCHandle, + GCHandleBridge_GCHandleIsTargetCollectible = &GCHandleBridge.GCHandleIsTargetCollectible, DebuggingUtils_GetCurrentStackInfo = &DebuggingUtils.GetCurrentStackInfo, DisposablesTracker_OnGodotShuttingDown = &DisposablesTracker.OnGodotShuttingDown, GD_OnCoreApiAssemblyLoaded = &GD.OnCoreApiAssemblyLoaded, diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs index 279dadf425..6c2fb7374c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs @@ -500,24 +500,17 @@ namespace Godot Type? returnType = hasReturn ? DeserializeType(reader) : typeof(void); int parametersCount = reader.ReadInt32(); + var parameterTypes = parametersCount == 0 ? Type.EmptyTypes : new Type[parametersCount]; - if (parametersCount > 0) + for (int i = 0; i < parametersCount; i++) { - var parameterTypes = new Type[parametersCount]; - - for (int i = 0; i < parametersCount; i++) - { - Type? parameterType = DeserializeType(reader); - if (parameterType == null) - return false; - parameterTypes[i] = parameterType; - } - - methodInfo = declaringType.GetMethod(methodName, (BindingFlags)flags, null, parameterTypes, null); - return methodInfo != null && methodInfo.ReturnType == returnType; + Type? parameterType = DeserializeType(reader); + if (parameterType == null) + return false; + parameterTypes[i] = parameterType; } - methodInfo = declaringType.GetMethod(methodName, (BindingFlags)flags); + methodInfo = declaringType.GetMethod(methodName, (BindingFlags)flags, null, parameterTypes, null); return methodInfo != null && methodInfo.ReturnType == returnType; } @@ -560,6 +553,38 @@ namespace Godot return type; } + // Returns true, if unloading the delegate is necessary for assembly unloading to succeed. + // This check is not perfect and only intended to prevent things in GodotTools from being reloaded. + internal static bool IsDelegateCollectible(Delegate @delegate) + { + if (@delegate.GetType().IsCollectible) + return true; + + if (@delegate is MulticastDelegate multicastDelegate) + { + Delegate[] invocationList = multicastDelegate.GetInvocationList(); + + if (invocationList.Length > 1) + { + foreach (Delegate oneDelegate in invocationList) + if (IsDelegateCollectible(oneDelegate)) + return true; + + return false; + } + } + + if (@delegate.Method.IsCollectible) + return true; + + object? target = @delegate.Target; + + if (target is not null && target.GetType().IsCollectible) + return true; + + return false; + } + internal static class RuntimeTypeConversionHelper { [SuppressMessage("ReSharper", "RedundantNameQualifier")] diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs index b9a5ac82d1..12e8a638d3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs @@ -125,7 +125,10 @@ namespace Godot NativePtr = IntPtr.Zero; } - DisposablesTracker.UnregisterGodotObject(this, _weakReferenceToSelf); + if (_weakReferenceToSelf != null) + { + DisposablesTracker.UnregisterGodotObject(this, _weakReferenceToSelf); + } } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index 1e2aaa299f..ae2c025137 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.ComponentModel; namespace Godot { @@ -175,14 +176,26 @@ namespace Godot /// </summary> /// <param name="target">The object to look at.</param> /// <param name="up">The relative up direction.</param> + /// <param name="useModelFront"> + /// If true, then the model is oriented in reverse, + /// towards the model front axis (+Z, Vector3.ModelFront), + /// which is more useful for orienting 3D models. + /// </param> /// <returns>The resulting transform.</returns> - public readonly Transform3D LookingAt(Vector3 target, Vector3 up) + public readonly Transform3D LookingAt(Vector3 target, Vector3? up = null, bool useModelFront = false) { Transform3D t = this; - t.SetLookAt(Origin, target, up); + t.SetLookAt(Origin, target, up ?? Vector3.Up, useModelFront); return t; } + /// <inheritdoc cref="LookingAt(Vector3, Nullable{Vector3}, bool)"/> + [EditorBrowsable(EditorBrowsableState.Never)] + public readonly Transform3D LookingAt(Vector3 target, Vector3 up) + { + return LookingAt(target, up, false); + } + /// <summary> /// Returns the transform with the basis orthogonal (90 degrees), /// and normalized axis vectors (scale of 1 or -1). @@ -247,9 +260,9 @@ namespace Godot return new Transform3D(Basis * tmpBasis, Origin); } - private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up) + private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up, bool useModelFront = false) { - Basis = Basis.LookingAt(target - eye, up); + Basis = Basis.LookingAt(target - eye, up, useModelFront); Origin = eye; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index c773c0fda6..d929b5c6ab 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -660,6 +660,13 @@ namespace Godot private static readonly Vector3 _forward = new Vector3(0, 0, -1); private static readonly Vector3 _back = new Vector3(0, 0, 1); + private static readonly Vector3 _modelLeft = new Vector3(1, 0, 0); + private static readonly Vector3 _modelRight = new Vector3(-1, 0, 0); + private static readonly Vector3 _modelTop = new Vector3(0, 1, 0); + private static readonly Vector3 _modelBottom = new Vector3(0, -1, 0); + private static readonly Vector3 _modelFront = new Vector3(0, 0, 1); + private static readonly Vector3 _modelRear = new Vector3(0, 0, -1); + /// <summary> /// Zero vector, a vector with all components set to <c>0</c>. /// </summary> @@ -712,6 +719,31 @@ namespace Godot public static Vector3 Back { get { return _back; } } /// <summary> + /// Unit vector pointing towards the left side of imported 3D assets. + /// </summary> + public static Vector3 ModelLeft { get { return _modelLeft; } } + /// <summary> + /// Unit vector pointing towards the right side of imported 3D assets. + /// </summary> + public static Vector3 ModelRight { get { return _modelRight; } } + /// <summary> + /// Unit vector pointing towards the top side (up) of imported 3D assets. + /// </summary> + public static Vector3 ModelTop { get { return _modelTop; } } + /// <summary> + /// Unit vector pointing towards the bottom side (down) of imported 3D assets. + /// </summary> + public static Vector3 ModelBottom { get { return _modelBottom; } } + /// <summary> + /// Unit vector pointing towards the front side (facing forward) of imported 3D assets. + /// </summary> + public static Vector3 ModelFront { get { return _modelFront; } } + /// <summary> + /// Unit vector pointing towards the rear side (back) of imported 3D assets. + /// </summary> + public static Vector3 ModelRear { get { return _modelRear; } } + + /// <summary> /// Constructs a new <see cref="Vector3"/> with the given components. /// </summary> /// <param name="x">The vector's X component.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 16820e363a..8a36b3e514 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -20,7 +20,7 @@ <Authors>Godot Engine contributors</Authors> <PackageId>GodotSharp</PackageId> - <Version>4.1.0</Version> + <Version>4.2.0</Version> <PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion> <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharp</RepositoryUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj index c249ebf804..db9337d4eb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj @@ -15,7 +15,7 @@ <Authors>Godot Engine contributors</Authors> <PackageId>GodotSharpEditor</PackageId> - <Version>4.1.0</Version> + <Version>4.2.0</Version> <PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion> <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharpEditor</RepositoryUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 92fa30e5e8..4337478370 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -488,15 +488,31 @@ bool GDMono::_load_project_assembly() { #endif #ifdef GD_MONO_HOT_RELOAD +void GDMono::reload_failure() { + if (++project_load_failure_count >= (int)GLOBAL_GET("dotnet/project/assembly_reload_attempts")) { + // After reloading a project has failed n times in a row, update the path and modification time + // to stop any further attempts at loading this assembly, which probably is never going to work anyways. + project_load_failure_count = 0; + + ERR_PRINT_ED(".NET: Giving up on assembly reloading. Please restart the editor if unloading was failing."); + + 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); + project_assembly_path = assembly_path.simplify_path(); + project_assembly_modified_time = FileAccess::get_modified_time(assembly_path); + } +} + Error GDMono::reload_project_assemblies() { ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG); finalizing_scripts_domain = true; - CSharpLanguage::get_singleton()->_on_scripts_domain_about_to_unload(); - if (!get_plugin_callbacks().UnloadProjectPluginCallback()) { - ERR_FAIL_V_MSG(Error::FAILED, ".NET: Failed to unload assemblies."); + ERR_PRINT_ED(".NET: Failed to unload assemblies. Please check https://github.com/godotengine/godot/issues/78513 for more information."); + reload_failure(); + return FAILED; } finalizing_scripts_domain = false; @@ -504,10 +520,16 @@ 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()) { - print_error(".NET: Failed to load project assembly."); + ERR_PRINT_ED(".NET: Failed to load project assembly."); + reload_failure(); return ERR_CANT_OPEN; } + if (project_load_failure_count > 0) { + project_load_failure_count = 0; + ERR_PRINT_ED(".NET: Assembly reloading succeeded after failures."); + } + return OK; } #endif diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 398f94d924..c629ab2eff 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -68,6 +68,7 @@ class GDMono { String project_assembly_path; uint64_t project_assembly_modified_time = 0; + int project_load_failure_count = 0; #ifdef TOOLS_ENABLED bool _load_project_assembly(); @@ -144,6 +145,7 @@ public: #endif #ifdef GD_MONO_HOT_RELOAD + void reload_failure(); Error reload_project_assemblies(); #endif diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp index e254484df9..8fdf163b26 100644 --- a/modules/mono/mono_gd/gd_mono_cache.cpp +++ b/modules/mono/mono_gd/gd_mono_cache.cpp @@ -79,6 +79,7 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) { CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, SerializeState); CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, DeserializeState); CHECK_CALLBACK_NOT_NULL(GCHandleBridge, FreeGCHandle); + CHECK_CALLBACK_NOT_NULL(GCHandleBridge, GCHandleIsTargetCollectible); CHECK_CALLBACK_NOT_NULL(DebuggingUtils, GetCurrentStackInfo); CHECK_CALLBACK_NOT_NULL(DisposablesTracker, OnGodotShuttingDown); CHECK_CALLBACK_NOT_NULL(GD, OnCoreApiAssemblyLoaded); diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index 9201da7cae..f604e4d681 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -104,6 +104,7 @@ struct ManagedCallbacks { using FuncCSharpInstanceBridge_SerializeState = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Dictionary *, const Dictionary *); using FuncCSharpInstanceBridge_DeserializeState = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Dictionary *, const Dictionary *); using FuncGCHandleBridge_FreeGCHandle = void(GD_CLR_STDCALL *)(GCHandleIntPtr); + using FuncGCHandleBridge_GCHandleIsTargetCollectible = bool(GD_CLR_STDCALL *)(GCHandleIntPtr); using FuncDebuggingUtils_GetCurrentStackInfo = void(GD_CLR_STDCALL *)(Vector<ScriptLanguage::StackInfo> *); using FuncDisposablesTracker_OnGodotShuttingDown = void(GD_CLR_STDCALL *)(); using FuncGD_OnCoreApiAssemblyLoaded = void(GD_CLR_STDCALL *)(bool); @@ -138,6 +139,7 @@ struct ManagedCallbacks { FuncCSharpInstanceBridge_SerializeState CSharpInstanceBridge_SerializeState; FuncCSharpInstanceBridge_DeserializeState CSharpInstanceBridge_DeserializeState; FuncGCHandleBridge_FreeGCHandle GCHandleBridge_FreeGCHandle; + FuncGCHandleBridge_GCHandleIsTargetCollectible GCHandleBridge_GCHandleIsTargetCollectible; FuncDebuggingUtils_GetCurrentStackInfo DebuggingUtils_GetCurrentStackInfo; FuncDisposablesTracker_OnGodotShuttingDown DisposablesTracker_OnGodotShuttingDown; FuncGD_OnCoreApiAssemblyLoaded GD_OnCoreApiAssemblyLoaded; |