diff options
Diffstat (limited to 'modules/mono')
32 files changed, 590 insertions, 317 deletions
diff --git a/modules/mono/SCsub b/modules/mono/SCsub index 7764ba0b45..a4667f784d 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -26,6 +26,6 @@ if env["platform"] in ["macos", "ios"]: elif env["platform"] == "android": env_mono.add_source_files(env.modules_sources, "mono_gd/android_mono_config.gen.cpp") -if env["tools"]: +if env.editor_build: env_mono.add_source_files(env.modules_sources, "editor/*.cpp") SConscript("editor/script_templates/SCsub") diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index 6f66ce9efa..d28c3a0c3a 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -5,6 +5,7 @@ import os.path import shlex import subprocess from dataclasses import dataclass +from typing import Optional, List def find_dotnet_cli(): @@ -150,10 +151,7 @@ def find_any_msbuild_tool(mono_prefix): return None -def run_msbuild(tools: ToolsLocation, sln: str, msbuild_args: [str] = None): - if msbuild_args is None: - msbuild_args = [] - +def run_msbuild(tools: ToolsLocation, sln: str, msbuild_args: Optional[List[str]] = None): using_msbuild_mono = False # Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild @@ -169,7 +167,7 @@ def run_msbuild(tools: ToolsLocation, sln: str, msbuild_args: [str] = None): args += [sln] - if len(msbuild_args) > 0: + if msbuild_args: args += msbuild_args print("Running MSBuild: ", " ".join(shlex.quote(arg) for arg in args), flush=True) @@ -315,6 +313,8 @@ def main(): output_dir = os.path.abspath(args.godot_output_dir) + push_nupkgs_local = os.path.abspath(args.push_nupkgs_local) if args.push_nupkgs_local else None + msbuild_tool = find_any_msbuild_tool(args.mono_prefix) if msbuild_tool is None: @@ -327,7 +327,7 @@ def main(): output_dir, args.godot_platform, args.dev_debug, - args.push_nupkgs_local, + push_nupkgs_local, args.float, ) sys.exit(exit_code) diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index 5d63773096..5cec8f41f5 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -20,10 +20,7 @@ def configure(env, env_mono): # is_ios = env["platform"] == "ios" # is_ios_sim = is_ios and env["arch"] in ["x86_32", "x86_64"] - tools_enabled = env["tools"] - - if tools_enabled and not module_supports_tools_on(env["platform"]): - raise RuntimeError("This module does not currently support building for this platform with tools enabled") - - if env["tools"]: + if env.editor_build: + if not module_supports_tools_on(env["platform"]): + raise RuntimeError("This module does not currently support building for this platform for editor builds.") env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"]) diff --git a/modules/mono/config.py b/modules/mono/config.py index 15fe79ef8c..a36083b64b 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -7,7 +7,7 @@ def can_build(env, platform): if env["arch"].startswith("rv"): return False - if env["tools"]: + if env.editor_build: env.module_add_dependencies("mono", ["regex"]) return True diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 990a95821e..97a1d5c8d8 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1299,7 +1299,7 @@ GDNativeBool CSharpLanguage::_instance_binding_reference_callback(void *p_token, MonoGCHandleData &gchandle = script_binding.gchandle; - int refcount = rc_owner->reference_get_count(); + int refcount = rc_owner->get_reference_count(); if (!script_binding.inited) { return refcount == 0; @@ -1818,7 +1818,7 @@ void CSharpInstance::refcount_incremented() { RefCounted *rc_owner = Object::cast_to<RefCounted>(owner); - if (rc_owner->reference_get_count() > 1 && gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 + if (rc_owner->get_reference_count() > 1 && gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 // The reference count was increased after the managed side was the only one referencing our owner. // This means the owner is being referenced again by the unmanaged side, // so the owner must hold the managed side alive again to avoid it from being GCed. @@ -1849,7 +1849,7 @@ bool CSharpInstance::refcount_decremented() { RefCounted *rc_owner = Object::cast_to<RefCounted>(owner); - int refcount = rc_owner->reference_get_count(); + int refcount = rc_owner->get_reference_count(); if (refcount == 1 && !gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 // If owner owner is no longer referenced by the unmanaged side, @@ -1995,7 +1995,7 @@ CSharpInstance::~CSharpInstance() { #ifdef DEBUG_ENABLED // The "instance binding" holds a reference so the refcount should be at least 2 before `scope_keep_owner_alive` goes out of scope - CRASH_COND(rc_owner->reference_get_count() <= 1); + CRASH_COND(rc_owner->get_reference_count() <= 1); #endif } @@ -2035,6 +2035,52 @@ void CSharpScript::_update_exports_values(HashMap<StringName, Variant> &values, } #endif +void GD_CLR_STDCALL CSharpScript::_add_property_info_list_callback(CSharpScript *p_script, const String *p_current_class_name, void *p_props, int32_t p_count) { + GDMonoCache::godotsharp_property_info *props = (GDMonoCache::godotsharp_property_info *)p_props; + +#ifdef TOOLS_ENABLED + p_script->exported_members_cache.push_back(PropertyInfo( + Variant::NIL, *p_current_class_name, PROPERTY_HINT_NONE, + p_script->get_path(), PROPERTY_USAGE_CATEGORY)); +#endif + + for (int i = 0; i < p_count; i++) { + const GDMonoCache::godotsharp_property_info &prop = props[i]; + + StringName name = *reinterpret_cast<const StringName *>(&prop.name); + String hint_string = *reinterpret_cast<const String *>(&prop.hint_string); + + PropertyInfo pinfo(prop.type, name, prop.hint, hint_string, prop.usage); + + p_script->member_info[name] = pinfo; + + if (prop.exported) { +#ifdef TOOLS_ENABLED + p_script->exported_members_cache.push_back(pinfo); +#endif + +#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED) + p_script->exported_members_names.insert(name); +#endif + } + } +} + +#ifdef TOOLS_ENABLED +void GD_CLR_STDCALL CSharpScript::_add_property_default_values_callback(CSharpScript *p_script, void *p_def_vals, int32_t p_count) { + GDMonoCache::godotsharp_property_def_val_pair *def_vals = (GDMonoCache::godotsharp_property_def_val_pair *)p_def_vals; + + for (int i = 0; i < p_count; i++) { + const GDMonoCache::godotsharp_property_def_val_pair &def_val_pair = def_vals[i]; + + StringName name = *reinterpret_cast<const StringName *>(&def_val_pair.name); + Variant value = *reinterpret_cast<const Variant *>(&def_val_pair.value); + + p_script->exported_members_defval_cache[name] = value; + } +} +#endif + bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_update) { #ifdef TOOLS_ENABLED bool is_editor = Engine::get_singleton()->is_editor_hint(); @@ -2066,49 +2112,10 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda #endif if (GDMonoCache::godot_api_cache_updated) { - GDMonoCache::managed_callbacks.ScriptManagerBridge_GetPropertyInfoList(this, - [](CSharpScript *p_script, const String *p_current_class_name, GDMonoCache::godotsharp_property_info *p_props, int32_t p_count) { -#ifdef TOOLS_ENABLED - p_script->exported_members_cache.push_back(PropertyInfo( - Variant::NIL, *p_current_class_name, PROPERTY_HINT_NONE, - p_script->get_path(), PROPERTY_USAGE_CATEGORY)); -#endif - - for (int i = 0; i < p_count; i++) { - const GDMonoCache::godotsharp_property_info &prop = p_props[i]; - - StringName name = *reinterpret_cast<const StringName *>(&prop.name); - String hint_string = *reinterpret_cast<const String *>(&prop.hint_string); - - PropertyInfo pinfo(prop.type, name, prop.hint, hint_string, prop.usage); - - p_script->member_info[name] = pinfo; - - if (prop.exported) { + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetPropertyInfoList(this, &_add_property_info_list_callback); #ifdef TOOLS_ENABLED - p_script->exported_members_cache.push_back(pinfo); -#endif - -#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED) - p_script->exported_members_names.insert(name); -#endif - } - } - }); - -#ifdef TOOLS_ENABLED - GDMonoCache::managed_callbacks.ScriptManagerBridge_GetPropertyDefaultValues(this, - [](CSharpScript *p_script, GDMonoCache::godotsharp_property_def_val_pair *p_def_vals, int32_t p_count) { - for (int i = 0; i < p_count; i++) { - const GDMonoCache::godotsharp_property_def_val_pair &def_val_pair = p_def_vals[i]; - - StringName name = *reinterpret_cast<const StringName *>(&def_val_pair.name); - Variant value = *reinterpret_cast<const Variant *>(&def_val_pair.value); - - p_script->exported_members_defval_cache[name] = value; - } - }); + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetPropertyDefaultValues(this, &_add_property_default_values_callback); #endif } } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index f2844a051d..e5e53acb07 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -48,20 +48,10 @@ class CSharpScript; class CSharpInstance; class CSharpLanguage; -#ifdef NO_SAFE_CAST -template <typename TScriptInstance, typename TScriptLanguage> -TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { - if (!p_inst) { - return nullptr; - } - return p_inst->get_language() == TScriptLanguage::get_singleton() ? static_cast<TScriptInstance *>(p_inst) : nullptr; -} -#else template <typename TScriptInstance, typename TScriptLanguage> TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { return dynamic_cast<TScriptInstance *>(p_inst); } -#endif #define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst)) @@ -133,6 +123,10 @@ class CSharpScript : public Script { void _clear(); + static void GD_CLR_STDCALL _add_property_info_list_callback(CSharpScript *p_script, const String *p_current_class_name, void *p_props, int32_t p_count); +#ifdef TOOLS_ENABLED + static void GD_CLR_STDCALL _add_property_default_values_callback(CSharpScript *p_script, void *p_def_vals, int32_t p_count); +#endif bool _update_exports(PlaceHolderScriptInstance *p_instance_to_update = nullptr); CSharpInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets index bff9760b32..859ea52c93 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets @@ -17,7 +17,7 @@ <!-- C# source generators --> <ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' "> - <PackageReference Include="Godot.SourceGenerators" Version="$(PackageFloatingVersion_Godot)" /> + <PackageReference Include="Godot.SourceGenerators" Version="$(PackageVersion_Godot_SourceGenerators)" /> </ItemGroup> <!-- Godot API references --> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/EventHandlerSuffixSuppressor.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/EventHandlerSuffixSuppressor.cs new file mode 100644 index 0000000000..ddde730fa2 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/EventHandlerSuffixSuppressor.cs @@ -0,0 +1,53 @@ +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Godot.SourceGenerators +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class EventHandlerSuffixSuppressor : DiagnosticSuppressor + { + private static readonly SuppressionDescriptor _descriptor = new( + id: "GDSP0001", + suppressedDiagnosticId: "CA1711", + justification: "Signal delegates are used in events so the naming follows the guidelines."); + + public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => + ImmutableArray.Create(_descriptor); + + public override void ReportSuppressions(SuppressionAnalysisContext context) + { + foreach (var diagnostic in context.ReportedDiagnostics) + { + AnalyzeDiagnostic(context, diagnostic, context.CancellationToken); + } + } + + private static void AnalyzeDiagnostic(SuppressionAnalysisContext context, Diagnostic diagnostic, CancellationToken cancellationToken = default) + { + var location = diagnostic.Location; + var root = location.SourceTree?.GetRoot(cancellationToken); + var dds = root? + .FindNode(location.SourceSpan) + .DescendantNodesAndSelf() + .OfType<DelegateDeclarationSyntax>() + .FirstOrDefault(); + + if (dds == null) + return; + + var semanticModel = context.GetSemanticModel(dds.SyntaxTree); + var delegateSymbol = semanticModel.GetDeclaredSymbol(dds, cancellationToken); + if (delegateSymbol == null) + return; + + if (delegateSymbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false)) + { + context.ReportSuppression(Suppression.Create(_descriptor, diagnostic)); + } + } + } +} 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 de3b6c862a..8de12de23b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -29,7 +29,7 @@ namespace Godot.SourceGenerators { while (symbol != null) { - if (symbol.ContainingAssembly.Name == assemblyName && + if (symbol.ContainingAssembly?.Name == assemblyName && symbol.ToString() == typeFullName) { return true; @@ -47,7 +47,7 @@ namespace Godot.SourceGenerators while (symbol != null) { - if (symbol.ContainingAssembly.Name == "GodotSharp") + if (symbol.ContainingAssembly?.Name == "GodotSharp") return symbol; symbol = symbol.BaseType; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs index efdd50098e..bd40675fd3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs @@ -124,8 +124,8 @@ namespace Godot.SourceGenerators if (typeKind == TypeKind.Struct) { - if (type.ContainingAssembly.Name == "GodotSharp" && - type.ContainingNamespace.Name == "Godot") + if (type.ContainingAssembly?.Name == "GodotSharp" && + type.ContainingNamespace?.Name == "Godot") { return type switch { @@ -156,6 +156,10 @@ namespace Godot.SourceGenerators else if (typeKind == TypeKind.Array) { var arrayType = (IArrayTypeSymbol)type; + + if (arrayType.Rank != 1) + return null; + var elementType = arrayType.ElementType; switch (elementType.SpecialType) @@ -177,8 +181,8 @@ namespace Godot.SourceGenerators if (elementType.SimpleDerivesFrom(typeCache.GodotObjectType)) return MarshalType.GodotObjectOrDerivedArray; - if (elementType.ContainingAssembly.Name == "GodotSharp" && - elementType.ContainingNamespace.Name == "Godot") + if (elementType.ContainingAssembly?.Name == "GodotSharp" && + elementType.ContainingNamespace?.Name == "Godot") { switch (elementType) { @@ -204,9 +208,9 @@ namespace Godot.SourceGenerators if (type.SimpleDerivesFrom(typeCache.GodotObjectType)) return MarshalType.GodotObjectOrDerived; - if (type.ContainingAssembly.Name == "GodotSharp") + if (type.ContainingAssembly?.Name == "GodotSharp") { - switch (type.ContainingNamespace.Name) + switch (type.ContainingNamespace?.Name) { case "Godot": return type switch @@ -216,7 +220,7 @@ namespace Godot.SourceGenerators _ => null }; case "Collections" - when type.ContainingNamespace.FullQualifiedName() == "Godot.Collections": + when type.ContainingNamespace?.FullQualifiedName() == "Godot.Collections": return type switch { { Name: "Dictionary" } => diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 1df41a905b..ea7cc3fe38 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -7,7 +7,7 @@ using Microsoft.CodeAnalysis.Text; // TODO: // Determine a proper way to emit the signal. -// 'Emit(nameof(TheEvent))' creates a StringName everytime and has the overhead of string marshaling. +// 'Emit(nameof(TheEvent))' creates a StringName every time and has the overhead of string marshaling. // I haven't decided on the best option yet. Some possibilities: // - Expose the generated StringName fields to the user, for use with 'Emit(...)'. // - Generate a 'EmitSignalName' method for each event signal. @@ -235,6 +235,8 @@ namespace Godot.SourceGenerators .Append(signalName) .Append(";\n"); + source.Append($" /// <inheritdoc cref=\"{signalDelegate.DelegateSymbol.FullQualifiedName()}\"/>\n"); + source.Append(" public event ") .Append(signalDelegate.DelegateSymbol.FullQualifiedName()) .Append(" ") diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets index 4baae77b34..37bd4a0be0 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets @@ -21,10 +21,13 @@ Outputs="$(GeneratedGodotNupkgsVersionsFile)"> <PropertyGroup> <GenerateGodotNupkgsVersionsCode><![CDATA[ -namespace $(RootNamespace) { - public class GeneratedGodotNupkgsVersions { +namespace $(RootNamespace) +{ + public class GeneratedGodotNupkgsVersions + { public const string GodotNETSdk = "$(PackageVersion_Godot_NET_Sdk)"%3b public const string GodotSourceGenerators = "$(PackageVersion_Godot_SourceGenerators)"%3b + public const string GodotSharp = "$(PackageVersion_GodotSharp)"%3b } } ]]></GenerateGodotNupkgsVersionsCode> diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs index ad4fce8daa..4d40724a83 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs @@ -69,51 +69,41 @@ namespace GodotTools.Build private void LoadIssuesFromFile(string csvFile) { - using (var file = new Godot.File()) + using var file = FileAccess.Open(csvFile, FileAccess.ModeFlags.Read); + + if (file == null) + return; + + while (!file.EofReached()) { - try - { - Error openError = file.Open(csvFile, Godot.File.ModeFlags.Read); + string[] csvColumns = file.GetCsvLine(); - if (openError != Error.Ok) - return; + if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0])) + return; - while (!file.EofReached()) - { - string[] csvColumns = file.GetCsvLine(); - - if (csvColumns.Length == 1 && string.IsNullOrEmpty(csvColumns[0])) - return; - - if (csvColumns.Length != 7) - { - GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); - continue; - } - - var issue = new BuildIssue - { - Warning = csvColumns[0] == "warning", - File = csvColumns[1], - Line = int.Parse(csvColumns[2]), - Column = int.Parse(csvColumns[3]), - Code = csvColumns[4], - Message = csvColumns[5], - ProjectFile = csvColumns[6] - }; - - if (issue.Warning) - WarningCount += 1; - else - ErrorCount += 1; - - _issues.Add(issue); - } - } - finally + if (csvColumns.Length != 7) { - file.Close(); // Disposing it is not enough. We need to call Close() + GD.PushError($"Expected 7 columns, got {csvColumns.Length}"); + continue; } + + var issue = new BuildIssue + { + Warning = csvColumns[0] == "warning", + File = csvColumns[1], + Line = int.Parse(csvColumns[2]), + Column = int.Parse(csvColumns[3]), + Code = csvColumns[4], + Message = csvColumns[5], + ProjectFile = csvColumns[6] + }; + + if (issue.Warning) + WarningCount += 1; + else + ErrorCount += 1; + + _issues.Add(issue); } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs index 7bce53308c..b437c7e742 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Runtime.InteropServices; using JetBrains.Annotations; using OS = GodotTools.Utils.OS; @@ -16,6 +17,23 @@ namespace GodotTools.Build // In the future, this method may do more than just search in PATH. We could look in // known locations or use Godot's linked nethost to search from the hostfxr location. + if (OS.IsMacOS) + { + if (RuntimeInformation.OSArchitecture == Architecture.X64) + { + string dotnet_x64 = "/usr/local/share/dotnet/x64/dotnet"; // Look for x64 version, when running under Rosetta 2. + if (File.Exists(dotnet_x64)) + { + return dotnet_x64; + } + } + string dotnet = "/usr/local/share/dotnet/dotnet"; // Look for native version. + if (File.Exists(dotnet)) + { + return dotnet; + } + } + return OS.PathWhich("dotnet"); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 4041026426..237ac85267 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -122,7 +122,7 @@ namespace GodotTools.Build { base._Ready(); - CustomMinimumSize = new Vector2(0, 228) * EditorScale; + CustomMinimumSize = new Vector2i(0, (int)(228 * EditorScale)); SizeFlagsVertical = (int)SizeFlags.ExpandFill; var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill }; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs index d2e0e128b5..fe309b8102 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs @@ -22,71 +22,13 @@ namespace GodotTools.Build public static string GodotFallbackFolderPath => Path.Combine(GodotSharpDirs.MonoUserDir, "GodotNuGetFallbackFolder"); - private static void AddFallbackFolderToNuGetConfig(string nuGetConfigPath, string name, string path) - { - var xmlDoc = new XmlDocument(); - xmlDoc.Load(nuGetConfigPath); - - const string nuGetConfigRootName = "configuration"; - - var rootNode = xmlDoc.DocumentElement; - - if (rootNode == null) - { - // No root node, create it - rootNode = xmlDoc.CreateElement(nuGetConfigRootName); - xmlDoc.AppendChild(rootNode); - - // Since this can be considered pretty much a new NuGet.Config, add the default nuget.org source as well - XmlElement nugetOrgSourceEntry = xmlDoc.CreateElement("add"); - nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("key")).Value = "nuget.org"; - nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("value")).Value = - "https://api.nuget.org/v3/index.json"; - nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("protocolVersion")).Value = "3"; - rootNode.AppendChild(xmlDoc.CreateElement("packageSources")).AppendChild(nugetOrgSourceEntry); - } - else - { - // Check that the root node is the expected one - if (rootNode.Name != nuGetConfigRootName) - throw new FormatException("Invalid root Xml node for NuGet.Config. " + - $"Expected '{nuGetConfigRootName}' got '{rootNode.Name}'."); - } - - var fallbackFoldersNode = rootNode["fallbackPackageFolders"] ?? - rootNode.AppendChild(xmlDoc.CreateElement("fallbackPackageFolders")); - - // Check if it already has our fallback package folder - for (var xmlNode = fallbackFoldersNode.FirstChild; xmlNode != null; xmlNode = xmlNode.NextSibling) - { - if (xmlNode.NodeType != XmlNodeType.Element) - continue; - - var xmlElement = (XmlElement)xmlNode; - if (xmlElement.Name == "add" && - xmlElement.Attributes["key"]?.Value == name && - xmlElement.Attributes["value"]?.Value == path) - { - return; - } - } - - XmlElement newEntry = xmlDoc.CreateElement("add"); - newEntry.Attributes.Append(xmlDoc.CreateAttribute("key")).Value = name; - newEntry.Attributes.Append(xmlDoc.CreateAttribute("value")).Value = path; - - fallbackFoldersNode.AppendChild(newEntry); - - xmlDoc.Save(nuGetConfigPath); - } - /// <summary> - /// Returns all the paths where the user NuGet.Config files can be found. + /// Returns all the paths where the Godot.Offline.Config files can be found. /// Does not determine whether the returned files exist or not. /// </summary> - private static string[] GetAllUserNuGetConfigFilePaths() + private static string[] GetAllGodotNuGetConfigFilePaths() { - // Where to find 'NuGet/NuGet.Config': + // Where to find 'NuGet/config/Godot.Offline.Config': // // - Mono/.NETFramework (standalone NuGet): // Uses Environment.SpecialFolder.ApplicationData @@ -98,10 +40,12 @@ namespace GodotTools.Build string applicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + const string configFileName = "Godot.Offline.Config"; + if (Utils.OS.IsWindows) { // %APPDATA% for both - return new[] { Path.Combine(applicationData, "NuGet", "NuGet.Config") }; + return new[] { Path.Combine(applicationData, "NuGet", "config", configFileName) }; } var paths = new string[2]; @@ -111,20 +55,20 @@ namespace GodotTools.Build string dotnetCliHome = Environment.GetEnvironmentVariable("DOTNET_CLI_HOME"); if (!string.IsNullOrEmpty(dotnetCliHome)) { - paths[0] = Path.Combine(dotnetCliHome, ".nuget", "NuGet", "NuGet.Config"); + paths[0] = Path.Combine(dotnetCliHome, ".nuget", "NuGet", "config", configFileName); } else { string home = Environment.GetEnvironmentVariable("HOME"); if (string.IsNullOrEmpty(home)) throw new InvalidOperationException("Required environment variable 'HOME' is not set."); - paths[0] = Path.Combine(home, ".nuget", "NuGet", "NuGet.Config"); + paths[0] = Path.Combine(home, ".nuget", "NuGet", "config", configFileName); } // Mono/.NETFramework (standalone NuGet) // ApplicationData is $HOME/.config on Linux/macOS - paths[1] = Path.Combine(applicationData, "NuGet", "NuGet.Config"); + paths[1] = Path.Combine(applicationData, "NuGet", "config", configFileName); return paths; } @@ -141,28 +85,26 @@ namespace GodotTools.Build // The nuspec is not lower case inside the nupkg but must be made lower case when extracted. /// <summary> - /// Adds the specified fallback folder to the user NuGet.Config files, + /// Adds the specified fallback folder to the Godot.Offline.Config files, /// for both standalone NuGet (Mono/.NETFramework) and dotnet CLI NuGet. /// </summary> - public static void AddFallbackFolderToUserNuGetConfigs(string name, string path) + public static void AddFallbackFolderToGodotNuGetConfigs(string name, string path) { - foreach (string nuGetConfigPath in GetAllUserNuGetConfigFilePaths()) + // Make sure the fallback folder exists to avoid error: + // MSB4018: The "ResolvePackageAssets" task failed unexpectedly. + System.IO.Directory.CreateDirectory(path); + + foreach (string nuGetConfigPath in GetAllGodotNuGetConfigFilePaths()) { - if (!System.IO.File.Exists(nuGetConfigPath)) - { - // It doesn't exist, so we create a default one - const string defaultConfig = @"<?xml version=""1.0"" encoding=""utf-8""?> + string defaultConfig = @$"<?xml version=""1.0"" encoding=""utf-8""?> <configuration> - <packageSources> - <add key=""nuget.org"" value=""https://api.nuget.org/v3/index.json"" protocolVersion=""3"" /> - </packageSources> + <fallbackPackageFolders> + <add key=""{name}"" value=""{path}"" /> + </fallbackPackageFolders> </configuration> "; - System.IO.Directory.CreateDirectory(Path.GetDirectoryName(nuGetConfigPath)); - System.IO.File.WriteAllText(nuGetConfigPath, defaultConfig, Encoding.UTF8); // UTF-8 with BOM - } - - AddFallbackFolderToNuGetConfig(nuGetConfigPath, name, path); + System.IO.Directory.CreateDirectory(Path.GetDirectoryName(nuGetConfigPath)); + System.IO.File.WriteAllText(nuGetConfigPath, defaultConfig, Encoding.UTF8); // UTF-8 with BOM } } @@ -189,6 +131,7 @@ namespace GodotTools.Build string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower); string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg"); string nupkgSha512DestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg.sha512"); + string nupkgMetadataDestPath = Path.Combine(destDir, ".nupkg.metadata"); if (File.Exists(nupkgDestPath) && File.Exists(nupkgSha512DestPath)) return; // Already added (for speed we don't check if every file is properly extracted) @@ -197,12 +140,18 @@ namespace GodotTools.Build // Generate .nupkg.sha512 file - using (var alg = SHA512.Create()) - { - alg.ComputeHash(File.ReadAllBytes(nupkgPath)); - string base64Hash = Convert.ToBase64String(alg.Hash); - File.WriteAllText(nupkgSha512DestPath, base64Hash); - } + byte[] hash = SHA512.HashData(File.ReadAllBytes(nupkgPath)); + string base64Hash = Convert.ToBase64String(hash); + File.WriteAllText(nupkgSha512DestPath, base64Hash); + + // Generate .nupkg.metadata file + // Spec: https://github.com/NuGet/Home/wiki/Nupkg-Metadata-File + + File.WriteAllText(nupkgMetadataDestPath, @$"{{ + ""version"": 2, + ""contentHash"": ""{base64Hash}"", + ""source"": null +}}"); // Extract nupkg ExtractNupkg(destDir, nupkgPath, packageId, packageVersion); @@ -251,7 +200,7 @@ namespace GodotTools.Build entryFullName.EndsWith(".nupkg.sha512", StringComparison.OrdinalIgnoreCase) || entryFullName.EndsWith(".nupkg.metadata", StringComparison.OrdinalIgnoreCase) || // Nuspec at root level. We already extracted it previously but in lower case. - entryFullName.IndexOf('/') == -1 && entryFullName.EndsWith(".nuspec")) + !entryFullName.Contains('/') && entryFullName.EndsWith(".nuspec")) { continue; } @@ -297,6 +246,8 @@ namespace GodotTools.Build { ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk), ("Godot.SourceGenerators", GeneratedGodotNupkgsVersions.GodotSourceGenerators), + ("GodotSharp", GeneratedGodotNupkgsVersions.GodotSharp), + ("GodotSharpEditor", GeneratedGodotNupkgsVersions.GodotSharp), }; } } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 1cfaea3ec9..89364d1c02 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -123,7 +123,7 @@ namespace GodotTools try { string fallbackFolder = NuGetUtils.GodotFallbackFolderPath; - NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, + NuGetUtils.AddFallbackFolderToGodotNuGetConfigs(NuGetUtils.GodotFallbackFolderName, fallbackFolder); NuGetUtils.AddBundledPackagesToFallbackFolder(fallbackFolder); } @@ -497,7 +497,7 @@ namespace GodotTools try { // At startup we make sure NuGet.Config files have our Godot NuGet fallback folder included - NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, + NuGetUtils.AddFallbackFolderToGodotNuGetConfigs(NuGetUtils.GodotFallbackFolderName, NuGetUtils.GodotFallbackFolderPath); } catch (Exception e) diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index c27bb959fe..95a44d3b7e 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -2229,6 +2229,35 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf // Generate signal { + p_output.append(MEMBER_BEGIN "/// <summary>\n"); + p_output.append(INDENT1 "/// "); + p_output.append("Represents the method that handles the "); + p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "." + p_isignal.proxy_name + "\"/>"); + p_output.append(" event of a "); + p_output.append("<see cref=\"" BINDINGS_NAMESPACE "." + p_itype.proxy_name + "\"/>"); + p_output.append(" class.\n"); + p_output.append(INDENT1 "/// </summary>"); + + if (p_isignal.is_deprecated) { + if (p_isignal.deprecation_message.is_empty()) { + WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'."); + } + + p_output.append(MEMBER_BEGIN "[Obsolete(\""); + p_output.append(p_isignal.deprecation_message); + p_output.append("\")]"); + } + + String delegate_name = p_isignal.proxy_name; + delegate_name += "EventHandler"; // Delegate name is [SignalName]EventHandler + + // Generate delegate + p_output.append(MEMBER_BEGIN "public delegate void "); + p_output.append(delegate_name); + p_output.append("("); + p_output.append(arguments_sig); + p_output.append(");\n"); + if (p_isignal.method_doc && p_isignal.method_doc->description.size()) { String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype); Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); @@ -2247,25 +2276,11 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf } if (p_isignal.is_deprecated) { - if (p_isignal.deprecation_message.is_empty()) { - WARN_PRINT("An empty deprecation message is discouraged. Signal: '" + p_isignal.proxy_name + "'."); - } - p_output.append(MEMBER_BEGIN "[Obsolete(\""); p_output.append(p_isignal.deprecation_message); p_output.append("\")]"); } - String delegate_name = p_isignal.proxy_name; - delegate_name += "EventHandler"; // Delegate name is [SignalName]EventHandler - - // Generate delegate - p_output.append(MEMBER_BEGIN "public delegate void "); - p_output.append(delegate_name); - p_output.append("("); - p_output.append(arguments_sig); - p_output.append(");\n"); - // TODO: // Could we assume the StringName instance of signal name will never be freed (it's stored in ClassDB) before the managed world is unloaded? // If so, we could store the pointer we get from `data_unique_pointer()` instead of allocating StringName here. diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 6f42ad6916..91392c8f79 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -152,7 +152,7 @@ bool godot_icall_Internal_ScriptEditorEdit(Resource *p_resource, int32_t p_line, } void godot_icall_Internal_EditorNodeShowScriptScreen() { - EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT); + EditorNode::get_singleton()->editor_select(EditorNode::EDITOR_SCRIPT); } void godot_icall_Internal_EditorRunPlay() { diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp index bdc8fac8b5..ea5978b2cd 100644 --- a/modules/mono/editor/hostfxr_resolver.cpp +++ b/modules/mono/editor/hostfxr_resolver.cpp @@ -82,7 +82,7 @@ namespace { String get_hostfxr_file_name() { #if defined(WINDOWS_ENABLED) || defined(UWP_ENABLED) return "hostfxr.dll"; -#elif defined(OSX_ENABLED) || defined(IOS_ENABLED) +#elif defined(MACOS_ENABLED) || defined(IOS_ENABLED) return "libhostfxr.dylib"; #else return "libhostfxr.so"; @@ -197,7 +197,7 @@ bool get_default_installation_dir(String &r_dotnet_root) { r_dotnet_root = path::join(program_files_dir, "dotnet"); return true; -#elif defined(TARGET_OSX) +#elif defined(MACOS_ENABLED) r_dotnet_root = "/usr/local/share/dotnet"; #if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(_M_X64) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index fbd59d649f..9c3bc51c44 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -4,6 +4,21 @@ using System.Runtime.InteropServices; namespace Godot { /// <summary> + /// Specifies which order Euler angle rotations should be in. + /// When composing, the order is the same as the letters. When decomposing, + /// the order is reversed (ex: YXZ decomposes Z first, then X, and Y last). + /// </summary> + public enum EulerOrder + { + XYZ, + XZY, + YXZ, + YZX, + ZXY, + ZYX + }; + + /// <summary> /// 3×3 matrix used for 3D rotation and scale. /// Almost always used as an orthogonal basis for a Transform. /// @@ -244,50 +259,258 @@ namespace Godot } /// <summary> - /// Returns the basis's rotation in the form of Euler angles - /// (in the YXZ convention: when *decomposing*, first Z, then X, and Y last). - /// The returned vector contains the rotation angles in - /// the format (X angle, Y angle, Z angle). + /// Returns the basis's rotation in the form of Euler angles. + /// The Euler order depends on the [param order] parameter, + /// by default it uses the YXZ convention: when decomposing, + /// first Z, then X, and Y last. The returned vector contains + /// the rotation angles in the format (X angle, Y angle, Z angle). /// /// Consider using the <see cref="GetRotationQuaternion"/> method instead, which /// returns a <see cref="Quaternion"/> quaternion instead of Euler angles. /// </summary> + /// <param name="order">The Euler order to use. By default, use YXZ order (most common).</param> /// <returns>A <see cref="Vector3"/> representing the basis rotation in Euler angles.</returns> - public Vector3 GetEuler() + public Vector3 GetEuler(EulerOrder order = EulerOrder.YXZ) { - Basis m = Orthonormalized(); - - Vector3 euler; - euler.z = 0.0f; - - real_t mzy = m.Row1[2]; - - if (mzy < 1.0f) + switch (order) { - if (mzy > -1.0f) + case EulerOrder.XYZ: { - euler.x = Mathf.Asin(-mzy); - euler.y = Mathf.Atan2(m.Row0[2], m.Row2[2]); - euler.z = Mathf.Atan2(m.Row1[0], m.Row1[1]); + // Euler angles in XYZ convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cy*cz -cy*sz sy + // cz*sx*sy+cx*sz cx*cz-sx*sy*sz -cy*sx + // -cx*cz*sy+sx*sz cz*sx+cx*sy*sz cx*cy + Vector3 euler; + real_t sy = Row0[2]; + if (sy < (1.0f - Mathf.Epsilon)) + { + if (sy > -(1.0f - Mathf.Epsilon)) + { + // is this a pure Y rotation? + if (Row1[0] == 0 && Row0[1] == 0 && Row1[2] == 0 && Row2[1] == 0 && Row1[1] == 1) + { + // return the simplest form (human friendlier in editor and scripts) + euler.x = 0; + euler.y = Mathf.Atan2(Row0[2], Row0[0]); + euler.z = 0; + } + else + { + euler.x = Mathf.Atan2(-Row1[2], Row2[2]); + euler.y = Mathf.Asin(sy); + euler.z = Mathf.Atan2(-Row0[1], Row0[0]); + } + } + else + { + euler.x = Mathf.Atan2(Row2[1], Row1[1]); + euler.y = -Mathf.Tau / 4.0f; + euler.z = 0.0f; + } + } + else + { + euler.x = Mathf.Atan2(Row2[1], Row1[1]); + euler.y = Mathf.Tau / 4.0f; + euler.z = 0.0f; + } + return euler; } - else + case EulerOrder.XZY: { - euler.x = Mathf.Pi * 0.5f; - euler.y = -Mathf.Atan2(-m.Row0[1], m.Row0[0]); + // Euler angles in XZY convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cz*cy -sz cz*sy + // sx*sy+cx*cy*sz cx*cz cx*sz*sy-cy*sx + // cy*sx*sz cz*sx cx*cy+sx*sz*sy + Vector3 euler; + real_t sz = Row0[1]; + if (sz < (1.0f - Mathf.Epsilon)) + { + if (sz > -(1.0f - Mathf.Epsilon)) + { + euler.x = Mathf.Atan2(Row2[1], Row1[1]); + euler.y = Mathf.Atan2(Row0[2], Row0[0]); + euler.z = Mathf.Asin(-sz); + } + else + { + // It's -1 + euler.x = -Mathf.Atan2(Row1[2], Row2[2]); + euler.y = 0.0f; + euler.z = Mathf.Tau / 4.0f; + } + } + else + { + // It's 1 + euler.x = -Mathf.Atan2(Row1[2], Row2[2]); + euler.y = 0.0f; + euler.z = -Mathf.Tau / 4.0f; + } + return euler; } - } - else - { - euler.x = -Mathf.Pi * 0.5f; - euler.y = -Mathf.Atan2(-m.Row0[1], m.Row0[0]); - } + case EulerOrder.YXZ: + { + // Euler angles in YXZ convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cy*cz+sy*sx*sz cz*sy*sx-cy*sz cx*sy + // cx*sz cx*cz -sx + // cy*sx*sz-cz*sy cy*cz*sx+sy*sz cy*cx + Vector3 euler; + real_t m12 = Row1[2]; + if (m12 < (1 - Mathf.Epsilon)) + { + if (m12 > -(1 - Mathf.Epsilon)) + { + // is this a pure X rotation? + if (Row1[0] == 0 && Row0[1] == 0 && Row0[2] == 0 && Row2[0] == 0 && Row0[0] == 1) + { + // return the simplest form (human friendlier in editor and scripts) + euler.x = Mathf.Atan2(-m12, Row1[1]); + euler.y = 0; + euler.z = 0; + } + else + { + euler.x = Mathf.Asin(-m12); + euler.y = Mathf.Atan2(Row0[2], Row2[2]); + euler.z = Mathf.Atan2(Row1[0], Row1[1]); + } + } + else + { // m12 == -1 + euler.x = Mathf.Tau / 4.0f; + euler.y = Mathf.Atan2(Row0[1], Row0[0]); + euler.z = 0; + } + } + else + { // m12 == 1 + euler.x = -Mathf.Tau / 4.0f; + euler.y = -Mathf.Atan2(Row0[1], Row0[0]); + euler.z = 0; + } - return euler; + return euler; + } + case EulerOrder.YZX: + { + // Euler angles in YZX convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cy*cz sy*sx-cy*cx*sz cx*sy+cy*sz*sx + // sz cz*cx -cz*sx + // -cz*sy cy*sx+cx*sy*sz cy*cx-sy*sz*sx + Vector3 euler; + real_t sz = Row1[0]; + if (sz < (1.0f - Mathf.Epsilon)) + { + if (sz > -(1.0f - Mathf.Epsilon)) + { + euler.x = Mathf.Atan2(-Row1[2], Row1[1]); + euler.y = Mathf.Atan2(-Row2[0], Row0[0]); + euler.z = Mathf.Asin(sz); + } + else + { + // It's -1 + euler.x = Mathf.Atan2(Row2[1], Row2[2]); + euler.y = 0.0f; + euler.z = -Mathf.Tau / 4.0f; + } + } + else + { + // It's 1 + euler.x = Mathf.Atan2(Row2[1], Row2[2]); + euler.y = 0.0f; + euler.z = Mathf.Tau / 4.0f; + } + return euler; + } + case EulerOrder.ZXY: + { + // Euler angles in ZXY convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cz*cy-sz*sx*sy -cx*sz cz*sy+cy*sz*sx + // cy*sz+cz*sx*sy cz*cx sz*sy-cz*cy*sx + // -cx*sy sx cx*cy + Vector3 euler; + real_t sx = Row2[1]; + if (sx < (1.0f - Mathf.Epsilon)) + { + if (sx > -(1.0f - Mathf.Epsilon)) + { + euler.x = Mathf.Asin(sx); + euler.y = Mathf.Atan2(-Row2[0], Row2[2]); + euler.z = Mathf.Atan2(-Row0[1], Row1[1]); + } + else + { + // It's -1 + euler.x = -Mathf.Tau / 4.0f; + euler.y = Mathf.Atan2(Row0[2], Row0[0]); + euler.z = 0; + } + } + else + { + // It's 1 + euler.x = Mathf.Tau / 4.0f; + euler.y = Mathf.Atan2(Row0[2], Row0[0]); + euler.z = 0; + } + return euler; + } + case EulerOrder.ZYX: + { + // Euler angles in ZYX convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cz*cy cz*sy*sx-cx*sz sz*sx+cz*cx*cy + // cy*sz cz*cx+sz*sy*sx cx*sz*sy-cz*sx + // -sy cy*sx cy*cx + Vector3 euler; + real_t sy = Row2[0]; + if (sy < (1.0f - Mathf.Epsilon)) + { + if (sy > -(1.0f - Mathf.Epsilon)) + { + euler.x = Mathf.Atan2(Row2[1], Row2[2]); + euler.y = Mathf.Asin(-sy); + euler.z = Mathf.Atan2(Row1[0], Row0[0]); + } + else + { + // It's -1 + euler.x = 0; + euler.y = Mathf.Tau / 4.0f; + euler.z = -Mathf.Atan2(Row0[1], Row1[1]); + } + } + else + { + // It's 1 + euler.x = 0; + euler.y = -Mathf.Tau / 4.0f; + euler.z = -Mathf.Atan2(Row0[1], Row1[1]); + } + return euler; + } + default: + throw new ArgumentOutOfRangeException(nameof(order)); + } } /// <summary> /// Returns the basis's rotation in the form of a quaternion. - /// See <see cref="GetEuler()"/> if you need Euler angles, but keep in + /// See <see cref="GetEuler"/> if you need Euler angles, but keep in /// mind that quaternions should generally be preferred to Euler angles. /// </summary> /// <returns>A <see cref="Quaternion"/> representing the basis's rotation.</returns> @@ -712,35 +935,6 @@ namespace Godot } /// <summary> - /// Constructs a pure rotation basis matrix from the given Euler angles - /// (in the YXZ convention: when *composing*, first Y, then X, and Z last), - /// given in the vector format as (X angle, Y angle, Z angle). - /// - /// Consider using the <see cref="Basis(Quaternion)"/> constructor instead, which - /// uses a <see cref="Quaternion"/> quaternion instead of Euler angles. - /// </summary> - /// <param name="eulerYXZ">The Euler angles to create the basis from.</param> - public Basis(Vector3 eulerYXZ) - { - real_t c; - real_t s; - - c = Mathf.Cos(eulerYXZ.x); - s = Mathf.Sin(eulerYXZ.x); - var xmat = new Basis(1, 0, 0, 0, c, -s, 0, s, c); - - c = Mathf.Cos(eulerYXZ.y); - s = Mathf.Sin(eulerYXZ.y); - var ymat = new Basis(c, 0, s, 0, 1, 0, -s, 0, c); - - c = Mathf.Cos(eulerYXZ.z); - s = Mathf.Sin(eulerYXZ.z); - var zmat = new Basis(c, -s, 0, s, c, 0, 0, 0, 1); - - this = ymat * xmat * zmat; - } - - /// <summary> /// Constructs a pure rotation basis matrix, rotated around the given <paramref name="axis"/> /// by <paramref name="angle"/> (in radians). The axis must be a normalized vector. /// </summary> @@ -800,6 +994,46 @@ namespace Godot } /// <summary> + /// Constructs a Basis matrix from Euler angles in the specified rotation order. By default, use YXZ order (most common). + /// </summary> + /// <param name="euler">The Euler angles to use.</param> + /// <param name="order">The order to compose the Euler angles.</param> + public static Basis FromEuler(Vector3 euler, EulerOrder order = EulerOrder.YXZ) + { + real_t c, s; + + c = Mathf.Cos(euler.x); + s = Mathf.Sin(euler.x); + Basis xmat = new Basis(new Vector3(1, 0, 0), new Vector3(0, c, s), new Vector3(0, -s, c)); + + c = Mathf.Cos(euler.y); + s = Mathf.Sin(euler.y); + Basis ymat = new Basis(new Vector3(c, 0, -s), new Vector3(0, 1, 0), new Vector3(s, 0, c)); + + c = Mathf.Cos(euler.z); + s = Mathf.Sin(euler.z); + Basis zmat = new Basis(new Vector3(c, s, 0), new Vector3(-s, c, 0), new Vector3(0, 0, 1)); + + switch (order) + { + case EulerOrder.XYZ: + return xmat * ymat * zmat; + case EulerOrder.XZY: + return xmat * zmat * ymat; + case EulerOrder.YXZ: + return ymat * xmat * zmat; + case EulerOrder.YZX: + return ymat * zmat * xmat; + case EulerOrder.ZXY: + return zmat * xmat * ymat; + case EulerOrder.ZYX: + return zmat * ymat * xmat; + default: + throw new ArgumentOutOfRangeException(nameof(order)); + } + } + + /// <summary> /// Constructs a pure scale basis matrix with no rotation or shearing. /// The scale values are set as the main diagonal of the matrix, /// and all of the other parts of the matrix are zero. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs index 1b7f5158fd..bdedd2e87a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs @@ -72,7 +72,7 @@ namespace Godot /// <param name="delegate">Delegate method that will be called.</param> public Callable(Delegate @delegate) { - _target = null; + _target = @delegate?.Target as Object; _method = null; _delegate = @delegate; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index 3483a04c83..ee9e59f9fa 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -597,7 +597,7 @@ namespace Godot /// <exception name="ArgumentOutOfRangeException"> /// <paramref name="rgba"/> color code is invalid. /// </exception> - private static Color FromHTML(string rgba) + private static Color FromHTML(ReadOnlySpan<char> rgba) { Color c; if (rgba.Length == 0) @@ -611,7 +611,7 @@ namespace Godot if (rgba[0] == '#') { - rgba = rgba.Substring(1); + rgba = rgba.Slice(1); } // If enabled, use 1 hex digit per channel instead of 2. @@ -665,22 +665,22 @@ namespace Godot if (c.r < 0) { - throw new ArgumentOutOfRangeException("Invalid color code. Red part is not valid hexadecimal: " + rgba); + throw new ArgumentOutOfRangeException($"Invalid color code. Red part is not valid hexadecimal: {rgba}"); } if (c.g < 0) { - throw new ArgumentOutOfRangeException("Invalid color code. Green part is not valid hexadecimal: " + rgba); + throw new ArgumentOutOfRangeException($"Invalid color code. Green part is not valid hexadecimal: {rgba}"); } if (c.b < 0) { - throw new ArgumentOutOfRangeException("Invalid color code. Blue part is not valid hexadecimal: " + rgba); + throw new ArgumentOutOfRangeException($"Invalid color code. Blue part is not valid hexadecimal: {rgba}"); } if (c.a < 0) { - throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba); + throw new ArgumentOutOfRangeException($"Invalid color code. Alpha part is not valid hexadecimal: {rgba}"); } return c; } @@ -817,9 +817,9 @@ namespace Godot value = max; } - private static int ParseCol4(string str, int ofs) + private static int ParseCol4(ReadOnlySpan<char> str, int index) { - char character = str[ofs]; + char character = str[index]; if (character >= '0' && character <= '9') { @@ -836,9 +836,9 @@ namespace Godot return -1; } - private static int ParseCol8(string str, int ofs) + private static int ParseCol8(ReadOnlySpan<char> str, int index) { - return ParseCol4(str, ofs) * 16 + ParseCol4(str, ofs + 1); + return ParseCol4(str, index) * 16 + ParseCol4(str, index + 1); } private static string ToHex32(float val) @@ -847,16 +847,16 @@ namespace Godot return b.HexEncode(); } - internal static bool HtmlIsValid(string color) + internal static bool HtmlIsValid(ReadOnlySpan<char> color) { - if (string.IsNullOrEmpty(color)) + if (color.IsEmpty) { return false; } if (color[0] == '#') { - color = color.Substring(1); + color = color.Slice(1); } // Check if the amount of hex digits is valid. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs index 140fc167ba..76b186cd15 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs @@ -721,8 +721,9 @@ namespace Godot.NativeInterop if (p_managed_callable.Delegate != null) { var gcHandle = CustomGCHandle.AllocStrong(p_managed_callable.Delegate); + IntPtr objectPtr = p_managed_callable.Target != null ? Object.GetPtr(p_managed_callable.Target) : IntPtr.Zero; NativeFuncs.godotsharp_callable_new_with_delegate( - GCHandle.ToIntPtr(gcHandle), out godot_callable callable); + GCHandle.ToIntPtr(gcHandle), objectPtr, out godot_callable callable); return callable; } else diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index bd00611383..20ede9f0dd 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -141,7 +141,7 @@ namespace Godot.NativeInterop public static partial void godotsharp_packed_string_array_add(ref godot_packed_string_array r_dest, in godot_string p_element); - public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, + public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, IntPtr p_object, out godot_callable r_callable); internal static partial godot_bool godotsharp_callable_get_data_for_marshalling(in godot_callable p_callable, diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index 44f951e314..d77baab24b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -1245,12 +1245,12 @@ namespace Godot /// <summary> /// If the string is a path, this concatenates <paramref name="file"/> /// at the end of the string as a subpath. - /// E.g. <c>"this/is".PlusFile("path") == "this/is/path"</c>. + /// E.g. <c>"this/is".PathJoin("path") == "this/is/path"</c>. /// </summary> /// <param name="instance">The path that will be concatenated.</param> /// <param name="file">File name to concatenate with the path.</param> /// <returns>The concatenated path with the given file name.</returns> - public static string PlusFile(this string instance, string file) + public static string PathJoin(this string instance, string file) { if (instance.Length > 0 && instance[instance.Length - 1] == '/') return instance + file; diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 276701cdaa..2717b945f6 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -447,9 +447,10 @@ void godotsharp_packed_string_array_add(PackedStringArray *r_dest, const String r_dest->append(*p_element); } -void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, Callable *r_callable) { +void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, const Object *p_object, Callable *r_callable) { // TODO: Use pooling for ManagedCallable instances. - CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle)); + ObjectID objid = p_object ? p_object->get_instance_id() : ObjectID(); + CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle, objid)); memnew_placement(r_callable, Callable(managed_callable)); } diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp index 9305dc645a..0c2c533090 100644 --- a/modules/mono/managed_callable.cpp +++ b/modules/mono/managed_callable.cpp @@ -79,7 +79,9 @@ CallableCustom::CompareLessFunc ManagedCallable::get_compare_less_func() const { } ObjectID ManagedCallable::get_object() const { - // TODO: If the delegate target extends Godot.Object, use that instead! + if (object_id != ObjectID()) { + return object_id; + } return CSharpLanguage::get_singleton()->get_managed_callable_middleman()->get_instance_id(); } @@ -104,7 +106,7 @@ void ManagedCallable::release_delegate_handle() { // Why you do this clang-format... /* clang-format off */ -ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle) : delegate_handle(p_delegate_handle) { +ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id) : delegate_handle(p_delegate_handle), object_id(p_object_id) { #ifdef GD_MONO_HOT_RELOAD { MutexLock lock(instances_mutex); diff --git a/modules/mono/managed_callable.h b/modules/mono/managed_callable.h index aa3344f4d5..26cd164fb6 100644 --- a/modules/mono/managed_callable.h +++ b/modules/mono/managed_callable.h @@ -40,6 +40,7 @@ class ManagedCallable : public CallableCustom { friend class CSharpLanguage; GCHandleIntPtr delegate_handle; + ObjectID object_id; #ifdef GD_MONO_HOT_RELOAD SelfList<ManagedCallable> self_instance = this; @@ -66,7 +67,7 @@ public: void release_delegate_handle(); - ManagedCallable(GCHandleIntPtr p_delegate_handle); + ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id); ~ManagedCallable(); }; diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 43811a4325..21252a5dca 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -35,11 +35,13 @@ #include "../godotsharp_defs.h" +#ifndef GD_CLR_STDCALL #ifdef WIN32 #define GD_CLR_STDCALL __stdcall #else #define GD_CLR_STDCALL #endif +#endif namespace gdmono { @@ -56,8 +58,6 @@ struct PluginCallbacks { } // namespace gdmono -#undef GD_CLR_STDCALL - class GDMono { bool runtime_initialized; bool finalizing_scripts_domain; diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index ca3a6c95a7..13b599fe55 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -47,11 +47,13 @@ class CSharpScript; namespace GDMonoCache { +#ifndef GD_CLR_STDCALL #ifdef WIN32 #define GD_CLR_STDCALL __stdcall #else #define GD_CLR_STDCALL #endif +#endif struct godotsharp_property_info { godot_string_name name; // Not owned @@ -68,8 +70,8 @@ struct godotsharp_property_def_val_pair { }; struct ManagedCallbacks { - using Callback_ScriptManagerBridge_GetPropertyInfoList_Add = void(GD_CLR_STDCALL *)(CSharpScript *p_script, const String *, godotsharp_property_info *p_props, int32_t p_count); - using Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add = void(GD_CLR_STDCALL *)(CSharpScript *p_script, godotsharp_property_def_val_pair *p_def_vals, int32_t p_count); + using Callback_ScriptManagerBridge_GetPropertyInfoList_Add = void(GD_CLR_STDCALL *)(CSharpScript *p_script, const String *, void *p_props, int32_t p_count); + using Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add = void(GD_CLR_STDCALL *)(CSharpScript *p_script, void *p_def_vals, int32_t p_count); using FuncSignalAwaiter_SignalCallback = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, int32_t, bool *); using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, uint32_t, const Variant *); @@ -145,6 +147,4 @@ void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks); } // namespace GDMonoCache -#undef GD_CLR_STDCALL - #endif // GD_MONO_CACHE_H diff --git a/modules/mono/utils/macos_utils.h b/modules/mono/utils/macos_utils.h index ca4957f5a7..0b74114685 100644 --- a/modules/mono/utils/macos_utils.h +++ b/modules/mono/utils/macos_utils.h @@ -28,13 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "core/string/ustring.h" - #ifndef MONO_MACOS_UTILS_H #define MONO_MACOS_UTILS_H #ifdef MACOS_ENABLED +#include "core/string/ustring.h" + bool macos_is_app_bundle_installed(const String &p_bundle_id); #endif |