summaryrefslogtreecommitdiff
path: root/modules/mono
diff options
context:
space:
mode:
Diffstat (limited to 'modules/mono')
-rw-r--r--modules/mono/Directory.Build.props3
-rw-r--r--modules/mono/SCsub54
-rw-r--r--modules/mono/SdkPackageVersions.props6
-rw-r--r--modules/mono/build_scripts/api_solution_build.py35
-rw-r--r--modules/mono/build_scripts/gen_cs_glue_version.py20
-rw-r--r--modules/mono/build_scripts/godot_net_sdk_build.py55
-rw-r--r--modules/mono/build_scripts/godot_tools_build.py27
-rw-r--r--modules/mono/build_scripts/make_android_mono_config.py31
-rw-r--r--modules/mono/build_scripts/mono_configure.py523
-rw-r--r--modules/mono/build_scripts/mono_reg_utils.py69
-rw-r--r--modules/mono/build_scripts/solution_builder.py195
-rw-r--r--modules/mono/build_scripts/tls_configure.py36
-rw-r--r--modules/mono/class_db_api_json.cpp45
-rw-r--r--modules/mono/class_db_api_json.h12
-rw-r--r--modules/mono/config.py68
-rw-r--r--modules/mono/csharp_script.cpp1961
-rw-r--r--modules/mono/csharp_script.h406
-rw-r--r--modules/mono/doc_classes/@C#.xml13
-rw-r--r--modules/mono/doc_classes/CSharpScript.xml9
-rw-r--r--modules/mono/doc_classes/GodotSharp.xml19
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln34
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj41
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props115
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets22
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs15
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs11
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj31
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs33
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs89
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj40
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props7
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs9
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs182
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs89
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj58
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs35
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs30
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj38
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs26
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs44
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs33
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs94
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs219
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs207
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj53
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs21
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs46
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs88
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs35
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs57
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj17
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs218
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs351
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs44
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs52
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs (renamed from modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs)6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ILogger.cs (renamed from modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs)2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IMessageHandler.cs9
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Message.cs52
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs100
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs298
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs129
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs23
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs (renamed from modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs)4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs32
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj12
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs270
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs45
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj69
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs56
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe0
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs61
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs160
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs181
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs27
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets36
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj6
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.sln14
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs343
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs (renamed from modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs)18
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs272
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs417
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildResult.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs99
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs181
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs117
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs297
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildManager.cs266
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildTab.cs267
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs112
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs618
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs527
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs93
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs261
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj128
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs163
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs212
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs390
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs20
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs121
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs35
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs21
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs57
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs19
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs26
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs48
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs99
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/packages.config5
-rw-r--r--modules/mono/editor/bindings_generator.cpp1355
-rw-r--r--modules/mono/editor/bindings_generator.h294
-rw-r--r--modules/mono/editor/code_completion.cpp261
-rw-r--r--modules/mono/editor/code_completion.h (renamed from modules/mono/glue/rid_glue.h)45
-rw-r--r--modules/mono/editor/csharp_project.cpp69
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp231
-rw-r--r--modules/mono/editor/editor_internal_calls.h4
-rw-r--r--modules/mono/editor/godotsharp_export.cpp108
-rw-r--r--modules/mono/editor/godotsharp_export.h15
-rw-r--r--modules/mono/editor/script_class_parser.cpp735
-rw-r--r--modules/mono/editor/script_class_parser.h110
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp.sln2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs238
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs75
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs22
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs9
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs6
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs15
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs428
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs31
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs742
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs590
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs395
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs31
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs27
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs17
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs62
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs21
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs178
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs337
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs49
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs59
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs30
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs152
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs414
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs558
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs200
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs402
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs12
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs17
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs330
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs82
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs216
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs249
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs410
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs20
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs433
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs528
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs469
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs533
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj52
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs24
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj47
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs25
-rw-r--r--modules/mono/glue/arguments_vector.h9
-rw-r--r--modules/mono/glue/base_object_glue.cpp127
-rw-r--r--modules/mono/glue/base_object_glue.h71
-rw-r--r--modules/mono/glue/collections_glue.cpp163
-rw-r--r--modules/mono/glue/collections_glue.h124
-rw-r--r--modules/mono/glue/gd_glue.cpp135
-rw-r--r--modules/mono/glue/gd_glue.h86
-rw-r--r--modules/mono/glue/glue_header.h41
-rw-r--r--modules/mono/glue/nodepath_glue.cpp37
-rw-r--r--modules/mono/glue/rid_glue.cpp21
-rw-r--r--modules/mono/glue/scene_tree_glue.cpp (renamed from modules/mono/glue/string_glue.h)64
-rw-r--r--modules/mono/glue/string_glue.cpp26
-rw-r--r--modules/mono/glue/string_name_glue.cpp (renamed from modules/mono/glue/nodepath_glue.h)54
-rw-r--r--modules/mono/godotsharp_defs.h4
-rw-r--r--modules/mono/godotsharp_dirs.cpp40
-rw-r--r--modules/mono/godotsharp_dirs.h7
-rw-r--r--modules/mono/icons/CSharpScript.svg1
-rw-r--r--modules/mono/icons/icon_c_sharp_script.svg5
-rw-r--r--modules/mono/managed_callable.cpp148
-rw-r--r--modules/mono/managed_callable.h (renamed from modules/mono/utils/mutex_utils.h)70
-rw-r--r--modules/mono/mono_gc_handle.cpp63
-rw-r--r--modules/mono/mono_gc_handle.h90
-rw-r--r--modules/mono/mono_gd/android_mono_config.h6
-rw-r--r--modules/mono/mono_gd/gd_mono.cpp480
-rw-r--r--modules/mono/mono_gd/gd_mono.h64
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.cpp483
-rw-r--r--modules/mono/mono_gd/gd_mono_assembly.h59
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.cpp239
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h67
-rw-r--r--modules/mono/mono_gd/gd_mono_class.cpp224
-rw-r--r--modules/mono/mono_gd/gd_mono_class.h32
-rw-r--r--modules/mono/mono_gd/gd_mono_field.cpp420
-rw-r--r--modules/mono/mono_gd/gd_mono_field.h20
-rw-r--r--modules/mono/mono_gd/gd_mono_header.h6
-rw-r--r--modules/mono/mono_gd/gd_mono_internals.cpp52
-rw-r--r--modules/mono/mono_gd/gd_mono_internals.h8
-rw-r--r--modules/mono/mono_gd/gd_mono_log.cpp77
-rw-r--r--modules/mono/mono_gd/gd_mono_log.h20
-rw-r--r--modules/mono/mono_gd/gd_mono_marshal.cpp1742
-rw-r--r--modules/mono/mono_gd/gd_mono_marshal.h266
-rw-r--r--modules/mono/mono_gd/gd_mono_method.cpp131
-rw-r--r--modules/mono/mono_gd/gd_mono_method.h40
-rw-r--r--modules/mono/mono_gd/gd_mono_method_thunk.h76
-rw-r--r--modules/mono/mono_gd/gd_mono_property.cpp57
-rw-r--r--modules/mono/mono_gd/gd_mono_property.h25
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.cpp300
-rw-r--r--modules/mono/mono_gd/gd_mono_utils.h93
-rw-r--r--modules/mono/mono_gd/gd_mono_wasm_m2n.cpp (renamed from modules/mono/utils/thread_local.cpp)98
-rw-r--r--modules/mono/mono_gd/gd_mono_wasm_m2n.h263
-rw-r--r--modules/mono/mono_gd/i_mono_class_member.h4
-rw-r--r--modules/mono/mono_gd/managed_type.cpp4
-rw-r--r--modules/mono/mono_gd/managed_type.h13
-rw-r--r--modules/mono/mono_gd/support/android_support.cpp (renamed from modules/mono/mono_gd/gd_mono_android.cpp)131
-rw-r--r--modules/mono/mono_gd/support/android_support.h (renamed from modules/mono/mono_gd/gd_mono_android.h)26
-rw-r--r--modules/mono/mono_gd/support/ios_support.h (renamed from modules/mono/editor/csharp_project.h)28
-rw-r--r--modules/mono/mono_gd/support/ios_support.mm149
-rw-r--r--modules/mono/register_types.cpp20
-rw-r--r--modules/mono/register_types.h9
-rw-r--r--modules/mono/signal_awaiter_utils.cpp219
-rw-r--r--modules/mono/signal_awaiter_utils.h74
-rw-r--r--modules/mono/utils/macros.h58
-rw-r--r--modules/mono/utils/mono_reg_utils.cpp25
-rw-r--r--modules/mono/utils/mono_reg_utils.h7
-rw-r--r--modules/mono/utils/osx_utils.cpp32
-rw-r--r--modules/mono/utils/osx_utils.h6
-rw-r--r--modules/mono/utils/path_utils.cpp93
-rw-r--r--modules/mono/utils/path_utils.h11
-rw-r--r--modules/mono/utils/string_utils.cpp47
-rw-r--r--modules/mono/utils/string_utils.h8
-rw-r--r--modules/mono/utils/thread_local.h177
243 files changed, 20208 insertions, 12442 deletions
diff --git a/modules/mono/Directory.Build.props b/modules/mono/Directory.Build.props
new file mode 100644
index 0000000000..fbf864b11b
--- /dev/null
+++ b/modules/mono/Directory.Build.props
@@ -0,0 +1,3 @@
+<Project>
+ <Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" />
+</Project>
diff --git a/modules/mono/SCsub b/modules/mono/SCsub
index 41be367f2f..3b94949470 100644
--- a/modules/mono/SCsub
+++ b/modules/mono/SCsub
@@ -1,54 +1,62 @@
#!/usr/bin/env python
-import build_scripts.tls_configure as tls_configure
import build_scripts.mono_configure as mono_configure
-Import('env')
-Import('env_modules')
+Import("env")
+Import("env_modules")
env_mono = env_modules.Clone()
-if env_mono['tools']:
+if env_mono["tools"]:
# NOTE: It is safe to generate this file here, since this is still executed serially
import build_scripts.gen_cs_glue_version as gen_cs_glue_version
- gen_cs_glue_version.generate_header('glue/GodotSharp', 'glue/cs_glue_version.gen.h')
+
+ gen_cs_glue_version.generate_header("glue/GodotSharp", "glue/cs_glue_version.gen.h")
# Glue sources
-if env_mono['mono_glue']:
- env_mono.Append(CPPDEFINES=['MONO_GLUE_ENABLED'])
+if env_mono["mono_glue"]:
+ env_mono.Append(CPPDEFINES=["MONO_GLUE_ENABLED"])
import os.path
- if not os.path.isfile('glue/mono_glue.gen.cpp'):
- raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?")
-
-if env_mono['tools'] or env_mono['target'] != 'release':
- env_mono.Append(CPPDEFINES=['GD_MONO_HOT_RELOAD'])
-# Configure Thread Local Storage
+ if not os.path.isfile("glue/mono_glue.gen.cpp"):
+ raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?")
-conf = Configure(env_mono)
-tls_configure.configure(conf)
-env_mono = conf.Finish()
+if env_mono["tools"] or env_mono["target"] != "release":
+ env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"])
# Configure Mono
mono_configure.configure(env, env_mono)
-if env_mono['tools'] and env_mono['mono_glue']:
+if env_mono["tools"] and env_mono["mono_glue"] and env_mono["build_cil"]:
# Build Godot API solution
import build_scripts.api_solution_build as api_solution_build
+
api_sln_cmd = api_solution_build.build(env_mono)
# Build GodotTools
import build_scripts.godot_tools_build as godot_tools_build
+
godot_tools_build.build(env_mono, api_sln_cmd)
+ # Build Godot.NET.Sdk
+ import build_scripts.godot_net_sdk_build as godot_net_sdk_build
+
+ godot_net_sdk_build.build(env_mono)
+
# Add sources
-env_mono.add_source_files(env.modules_sources, '*.cpp')
-env_mono.add_source_files(env.modules_sources, 'glue/*.cpp')
-env_mono.add_source_files(env.modules_sources, 'mono_gd/*.cpp')
-env_mono.add_source_files(env.modules_sources, 'utils/*.cpp')
+env_mono.add_source_files(env.modules_sources, "*.cpp")
+env_mono.add_source_files(env.modules_sources, "glue/*.cpp")
+env_mono.add_source_files(env.modules_sources, "mono_gd/*.cpp")
+env_mono.add_source_files(env.modules_sources, "utils/*.cpp")
+
+env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.cpp")
+
+if env["platform"] in ["osx", "iphone"]:
+ env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.mm")
+ env_mono.add_source_files(env.modules_sources, "mono_gd/support/*.m")
-if env['tools']:
- env_mono.add_source_files(env.modules_sources, 'editor/*.cpp')
+if env["tools"]:
+ env_mono.add_source_files(env.modules_sources, "editor/*.cpp")
diff --git a/modules/mono/SdkPackageVersions.props b/modules/mono/SdkPackageVersions.props
new file mode 100644
index 0000000000..df3ebe581c
--- /dev/null
+++ b/modules/mono/SdkPackageVersions.props
@@ -0,0 +1,6 @@
+<Project>
+ <PropertyGroup>
+ <PackageVersion_Godot_NET_Sdk>4.0.0-dev5</PackageVersion_Godot_NET_Sdk>
+ <PackageVersion_Godot_SourceGenerators>4.0.0-dev2</PackageVersion_Godot_SourceGenerators>
+ </PropertyGroup>
+</Project>
diff --git a/modules/mono/build_scripts/api_solution_build.py b/modules/mono/build_scripts/api_solution_build.py
index 639197c285..9abac22df6 100644
--- a/modules/mono/build_scripts/api_solution_build.py
+++ b/modules/mono/build_scripts/api_solution_build.py
@@ -8,21 +8,22 @@ from SCons.Script import Dir
def build_api_solution(source, target, env):
# source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
- module_dir = env['module_dir']
+ module_dir = env["module_dir"]
- solution_path = os.path.join(module_dir, 'glue/GodotSharp/GodotSharp.sln')
+ solution_path = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln")
- build_config = env['solution_build_config']
+ build_config = env["solution_build_config"]
- extra_msbuild_args = ['/p:NoWarn=1591'] # Ignore missing documentation warnings
+ extra_msbuild_args = ["/p:NoWarn=1591"] # Ignore missing documentation warnings
from .solution_builder import build_solution
+
build_solution(env, solution_path, build_config, extra_msbuild_args=extra_msbuild_args)
# Copy targets
- core_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharp', 'bin', build_config))
- editor_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharpEditor', 'bin', build_config))
+ core_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, "GodotSharp", "bin", build_config))
+ editor_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, "GodotSharpEditor", "bin", build_config))
dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
@@ -32,6 +33,7 @@ def build_api_solution(source, target, env):
def copy_target(target_path):
from shutil import copy
+
filename = os.path.basename(target_path)
src_path = os.path.join(core_src_dir, filename)
@@ -45,23 +47,28 @@ def build_api_solution(source, target, env):
def build(env_mono):
- assert env_mono['tools']
+ assert env_mono["tools"]
target_filenames = [
- 'GodotSharp.dll', 'GodotSharp.pdb', 'GodotSharp.xml',
- 'GodotSharpEditor.dll', 'GodotSharpEditor.pdb', 'GodotSharpEditor.xml'
+ "GodotSharp.dll",
+ "GodotSharp.pdb",
+ "GodotSharp.xml",
+ "GodotSharpEditor.dll",
+ "GodotSharpEditor.pdb",
+ "GodotSharpEditor.xml",
]
depend_cmd = []
- for build_config in ['Debug', 'Release']:
- output_dir = Dir('#bin').abspath
- editor_api_dir = os.path.join(output_dir, 'GodotSharp', 'Api', build_config)
+ for build_config in ["Debug", "Release"]:
+ output_dir = Dir("#bin").abspath
+ editor_api_dir = os.path.join(output_dir, "GodotSharp", "Api", build_config)
targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames]
- cmd = env_mono.CommandNoCache(targets, depend_cmd, build_api_solution,
- module_dir=os.getcwd(), solution_build_config=build_config)
+ cmd = env_mono.CommandNoCache(
+ targets, depend_cmd, build_api_solution, module_dir=os.getcwd(), solution_build_config=build_config
+ )
env_mono.AlwaysBuild(cmd)
# Make the Release build of the API solution depend on the Debug build.
diff --git a/modules/mono/build_scripts/gen_cs_glue_version.py b/modules/mono/build_scripts/gen_cs_glue_version.py
index 5d1056c2fc..98bbb4d9be 100644
--- a/modules/mono/build_scripts/gen_cs_glue_version.py
+++ b/modules/mono/build_scripts/gen_cs_glue_version.py
@@ -1,20 +1,20 @@
-
def generate_header(solution_dir, version_header_dst):
import os
+
latest_mtime = 0
for root, dirs, files in os.walk(solution_dir, topdown=True):
- dirs[:] = [d for d in dirs if d not in ['Generated']] # Ignored generated files
- files = [f for f in files if f.endswith('.cs')]
+ dirs[:] = [d for d in dirs if d not in ["Generated"]] # Ignored generated files
+ files = [f for f in files if f.endswith(".cs")]
for file in files:
filepath = os.path.join(root, file)
mtime = os.path.getmtime(filepath)
latest_mtime = mtime if mtime > latest_mtime else latest_mtime
- glue_version = int(latest_mtime) # The latest modified time will do for now
+ glue_version = int(latest_mtime) # The latest modified time will do for now
- with open(version_header_dst, 'w') as version_header:
- version_header.write('/* THIS FILE IS GENERATED DO NOT EDIT */\n')
- version_header.write('#ifndef CS_GLUE_VERSION_H\n')
- version_header.write('#define CS_GLUE_VERSION_H\n\n')
- version_header.write('#define CS_GLUE_VERSION UINT32_C(' + str(glue_version) + ')\n')
- version_header.write('\n#endif // CS_GLUE_VERSION_H\n')
+ with open(version_header_dst, "w") as version_header:
+ version_header.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
+ version_header.write("#ifndef CS_GLUE_VERSION_H\n")
+ version_header.write("#define CS_GLUE_VERSION_H\n\n")
+ version_header.write("#define CS_GLUE_VERSION UINT32_C(" + str(glue_version) + ")\n")
+ version_header.write("\n#endif // CS_GLUE_VERSION_H\n")
diff --git a/modules/mono/build_scripts/godot_net_sdk_build.py b/modules/mono/build_scripts/godot_net_sdk_build.py
new file mode 100644
index 0000000000..8c5a60d2db
--- /dev/null
+++ b/modules/mono/build_scripts/godot_net_sdk_build.py
@@ -0,0 +1,55 @@
+# Build Godot.NET.Sdk solution
+
+import os
+
+from SCons.Script import Dir
+
+
+def build_godot_net_sdk(source, target, env):
+ # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
+
+ module_dir = env["module_dir"]
+
+ solution_path = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln")
+ build_config = "Release"
+
+ from .solution_builder import build_solution
+
+ extra_msbuild_args = ["/p:GodotPlatform=" + env["platform"]]
+
+ build_solution(env, solution_path, build_config, extra_msbuild_args)
+ # No need to copy targets. The Godot.NET.Sdk csproj takes care of copying them.
+
+
+def get_nupkgs_versions(props_file):
+ import xml.etree.ElementTree as ET
+
+ tree = ET.parse(props_file)
+ root = tree.getroot()
+
+ return {
+ "Godot.NET.Sdk": root.find("./PropertyGroup/PackageVersion_Godot_NET_Sdk").text.strip(),
+ "Godot.SourceGenerators": root.find("./PropertyGroup/PackageVersion_Godot_SourceGenerators").text.strip(),
+ }
+
+
+def build(env_mono):
+ assert env_mono["tools"]
+
+ output_dir = Dir("#bin").abspath
+ editor_tools_dir = os.path.join(output_dir, "GodotSharp", "Tools")
+ nupkgs_dir = os.path.join(editor_tools_dir, "nupkgs")
+
+ module_dir = os.getcwd()
+
+ nupkgs_versions = get_nupkgs_versions(os.path.join(module_dir, "SdkPackageVersions.props"))
+
+ target_filenames = [
+ "Godot.NET.Sdk.%s.nupkg" % nupkgs_versions["Godot.NET.Sdk"],
+ "Godot.SourceGenerators.%s.nupkg" % nupkgs_versions["Godot.SourceGenerators"],
+ ]
+
+ targets = [os.path.join(nupkgs_dir, filename) for filename in target_filenames]
+
+ cmd = env_mono.CommandNoCache(targets, [], build_godot_net_sdk, module_dir=module_dir)
+ env_mono.AlwaysBuild(cmd)
diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py
index 99341c631e..3bbbf29d3b 100644
--- a/modules/mono/build_scripts/godot_tools_build.py
+++ b/modules/mono/build_scripts/godot_tools_build.py
@@ -8,30 +8,29 @@ from SCons.Script import Dir
def build_godot_tools(source, target, env):
# source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
- module_dir = env['module_dir']
+ module_dir = env["module_dir"]
- solution_path = os.path.join(module_dir, 'editor/GodotTools/GodotTools.sln')
- build_config = 'Debug' if env['target'] == 'debug' else 'Release'
+ solution_path = os.path.join(module_dir, "editor/GodotTools/GodotTools.sln")
+ build_config = "Debug" if env["target"] == "debug" else "Release"
- # Custom build target to make sure output is always copied to the data dir.
- extra_build_args = ['/Target:Build;GodotTools:BuildAlwaysCopyToDataDir']
+ from .solution_builder import build_solution
- from . solution_builder import build_solution, nuget_restore
- nuget_restore(env, solution_path)
- build_solution(env, solution_path, build_config, extra_build_args)
+ extra_msbuild_args = ["/p:GodotPlatform=" + env["platform"]]
+
+ build_solution(env, solution_path, build_config, extra_msbuild_args)
# No need to copy targets. The GodotTools csproj takes care of copying them.
def build(env_mono, api_sln_cmd):
- assert env_mono['tools']
+ assert env_mono["tools"]
- output_dir = Dir('#bin').abspath
- editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools')
+ output_dir = Dir("#bin").abspath
+ editor_tools_dir = os.path.join(output_dir, "GodotSharp", "Tools")
- target_filenames = ['GodotTools.dll']
+ target_filenames = ["GodotTools.dll"]
- if env_mono['target'] == 'debug':
- target_filenames += ['GodotTools.pdb']
+ if env_mono["target"] == "debug":
+ target_filenames += ["GodotTools.pdb"]
targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames]
diff --git a/modules/mono/build_scripts/make_android_mono_config.py b/modules/mono/build_scripts/make_android_mono_config.py
index 8cad204d7b..28494bff6e 100644
--- a/modules/mono/build_scripts/make_android_mono_config.py
+++ b/modules/mono/build_scripts/make_android_mono_config.py
@@ -1,30 +1,30 @@
-
def generate_compressed_config(config_src, output_dir):
import os.path
- from compat import byte_to_str
# Source file
- with open(os.path.join(output_dir, 'android_mono_config.gen.cpp'), 'w') as cpp:
- with open(config_src, 'rb') as f:
+ with open(os.path.join(output_dir, "android_mono_config.gen.cpp"), "w") as cpp:
+ with open(config_src, "rb") as f:
buf = f.read()
decompr_size = len(buf)
import zlib
+
buf = zlib.compress(buf)
compr_size = len(buf)
- bytes_seq_str = ''
+ bytes_seq_str = ""
for i, buf_idx in enumerate(range(compr_size)):
if i > 0:
- bytes_seq_str += ', '
- bytes_seq_str += byte_to_str(buf[buf_idx])
+ bytes_seq_str += ", "
+ bytes_seq_str += str(buf[buf_idx])
- cpp.write('''/* THIS FILE IS GENERATED DO NOT EDIT */
+ cpp.write(
+ """/* THIS FILE IS GENERATED DO NOT EDIT */
#include "android_mono_config.h"
#ifdef ANDROID_ENABLED
#include "core/io/compression.h"
-#include "core/pool_vector.h"
+
namespace {
@@ -32,21 +32,22 @@ namespace {
static const int config_compressed_size = %d;
static const int config_uncompressed_size = %d;
static const unsigned char config_compressed_data[] = { %s };
-
} // namespace
String get_godot_android_mono_config() {
- PoolVector<uint8_t> data;
+ Vector<uint8_t> data;
data.resize(config_uncompressed_size);
- PoolVector<uint8_t>::Write w = data.write();
- Compression::decompress(w.ptr(), config_uncompressed_size, config_compressed_data,
+ uint8_t* w = data.ptrw();
+ Compression::decompress(w, config_uncompressed_size, config_compressed_data,
config_compressed_size, Compression::MODE_DEFLATE);
String s;
- if (s.parse_utf8((const char *)w.ptr(), data.size())) {
+ if (s.parse_utf8((const char *)w, data.size())) {
ERR_FAIL_V(String());
}
return s;
}
#endif // ANDROID_ENABLED
-''' % (compr_size, decompr_size, bytes_seq_str))
+"""
+ % (compr_size, decompr_size, bytes_seq_str)
+ )
diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py
index 033c467da9..8e441e7e07 100644
--- a/modules/mono/build_scripts/mono_configure.py
+++ b/modules/mono/build_scripts/mono_configure.py
@@ -1,182 +1,228 @@
import os
import os.path
-import sys
import subprocess
from SCons.Script import Dir, Environment
-if os.name == 'nt':
+if os.name == "nt":
from . import mono_reg_utils as monoreg
android_arch_dirs = {
- 'armv7': 'armeabi-v7a',
- 'arm64v8': 'arm64-v8a',
- 'x86': 'x86',
- 'x86_64': 'x86_64'
+ "armv7": "armeabi-v7a",
+ "arm64v8": "arm64-v8a",
+ "x86": "x86",
+ "x86_64": "x86_64",
}
def get_android_out_dir(env):
- return os.path.join(Dir('#platform/android/java/lib/libs').abspath,
- 'release' if env['target'] == 'release' else 'debug',
- android_arch_dirs[env['android_arch']])
-
-
-def find_file_in_dir(directory, files, prefix='', extension=''):
- if not extension.startswith('.'):
- extension = '.' + extension
- for curfile in files:
- if os.path.isfile(os.path.join(directory, prefix + curfile + extension)):
- return curfile
- return ''
-
-
-def copy_file(src_dir, dst_dir, name):
+ return os.path.join(
+ Dir("#platform/android/java/lib/libs").abspath,
+ "release" if env["target"] == "release" else "debug",
+ android_arch_dirs[env["android_arch"]],
+ )
+
+
+def find_name_in_dir_files(directory, names, prefixes=[""], extensions=[""]):
+ for extension in extensions:
+ if extension and not extension.startswith("."):
+ extension = "." + extension
+ for prefix in prefixes:
+ for curname in names:
+ if os.path.isfile(os.path.join(directory, prefix + curname + extension)):
+ return curname
+ return ""
+
+
+def find_file_in_dir(directory, names, prefixes=[""], extensions=[""]):
+ for extension in extensions:
+ if extension and not extension.startswith("."):
+ extension = "." + extension
+ for prefix in prefixes:
+ for curname in names:
+ filename = prefix + curname + extension
+ if os.path.isfile(os.path.join(directory, filename)):
+ return filename
+ return ""
+
+
+def copy_file(src_dir, dst_dir, src_name, dst_name=""):
from shutil import copy
- src_path = os.path.join(Dir(src_dir).abspath, name)
+ src_path = os.path.join(Dir(src_dir).abspath, src_name)
dst_dir = Dir(dst_dir).abspath
if not os.path.isdir(dst_dir):
os.makedirs(dst_dir)
- copy(src_path, dst_dir)
+ if dst_name:
+ copy(src_path, os.path.join(dst_dir, dst_name))
+ else:
+ copy(src_path, dst_dir)
def is_desktop(platform):
- return platform in ['windows', 'osx', 'x11', 'server', 'uwp', 'haiku']
+ return platform in ["windows", "osx", "linuxbsd", "server", "uwp", "haiku"]
def is_unix_like(platform):
- return platform in ['osx', 'x11', 'server', 'android', 'haiku']
+ return platform in ["osx", "linuxbsd", "server", "android", "haiku", "iphone"]
def module_supports_tools_on(platform):
- return platform not in ['android', 'javascript']
+ return platform not in ["android", "javascript", "iphone"]
def find_wasm_src_dir(mono_root):
hint_dirs = [
- os.path.join(mono_root, 'src'),
- os.path.join(mono_root, '../src'),
+ os.path.join(mono_root, "src"),
+ os.path.join(mono_root, "../src"),
]
for hint_dir in hint_dirs:
- if os.path.isfile(os.path.join(hint_dir, 'driver.c')):
+ if os.path.isfile(os.path.join(hint_dir, "driver.c")):
return hint_dir
- return ''
+ return ""
def configure(env, env_mono):
- bits = env['bits']
- is_android = env['platform'] == 'android'
- is_javascript = env['platform'] == 'javascript'
+ bits = env["bits"]
+ is_android = env["platform"] == "android"
+ is_javascript = env["platform"] == "javascript"
+ is_ios = env["platform"] == "iphone"
+ is_ios_sim = is_ios and env["arch"] in ["x86", "x86_64"]
- tools_enabled = env['tools']
- mono_static = env['mono_static']
- copy_mono_root = env['copy_mono_root']
+ tools_enabled = env["tools"]
+ mono_static = env["mono_static"]
+ copy_mono_root = env["copy_mono_root"]
- mono_prefix = env['mono_prefix']
+ mono_prefix = env["mono_prefix"]
+ mono_bcl = env["mono_bcl"]
- mono_lib_names = ['mono-2.0-sgen', 'monosgen-2.0']
+ mono_lib_names = ["mono-2.0-sgen", "monosgen-2.0"]
- is_travis = os.environ.get('TRAVIS') == 'true'
+ if is_android and not env["android_arch"] in android_arch_dirs:
+ raise RuntimeError("This module does not support the specified 'android_arch': " + env["android_arch"])
- if is_travis:
- # Travis CI may have a Mono version lower than 5.12
- env_mono.Append(CPPDEFINES=['NO_PENDING_EXCEPTIONS'])
-
- if is_android and not env['android_arch'] in android_arch_dirs:
- raise RuntimeError('This module does not support the specified \'android_arch\': ' + env['android_arch'])
-
- if tools_enabled and not module_supports_tools_on(env['platform']):
+ if tools_enabled and not module_supports_tools_on(env["platform"]):
# TODO:
# Android: We have to add the data directory to the apk, concretely the Api and Tools folders.
- raise RuntimeError('This module does not currently support building for this platform with tools enabled')
+ raise RuntimeError("This module does not currently support building for this platform with tools enabled")
if is_android and mono_static:
- # Android: When static linking and doing something that requires libmono-native, we get a dlopen error as libmono-native seems to depend on libmonosgen-2.0
- raise RuntimeError('Statically linking Mono is not currently supported on this platform')
+ # FIXME: When static linking and doing something that requires libmono-native, we get a dlopen error as 'libmono-native'
+ # seems to depend on 'libmonosgen-2.0'. Could be fixed by re-directing to '__Internal' with a dllmap or in the dlopen hook.
+ raise RuntimeError("Statically linking Mono is not currently supported for this platform")
+
+ if not mono_static and (is_javascript or is_ios):
+ raise RuntimeError("Dynamically linking Mono is not currently supported for this platform")
+
+ if not mono_prefix and (os.getenv("MONO32_PREFIX") or os.getenv("MONO64_PREFIX")):
+ print(
+ "WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the"
+ " 'mono_prefix' SCons parameter instead"
+ )
- if is_javascript:
- mono_static = True
+ # Although we don't support building with tools for any platform where we currently use static AOT,
+ # if these are supported in the future, we won't be using static AOT for them as that would be
+ # too restrictive for the editor. These builds would probably be made to only use the interpreter.
+ mono_aot_static = (is_ios and not is_ios_sim) and not env["tools"]
- if not mono_prefix and (os.getenv('MONO32_PREFIX') or os.getenv('MONO64_PREFIX')):
- print("WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the 'mono_prefix' SCons parameter instead")
+ # Static AOT is only supported on the root domain
+ mono_single_appdomain = mono_aot_static
- if env['platform'] == 'windows':
+ if mono_single_appdomain:
+ env_mono.Append(CPPDEFINES=["GD_MONO_SINGLE_APPDOMAIN"])
+
+ if (env["tools"] or env["target"] != "release") and not mono_single_appdomain:
+ env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"])
+
+ if env["platform"] == "windows":
mono_root = mono_prefix
- if not mono_root and os.name == 'nt':
+ if not mono_root and os.name == "nt":
mono_root = monoreg.find_mono_root_dir(bits)
if not mono_root:
- raise RuntimeError("Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter")
+ raise RuntimeError(
+ "Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter"
+ )
- print('Found Mono root directory: ' + mono_root)
+ print("Found Mono root directory: " + mono_root)
- mono_lib_path = os.path.join(mono_root, 'lib')
+ mono_lib_path = os.path.join(mono_root, "lib")
env.Append(LIBPATH=mono_lib_path)
- env_mono.Prepend(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0'))
+ env_mono.Prepend(CPPPATH=os.path.join(mono_root, "include", "mono-2.0"))
+
+ lib_suffixes = [".lib"]
- lib_suffix = Environment()['LIBSUFFIX']
+ if not env.msvc:
+ # MingW supports both '.a' and '.lib'
+ lib_suffixes.insert(0, ".a")
if mono_static:
if env.msvc:
- mono_static_lib_name = 'libmono-static-sgen'
+ mono_static_lib_name = "libmono-static-sgen"
else:
- mono_static_lib_name = 'libmonosgen-2.0'
+ mono_static_lib_name = "libmonosgen-2.0"
- if not os.path.isfile(os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix)):
- raise RuntimeError('Could not find static mono library in: ' + mono_lib_path)
+ mono_static_lib_file = find_file_in_dir(mono_lib_path, [mono_static_lib_name], extensions=lib_suffixes)
+
+ if not mono_static_lib_file:
+ raise RuntimeError("Could not find static mono library in: " + mono_lib_path)
if env.msvc:
- env.Append(LINKFLAGS=mono_static_lib_name + lib_suffix)
+ env.Append(LINKFLAGS=mono_static_lib_file)
- env.Append(LINKFLAGS='Mincore' + lib_suffix)
- env.Append(LINKFLAGS='msvcrt' + lib_suffix)
- env.Append(LINKFLAGS='LIBCMT' + lib_suffix)
- env.Append(LINKFLAGS='Psapi' + lib_suffix)
+ env.Append(LINKFLAGS="Mincore.lib")
+ env.Append(LINKFLAGS="msvcrt.lib")
+ env.Append(LINKFLAGS="LIBCMT.lib")
+ env.Append(LINKFLAGS="Psapi.lib")
else:
- env.Append(LINKFLAGS=os.path.join(mono_lib_path, mono_static_lib_name + lib_suffix))
+ mono_static_lib_file_path = os.path.join(mono_lib_path, mono_static_lib_file)
+ env.Append(LINKFLAGS=["-Wl,-whole-archive", mono_static_lib_file_path, "-Wl,-no-whole-archive"])
- env.Append(LIBS=['psapi'])
- env.Append(LIBS=['version'])
+ env.Append(LIBS=["psapi"])
+ env.Append(LIBS=["version"])
else:
- mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension=lib_suffix)
+ mono_lib_file = find_file_in_dir(mono_lib_path, mono_lib_names, extensions=lib_suffixes)
- if not mono_lib_name:
- raise RuntimeError('Could not find mono library in: ' + mono_lib_path)
+ if not mono_lib_file:
+ raise RuntimeError("Could not find mono library in: " + mono_lib_path)
if env.msvc:
- env.Append(LINKFLAGS=mono_lib_name + lib_suffix)
+ env.Append(LINKFLAGS=mono_lib_file)
else:
- env.Append(LIBS=[mono_lib_name])
+ mono_lib_file_path = os.path.join(mono_lib_path, mono_lib_file)
+ env.Append(LINKFLAGS=mono_lib_file_path)
- mono_bin_path = os.path.join(mono_root, 'bin')
+ mono_bin_path = os.path.join(mono_root, "bin")
- mono_dll_name = find_file_in_dir(mono_bin_path, mono_lib_names, extension='.dll')
+ mono_dll_file = find_file_in_dir(mono_bin_path, mono_lib_names, prefixes=["", "lib"], extensions=[".dll"])
- if not mono_dll_name:
- raise RuntimeError('Could not find mono shared library in: ' + mono_bin_path)
+ if not mono_dll_file:
+ raise RuntimeError("Could not find mono shared library in: " + mono_bin_path)
- copy_file(mono_bin_path, '#bin', mono_dll_name + '.dll')
+ copy_file(mono_bin_path, "#bin", mono_dll_file)
else:
- is_apple = (sys.platform == 'darwin' or "osxcross" in env)
+ is_apple = env["platform"] in ["osx", "iphone"]
+ is_macos = is_apple and not is_ios
- sharedlib_ext = '.dylib' if is_apple else '.so'
+ sharedlib_ext = ".dylib" if is_apple else ".so"
mono_root = mono_prefix
- mono_lib_path = ''
- mono_so_name = ''
+ mono_lib_path = ""
+ mono_so_file = ""
- if not mono_root and (is_android or is_javascript):
- raise RuntimeError("Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter")
+ if not mono_root and (is_android or is_javascript or is_ios):
+ raise RuntimeError(
+ "Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter"
+ )
- if not mono_root and is_apple:
+ if not mono_root and is_macos:
# Try with some known directories under OSX
- hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current', '/usr/local/var/homebrew/linked/mono']
+ hint_dirs = ["/Library/Frameworks/Mono.framework/Versions/Current", "/usr/local/var/homebrew/linked/mono"]
for hint_dir in hint_dirs:
if os.path.isdir(hint_dir):
mono_root = hint_dir
@@ -187,153 +233,192 @@ def configure(env, env_mono):
if not mono_root and mono_static:
mono_root = pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext)
if not mono_root:
- raise RuntimeError("Building with mono_static=yes, but failed to find the mono prefix with pkg-config; " + \
- "specify one manually with the 'mono_prefix' SCons parameter")
+ raise RuntimeError(
+ "Building with mono_static=yes, but failed to find the mono prefix with pkg-config; "
+ + "specify one manually with the 'mono_prefix' SCons parameter"
+ )
+
+ if is_ios and not is_ios_sim:
+ env_mono.Append(CPPDEFINES=["IOS_DEVICE"])
if mono_root:
- print('Found Mono root directory: ' + mono_root)
+ print("Found Mono root directory: " + mono_root)
- mono_lib_path = os.path.join(mono_root, 'lib')
+ mono_lib_path = os.path.join(mono_root, "lib")
env.Append(LIBPATH=[mono_lib_path])
- env_mono.Prepend(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0'))
+ env_mono.Prepend(CPPPATH=os.path.join(mono_root, "include", "mono-2.0"))
- mono_lib = find_file_in_dir(mono_lib_path, mono_lib_names, prefix='lib', extension='.a')
+ mono_lib = find_name_in_dir_files(mono_lib_path, mono_lib_names, prefixes=["lib"], extensions=[".a"])
if not mono_lib:
- raise RuntimeError('Could not find mono library in: ' + mono_lib_path)
+ raise RuntimeError("Could not find mono library in: " + mono_lib_path)
- env_mono.Append(CPPDEFINES=['_REENTRANT'])
+ env_mono.Append(CPPDEFINES=["_REENTRANT"])
if mono_static:
- env.Append(LINKFLAGS=['-rdynamic'])
+ if not is_javascript:
+ env.Append(LINKFLAGS=["-rdynamic"])
- mono_lib_file = os.path.join(mono_lib_path, 'lib' + mono_lib + '.a')
+ mono_lib_file = os.path.join(mono_lib_path, "lib" + mono_lib + ".a")
if is_apple:
- env.Append(LINKFLAGS=['-Wl,-force_load,' + mono_lib_file])
+ if is_macos:
+ env.Append(LINKFLAGS=["-Wl,-force_load," + mono_lib_file])
+ else:
+ arch = env["arch"]
+
+ def copy_mono_lib(libname_wo_ext):
+ copy_file(
+ mono_lib_path, "#bin", libname_wo_ext + ".a", "%s.iphone.%s.a" % (libname_wo_ext, arch)
+ )
+
+ # Copy Mono libraries to the output folder. These are meant to be bundled with
+ # the export templates and added to the Xcode project when exporting a game.
+ copy_mono_lib("lib" + mono_lib)
+ copy_mono_lib("libmono-native")
+ copy_mono_lib("libmono-profiler-log")
+
+ if not is_ios_sim:
+ copy_mono_lib("libmono-ee-interp")
+ copy_mono_lib("libmono-icall-table")
+ copy_mono_lib("libmono-ilgen")
else:
- assert is_desktop(env['platform']) or is_android or is_javascript
- env.Append(LINKFLAGS=['-Wl,-whole-archive', mono_lib_file, '-Wl,-no-whole-archive'])
+ assert is_desktop(env["platform"]) or is_android or is_javascript
+ env.Append(LINKFLAGS=["-Wl,-whole-archive", mono_lib_file, "-Wl,-no-whole-archive"])
if is_javascript:
- env.Append(LIBS=['mono-icall-table', 'mono-native', 'mono-ilgen', 'mono-ee-interp'])
+ env.Append(LIBS=["mono-icall-table", "mono-native", "mono-ilgen", "mono-ee-interp"])
- wasm_src_dir = os.path.join(mono_root, 'src')
+ wasm_src_dir = os.path.join(mono_root, "src")
if not os.path.isdir(wasm_src_dir):
- raise RuntimeError('Could not find mono wasm src directory')
+ raise RuntimeError("Could not find mono wasm src directory")
# Ideally this should be defined only for 'driver.c', but I can't fight scons for another 2 hours
- env_mono.Append(CPPDEFINES=['CORE_BINDINGS'])
-
- env_mono.add_source_files(env.modules_sources, [
- os.path.join(wasm_src_dir, 'driver.c'),
- os.path.join(wasm_src_dir, 'zlib-helper.c'),
- os.path.join(wasm_src_dir, 'corebindings.c')
- ])
-
- env.Append(LINKFLAGS=[
- '--js-library', os.path.join(wasm_src_dir, 'library_mono.js'),
- '--js-library', os.path.join(wasm_src_dir, 'binding_support.js'),
- '--js-library', os.path.join(wasm_src_dir, 'dotnet_support.js')
- ])
+ env_mono.Append(CPPDEFINES=["CORE_BINDINGS"])
+
+ env_mono.add_source_files(
+ env.modules_sources,
+ [
+ os.path.join(wasm_src_dir, "driver.c"),
+ os.path.join(wasm_src_dir, "zlib-helper.c"),
+ os.path.join(wasm_src_dir, "corebindings.c"),
+ ],
+ )
+
+ env.Append(
+ LINKFLAGS=[
+ "--js-library",
+ os.path.join(wasm_src_dir, "library_mono.js"),
+ "--js-library",
+ os.path.join(wasm_src_dir, "binding_support.js"),
+ "--js-library",
+ os.path.join(wasm_src_dir, "dotnet_support.js"),
+ ]
+ )
else:
env.Append(LIBS=[mono_lib])
- if is_apple:
- env.Append(LIBS=['iconv', 'pthread'])
+ if is_macos:
+ env.Append(LIBS=["iconv", "pthread"])
elif is_android:
- pass # Nothing
+ pass # Nothing
+ elif is_ios:
+ pass # Nothing, linking is delegated to the exported Xcode project
elif is_javascript:
- env.Append(LIBS=['m', 'rt', 'dl', 'pthread'])
+ env.Append(LIBS=["m", "rt", "dl", "pthread"])
else:
- env.Append(LIBS=['m', 'rt', 'dl', 'pthread'])
+ env.Append(LIBS=["m", "rt", "dl", "pthread"])
if not mono_static:
- mono_so_name = find_file_in_dir(mono_lib_path, mono_lib_names, prefix='lib', extension=sharedlib_ext)
+ mono_so_file = find_file_in_dir(
+ mono_lib_path, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext]
+ )
- if not mono_so_name:
- raise RuntimeError('Could not find mono shared library in: ' + mono_lib_path)
-
- copy_file(mono_lib_path, '#bin', 'lib' + mono_so_name + sharedlib_ext)
+ if not mono_so_file:
+ raise RuntimeError("Could not find mono shared library in: " + mono_lib_path)
else:
assert not mono_static
# TODO: Add option to force using pkg-config
- print('Mono root directory not found. Using pkg-config instead')
+ print("Mono root directory not found. Using pkg-config instead")
- env.ParseConfig('pkg-config monosgen-2 --libs')
- env_mono.ParseConfig('pkg-config monosgen-2 --cflags')
+ env.ParseConfig("pkg-config monosgen-2 --libs")
+ env_mono.ParseConfig("pkg-config monosgen-2 --cflags")
tmpenv = Environment()
- tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH'))
- tmpenv.ParseConfig('pkg-config monosgen-2 --libs-only-L')
+ tmpenv.AppendENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH"))
+ tmpenv.ParseConfig("pkg-config monosgen-2 --libs-only-L")
- for hint_dir in tmpenv['LIBPATH']:
- name_found = find_file_in_dir(hint_dir, mono_lib_names, prefix='lib', extension=sharedlib_ext)
- if name_found:
+ for hint_dir in tmpenv["LIBPATH"]:
+ file_found = find_file_in_dir(hint_dir, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext])
+ if file_found:
mono_lib_path = hint_dir
- mono_so_name = name_found
+ mono_so_file = file_found
break
- if not mono_so_name:
- raise RuntimeError('Could not find mono shared library in: ' + str(tmpenv['LIBPATH']))
+ if not mono_so_file:
+ raise RuntimeError("Could not find mono shared library in: " + str(tmpenv["LIBPATH"]))
if not mono_static:
- libs_output_dir = get_android_out_dir(env) if is_android else '#bin'
- copy_file(mono_lib_path, libs_output_dir, 'lib' + mono_so_name + sharedlib_ext)
+ libs_output_dir = get_android_out_dir(env) if is_android else "#bin"
+ copy_file(mono_lib_path, libs_output_dir, mono_so_file)
if not tools_enabled:
- if is_desktop(env['platform']):
+ if is_desktop(env["platform"]):
if not mono_root:
- mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip()
+ mono_root = (
+ subprocess.check_output(["pkg-config", "mono-2", "--variable=prefix"]).decode("utf8").strip()
+ )
make_template_dir(env, mono_root)
elif is_android:
# Compress Android Mono Config
from . import make_android_mono_config
+
module_dir = os.getcwd()
- config_file_path = os.path.join(module_dir, 'build_scripts', 'mono_android_config.xml')
- make_android_mono_config.generate_compressed_config(config_file_path, 'mono_gd/')
+ config_file_path = os.path.join(module_dir, "build_scripts", "mono_android_config.xml")
+ make_android_mono_config.generate_compressed_config(config_file_path, "mono_gd/")
# Copy the required shared libraries
copy_mono_shared_libs(env, mono_root, None)
elif is_javascript:
- pass # No data directory for this platform
+ pass # No data directory for this platform
+ elif is_ios:
+ pass # No data directory for this platform
if copy_mono_root:
if not mono_root:
- mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip()
+ mono_root = subprocess.check_output(["pkg-config", "mono-2", "--variable=prefix"]).decode("utf8").strip()
if tools_enabled:
- copy_mono_root_files(env, mono_root)
- else:
- print("Ignoring option: 'copy_mono_root'; only available for builds with 'tools' enabled.")
+ # Only supported for editor builds.
+ copy_mono_root_files(env, mono_root, mono_bcl)
def make_template_dir(env, mono_root):
from shutil import rmtree
- platform = env['platform']
- target = env['target']
+ platform = env["platform"]
+ target = env["target"]
- template_dir_name = ''
+ template_dir_name = ""
assert is_desktop(platform)
- template_dir_name = 'data.mono.%s.%s.%s' % (platform, env['bits'], target)
+ template_dir_name = "data.mono.%s.%s.%s" % (platform, env["bits"], target)
- output_dir = Dir('#bin').abspath
+ output_dir = Dir("#bin").abspath
template_dir = os.path.join(output_dir, template_dir_name)
- template_mono_root_dir = os.path.join(template_dir, 'Mono')
+ template_mono_root_dir = os.path.join(template_dir, "Mono")
if os.path.isdir(template_mono_root_dir):
- rmtree(template_mono_root_dir) # Clean first
+ rmtree(template_mono_root_dir) # Clean first
# Copy etc/mono/
- template_mono_config_dir = os.path.join(template_mono_root_dir, 'etc', 'mono')
+ template_mono_config_dir = os.path.join(template_mono_root_dir, "etc", "mono")
copy_mono_etc_dir(mono_root, template_mono_config_dir, platform)
# Copy the required shared libraries
@@ -341,24 +426,24 @@ def make_template_dir(env, mono_root):
copy_mono_shared_libs(env, mono_root, template_mono_root_dir)
-def copy_mono_root_files(env, mono_root):
+def copy_mono_root_files(env, mono_root, mono_bcl):
from glob import glob
from shutil import copy
from shutil import rmtree
if not mono_root:
- raise RuntimeError('Mono installation directory not found')
+ raise RuntimeError("Mono installation directory not found")
- output_dir = Dir('#bin').abspath
- editor_mono_root_dir = os.path.join(output_dir, 'GodotSharp', 'Mono')
+ output_dir = Dir("#bin").abspath
+ editor_mono_root_dir = os.path.join(output_dir, "GodotSharp", "Mono")
if os.path.isdir(editor_mono_root_dir):
- rmtree(editor_mono_root_dir) # Clean first
+ rmtree(editor_mono_root_dir) # Clean first
# Copy etc/mono/
- editor_mono_config_dir = os.path.join(editor_mono_root_dir, 'etc', 'mono')
- copy_mono_etc_dir(mono_root, editor_mono_config_dir, env['platform'])
+ editor_mono_config_dir = os.path.join(editor_mono_root_dir, "etc", "mono")
+ copy_mono_etc_dir(mono_root, editor_mono_config_dir, env["platform"])
# Copy the required shared libraries
@@ -366,20 +451,20 @@ def copy_mono_root_files(env, mono_root):
# Copy framework assemblies
- mono_framework_dir = os.path.join(mono_root, 'lib', 'mono', '4.5')
- mono_framework_facades_dir = os.path.join(mono_framework_dir, 'Facades')
+ mono_framework_dir = mono_bcl or os.path.join(mono_root, "lib", "mono", "4.5")
+ mono_framework_facades_dir = os.path.join(mono_framework_dir, "Facades")
- editor_mono_framework_dir = os.path.join(editor_mono_root_dir, 'lib', 'mono', '4.5')
- editor_mono_framework_facades_dir = os.path.join(editor_mono_framework_dir, 'Facades')
+ editor_mono_framework_dir = os.path.join(editor_mono_root_dir, "lib", "mono", "4.5")
+ editor_mono_framework_facades_dir = os.path.join(editor_mono_framework_dir, "Facades")
if not os.path.isdir(editor_mono_framework_dir):
os.makedirs(editor_mono_framework_dir)
if not os.path.isdir(editor_mono_framework_facades_dir):
os.makedirs(editor_mono_framework_facades_dir)
- for assembly in glob(os.path.join(mono_framework_dir, '*.dll')):
+ for assembly in glob(os.path.join(mono_framework_dir, "*.dll")):
copy(assembly, editor_mono_framework_dir)
- for assembly in glob(os.path.join(mono_framework_facades_dir, '*.dll')):
+ for assembly in glob(os.path.join(mono_framework_facades_dir, "*.dll")):
copy(assembly, editor_mono_framework_facades_dir)
@@ -391,28 +476,28 @@ def copy_mono_etc_dir(mono_root, target_mono_config_dir, platform):
if not os.path.isdir(target_mono_config_dir):
os.makedirs(target_mono_config_dir)
- mono_etc_dir = os.path.join(mono_root, 'etc', 'mono')
+ mono_etc_dir = os.path.join(mono_root, "etc", "mono")
if not os.path.isdir(mono_etc_dir):
- mono_etc_dir = ''
+ mono_etc_dir = ""
etc_hint_dirs = []
- if platform != 'windows':
- etc_hint_dirs += ['/etc/mono', '/usr/local/etc/mono']
- if 'MONO_CFG_DIR' in os.environ:
- etc_hint_dirs += [os.path.join(os.environ['MONO_CFG_DIR'], 'mono')]
+ if platform != "windows":
+ etc_hint_dirs += ["/etc/mono", "/usr/local/etc/mono"]
+ if "MONO_CFG_DIR" in os.environ:
+ etc_hint_dirs += [os.path.join(os.environ["MONO_CFG_DIR"], "mono")]
for etc_hint_dir in etc_hint_dirs:
if os.path.isdir(etc_hint_dir):
mono_etc_dir = etc_hint_dir
break
if not mono_etc_dir:
- raise RuntimeError('Mono installation etc directory not found')
+ raise RuntimeError("Mono installation etc directory not found")
- copy_tree(os.path.join(mono_etc_dir, '2.0'), os.path.join(target_mono_config_dir, '2.0'))
- copy_tree(os.path.join(mono_etc_dir, '4.0'), os.path.join(target_mono_config_dir, '4.0'))
- copy_tree(os.path.join(mono_etc_dir, '4.5'), os.path.join(target_mono_config_dir, '4.5'))
- if os.path.isdir(os.path.join(mono_etc_dir, 'mconfig')):
- copy_tree(os.path.join(mono_etc_dir, 'mconfig'), os.path.join(target_mono_config_dir, 'mconfig'))
+ copy_tree(os.path.join(mono_etc_dir, "2.0"), os.path.join(target_mono_config_dir, "2.0"))
+ copy_tree(os.path.join(mono_etc_dir, "4.0"), os.path.join(target_mono_config_dir, "4.0"))
+ copy_tree(os.path.join(mono_etc_dir, "4.5"), os.path.join(target_mono_config_dir, "4.5"))
+ if os.path.isdir(os.path.join(mono_etc_dir, "mconfig")):
+ copy_tree(os.path.join(mono_etc_dir, "mconfig"), os.path.join(target_mono_config_dir, "mconfig"))
- for file in glob(os.path.join(mono_etc_dir, '*')):
+ for file in glob(os.path.join(mono_etc_dir, "*")):
if os.path.isfile(file):
copy(file, target_mono_config_dir)
@@ -424,48 +509,66 @@ def copy_mono_shared_libs(env, mono_root, target_mono_root_dir):
if os.path.isfile(src):
copy(src, dst)
- platform = env['platform']
+ platform = env["platform"]
- if platform == 'windows':
- src_mono_bin_dir = os.path.join(mono_root, 'bin')
- target_mono_bin_dir = os.path.join(target_mono_root_dir, 'bin')
+ if platform == "windows":
+ src_mono_bin_dir = os.path.join(mono_root, "bin")
+ target_mono_bin_dir = os.path.join(target_mono_root_dir, "bin")
if not os.path.isdir(target_mono_bin_dir):
os.makedirs(target_mono_bin_dir)
- mono_posix_helper_name = find_file_in_dir(src_mono_bin_dir, ['MonoPosixHelper', 'libMonoPosixHelper'], extension='.dll')
- copy(os.path.join(src_mono_bin_dir, mono_posix_helper_name + '.dll'), os.path.join(target_mono_bin_dir, 'MonoPosixHelper.dll'))
+ mono_posix_helper_file = find_file_in_dir(
+ src_mono_bin_dir, ["MonoPosixHelper"], prefixes=["", "lib"], extensions=[".dll"]
+ )
+ copy(
+ os.path.join(src_mono_bin_dir, mono_posix_helper_file),
+ os.path.join(target_mono_bin_dir, "MonoPosixHelper.dll"),
+ )
# For newer versions
- btls_dll_path = os.path.join(src_mono_bin_dir, 'libmono-btls-shared.dll')
+ btls_dll_path = os.path.join(src_mono_bin_dir, "libmono-btls-shared.dll")
if os.path.isfile(btls_dll_path):
copy(btls_dll_path, target_mono_bin_dir)
else:
- target_mono_lib_dir = get_android_out_dir(env) if platform == 'android' else os.path.join(target_mono_root_dir, 'lib')
+ target_mono_lib_dir = (
+ get_android_out_dir(env) if platform == "android" else os.path.join(target_mono_root_dir, "lib")
+ )
if not os.path.isdir(target_mono_lib_dir):
os.makedirs(target_mono_lib_dir)
lib_file_names = []
- if platform == 'osx':
- lib_file_names = [lib_name + '.dylib' for lib_name in [
- 'libmono-btls-shared', 'libmono-native-compat', 'libMonoPosixHelper'
- ]]
+ if platform == "osx":
+ lib_file_names = [
+ lib_name + ".dylib"
+ for lib_name in ["libmono-btls-shared", "libmono-native-compat", "libMonoPosixHelper"]
+ ]
elif is_unix_like(platform):
- lib_file_names = [lib_name + '.so' for lib_name in [
- 'libmono-btls-shared', 'libmono-ee-interp', 'libmono-native', 'libMonoPosixHelper',
- 'libmono-profiler-aot', 'libmono-profiler-coverage', 'libmono-profiler-log', 'libMonoSupportW'
- ]]
+ lib_file_names = [
+ lib_name + ".so"
+ for lib_name in [
+ "libmono-btls-shared",
+ "libmono-ee-interp",
+ "libmono-native",
+ "libMonoPosixHelper",
+ "libmono-profiler-aot",
+ "libmono-profiler-coverage",
+ "libmono-profiler-log",
+ "libMonoSupportW",
+ ]
+ ]
for lib_file_name in lib_file_names:
- copy_if_exists(os.path.join(mono_root, 'lib', lib_file_name), target_mono_lib_dir)
+ copy_if_exists(os.path.join(mono_root, "lib", lib_file_name), target_mono_lib_dir)
+
def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext):
tmpenv = Environment()
- tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH'))
- tmpenv.ParseConfig('pkg-config monosgen-2 --libs-only-L')
- for hint_dir in tmpenv['LIBPATH']:
- name_found = find_file_in_dir(hint_dir, mono_lib_names, prefix='lib', extension=sharedlib_ext)
- if name_found and os.path.isdir(os.path.join(hint_dir, '..', 'include', 'mono-2.0')):
- return os.path.join(hint_dir, '..')
- return ''
+ tmpenv.AppendENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH"))
+ tmpenv.ParseConfig("pkg-config monosgen-2 --libs-only-L")
+ for hint_dir in tmpenv["LIBPATH"]:
+ name_found = find_name_in_dir_files(hint_dir, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext])
+ if name_found and os.path.isdir(os.path.join(hint_dir, "..", "include", "mono-2.0")):
+ return os.path.join(hint_dir, "..")
+ return ""
diff --git a/modules/mono/build_scripts/mono_reg_utils.py b/modules/mono/build_scripts/mono_reg_utils.py
index b2c48f0a61..0ec7e2f433 100644
--- a/modules/mono/build_scripts/mono_reg_utils.py
+++ b/modules/mono/build_scripts/mono_reg_utils.py
@@ -1,21 +1,16 @@
import os
import platform
-from compat import decode_utf8
-
-if os.name == 'nt':
+if os.name == "nt":
import sys
- if sys.version_info < (3,):
- import _winreg as winreg
- else:
- import winreg
+ import winreg
def _reg_open_key(key, subkey):
try:
return winreg.OpenKey(key, subkey)
- except (WindowsError, OSError):
- if platform.architecture()[0] == '32bit':
+ except OSError:
+ if platform.architecture()[0] == "32bit":
bitness_sam = winreg.KEY_WOW64_64KEY
else:
bitness_sam = winreg.KEY_WOW64_32KEY
@@ -25,12 +20,12 @@ def _reg_open_key(key, subkey):
def _reg_open_key_bits(key, subkey, bits):
sam = winreg.KEY_READ
- if platform.architecture()[0] == '32bit':
- if bits == '64':
+ if platform.architecture()[0] == "32bit":
+ if bits == "64":
# Force 32bit process to search in 64bit registry
sam |= winreg.KEY_WOW64_64KEY
else:
- if bits == '32':
+ if bits == "32":
# Force 64bit process to search in 32bit registry
sam |= winreg.KEY_WOW64_32KEY
@@ -40,79 +35,79 @@ def _reg_open_key_bits(key, subkey, bits):
def _find_mono_in_reg(subkey, bits):
try:
with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey:
- value = winreg.QueryValueEx(hKey, 'SdkInstallRoot')[0]
+ value = winreg.QueryValueEx(hKey, "SdkInstallRoot")[0]
return value
- except (WindowsError, OSError):
+ except OSError:
return None
def _find_mono_in_reg_old(subkey, bits):
try:
with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey:
- default_clr = winreg.QueryValueEx(hKey, 'DefaultCLR')[0]
+ default_clr = winreg.QueryValueEx(hKey, "DefaultCLR")[0]
if default_clr:
- return _find_mono_in_reg(subkey + '\\' + default_clr, bits)
+ return _find_mono_in_reg(subkey + "\\" + default_clr, bits)
return None
- except (WindowsError, EnvironmentError):
+ except OSError:
return None
def find_mono_root_dir(bits):
- root_dir = _find_mono_in_reg(r'SOFTWARE\Mono', bits)
+ root_dir = _find_mono_in_reg(r"SOFTWARE\Mono", bits)
if root_dir is not None:
return str(root_dir)
- root_dir = _find_mono_in_reg_old(r'SOFTWARE\Novell\Mono', bits)
+ root_dir = _find_mono_in_reg_old(r"SOFTWARE\Novell\Mono", bits)
if root_dir is not None:
return str(root_dir)
- return ''
+ return ""
def find_msbuild_tools_path_reg():
import subprocess
- vswhere = os.getenv('PROGRAMFILES(X86)')
+ vswhere = os.getenv("PROGRAMFILES(X86)")
if not vswhere:
- vswhere = os.getenv('PROGRAMFILES')
- vswhere += r'\Microsoft Visual Studio\Installer\vswhere.exe'
+ vswhere = os.getenv("PROGRAMFILES")
+ vswhere += r"\Microsoft Visual Studio\Installer\vswhere.exe"
- vswhere_args = ['-latest', '-products', '*', '-requires', 'Microsoft.Component.MSBuild']
+ vswhere_args = ["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"]
try:
lines = subprocess.check_output([vswhere] + vswhere_args).splitlines()
for line in lines:
- parts = decode_utf8(line).split(':', 1)
+ parts = line.decode("utf-8").split(":", 1)
- if len(parts) < 2 or parts[0] != 'installationPath':
+ if len(parts) < 2 or parts[0] != "installationPath":
continue
val = parts[1].strip()
if not val:
- raise ValueError('Value of `installationPath` entry is empty')
+ raise ValueError("Value of `installationPath` entry is empty")
# Since VS2019, the directory is simply named "Current"
- msbuild_dir = os.path.join(val, 'MSBuild\\Current\\Bin')
+ msbuild_dir = os.path.join(val, "MSBuild\\Current\\Bin")
if os.path.isdir(msbuild_dir):
return msbuild_dir
# Directory name "15.0" is used in VS 2017
- return os.path.join(val, 'MSBuild\\15.0\\Bin')
+ return os.path.join(val, "MSBuild\\15.0\\Bin")
- raise ValueError('Cannot find `installationPath` entry')
+ raise ValueError("Cannot find `installationPath` entry")
except ValueError as e:
- print('Error reading output from vswhere: ' + e.message)
- except WindowsError:
- pass # Fine, vswhere not found
+ print("Error reading output from vswhere: " + e.message)
+ except OSError:
+ pass # Fine, vswhere not found
except (subprocess.CalledProcessError, OSError):
pass
# Try to find 14.0 in the Registry
try:
- subkey = r'SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0'
+ subkey = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0"
with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, subkey) as hKey:
- value = winreg.QueryValueEx(hKey, 'MSBuildToolsPath')[0]
+ value = winreg.QueryValueEx(hKey, "MSBuildToolsPath")[0]
return value
- except (WindowsError, OSError):
- return ''
+ except OSError:
+ return ""
diff --git a/modules/mono/build_scripts/solution_builder.py b/modules/mono/build_scripts/solution_builder.py
index d1529a64d2..6a621c3c8b 100644
--- a/modules/mono/build_scripts/solution_builder.py
+++ b/modules/mono/build_scripts/solution_builder.py
@@ -1,129 +1,81 @@
-
import os
verbose = False
-def find_nuget_unix():
- import os
-
- if 'NUGET_PATH' in os.environ:
- hint_path = os.environ['NUGET_PATH']
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
- hint_path = os.path.join(hint_path, 'nuget')
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
-
+def find_dotnet_cli():
import os.path
- import sys
-
- hint_dirs = ['/opt/novell/mono/bin']
- if sys.platform == 'darwin':
- hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin', '/usr/local/var/homebrew/linked/mono/bin'] + hint_dirs
-
- for hint_dir in hint_dirs:
- hint_path = os.path.join(hint_dir, 'nuget')
- if os.path.isfile(hint_path):
- return hint_path
- elif os.path.isfile(hint_path + '.exe'):
- return hint_path + '.exe'
-
- for hint_dir in os.environ['PATH'].split(os.pathsep):
- hint_dir = hint_dir.strip('"')
- hint_path = os.path.join(hint_dir, 'nuget')
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
- if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK):
- return hint_path + '.exe'
-
- return None
-
-
-def find_nuget_windows(env):
- import os
-
- if 'NUGET_PATH' in os.environ:
- hint_path = os.environ['NUGET_PATH']
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
- hint_path = os.path.join(hint_path, 'nuget.exe')
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
-
- from . mono_reg_utils import find_mono_root_dir
-
- mono_root = env['mono_prefix'] or find_mono_root_dir(env['bits'])
-
- if mono_root:
- mono_bin_dir = os.path.join(mono_root, 'bin')
- nuget_mono = os.path.join(mono_bin_dir, 'nuget.bat')
-
- if os.path.isfile(nuget_mono):
- return nuget_mono
- # Standalone NuGet
-
- for hint_dir in os.environ['PATH'].split(os.pathsep):
- hint_dir = hint_dir.strip('"')
- hint_path = os.path.join(hint_dir, 'nuget.exe')
- if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
- return hint_path
-
- return None
+ if os.name == "nt":
+ for hint_dir in os.environ["PATH"].split(os.pathsep):
+ hint_dir = hint_dir.strip('"')
+ hint_path = os.path.join(hint_dir, "dotnet")
+ if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+ return hint_path
+ if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):
+ return hint_path + ".exe"
+ else:
+ for hint_dir in os.environ["PATH"].split(os.pathsep):
+ hint_dir = hint_dir.strip('"')
+ hint_path = os.path.join(hint_dir, "dotnet")
+ if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+ return hint_path
-def find_msbuild_unix(filename):
+def find_msbuild_unix():
import os.path
import sys
- hint_dirs = ['/opt/novell/mono/bin']
- if sys.platform == 'darwin':
- hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin', '/usr/local/var/homebrew/linked/mono/bin'] + hint_dirs
+ hint_dirs = []
+ if sys.platform == "darwin":
+ hint_dirs[:0] = [
+ "/Library/Frameworks/Mono.framework/Versions/Current/bin",
+ "/usr/local/var/homebrew/linked/mono/bin",
+ ]
for hint_dir in hint_dirs:
- hint_path = os.path.join(hint_dir, filename)
+ hint_path = os.path.join(hint_dir, "msbuild")
if os.path.isfile(hint_path):
return hint_path
- elif os.path.isfile(hint_path + '.exe'):
- return hint_path + '.exe'
+ elif os.path.isfile(hint_path + ".exe"):
+ return hint_path + ".exe"
- for hint_dir in os.environ['PATH'].split(os.pathsep):
+ for hint_dir in os.environ["PATH"].split(os.pathsep):
hint_dir = hint_dir.strip('"')
- hint_path = os.path.join(hint_dir, filename)
+ hint_path = os.path.join(hint_dir, "msbuild")
if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
return hint_path
- if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK):
- return hint_path + '.exe'
+ if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK):
+ return hint_path + ".exe"
return None
def find_msbuild_windows(env):
- from . mono_reg_utils import find_mono_root_dir, find_msbuild_tools_path_reg
+ from .mono_reg_utils import find_mono_root_dir, find_msbuild_tools_path_reg
- mono_root = env['mono_prefix'] or find_mono_root_dir(env['bits'])
+ mono_root = env["mono_prefix"] or find_mono_root_dir(env["bits"])
if not mono_root:
- raise RuntimeError('Cannot find mono root directory')
+ raise RuntimeError("Cannot find mono root directory")
- mono_bin_dir = os.path.join(mono_root, 'bin')
- msbuild_mono = os.path.join(mono_bin_dir, 'msbuild.bat')
+ mono_bin_dir = os.path.join(mono_root, "bin")
+ msbuild_mono = os.path.join(mono_bin_dir, "msbuild.bat")
msbuild_tools_path = find_msbuild_tools_path_reg()
if msbuild_tools_path:
- return (os.path.join(msbuild_tools_path, 'MSBuild.exe'), {})
+ return (os.path.join(msbuild_tools_path, "MSBuild.exe"), {})
if os.path.isfile(msbuild_mono):
# The (Csc/Vbc/Fsc)ToolExe environment variables are required when
# building with Mono's MSBuild. They must point to the batch files
# in Mono's bin directory to make sure they are executed with Mono.
mono_msbuild_env = {
- 'CscToolExe': os.path.join(mono_bin_dir, 'csc.bat'),
- 'VbcToolExe': os.path.join(mono_bin_dir, 'vbc.bat'),
- 'FscToolExe': os.path.join(mono_bin_dir, 'fsharpc.bat')
+ "CscToolExe": os.path.join(mono_bin_dir, "csc.bat"),
+ "VbcToolExe": os.path.join(mono_bin_dir, "vbc.bat"),
+ "FscToolExe": os.path.join(mono_bin_dir, "fsharpc.bat"),
}
return (msbuild_mono, mono_msbuild_env)
@@ -132,7 +84,7 @@ def find_msbuild_windows(env):
def run_command(command, args, env_override=None, name=None):
def cmd_args_to_str(cmd_args):
- return ' '.join([arg if not ' ' in arg else '"%s"' % arg for arg in cmd_args])
+ return " ".join([arg if not " " in arg else '"%s"' % arg for arg in cmd_args])
args = [command] + args
@@ -143,6 +95,7 @@ def run_command(command, args, env_override=None, name=None):
print("Running '%s': %s" % (name, cmd_args_to_str(args)))
import subprocess
+
try:
if env_override is None:
subprocess.check_call(args)
@@ -152,63 +105,41 @@ def run_command(command, args, env_override=None, name=None):
raise RuntimeError("'%s' exited with error code: %s" % (name, e.returncode))
-def nuget_restore(env, *args):
- global verbose
- verbose = env['verbose']
-
- # Find NuGet
- nuget_path = find_nuget_windows(env) if os.name == 'nt' else find_nuget_unix()
- if nuget_path is None:
- raise RuntimeError('Cannot find NuGet executable')
-
- print('NuGet path: ' + nuget_path)
-
- # Do NuGet restore
- run_command(nuget_path, ['restore'] + list(args), name='nuget restore')
-
-
def build_solution(env, solution_path, build_config, extra_msbuild_args=[]):
global verbose
- verbose = env['verbose']
+ verbose = env["verbose"]
msbuild_env = os.environ.copy()
# Needed when running from Developer Command Prompt for VS
- if 'PLATFORM' in msbuild_env:
- del msbuild_env['PLATFORM']
-
- # Find MSBuild
- if os.name == 'nt':
- msbuild_info = find_msbuild_windows(env)
- if msbuild_info is None:
- raise RuntimeError('Cannot find MSBuild executable')
- msbuild_path = msbuild_info[0]
- msbuild_env.update(msbuild_info[1])
- else:
- msbuild_path = find_msbuild_unix('msbuild')
- if msbuild_path is None:
- xbuild_fallback = env['xbuild_fallback']
+ if "PLATFORM" in msbuild_env:
+ del msbuild_env["PLATFORM"]
- if xbuild_fallback and os.name == 'nt':
- print('Option \'xbuild_fallback\' not supported on Windows')
- xbuild_fallback = False
+ msbuild_args = []
- if xbuild_fallback:
- print('Cannot find MSBuild executable, trying with xbuild')
- print('Warning: xbuild is deprecated')
+ dotnet_cli = find_dotnet_cli()
- msbuild_path = find_msbuild_unix('xbuild')
-
- if msbuild_path is None:
- raise RuntimeError('Cannot find xbuild executable')
- else:
- raise RuntimeError('Cannot find MSBuild executable')
+ if dotnet_cli:
+ msbuild_path = dotnet_cli
+ msbuild_args += ["msbuild"] # `dotnet msbuild` command
+ else:
+ # Find MSBuild
+ if os.name == "nt":
+ msbuild_info = find_msbuild_windows(env)
+ if msbuild_info is None:
+ raise RuntimeError("Cannot find MSBuild executable")
+ msbuild_path = msbuild_info[0]
+ msbuild_env.update(msbuild_info[1])
+ else:
+ msbuild_path = find_msbuild_unix()
+ if msbuild_path is None:
+ raise RuntimeError("Cannot find MSBuild executable")
- print('MSBuild path: ' + msbuild_path)
+ print("MSBuild path: " + msbuild_path)
# Build solution
- msbuild_args = [solution_path, '/p:Configuration=' + build_config]
+ msbuild_args += [solution_path, "/restore", "/t:Build", "/p:Configuration=" + build_config]
msbuild_args += extra_msbuild_args
- run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name='msbuild')
+ run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name="msbuild")
diff --git a/modules/mono/build_scripts/tls_configure.py b/modules/mono/build_scripts/tls_configure.py
deleted file mode 100644
index 622280b00b..0000000000
--- a/modules/mono/build_scripts/tls_configure.py
+++ /dev/null
@@ -1,36 +0,0 @@
-from __future__ import print_function
-
-def supported(result):
- return 'supported' if result else 'not supported'
-
-
-def check_cxx11_thread_local(conf):
- print('Checking for `thread_local` support...', end=" ")
- result = conf.TryCompile('thread_local int foo = 0; int main() { return foo; }', '.cpp')
- print(supported(result))
- return bool(result)
-
-
-def check_declspec_thread(conf):
- print('Checking for `__declspec(thread)` support...', end=" ")
- result = conf.TryCompile('__declspec(thread) int foo = 0; int main() { return foo; }', '.cpp')
- print(supported(result))
- return bool(result)
-
-
-def check_gcc___thread(conf):
- print('Checking for `__thread` support...', end=" ")
- result = conf.TryCompile('__thread int foo = 0; int main() { return foo; }', '.cpp')
- print(supported(result))
- return bool(result)
-
-
-def configure(conf):
- if check_cxx11_thread_local(conf):
- conf.env.Append(CPPDEFINES=['HAVE_CXX11_THREAD_LOCAL'])
- else:
- if conf.env.msvc:
- if check_declspec_thread(conf):
- conf.env.Append(CPPDEFINES=['HAVE_DECLSPEC_THREAD'])
- elif check_gcc___thread(conf):
- conf.env.Append(CPPDEFINES=['HAVE_GCC___THREAD'])
diff --git a/modules/mono/class_db_api_json.cpp b/modules/mono/class_db_api_json.cpp
index b04e53bd81..25193a1352 100644
--- a/modules/mono/class_db_api_json.cpp
+++ b/modules/mono/class_db_api_json.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -32,9 +32,9 @@
#ifdef DEBUG_METHODS_ENABLED
+#include "core/config/project_settings.h"
+#include "core/io/file_access.h"
#include "core/io/json.h"
-#include "core/os/file_access.h"
-#include "core/project_settings.h"
#include "core/version.h"
void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
@@ -42,21 +42,20 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
List<StringName> names;
- const StringName *k = NULL;
+ const StringName *k = nullptr;
while ((k = ClassDB::classes.next(k))) {
-
names.push_back(*k);
}
//must be alphabetically sorted for hash to compute
names.sort_custom<StringName::AlphCompare>();
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
-
ClassDB::ClassInfo *t = ClassDB::classes.getptr(E->get());
ERR_FAIL_COND(!t);
- if (t->api != p_api || !t->exposed)
+ if (t->api != p_api || !t->exposed) {
continue;
+ }
Dictionary class_dict;
classes_dict[t->name] = class_dict;
@@ -67,16 +66,16 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
List<StringName> snames;
- k = NULL;
+ k = nullptr;
while ((k = t->method_map.next(k))) {
-
String name = k->operator String();
- ERR_CONTINUE(name.empty());
+ ERR_CONTINUE(name.is_empty());
- if (name[0] == '_')
+ if (name[0] == '_') {
continue; // Ignore non-virtual methods that start with an underscore
+ }
snames.push_back(*k);
}
@@ -123,7 +122,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
method_dict["hint_flags"] = mb->get_hint_flags();
}
- if (!methods.empty()) {
+ if (!methods.is_empty()) {
class_dict["methods"] = methods;
}
}
@@ -132,10 +131,9 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
List<StringName> snames;
- k = NULL;
+ k = nullptr;
while ((k = t->constant_map.next(k))) {
-
snames.push_back(*k);
}
@@ -151,7 +149,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
constant_dict["value"] = t->constant_map[F->get()];
}
- if (!constants.empty()) {
+ if (!constants.is_empty()) {
class_dict["constants"] = constants;
}
}
@@ -160,10 +158,9 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
List<StringName> snames;
- k = NULL;
+ k = nullptr;
while ((k = t->signal_map.next(k))) {
-
snames.push_back(*k);
}
@@ -187,7 +184,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
}
}
- if (!signals.empty()) {
+ if (!signals.is_empty()) {
class_dict["signals"] = signals;
}
}
@@ -196,10 +193,9 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
List<StringName> snames;
- k = NULL;
+ k = nullptr;
while ((k = t->property_setget.next(k))) {
-
snames.push_back(*k);
}
@@ -218,7 +214,7 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
property_dict["getter"] = psg->getter;
}
- if (!properties.empty()) {
+ if (!properties.is_empty()) {
class_dict["property_setget"] = properties;
}
}
@@ -237,14 +233,15 @@ void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api) {
property_dict["usage"] = F->get().usage;
}
- if (!property_list.empty()) {
+ if (!property_list.is_empty()) {
class_dict["property_list"] = property_list;
}
}
FileAccessRef f = FileAccess::open(p_output_file, FileAccess::WRITE);
ERR_FAIL_COND_MSG(!f, "Cannot open file '" + p_output_file + "'.");
- f->store_string(JSON::print(classes_dict, /*indent: */ "\t"));
+ JSON json;
+ f->store_string(json.stringify(classes_dict, "\t"));
f->close();
print_line(String() + "ClassDB API JSON written to: " + ProjectSettings::get_singleton()->globalize_path(p_output_file));
diff --git a/modules/mono/class_db_api_json.h b/modules/mono/class_db_api_json.h
index 7f016ac3d6..6698a6260f 100644
--- a/modules/mono/class_db_api_json.h
+++ b/modules/mono/class_db_api_json.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,13 +31,13 @@
#ifndef CLASS_DB_API_JSON_H
#define CLASS_DB_API_JSON_H
-// 'core/method_bind.h' defines DEBUG_METHODS_ENABLED, but it looks like we
-// cannot include it here. That's why we include it through 'core/class_db.h'.
-#include "core/class_db.h"
+// 'core/object/method_bind.h' defines DEBUG_METHODS_ENABLED, but it looks like we
+// cannot include it here. That's why we include it through 'core/object/class_db.h'.
+#include "core/object/class_db.h"
#ifdef DEBUG_METHODS_ENABLED
-#include "core/ustring.h"
+#include "core/string/ustring.h"
void class_db_api_to_json(const String &p_output_file, ClassDB::APIType p_api);
diff --git a/modules/mono/config.py b/modules/mono/config.py
index 70cb464c7a..4c851a2989 100644
--- a/modules/mono/config.py
+++ b/modules/mono/config.py
@@ -1,42 +1,74 @@
+supported_platforms = ["windows", "osx", "linuxbsd", "server", "android", "haiku", "javascript", "iphone"]
+
+
def can_build(env, platform):
return True
def configure(env):
- if env['platform'] not in ['windows', 'osx', 'x11', 'server', 'android', 'haiku', 'javascript']:
- raise RuntimeError('This module does not currently support building for this platform')
+ platform = env["platform"]
+
+ if platform not in supported_platforms:
+ raise RuntimeError("This module does not currently support building for this platform")
- env.use_ptrcall = True
- env.add_module_version_string('mono')
+ env.add_module_version_string("mono")
- from SCons.Script import BoolVariable, PathVariable, Variables
+ from SCons.Script import BoolVariable, PathVariable, Variables, Help
+
+ default_mono_static = platform in ["iphone", "javascript"]
+ default_mono_bundles_zlib = platform in ["javascript"]
envvars = Variables()
- envvars.Add(PathVariable('mono_prefix', 'Path to the mono installation directory for the target platform and architecture', '', PathVariable.PathAccept))
- envvars.Add(BoolVariable('mono_static', 'Statically link mono', False))
- envvars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True))
- envvars.Add(BoolVariable('copy_mono_root', 'Make a copy of the mono installation directory to bundle with the editor', False))
- envvars.Add(BoolVariable('xbuild_fallback', 'If MSBuild is not found, fallback to xbuild', False))
+ envvars.Add(
+ PathVariable(
+ "mono_prefix",
+ "Path to the Mono installation directory for the target platform and architecture",
+ "",
+ PathVariable.PathAccept,
+ )
+ )
+ envvars.Add(
+ PathVariable(
+ "mono_bcl",
+ "Path to a custom Mono BCL (Base Class Library) directory for the target platform",
+ "",
+ PathVariable.PathAccept,
+ )
+ )
+ envvars.Add(BoolVariable("mono_static", "Statically link Mono", default_mono_static))
+ envvars.Add(BoolVariable("mono_glue", "Build with the Mono glue sources", True))
+ envvars.Add(BoolVariable("build_cil", "Build C# solutions", True))
+ envvars.Add(
+ BoolVariable("copy_mono_root", "Make a copy of the Mono installation directory to bundle with the editor", True)
+ )
+
+ # TODO: It would be great if this could be detected automatically instead
+ envvars.Add(
+ BoolVariable(
+ "mono_bundles_zlib", "Specify if the Mono runtime was built with bundled zlib", default_mono_bundles_zlib
+ )
+ )
+
envvars.Update(env)
+ Help(envvars.GenerateHelpText(env))
- if env['platform'] == 'javascript':
- # Mono wasm already has zlib builtin, so we need this workaround to avoid symbol collisions
- print('Compiling with Mono wasm disables \'builtin_zlib\'')
- env['builtin_zlib'] = False
+ if env["mono_bundles_zlib"]:
+ # Mono may come with zlib bundled for WASM or on newer version when built with MinGW.
+ print("This Mono runtime comes with zlib bundled. Disabling 'builtin_zlib'...")
+ env["builtin_zlib"] = False
thirdparty_zlib_dir = "#thirdparty/zlib/"
env.Prepend(CPPPATH=[thirdparty_zlib_dir])
def get_doc_classes():
return [
- '@C#',
- 'CSharpScript',
- 'GodotSharp',
+ "CSharpScript",
+ "GodotSharp",
]
def get_doc_path():
- return 'doc_classes'
+ return "doc_classes"
def is_enabled():
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 9e19e5f8c4..c48230f524 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,16 +31,19 @@
#include "csharp_script.h"
#include <mono/metadata/threads.h>
-
-#include "core/io/json.h"
-#include "core/os/file_access.h"
+#include <mono/metadata/tokentype.h>
+#include <stdint.h>
+
+#include "core/config/project_settings.h"
+#include "core/debugger/engine_debugger.h"
+#include "core/debugger/script_debugger.h"
+#include "core/io/file_access.h"
+#include "core/os/mutex.h"
#include "core/os/os.h"
#include "core/os/thread.h"
-#include "core/project_settings.h"
#ifdef TOOLS_ENABLED
#include "editor/bindings_generator.h"
-#include "editor/csharp_project.h"
#include "editor/editor_node.h"
#include "editor/node_dock.h"
#endif
@@ -57,22 +60,19 @@
#include "mono_gd/gd_mono_utils.h"
#include "signal_awaiter_utils.h"
#include "utils/macros.h"
-#include "utils/mutex_utils.h"
#include "utils/string_utils.h"
-#include "utils/thread_local.h"
#define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var)
#ifdef TOOLS_ENABLED
static bool _create_project_solution_if_needed() {
-
String sln_path = GodotSharpDirs::get_project_sln_path();
String csproj_path = GodotSharpDirs::get_project_csproj_path();
if (!FileAccess::exists(sln_path) || !FileAccess::exists(csproj_path)) {
// A solution does not yet exist, create a new one
- CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == NULL);
+ CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == nullptr);
return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolution");
}
@@ -80,31 +80,26 @@ static bool _create_project_solution_if_needed() {
}
#endif
-CSharpLanguage *CSharpLanguage::singleton = NULL;
+CSharpLanguage *CSharpLanguage::singleton = nullptr;
String CSharpLanguage::get_name() const {
-
return "C#";
}
String CSharpLanguage::get_type() const {
-
return "CSharpScript";
}
String CSharpLanguage::get_extension() const {
-
return "cs";
}
Error CSharpLanguage::execute_file(const String &p_path) {
-
// ??
return OK;
}
void CSharpLanguage::init() {
-
#ifdef DEBUG_METHODS_ENABLED
if (OS::get_singleton()->get_cmdline_args().find("--class-db-json")) {
class_db_api_to_json("user://class_db_api.json", ClassDB::API_CORE);
@@ -129,8 +124,9 @@ void CSharpLanguage::init() {
print_line("Run this binary with '--generate-mono-glue path/to/modules/mono/glue'");
#endif
- if (gdmono->is_runtime_initialized())
+ if (gdmono->is_runtime_initialized()) {
gdmono->initialize_load_assemblies();
+ }
#ifdef TOOLS_ENABLED
EditorNode::add_init_callback(&_editor_init_callback);
@@ -138,6 +134,13 @@ void CSharpLanguage::init() {
}
void CSharpLanguage::finish() {
+ finalize();
+}
+
+void CSharpLanguage::finalize() {
+ if (finalized) {
+ return;
+ }
finalizing = true;
@@ -145,15 +148,15 @@ void CSharpLanguage::finish() {
for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) {
CSharpScriptBinding &script_binding = E->value();
- if (script_binding.gchandle.is_valid()) {
- script_binding.gchandle->release();
+ if (!script_binding.gchandle.is_released()) {
+ script_binding.gchandle.release();
script_binding.inited = false;
}
}
if (gdmono) {
memdelete(gdmono);
- gdmono = NULL;
+ gdmono = nullptr;
}
// Clear here, after finalizing all domains to make sure there is nothing else referencing the elements.
@@ -161,22 +164,24 @@ void CSharpLanguage::finish() {
#ifdef DEBUG_ENABLED
for (Map<ObjectID, int>::Element *E = unsafe_object_references.front(); E; E = E->next()) {
- const ObjectID &id = E->get();
+ const ObjectID &id = E->key();
Object *obj = ObjectDB::get_instance(id);
if (obj) {
- ERR_PRINTS("Leaked unsafe reference to object: " + obj->get_class() + ":" + itos(id));
+ ERR_PRINT("Leaked unsafe reference to object: " + obj->to_string());
} else {
- ERR_PRINTS("Leaked unsafe reference to deleted object: " + itos(id));
+ ERR_PRINT("Leaked unsafe reference to deleted object: " + itos(id));
}
}
#endif
+ memdelete(managed_callable_middleman);
+
finalizing = false;
+ finalized = true;
}
void CSharpLanguage::get_reserved_words(List<String> *p_words) const {
-
static const char *_reserved_words[] = {
// Reserved keywords
"abstract",
@@ -287,7 +292,7 @@ void CSharpLanguage::get_reserved_words(List<String> *p_words) const {
"when",
"where",
"yield",
- 0
+ nullptr
};
const char **w = _reserved_words;
@@ -298,21 +303,39 @@ void CSharpLanguage::get_reserved_words(List<String> *p_words) const {
}
}
-void CSharpLanguage::get_comment_delimiters(List<String> *p_delimiters) const {
+bool CSharpLanguage::is_control_flow_keyword(String p_keyword) const {
+ return p_keyword == "break" ||
+ p_keyword == "case" ||
+ p_keyword == "catch" ||
+ p_keyword == "continue" ||
+ p_keyword == "default" ||
+ p_keyword == "do" ||
+ p_keyword == "else" ||
+ p_keyword == "finally" ||
+ p_keyword == "for" ||
+ p_keyword == "foreach" ||
+ p_keyword == "goto" ||
+ p_keyword == "if" ||
+ p_keyword == "return" ||
+ p_keyword == "switch" ||
+ p_keyword == "throw" ||
+ p_keyword == "try" ||
+ p_keyword == "while";
+}
+void CSharpLanguage::get_comment_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("//"); // single-line comment
p_delimiters->push_back("/* */"); // delimited comment
}
void CSharpLanguage::get_string_delimiters(List<String> *p_delimiters) const {
-
p_delimiters->push_back("' '"); // character literal
p_delimiters->push_back("\" \""); // regular string literal
- p_delimiters->push_back("@\" \""); // verbatim string literal
+ // Verbatim string literals (`@" "`) don't render correctly, so don't highlight them.
+ // Generic string highlighting suffices as a workaround for now.
}
static String get_base_class_name(const String &p_base_class_name, const String p_class_name) {
-
String base_class = p_base_class_name;
if (p_class_name == base_class) {
base_class = "Godot." + base_class;
@@ -321,11 +344,10 @@ static String get_base_class_name(const String &p_base_class_name, const String
}
Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const {
-
String script_template = "using " BINDINGS_NAMESPACE ";\n"
"using System;\n"
"\n"
- "public class %CLASS% : %BASE%\n"
+ "public partial class %CLASS% : %BASE%\n"
"{\n"
" // Declare member variables here. Examples:\n"
" // private int a = 2;\n"
@@ -344,35 +366,37 @@ Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const Strin
"// }\n"
"}\n";
- String base_class_name = get_base_class_name(p_base_class_name, p_class_name);
+ // Replaces all spaces in p_class_name with underscores to prevent
+ // invalid C# Script templates from being generated when the object name
+ // has spaces in it.
+ String class_name_no_spaces = p_class_name.replace(" ", "_");
+ String base_class_name = get_base_class_name(p_base_class_name, class_name_no_spaces);
script_template = script_template.replace("%BASE%", base_class_name)
- .replace("%CLASS%", p_class_name);
+ .replace("%CLASS%", class_name_no_spaces);
Ref<CSharpScript> script;
- script.instance();
+ script.instantiate();
script->set_source_code(script_template);
- script->set_name(p_class_name);
+ script->set_name(class_name_no_spaces);
return script;
}
bool CSharpLanguage::is_using_templates() {
-
return true;
}
void CSharpLanguage::make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) {
-
String src = p_script->get_source_code();
- String base_class_name = get_base_class_name(p_base_class_name, p_class_name);
+ String class_name_no_spaces = p_class_name.replace(" ", "_");
+ String base_class_name = get_base_class_name(p_base_class_name, class_name_no_spaces);
src = src.replace("%BASE%", base_class_name)
- .replace("%CLASS%", p_class_name)
+ .replace("%CLASS%", class_name_no_spaces)
.replace("%TS%", _get_indentation());
p_script->set_source_code(src);
}
String CSharpLanguage::validate_path(const String &p_path) const {
-
String class_name = p_path.get_file().get_basename();
List<String> keywords;
get_reserved_words(&keywords);
@@ -383,34 +407,32 @@ String CSharpLanguage::validate_path(const String &p_path) const {
}
Script *CSharpLanguage::create_script() const {
-
return memnew(CSharpScript);
}
bool CSharpLanguage::has_named_classes() const {
-
return false;
}
bool CSharpLanguage::supports_builtin_mode() const {
-
return false;
}
#ifdef TOOLS_ENABLED
static String variant_type_to_managed_name(const String &p_var_type_name) {
-
- if (p_var_type_name.empty())
+ if (p_var_type_name.is_empty()) {
return "object";
+ }
if (!ClassDB::class_exists(p_var_type_name)) {
return p_var_type_name;
}
- if (p_var_type_name == Variant::get_type_name(Variant::OBJECT))
+ if (p_var_type_name == Variant::get_type_name(Variant::OBJECT)) {
return "Godot.Object";
+ }
- if (p_var_type_name == Variant::get_type_name(Variant::REAL)) {
+ if (p_var_type_name == Variant::get_type_name(Variant::FLOAT)) {
#ifdef REAL_T_IS_DOUBLE
return "double";
#else
@@ -418,61 +440,82 @@ static String variant_type_to_managed_name(const String &p_var_type_name) {
#endif
}
- if (p_var_type_name == Variant::get_type_name(Variant::STRING))
+ if (p_var_type_name == Variant::get_type_name(Variant::STRING)) {
return "string"; // I prefer this one >:[
+ }
- if (p_var_type_name == Variant::get_type_name(Variant::DICTIONARY))
+ if (p_var_type_name == Variant::get_type_name(Variant::DICTIONARY)) {
return "Collections.Dictionary";
+ }
- if (p_var_type_name == Variant::get_type_name(Variant::ARRAY))
+ if (p_var_type_name == Variant::get_type_name(Variant::ARRAY)) {
return "Collections.Array";
+ }
- if (p_var_type_name == Variant::get_type_name(Variant::POOL_BYTE_ARRAY))
+ if (p_var_type_name == Variant::get_type_name(Variant::PACKED_BYTE_ARRAY)) {
return "byte[]";
- if (p_var_type_name == Variant::get_type_name(Variant::POOL_INT_ARRAY))
+ }
+ if (p_var_type_name == Variant::get_type_name(Variant::PACKED_INT32_ARRAY)) {
return "int[]";
- if (p_var_type_name == Variant::get_type_name(Variant::POOL_REAL_ARRAY)) {
-#ifdef REAL_T_IS_DOUBLE
- return "double[]";
-#else
+ }
+ if (p_var_type_name == Variant::get_type_name(Variant::PACKED_INT64_ARRAY)) {
+ return "long[]";
+ }
+ if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) {
return "float[]";
-#endif
}
- if (p_var_type_name == Variant::get_type_name(Variant::POOL_STRING_ARRAY))
+ if (p_var_type_name == Variant::get_type_name(Variant::PACKED_FLOAT64_ARRAY)) {
+ return "double[]";
+ }
+ if (p_var_type_name == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) {
return "string[]";
- if (p_var_type_name == Variant::get_type_name(Variant::POOL_VECTOR2_ARRAY))
+ }
+ if (p_var_type_name == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) {
return "Vector2[]";
- if (p_var_type_name == Variant::get_type_name(Variant::POOL_VECTOR3_ARRAY))
+ }
+ if (p_var_type_name == Variant::get_type_name(Variant::PACKED_VECTOR3_ARRAY)) {
return "Vector3[]";
- if (p_var_type_name == Variant::get_type_name(Variant::POOL_COLOR_ARRAY))
+ }
+ if (p_var_type_name == Variant::get_type_name(Variant::PACKED_COLOR_ARRAY)) {
return "Color[]";
+ }
+
+ if (p_var_type_name == Variant::get_type_name(Variant::SIGNAL)) {
+ return "SignalInfo";
+ }
Variant::Type var_types[] = {
Variant::BOOL,
Variant::INT,
Variant::VECTOR2,
+ Variant::VECTOR2I,
Variant::RECT2,
+ Variant::RECT2I,
Variant::VECTOR3,
+ Variant::VECTOR3I,
Variant::TRANSFORM2D,
Variant::PLANE,
- Variant::QUAT,
+ Variant::QUATERNION,
Variant::AABB,
Variant::BASIS,
- Variant::TRANSFORM,
+ Variant::TRANSFORM3D,
Variant::COLOR,
+ Variant::STRING_NAME,
Variant::NODE_PATH,
- Variant::_RID
+ Variant::RID,
+ Variant::CALLABLE
};
for (unsigned int i = 0; i < sizeof(var_types) / sizeof(Variant::Type); i++) {
- if (p_var_type_name == Variant::get_type_name(var_types[i]))
+ if (p_var_type_name == Variant::get_type_name(var_types[i])) {
return p_var_type_name;
+ }
}
return "object";
}
-String CSharpLanguage::make_function(const String &, const String &p_name, const PoolStringArray &p_args) const {
+String CSharpLanguage::make_function(const String &, const String &p_name, const PackedStringArray &p_args) const {
// FIXME
// - Due to Godot's API limitation this just appends the function to the end of the file
// - Use fully qualified name if there is ambiguity
@@ -480,8 +523,9 @@ String CSharpLanguage::make_function(const String &, const String &p_name, const
for (int i = 0; i < p_args.size(); i++) {
const String &arg = p_args[i];
- if (i > 0)
+ if (i > 0) {
s += ", ";
+ }
s += variant_type_to_managed_name(arg.get_slice(":", 1)) + " " + escape_csharp_keyword(arg.get_slice(":", 0));
}
@@ -490,7 +534,7 @@ String CSharpLanguage::make_function(const String &, const String &p_name, const
return s;
}
#else
-String CSharpLanguage::make_function(const String &, const String &, const PoolStringArray &) const {
+String CSharpLanguage::make_function(const String &, const String &, const PackedStringArray &) const {
return String();
}
#endif
@@ -515,54 +559,60 @@ String CSharpLanguage::_get_indentation() const {
}
String CSharpLanguage::debug_get_error() const {
-
return _debug_error;
}
int CSharpLanguage::debug_get_stack_level_count() const {
-
- if (_debug_parse_err_line >= 0)
+ if (_debug_parse_err_line >= 0) {
return 1;
+ }
// TODO: StackTrace
return 1;
}
int CSharpLanguage::debug_get_stack_level_line(int p_level) const {
-
- if (_debug_parse_err_line >= 0)
+ if (_debug_parse_err_line >= 0) {
return _debug_parse_err_line;
+ }
// TODO: StackTrace
return 1;
}
String CSharpLanguage::debug_get_stack_level_function(int p_level) const {
-
- if (_debug_parse_err_line >= 0)
+ if (_debug_parse_err_line >= 0) {
return String();
+ }
// TODO: StackTrace
return String();
}
String CSharpLanguage::debug_get_stack_level_source(int p_level) const {
-
- if (_debug_parse_err_line >= 0)
+ if (_debug_parse_err_line >= 0) {
return _debug_parse_err_file;
+ }
// TODO: StackTrace
return String();
}
Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() {
-
#ifdef DEBUG_ENABLED
- _TLS_RECURSION_GUARD_V_(Vector<StackInfo>());
+ // Printing an error here will result in endless recursion, so we must be careful
+ static thread_local bool _recursion_flag_ = false;
+ if (_recursion_flag_) {
+ return Vector<StackInfo>();
+ }
+ _recursion_flag_ = true;
+ SCOPE_EXIT { _recursion_flag_ = false; };
+
GD_MONO_SCOPE_THREAD_ATTACH;
- if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoCache::cached_data.corlib_cache_updated)
+ if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoCache::cached_data.corlib_cache_updated) {
return Vector<StackInfo>();
+ }
MonoObject *stack_trace = mono_object_new(mono_domain_get(), CACHED_CLASS(System_Diagnostics_StackTrace)->get_mono_ptr());
@@ -582,11 +632,17 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info()
#ifdef DEBUG_ENABLED
Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObject *p_stack_trace) {
+ // Printing an error here will result in endless recursion, so we must be careful
+ static thread_local bool _recursion_flag_ = false;
+ if (_recursion_flag_) {
+ return Vector<StackInfo>();
+ }
+ _recursion_flag_ = true;
+ SCOPE_EXIT { _recursion_flag_ = false; };
- _TLS_RECURSION_GUARD_V_(Vector<StackInfo>());
GD_MONO_SCOPE_THREAD_ATTACH;
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
MonoArray *frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames).invoke(p_stack_trace, &exc);
@@ -597,8 +653,9 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec
int frame_count = mono_array_length(frames);
- if (frame_count <= 0)
+ if (frame_count <= 0) {
return Vector<StackInfo>();
+ }
Vector<StackInfo> si;
si.resize(frame_count);
@@ -632,7 +689,7 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObjec
void CSharpLanguage::post_unsafe_reference(Object *p_obj) {
#ifdef DEBUG_ENABLED
- SCOPED_MUTEX_LOCK(unsafe_object_references_lock);
+ MutexLock lock(unsafe_object_references_lock);
ObjectID id = p_obj->get_instance_id();
unsafe_object_references[id]++;
#endif
@@ -640,25 +697,25 @@ void CSharpLanguage::post_unsafe_reference(Object *p_obj) {
void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) {
#ifdef DEBUG_ENABLED
- SCOPED_MUTEX_LOCK(unsafe_object_references_lock);
+ MutexLock lock(unsafe_object_references_lock);
ObjectID id = p_obj->get_instance_id();
Map<ObjectID, int>::Element *elem = unsafe_object_references.find(id);
ERR_FAIL_NULL(elem);
- if (--elem->value() == 0)
+ if (--elem->value() == 0) {
unsafe_object_references.erase(elem);
+ }
#endif
}
void CSharpLanguage::frame() {
-
- if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != NULL) {
- const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoCache::cached_data.task_scheduler_handle;
+ if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != nullptr) {
+ const Ref<MonoGCHandleRef> &task_scheduler_handle = GDMonoCache::cached_data.task_scheduler_handle;
if (task_scheduler_handle.is_valid()) {
MonoObject *task_scheduler = task_scheduler_handle->get_target();
if (task_scheduler) {
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
CACHED_METHOD_THUNK(GodotTaskScheduler, Activate).invoke(task_scheduler, &exc);
if (exc) {
@@ -670,11 +727,11 @@ void CSharpLanguage::frame() {
}
struct CSharpScriptDepSort {
-
// must support sorting so inheritance works properly (parent must be reloaded first)
bool operator()(const Ref<CSharpScript> &A, const Ref<CSharpScript> &B) const {
- if (A == B)
+ if (A == B) {
return false; // shouldn't happen but..
+ }
GDMonoClass *I = B->base;
while (I) {
if (I == A->script_class) {
@@ -690,7 +747,6 @@ struct CSharpScriptDepSort {
};
void CSharpLanguage::reload_all_scripts() {
-
#ifdef GD_MONO_HOT_RELOAD
if (is_assembly_reloading_needed()) {
GD_MONO_SCOPE_THREAD_ATTACH;
@@ -700,7 +756,6 @@ void CSharpLanguage::reload_all_scripts() {
}
void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
-
(void)p_script; // UNUSED
CRASH_COND(!Engine::get_singleton()->is_editor_hint());
@@ -719,15 +774,15 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft
#ifdef GD_MONO_HOT_RELOAD
bool CSharpLanguage::is_assembly_reloading_needed() {
-
- if (!gdmono->is_runtime_initialized())
+ if (!gdmono->is_runtime_initialized()) {
return false;
+ }
GDMonoAssembly *proj_assembly = gdmono->get_project_assembly();
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
- if (appname_safe.empty()) {
+ if (appname_safe.is_empty()) {
appname_safe = "UnnamedProject";
}
@@ -736,34 +791,37 @@ bool CSharpLanguage::is_assembly_reloading_needed() {
if (proj_assembly) {
String proj_asm_path = proj_assembly->get_path();
- if (!FileAccess::exists(proj_assembly->get_path())) {
+ if (!FileAccess::exists(proj_asm_path)) {
// Maybe it wasn't loaded from the default path, so check this as well
proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe);
- if (!FileAccess::exists(proj_asm_path))
+ if (!FileAccess::exists(proj_asm_path)) {
return false; // No assembly to load
+ }
}
- if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time())
+ if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) {
return false; // Already up to date
+ }
} else {
- if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe)))
+ if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe))) {
return false; // No assembly to load
+ }
}
return true;
}
void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
-
- if (!gdmono->is_runtime_initialized())
+ if (!gdmono->is_runtime_initialized()) {
return;
+ }
// There is no soft reloading with Mono. It's always hard reloading.
- List<Ref<CSharpScript> > scripts;
+ List<Ref<CSharpScript>> scripts;
{
- SCOPED_MUTEX_LOCK(script_instances_mutex);
+ 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
@@ -771,30 +829,58 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
}
}
- List<Ref<CSharpScript> > to_reload;
+ scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order
+
+ // Serialize managed callables
+ {
+ MutexLock lock(ManagedCallable::instances_mutex);
+
+ for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) {
+ ManagedCallable *managed_callable = elem->self();
+
+ MonoDelegate *delegate = (MonoDelegate *)managed_callable->delegate_handle.get_target();
+
+ Array serialized_data;
+ MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
+
+ MonoException *exc = nullptr;
+ bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TrySerializeDelegate).invoke(delegate, managed_serialized_data, &exc);
+
+ if (exc) {
+ GDMonoUtils::debug_print_unhandled_exception(exc);
+ continue;
+ }
+
+ if (success) {
+ ManagedCallable::instances_pending_reload.insert(managed_callable, serialized_data);
+ } else if (OS::get_singleton()->is_stdout_verbose()) {
+ OS::get_singleton()->print("Failed to serialize delegate\n");
+ }
+ }
+ }
+
+ List<Ref<CSharpScript>> to_reload;
// We need to keep reference instances alive during reloading
- List<Ref<Reference> > ref_instances;
+ List<Ref<RefCounted>> rc_instances;
for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) {
CSharpScriptBinding &script_binding = E->value();
- Reference *ref = Object::cast_to<Reference>(script_binding.owner);
- if (ref) {
- ref_instances.push_back(Ref<Reference>(ref));
+ RefCounted *rc = Object::cast_to<RefCounted>(script_binding.owner);
+ if (rc) {
+ rc_instances.push_back(Ref<RefCounted>(rc));
}
}
// As scripts are going to be reloaded, must proceed without locking here
- scripts.sort_custom<CSharpScriptDepSort>(); // Update in inheritance dependency order
-
- for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) {
+ for (List<Ref<CSharpScript>>::Element *E = scripts.front(); E; E = E->next()) {
Ref<CSharpScript> &script = E->get();
to_reload.push_back(script);
- if (script->get_path().empty()) {
- script->tied_class_name_for_reload = script->script_class->get_name();
+ if (script->get_path().is_empty()) {
+ script->tied_class_name_for_reload = script->script_class->get_name_for_lookup();
script->tied_class_namespace_for_reload = script->script_class->get_namespace();
}
@@ -805,9 +891,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
Object *obj = F->get();
script->pending_reload_instances.insert(obj->get_instance_id());
- Reference *ref = Object::cast_to<Reference>(obj);
- if (ref) {
- ref_instances.push_back(Ref<Reference>(ref));
+ RefCounted *rc = Object::cast_to<RefCounted>(obj);
+ if (rc) {
+ rc_instances.push_back(Ref<RefCounted>(rc));
}
}
@@ -816,9 +902,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
Object *obj = F->get()->get_owner();
script->pending_reload_instances.insert(obj->get_instance_id());
- Reference *ref = Object::cast_to<Reference>(obj);
- if (ref) {
- ref_instances.push_back(Ref<Reference>(ref));
+ RefCounted *rc = Object::cast_to<RefCounted>(obj);
+ if (rc) {
+ rc_instances.push_back(Ref<RefCounted>(rc));
}
}
#endif
@@ -834,26 +920,28 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance());
// Call OnBeforeSerialize
- if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
- obj->get_script_instance()->call_multilevel(string_names.on_before_serialize);
+ if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) {
+ obj->get_script_instance()->call(string_names.on_before_serialize);
+ }
// Save instance info
CSharpScript::StateBackup state;
// TODO: Proper state backup (Not only variants, serialize managed state of scripts)
csi->get_properties_state_for_reloading(state.properties);
+ csi->get_event_signals_state_for_reloading(state.event_signals);
owners_map[obj->get_instance_id()] = state;
}
}
// After the state of all instances is saved, clear scripts and script instances
- for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) {
+ for (List<Ref<CSharpScript>>::Element *E = scripts.front(); E; E = E->next()) {
Ref<CSharpScript> &script = E->get();
while (script->instances.front()) {
Object *obj = script->instances.front()->get();
- obj->set_script(RefPtr()); // Remove script and existing script instances (placeholder are not removed before domain reload)
+ obj->set_script(REF()); // Remove script and existing script instances (placeholder are not removed before domain reload)
}
script->_clear();
@@ -863,21 +951,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
if (gdmono->reload_scripts_domain() != OK) {
// Failed to reload the scripts domain
// Make sure to add the scripts back to their owners before returning
- for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
+ for (List<Ref<CSharpScript>>::Element *E = to_reload.front(); E; E = E->next()) {
Ref<CSharpScript> scr = E->get();
for (const Map<ObjectID, CSharpScript::StateBackup>::Element *F = scr->pending_reload_state.front(); F; F = F->next()) {
Object *obj = ObjectDB::get_instance(F->key());
- if (!obj)
+ if (!obj) {
continue;
+ }
ObjectID obj_id = obj->get_instance_id();
// Use a placeholder for now to avoid losing the state when saving a scene
- obj->set_script(scr.get_ref_ptr());
-
PlaceHolderScriptInstance *placeholder = scr->placeholder_instance_create(obj);
obj->set_script_instance(placeholder);
@@ -888,8 +975,8 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
#endif
// Restore Variant properties state, it will be kept by the placeholder until the next script reloading
- for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) {
- placeholder->property_set_fallback(G->get().first, G->get().second, NULL);
+ for (List<Pair<StringName, Variant>>::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) {
+ placeholder->property_set_fallback(G->get().first, G->get().second, nullptr);
}
scr->pending_reload_state.erase(obj_id);
@@ -899,19 +986,18 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
return;
}
- List<Ref<CSharpScript> > to_reload_state;
+ List<Ref<CSharpScript>> to_reload_state;
- for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
+ for (List<Ref<CSharpScript>>::Element *E = to_reload.front(); E; E = E->next()) {
Ref<CSharpScript> script = E->get();
- if (!script->get_path().empty()) {
#ifdef TOOLS_ENABLED
- script->exports_invalidated = true;
+ script->exports_invalidated = true;
#endif
- script->signals_invalidated = true;
+ script->signals_invalidated = true;
+ if (!script->get_path().is_empty()) {
script->reload(p_soft_reload);
- script->update_exports();
if (!script->valid) {
script->pending_reload_instances.clear();
@@ -923,12 +1009,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
GDMonoAssembly *project_assembly = gdmono->get_project_assembly();
// Search in project and tools assemblies first as those are the most likely to have the class
- GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : NULL);
+ GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : nullptr);
#ifdef TOOLS_ENABLED
if (!script_class) {
GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly();
- script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : NULL);
+ script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : nullptr);
}
#endif
@@ -954,7 +1040,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
CSharpScript::initialize_for_managed_type(script, script_class, native);
}
- String native_name = NATIVE_GDMONOCLASS_NAME(script->native);
+ StringName native_name = NATIVE_GDMONOCLASS_NAME(script->native);
{
for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) {
@@ -999,17 +1085,17 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
continue;
}
#else
- CRASH_COND(si != NULL);
+ CRASH_COND(si != nullptr);
#endif
// Re-create script instance
- obj->set_script(script.get_ref_ptr()); // will create the script instance as well
+ obj->set_script(script); // will create the script instance as well
}
}
to_reload_state.push_back(script);
}
- for (List<Ref<CSharpScript> >::Element *E = to_reload_state.front(); E; E = E->next()) {
+ for (List<Ref<CSharpScript>>::Element *E = to_reload_state.front(); E; E = E->next()) {
Ref<CSharpScript> script = E->get();
for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) {
@@ -1027,19 +1113,85 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id];
- for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) {
+ for (List<Pair<StringName, Variant>>::Element *G = state_backup.properties.front(); G; G = G->next()) {
obj->get_script_instance()->set(G->get().first, G->get().second);
}
- // Call OnAfterDeserialization
CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance());
- if (csi && csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
- obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize);
+
+ if (csi) {
+ for (List<Pair<StringName, Array>>::Element *G = state_backup.event_signals.front(); G; G = G->next()) {
+ const StringName &name = G->get().first;
+ const Array &serialized_data = G->get().second;
+
+ Map<StringName, CSharpScript::EventSignal>::Element *match = script->event_signals.find(name);
+
+ if (!match) {
+ // The event or its signal attribute were removed
+ continue;
+ }
+
+ const CSharpScript::EventSignal &event_signal = match->value();
+
+ MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
+ MonoDelegate *delegate = nullptr;
+
+ MonoException *exc = nullptr;
+ bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TryDeserializeDelegate).invoke(managed_serialized_data, &delegate, &exc);
+
+ if (exc) {
+ GDMonoUtils::debug_print_unhandled_exception(exc);
+ continue;
+ }
+
+ if (success) {
+ ERR_CONTINUE(delegate == nullptr);
+ event_signal.field->set_value(csi->get_mono_object(), (MonoObject *)delegate);
+ } else if (OS::get_singleton()->is_stdout_verbose()) {
+ OS::get_singleton()->print("Failed to deserialize event signal delegate\n");
+ }
+ }
+
+ // Call OnAfterDeserialization
+ if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) {
+ obj->get_script_instance()->call(string_names.on_after_deserialize);
+ }
+ }
}
script->pending_reload_instances.clear();
}
+ // Deserialize managed callables
+ {
+ MutexLock lock(ManagedCallable::instances_mutex);
+
+ for (Map<ManagedCallable *, Array>::Element *elem = ManagedCallable::instances_pending_reload.front(); elem; elem = elem->next()) {
+ ManagedCallable *managed_callable = elem->key();
+ const Array &serialized_data = elem->value();
+
+ MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
+ MonoDelegate *delegate = nullptr;
+
+ MonoException *exc = nullptr;
+ bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TryDeserializeDelegate).invoke(managed_serialized_data, &delegate, &exc);
+
+ if (exc) {
+ GDMonoUtils::debug_print_unhandled_exception(exc);
+ continue;
+ }
+
+ if (success) {
+ ERR_CONTINUE(delegate == nullptr);
+ managed_callable->set_delegate(delegate);
+ } else if (OS::get_singleton()->is_stdout_verbose()) {
+ OS::get_singleton()->print("Failed to deserialize delegate\n");
+ }
+ }
+
+ ManagedCallable::instances_pending_reload.clear();
+ }
+
#ifdef TOOLS_ENABLED
// FIXME: Hack to refresh editor in order to display new properties and signals. See if there is a better alternative.
if (Engine::get_singleton()->is_editor_hint()) {
@@ -1050,70 +1202,75 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
}
#endif
-void CSharpLanguage::_load_scripts_metadata() {
+void CSharpLanguage::lookup_script_for_class(GDMonoClass *p_class) {
+ if (!p_class->has_attribute(CACHED_CLASS(ScriptPathAttribute))) {
+ return;
+ }
- scripts_metadata.clear();
+ MonoObject *attr = p_class->get_attribute(CACHED_CLASS(ScriptPathAttribute));
+ String path = CACHED_FIELD(ScriptPathAttribute, path)->get_string_value(attr);
- String scripts_metadata_filename = "scripts_metadata.";
+ dotnet_script_lookup_map[path] = DotNetScriptLookupInfo(
+ p_class->get_namespace(), p_class->get_name(), p_class);
+}
-#ifdef TOOLS_ENABLED
- scripts_metadata_filename += Engine::get_singleton()->is_editor_hint() ? "editor" : "editor_player";
-#else
-#ifdef DEBUG_ENABLED
- scripts_metadata_filename += "debug";
-#else
- scripts_metadata_filename += "release";
-#endif
-#endif
+void CSharpLanguage::lookup_scripts_in_assembly(GDMonoAssembly *p_assembly) {
+ if (p_assembly->has_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute))) {
+ MonoObject *attr = p_assembly->get_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute));
+ bool requires_lookup = CACHED_FIELD(AssemblyHasScriptsAttribute, requiresLookup)->get_bool_value(attr);
- String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename);
+ if (requires_lookup) {
+ // This is supported for scenarios where specifying all types would be cumbersome,
+ // such as when disabling C# source generators (for whatever reason) or when using a
+ // language other than C# that has nothing similar to source generators to automate it.
+ MonoImage *image = p_assembly->get_image();
- if (FileAccess::exists(scripts_metadata_path)) {
- String old_json;
+ int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF);
- Error ferr = read_all_file_utf8(scripts_metadata_path, old_json);
+ for (int i = 1; i < rows; i++) {
+ // We don't search inner classes, only top-level.
+ MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF);
- ERR_FAIL_COND(ferr != OK);
+ if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) {
+ continue;
+ }
- Variant old_dict_var;
- String err_str;
- int err_line;
- Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line);
- if (json_err != OK) {
- ERR_PRINTS("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ").");
- return;
- }
+ GDMonoClass *current = p_assembly->get_class(mono_class);
+ if (current) {
+ lookup_script_for_class(current);
+ }
+ }
+ } else {
+ // This is the most likely scenario as we use C# source generators
+ MonoArray *script_types = (MonoArray *)CACHED_FIELD(AssemblyHasScriptsAttribute, scriptTypes)->get_value(attr);
- scripts_metadata = old_dict_var.operator Dictionary();
- scripts_metadata_invalidated = false;
+ int length = mono_array_length(script_types);
- print_verbose("Successfully loaded scripts metadata");
- } else {
- if (!Engine::get_singleton()->is_editor_hint()) {
- ERR_PRINT("Missing scripts metadata file.");
+ for (int i = 0; i < length; i++) {
+ MonoReflectionType *reftype = mono_array_get(script_types, MonoReflectionType *, i);
+ ManagedType type = ManagedType::from_reftype(reftype);
+ ERR_CONTINUE(!type.type_class);
+ lookup_script_for_class(type.type_class);
+ }
}
}
}
void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const {
-
p_extensions->push_back("cs");
}
#ifdef TOOLS_ENABLED
Error CSharpLanguage::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) {
-
return (Error)(int)get_godotsharp_editor()->call("OpenInExternalEditor", p_script, p_line, p_col);
}
bool CSharpLanguage::overrides_external_editor() {
-
return get_godotsharp_editor()->call("OverridesExternalEditor");
}
#endif
void CSharpLanguage::thread_enter() {
-
#if 0
if (gdmono->is_runtime_initialized()) {
GDMonoUtils::attach_current_thread();
@@ -1122,7 +1279,6 @@ void CSharpLanguage::thread_enter() {
}
void CSharpLanguage::thread_exit() {
-
#if 0
if (gdmono->is_runtime_initialized()) {
GDMonoUtils::detach_current_thread();
@@ -1131,13 +1287,12 @@ void CSharpLanguage::thread_exit() {
}
bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) {
-
// Not a parser error in our case, but it's still used for other type of errors
- if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
+ if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
_debug_parse_err_line = p_line;
_debug_parse_err_file = p_file;
_debug_error = p_error;
- ScriptDebugger::get_singleton()->debug(this, false, true);
+ EngineDebugger::get_script_debugger()->debug(this, false, true);
return true;
} else {
return false;
@@ -1145,12 +1300,11 @@ bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const S
}
bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) {
-
- if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) {
+ if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) {
_debug_parse_err_line = -1;
_debug_parse_err_file = "";
_debug_error = p_error;
- ScriptDebugger::get_singleton()->debug(this, p_allow_continue);
+ EngineDebugger::get_script_debugger()->debug(this, p_allow_continue);
return true;
} else {
return false;
@@ -1160,31 +1314,44 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) {
void CSharpLanguage::_on_scripts_domain_unloaded() {
for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) {
CSharpScriptBinding &script_binding = E->value();
+ script_binding.gchandle.release();
script_binding.inited = false;
}
- scripts_metadata_invalidated = true;
+#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->delegate_handle.release();
+ managed_callable->delegate_invoke = nullptr;
+ }
+ }
+#endif
+
+ dotnet_script_lookup_map.clear();
}
#ifdef TOOLS_ENABLED
void CSharpLanguage::_editor_init_callback() {
-
register_editor_internal_calls();
// Initialize GodotSharpEditor
GDMonoClass *editor_klass = GDMono::get_singleton()->get_tools_assembly()->get_class("GodotTools", "GodotSharpEditor");
- CRASH_COND(editor_klass == NULL);
+ CRASH_COND(editor_klass == nullptr);
MonoObject *mono_object = mono_object_new(mono_domain_get(), editor_klass->get_mono_ptr());
- CRASH_COND(mono_object == NULL);
+ CRASH_COND(mono_object == nullptr);
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
GDMonoUtils::runtime_object_init(mono_object, editor_klass, &exc);
UNHANDLED_EXCEPTION(exc);
- EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(GDMonoMarshal::mono_object_to_variant(mono_object));
- CRASH_COND(godotsharp_editor == NULL);
+ EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(
+ GDMonoMarshal::mono_object_to_variant(mono_object).operator Object *());
+ CRASH_COND(godotsharp_editor == nullptr);
// Enable it as a plugin
EditorNode::add_editor_plugin(godotsharp_editor);
@@ -1195,112 +1362,53 @@ void CSharpLanguage::_editor_init_callback() {
#endif
void CSharpLanguage::set_language_index(int p_idx) {
-
ERR_FAIL_COND(lang_idx != -1);
lang_idx = p_idx;
}
-void CSharpLanguage::release_script_gchandle(Ref<MonoGCHandle> &p_gchandle) {
-
- if (!p_gchandle->is_released()) { // Do not lock unnecessarily
- SCOPED_MUTEX_LOCK(get_singleton()->script_gchandle_release_mutex);
- p_gchandle->release();
+void CSharpLanguage::release_script_gchandle(MonoGCHandleData &p_gchandle) {
+ if (!p_gchandle.is_released()) { // Do not lock unnecessarily
+ MutexLock lock(get_singleton()->script_gchandle_release_mutex);
+ p_gchandle.release();
}
}
-void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle) {
-
- uint32_t pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(p_expected_obj); // We might lock after this, so pin it
+void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, MonoGCHandleData &p_gchandle) {
+ uint32_t pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(p_expected_obj); // We might lock after this, so pin it
- if (!p_gchandle->is_released()) { // Do not lock unnecessarily
- SCOPED_MUTEX_LOCK(get_singleton()->script_gchandle_release_mutex);
+ if (!p_gchandle.is_released()) { // Do not lock unnecessarily
+ MutexLock lock(get_singleton()->script_gchandle_release_mutex);
- MonoObject *target = p_gchandle->get_target();
+ MonoObject *target = p_gchandle.get_target();
// We release the gchandle if it points to the MonoObject* we expect (otherwise it was
// already released and could have been replaced) or if we can't get its target MonoObject*
// (which doesn't necessarily mean it was released, and we want it released in order to
// avoid locking other threads unnecessarily).
- if (target == p_expected_obj || target == NULL) {
- p_gchandle->release();
+ if (target == p_expected_obj || target == nullptr) {
+ p_gchandle.release();
}
}
- MonoGCHandle::free_handle(pinned_gchandle);
+ GDMonoUtils::free_gchandle(pinned_gchandle);
}
CSharpLanguage::CSharpLanguage() {
-
ERR_FAIL_COND_MSG(singleton, "C# singleton already exist.");
singleton = this;
-
- finalizing = false;
-
- gdmono = NULL;
-
-#ifdef NO_THREADS
- script_instances_mutex = NULL;
- script_gchandle_release_mutex = NULL;
- language_bind_mutex = NULL;
-#else
- script_instances_mutex = Mutex::create();
- script_gchandle_release_mutex = Mutex::create();
- language_bind_mutex = Mutex::create();
-#endif
-
-#ifdef DEBUG_ENABLED
-#ifdef NO_THREADS
- unsafe_object_references_lock = NULL;
-#else
- unsafe_object_references_lock = Mutex::create();
-#endif
-#endif
-
- lang_idx = -1;
-
- scripts_metadata_invalidated = true;
-
-#ifdef TOOLS_ENABLED
- godotsharp_editor = NULL;
-#endif
}
CSharpLanguage::~CSharpLanguage() {
-
- finish();
-
- if (script_instances_mutex) {
- memdelete(script_instances_mutex);
- script_instances_mutex = NULL;
- }
-
- if (language_bind_mutex) {
- memdelete(language_bind_mutex);
- language_bind_mutex = NULL;
- }
-
- if (script_gchandle_release_mutex) {
- memdelete(script_gchandle_release_mutex);
- script_gchandle_release_mutex = NULL;
- }
-
-#ifdef DEBUG_ENABLED
- if (unsafe_object_references_lock) {
- memdelete(unsafe_object_references_lock);
- unsafe_object_references_lock = NULL;
- }
-#endif
-
- singleton = NULL;
+ finalize();
+ singleton = nullptr;
}
bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_binding, Object *p_object) {
-
#ifdef DEBUG_ENABLED
// I don't trust you
if (p_object->get_script_instance()) {
CSharpInstance *csharp_instance = CAST_CSHARP_INSTANCE(p_object->get_script_instance());
- CRASH_COND(csharp_instance != NULL && !csharp_instance->is_destructing_script_instance());
+ CRASH_COND(csharp_instance != nullptr && !csharp_instance->is_destructing_script_instance());
}
#endif
@@ -1308,8 +1416,9 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b
// ¯\_(ツ)_/¯
const ClassDB::ClassInfo *classinfo = ClassDB::classes.getptr(type_name);
- while (classinfo && !classinfo->exposed)
+ while (classinfo && !classinfo->exposed) {
classinfo = classinfo->inherits_ptr;
+ }
ERR_FAIL_NULL_V(classinfo, false);
type_name = classinfo->name;
@@ -1324,63 +1433,63 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b
r_script_binding.inited = true;
r_script_binding.type_name = type_name;
r_script_binding.wrapper_class = type_class; // cache
- r_script_binding.gchandle = MonoGCHandle::create_strong(mono_object);
+ r_script_binding.gchandle = MonoGCHandleData::new_strong_handle(mono_object);
r_script_binding.owner = p_object;
// Tie managed to unmanaged
- Reference *ref = Object::cast_to<Reference>(p_object);
+ RefCounted *rc = Object::cast_to<RefCounted>(p_object);
- if (ref) {
+ if (rc) {
// Unsafe refcount increment. The managed instance also counts as a reference.
// This way if the unmanaged world has no references to our owner
// but the managed instance is alive, the refcount will be 1 instead of 0.
- // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)
+ // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr)
- ref->reference();
- CSharpLanguage::get_singleton()->post_unsafe_reference(ref);
+ rc->reference();
+ CSharpLanguage::get_singleton()->post_unsafe_reference(rc);
}
return true;
}
void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) {
-
- SCOPED_MUTEX_LOCK(language_bind_mutex);
+ MutexLock lock(language_bind_mutex);
Map<Object *, CSharpScriptBinding>::Element *match = script_bindings.find(p_object);
- if (match)
+ if (match) {
return (void *)match;
+ }
CSharpScriptBinding script_binding;
- if (!setup_csharp_script_binding(script_binding, p_object))
- return NULL;
+ if (!setup_csharp_script_binding(script_binding, p_object)) {
+ return nullptr;
+ }
return (void *)insert_script_binding(p_object, script_binding);
}
Map<Object *, CSharpScriptBinding>::Element *CSharpLanguage::insert_script_binding(Object *p_object, const CSharpScriptBinding &p_script_binding) {
-
return script_bindings.insert(p_object, p_script_binding);
}
void CSharpLanguage::free_instance_binding_data(void *p_data) {
-
- if (GDMono::get_singleton() == NULL) {
+ if (GDMono::get_singleton() == nullptr) {
#ifdef DEBUG_ENABLED
- CRASH_COND(!script_bindings.empty());
+ CRASH_COND(!script_bindings.is_empty());
#endif
// Mono runtime finalized, all the gchandle bindings were already released
return;
}
- if (finalizing)
+ if (finalizing) {
return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there
+ }
GD_MONO_ASSERT_THREAD_ATTACHED;
{
- SCOPED_MUTEX_LOCK(language_bind_mutex);
+ MutexLock lock(language_bind_mutex);
Map<Object *, CSharpScriptBinding>::Element *data = (Map<Object *, CSharpScriptBinding>::Element *)p_data;
@@ -1389,10 +1498,11 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) {
if (script_binding.inited) {
// Set the native instance field to IntPtr.Zero, if not yet garbage collected.
// This is done to avoid trying to dispose the native instance from Dispose(bool).
- MonoObject *mono_object = script_binding.gchandle->get_target();
+ MonoObject *mono_object = script_binding.gchandle.get_target();
if (mono_object) {
- CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, NULL);
+ CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, nullptr);
}
+ script_binding.gchandle.release();
}
script_bindings.erase(data);
@@ -1400,11 +1510,10 @@ void CSharpLanguage::free_instance_binding_data(void *p_data) {
}
void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
-
- Reference *ref_owner = Object::cast_to<Reference>(p_object);
+ RefCounted *rc_owner = Object::cast_to<RefCounted>(p_object);
#ifdef DEBUG_ENABLED
- CRASH_COND(!ref_owner);
+ CRASH_COND(!rc_owner);
CRASH_COND(!p_object->has_script_instance_binding(get_language_index()));
#endif
@@ -1412,35 +1521,36 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
CRASH_COND(!data);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
- Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+ MonoGCHandleData &gchandle = script_binding.gchandle;
- if (!script_binding.inited)
+ if (!script_binding.inited) {
return;
+ }
- if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
+ if (rc_owner->reference_get_count() > 1 && gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
GD_MONO_SCOPE_THREAD_ATTACH;
// 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.
- MonoObject *target = gchandle->get_target();
- if (!target)
+ MonoObject *target = gchandle.get_target();
+ if (!target) {
return; // Called after the managed side was collected, so nothing to do here
+ }
// Release the current weak handle and replace it with a strong handle.
- uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(target);
- gchandle->release();
- gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE);
+ MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(target);
+ gchandle.release();
+ gchandle = strong_gchandle;
}
}
bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
-
- Reference *ref_owner = Object::cast_to<Reference>(p_object);
+ RefCounted *rc_owner = Object::cast_to<RefCounted>(p_object);
#ifdef DEBUG_ENABLED
- CRASH_COND(!ref_owner);
+ CRASH_COND(!rc_owner);
CRASH_COND(!p_object->has_script_instance_binding(get_language_index()));
#endif
@@ -1448,27 +1558,29 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
CRASH_COND(!data);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
- Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
+ MonoGCHandleData &gchandle = script_binding.gchandle;
- int refcount = ref_owner->reference_get_count();
+ int refcount = rc_owner->reference_get_count();
- if (!script_binding.inited)
+ if (!script_binding.inited) {
return refcount == 0;
+ }
- if (refcount == 1 && gchandle.is_valid() && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
+ if (refcount == 1 && !gchandle.is_released() && !gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
GD_MONO_SCOPE_THREAD_ATTACH;
// If owner owner is no longer referenced by the unmanaged side,
// the managed instance takes responsibility of deleting the owner when GCed.
- MonoObject *target = gchandle->get_target();
- if (!target)
+ MonoObject *target = gchandle.get_target();
+ if (!target) {
return refcount == 0; // Called after the managed side was collected, so nothing to do here
+ }
// Release the current strong handle and replace it with a weak handle.
- uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(target);
- gchandle->release();
- gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE);
+ MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(target);
+ gchandle.release();
+ gchandle = weak_gchandle;
return false;
}
@@ -1476,19 +1588,18 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
return refcount == 0;
}
-CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle) {
+CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) {
+ CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script)));
- CSharpInstance *instance = memnew(CSharpInstance);
+ RefCounted *rc = Object::cast_to<RefCounted>(p_owner);
- Reference *ref = Object::cast_to<Reference>(p_owner);
-
- instance->base_ref = ref != NULL;
- instance->script = Ref<CSharpScript>(p_script);
+ instance->base_ref_counted = rc != nullptr;
instance->owner = p_owner;
instance->gchandle = p_gchandle;
- if (instance->base_ref)
+ if (instance->base_ref_counted) {
instance->_reference_owner_unsafe();
+ }
p_script->instances.insert(p_owner);
@@ -1496,9 +1607,8 @@ CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpS
}
MonoObject *CSharpInstance::get_mono_object() const {
-
- ERR_FAIL_COND_V(gchandle.is_null(), NULL);
- return gchandle->get_target();
+ ERR_FAIL_COND_V(gchandle.is_released(), nullptr);
+ return gchandle.get_target();
}
Object *CSharpInstance::get_owner() {
@@ -1506,7 +1616,6 @@ Object *CSharpInstance::get_owner() {
}
bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) {
-
ERR_FAIL_COND_V(!script.is_valid(), false);
GD_MONO_SCOPE_THREAD_ATTACH;
@@ -1547,8 +1656,9 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) {
MonoObject *ret = method->invoke(mono_object, args);
- if (ret && GDMonoMarshal::unbox<MonoBoolean>(ret))
+ if (ret && GDMonoMarshal::unbox<MonoBoolean>(ret)) {
return true;
+ }
break;
}
@@ -1560,7 +1670,6 @@ bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) {
}
bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const {
-
ERR_FAIL_COND_V(!script.is_valid(), false);
GD_MONO_SCOPE_THREAD_ATTACH;
@@ -1582,7 +1691,7 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const {
GDMonoProperty *property = top->get_property(p_name);
if (property) {
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
MonoObject *value = property->get_value(mono_object, &exc);
if (exc) {
r_ret = Variant();
@@ -1623,8 +1732,7 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const {
return false;
}
-void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state) {
-
+void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state) {
List<PropertyInfo> pinfo;
get_property_list(&pinfo);
@@ -1635,8 +1743,9 @@ void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Va
ManagedType managedType;
GDMonoField *field = script->script_class->get_field(state_pair.first);
- if (!field)
+ if (!field) {
continue; // Properties ignored. We get the property baking fields instead.
+ }
managedType = field->get_type();
@@ -1648,8 +1757,38 @@ void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Va
}
}
-void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
+void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state) {
+ MonoObject *owner_managed = get_mono_object();
+ ERR_FAIL_NULL(owner_managed);
+
+ for (const Map<StringName, CSharpScript::EventSignal>::Element *E = script->event_signals.front(); E; E = E->next()) {
+ const CSharpScript::EventSignal &event_signal = E->value();
+
+ MonoDelegate *delegate_field_value = (MonoDelegate *)event_signal.field->get_value(owner_managed);
+ if (!delegate_field_value) {
+ continue; // Empty
+ }
+ Array serialized_data;
+ MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data);
+
+ MonoException *exc = nullptr;
+ bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TrySerializeDelegate).invoke(delegate_field_value, managed_serialized_data, &exc);
+
+ if (exc) {
+ GDMonoUtils::debug_print_unhandled_exception(exc);
+ continue;
+ }
+
+ if (success) {
+ r_state.push_back(Pair<StringName, Array>(event_signal.field->get_name(), serialized_data));
+ } else if (OS::get_singleton()->is_stdout_verbose()) {
+ OS::get_singleton()->print("Failed to serialize event signal delegate\n");
+ }
+ }
+}
+
+void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
for (Map<StringName, PropertyInfo>::Element *E = script->member_info.front(); E; E = E->next()) {
p_properties->push_back(E->value());
}
@@ -1673,8 +1812,9 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
if (ret) {
Array array = Array(GDMonoMarshal::mono_object_to_variant(ret));
- for (int i = 0, size = array.size(); i < size; i++)
+ for (int i = 0, size = array.size(); i < size; i++) {
p_properties->push_back(PropertyInfo::from_dict(array.get(i)));
+ }
return;
}
@@ -1686,23 +1826,24 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
}
Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const {
-
if (script->member_info.has(p_name)) {
- if (r_is_valid)
+ if (r_is_valid) {
*r_is_valid = true;
+ }
return script->member_info[p_name].type;
}
- if (r_is_valid)
+ if (r_is_valid) {
*r_is_valid = false;
+ }
return Variant::NIL;
}
bool CSharpInstance::has_method(const StringName &p_method) const {
-
- if (!script.is_valid())
+ if (!script.is_valid()) {
return false;
+ }
GD_MONO_SCOPE_THREAD_ATTACH;
@@ -1719,17 +1860,15 @@ bool CSharpInstance::has_method(const StringName &p_method) const {
return false;
}
-Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
-
- if (!script.is_valid())
- ERR_FAIL_V(Variant());
+Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
+ ERR_FAIL_COND_V(!script.is_valid(), Variant());
GD_MONO_SCOPE_THREAD_ATTACH;
MonoObject *mono_object = get_mono_object();
if (!mono_object) {
- r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL;
+ r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
ERR_FAIL_V(Variant());
}
@@ -1741,7 +1880,7 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args,
if (method) {
MonoObject *return_value = method->invoke(mono_object, p_args);
- r_error.error = Variant::CallError::CALL_OK;
+ r_error.error = Callable::CallError::CALL_OK;
if (return_value) {
return GDMonoMarshal::mono_object_to_variant(return_value);
@@ -1753,54 +1892,15 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args,
top = top->get_parent_class();
}
- r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD;
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
return Variant();
}
-void CSharpInstance::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) {
-
- GD_MONO_SCOPE_THREAD_ATTACH;
-
- if (script.is_valid()) {
- MonoObject *mono_object = get_mono_object();
-
- ERR_FAIL_NULL(mono_object);
-
- _call_multilevel(mono_object, p_method, p_args, p_argcount);
- }
-}
-
-void CSharpInstance::_call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount) {
-
- GD_MONO_ASSERT_THREAD_ATTACHED;
-
- GDMonoClass *top = script->script_class;
-
- while (top && top != script->native) {
- GDMonoMethod *method = top->get_method(p_method, p_argcount);
-
- if (method) {
- method->invoke(p_mono_object, p_args);
- return;
- }
-
- top = top->get_parent_class();
- }
-}
-
-void CSharpInstance::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) {
-
- // Sorry, the method is the one that controls the call order
-
- call_multilevel(p_method, p_args, p_argcount);
-}
-
bool CSharpInstance::_reference_owner_unsafe() {
-
#ifdef DEBUG_ENABLED
- CRASH_COND(!base_ref);
- CRASH_COND(owner == NULL);
+ CRASH_COND(!base_ref_counted);
+ CRASH_COND(owner == nullptr);
CRASH_COND(unsafe_referenced); // already referenced
#endif
@@ -1810,7 +1910,7 @@ bool CSharpInstance::_reference_owner_unsafe() {
// See: _unreference_owner_unsafe()
// May not me referenced yet, so we must use init_ref() instead of reference()
- if (static_cast<Reference *>(owner)->init_ref()) {
+ if (static_cast<RefCounted *>(owner)->init_ref()) {
CSharpLanguage::get_singleton()->post_unsafe_reference(owner);
unsafe_referenced = true;
}
@@ -1819,14 +1919,14 @@ bool CSharpInstance::_reference_owner_unsafe() {
}
bool CSharpInstance::_unreference_owner_unsafe() {
-
#ifdef DEBUG_ENABLED
- CRASH_COND(!base_ref);
- CRASH_COND(owner == NULL);
+ CRASH_COND(!base_ref_counted);
+ CRASH_COND(owner == nullptr);
#endif
- if (!unsafe_referenced)
+ if (!unsafe_referenced) {
return false; // Already unreferenced
+ }
unsafe_referenced = false;
@@ -1837,23 +1937,19 @@ bool CSharpInstance::_unreference_owner_unsafe() {
// Destroying the owner here means self destructing, so we defer the owner destruction to the caller.
CSharpLanguage::get_singleton()->pre_unsafe_unreference(owner);
- return static_cast<Reference *>(owner)->unreference();
+ return static_cast<RefCounted *>(owner)->unreference();
}
MonoObject *CSharpInstance::_internal_new_managed() {
-#ifdef DEBUG_ENABLED
- CRASH_COND(!gchandle.is_valid());
-#endif
-
// Search the constructor first, to fail with an error if it's not found before allocating anything else.
GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
- ERR_FAIL_NULL_V_MSG(ctor, NULL,
+ ERR_FAIL_NULL_V_MSG(ctor, nullptr,
"Cannot create script instance because the class does not define a parameterless constructor: '" + script->get_path() + "'.");
CSharpLanguage::get_singleton()->release_script_gchandle(gchandle);
- ERR_FAIL_NULL_V(owner, NULL);
- ERR_FAIL_COND_V(script.is_null(), NULL);
+ ERR_FAIL_NULL_V(owner, nullptr);
+ ERR_FAIL_COND_V(script.is_null(), nullptr);
MonoObject *mono_object = mono_object_new(mono_domain_get(), script->script_class->get_mono_ptr());
@@ -1863,43 +1959,48 @@ MonoObject *CSharpInstance::_internal_new_managed() {
bool die = _unreference_owner_unsafe();
// Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug.
- CRASH_COND(die == true);
+ CRASH_COND(die);
- owner = NULL;
+ owner = nullptr;
- ERR_FAIL_V_MSG(NULL, "Failed to allocate memory for the object.");
+ ERR_FAIL_V_MSG(nullptr, "Failed to allocate memory for the object.");
}
// Tie managed to unmanaged
- gchandle = MonoGCHandle::create_strong(mono_object);
+ gchandle = MonoGCHandleData::new_strong_handle(mono_object);
- if (base_ref)
+ if (base_ref_counted) {
_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback)
+ }
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, owner);
// Construct
- ctor->invoke_raw(mono_object, NULL);
+ ctor->invoke_raw(mono_object, nullptr);
return mono_object;
}
void CSharpInstance::mono_object_disposed(MonoObject *p_obj) {
+ // Must make sure event signals are not left dangling
+ disconnect_event_signals();
#ifdef DEBUG_ENABLED
- CRASH_COND(base_ref);
- CRASH_COND(gchandle.is_null());
+ CRASH_COND(base_ref_counted);
+ CRASH_COND(gchandle.is_released());
#endif
CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle);
}
void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance) {
-
#ifdef DEBUG_ENABLED
- CRASH_COND(!base_ref);
- CRASH_COND(gchandle.is_null());
+ CRASH_COND(!base_ref_counted);
+ CRASH_COND(gchandle.is_released());
#endif
+ // Must make sure event signals are not left dangling
+ disconnect_event_signals();
+
r_remove_script_instance = false;
if (_unreference_owner_unsafe()) {
@@ -1927,16 +2028,40 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f
}
}
-void CSharpInstance::refcount_incremented() {
+void CSharpInstance::connect_event_signals() {
+ for (const Map<StringName, CSharpScript::EventSignal>::Element *E = script->event_signals.front(); E; E = E->next()) {
+ const CSharpScript::EventSignal &event_signal = E->value();
+
+ StringName signal_name = event_signal.field->get_name();
+ // TODO: Use pooling for ManagedCallable instances.
+ EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, &event_signal));
+
+ Callable callable(event_signal_callable);
+ connected_event_signals.push_back(callable);
+ owner->connect(signal_name, callable);
+ }
+}
+
+void CSharpInstance::disconnect_event_signals() {
+ for (const List<Callable>::Element *E = connected_event_signals.front(); E; E = E->next()) {
+ const Callable &callable = E->get();
+ const EventSignalCallable *event_signal_callable = static_cast<const EventSignalCallable *>(callable.get_custom());
+ owner->disconnect(event_signal_callable->get_signal(), callable);
+ }
+
+ connected_event_signals.clear();
+}
+
+void CSharpInstance::refcount_incremented() {
#ifdef DEBUG_ENABLED
- CRASH_COND(!base_ref);
- CRASH_COND(owner == NULL);
+ CRASH_COND(!base_ref_counted);
+ CRASH_COND(owner == nullptr);
#endif
- Reference *ref_owner = Object::cast_to<Reference>(owner);
+ RefCounted *rc_owner = Object::cast_to<RefCounted>(owner);
- if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
+ if (rc_owner->reference_get_count() > 1 && gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
GD_MONO_SCOPE_THREAD_ATTACH;
// The reference count was increased after the managed side was the only one referencing our owner.
@@ -1944,33 +2069,32 @@ void CSharpInstance::refcount_incremented() {
// so the owner must hold the managed side alive again to avoid it from being GCed.
// Release the current weak handle and replace it with a strong handle.
- uint32_t strong_gchandle = MonoGCHandle::new_strong_handle(gchandle->get_target());
- gchandle->release();
- gchandle->set_handle(strong_gchandle, MonoGCHandle::STRONG_HANDLE);
+ MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(gchandle.get_target());
+ gchandle.release();
+ gchandle = strong_gchandle;
}
}
bool CSharpInstance::refcount_decremented() {
-
#ifdef DEBUG_ENABLED
- CRASH_COND(!base_ref);
- CRASH_COND(owner == NULL);
+ CRASH_COND(!base_ref_counted);
+ CRASH_COND(owner == nullptr);
#endif
- Reference *ref_owner = Object::cast_to<Reference>(owner);
+ RefCounted *rc_owner = Object::cast_to<RefCounted>(owner);
- int refcount = ref_owner->reference_get_count();
+ int refcount = rc_owner->reference_get_count();
- if (refcount == 1 && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
+ if (refcount == 1 && !gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
GD_MONO_SCOPE_THREAD_ATTACH;
// If owner owner is no longer referenced by the unmanaged side,
// the managed instance takes responsibility of deleting the owner when GCed.
// Release the current strong handle and replace it with a weak handle.
- uint32_t weak_gchandle = MonoGCHandle::new_weak_handle(gchandle->get_target());
- gchandle->release();
- gchandle->set_handle(weak_gchandle, MonoGCHandle::WEAK_HANDLE);
+ MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(gchandle.get_target());
+ gchandle.release();
+ gchandle = weak_gchandle;
return false;
}
@@ -1980,71 +2104,11 @@ bool CSharpInstance::refcount_decremented() {
return ref_dying;
}
-MultiplayerAPI::RPCMode CSharpInstance::_member_get_rpc_mode(IMonoClassMember *p_member) const {
-
- if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute)))
- return MultiplayerAPI::RPC_MODE_REMOTE;
- if (p_member->has_attribute(CACHED_CLASS(MasterAttribute)))
- return MultiplayerAPI::RPC_MODE_MASTER;
- if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute)))
- return MultiplayerAPI::RPC_MODE_PUPPET;
- if (p_member->has_attribute(CACHED_CLASS(SlaveAttribute)))
- return MultiplayerAPI::RPC_MODE_PUPPET;
- if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute)))
- return MultiplayerAPI::RPC_MODE_REMOTESYNC;
- if (p_member->has_attribute(CACHED_CLASS(SyncAttribute)))
- return MultiplayerAPI::RPC_MODE_REMOTESYNC;
- if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute)))
- return MultiplayerAPI::RPC_MODE_MASTERSYNC;
- if (p_member->has_attribute(CACHED_CLASS(PuppetSyncAttribute)))
- return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
-
- return MultiplayerAPI::RPC_MODE_DISABLED;
-}
-
-MultiplayerAPI::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) const {
-
- GD_MONO_SCOPE_THREAD_ATTACH;
-
- GDMonoClass *top = script->script_class;
-
- while (top && top != script->native) {
- GDMonoMethod *method = top->get_fetched_method_unknown_params(p_method);
-
- if (method && !method->is_static())
- return _member_get_rpc_mode(method);
-
- top = top->get_parent_class();
- }
-
- return MultiplayerAPI::RPC_MODE_DISABLED;
-}
-
-MultiplayerAPI::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variable) const {
-
- GD_MONO_SCOPE_THREAD_ATTACH;
-
- GDMonoClass *top = script->script_class;
-
- while (top && top != script->native) {
- GDMonoField *field = top->get_field(p_variable);
-
- if (field && !field->is_static())
- return _member_get_rpc_mode(field);
-
- GDMonoProperty *property = top->get_property(p_variable);
-
- if (property && !property->is_static())
- return _member_get_rpc_mode(property);
-
- top = top->get_parent_class();
- }
-
- return MultiplayerAPI::RPC_MODE_DISABLED;
+const Vector<MultiplayerAPI::RPCConfig> CSharpInstance::get_rpc_methods() const {
+ return script->get_rpc_methods();
}
void CSharpInstance::notification(int p_notification) {
-
GD_MONO_SCOPE_THREAD_ATTACH;
if (p_notification == Object::NOTIFICATION_PREDELETE) {
@@ -2054,12 +2118,12 @@ void CSharpInstance::notification(int p_notification) {
predelete_notified = true;
- if (base_ref) {
- // It's not safe to proceed if the owner derives Reference and the refcount reached 0.
+ if (base_ref_counted) {
+ // It's not safe to proceed if the owner derives RefCounted and the refcount reached 0.
// At this point, Dispose() was already called (manually or from the finalizer) so
// that's not a problem. The refcount wouldn't have reached 0 otherwise, since the
// managed side references it and Dispose() needs to be called to release it.
- // However, this means C# Reference scripts can't receive NOTIFICATION_PREDELETE, but
+ // However, this means C# RefCounted scripts can't receive NOTIFICATION_PREDELETE, but
// this is likely the case with GDScript as well: https://github.com/godotengine/godot/issues/6784
return;
}
@@ -2069,7 +2133,7 @@ void CSharpInstance::notification(int p_notification) {
MonoObject *mono_object = get_mono_object();
ERR_FAIL_NULL(mono_object);
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
GDMonoUtils::dispose(mono_object, &exc);
if (exc) {
@@ -2083,7 +2147,6 @@ void CSharpInstance::notification(int p_notification) {
}
void CSharpInstance::_call_notification(int p_notification) {
-
GD_MONO_ASSERT_THREAD_ATTACHED;
MonoObject *mono_object = get_mono_object();
@@ -2091,7 +2154,7 @@ void CSharpInstance::_call_notification(int p_notification) {
// Custom version of _call_multilevel, optimized for _notification
- uint32_t arg = p_notification;
+ int32_t arg = p_notification;
void *args[1] = { &arg };
StringName method_name = CACHED_STRING_NAME(_notification);
@@ -2114,25 +2177,28 @@ String CSharpInstance::to_string(bool *r_valid) {
MonoObject *mono_object = get_mono_object();
- if (mono_object == NULL) {
- if (r_valid)
+ if (mono_object == nullptr) {
+ if (r_valid) {
*r_valid = false;
+ }
return String();
}
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
MonoString *result = GDMonoUtils::object_to_string(mono_object, &exc);
if (exc) {
GDMonoUtils::set_pending_exception(exc);
- if (r_valid)
+ if (r_valid) {
*r_valid = false;
+ }
return String();
}
- if (result == NULL) {
- if (r_valid)
+ if (result == nullptr) {
+ if (r_valid) {
*r_valid = false;
+ }
return String();
}
@@ -2140,42 +2206,37 @@ String CSharpInstance::to_string(bool *r_valid) {
}
Ref<Script> CSharpInstance::get_script() const {
-
return script;
}
ScriptLanguage *CSharpInstance::get_language() {
-
return CSharpLanguage::get_singleton();
}
-CSharpInstance::CSharpInstance() :
- owner(NULL),
- base_ref(false),
- ref_dying(false),
- unsafe_referenced(false),
- predelete_notified(false),
- destructing_script_instance(false) {
+CSharpInstance::CSharpInstance(const Ref<CSharpScript> &p_script) :
+ script(p_script) {
}
CSharpInstance::~CSharpInstance() {
-
GD_MONO_SCOPE_THREAD_ATTACH;
destructing_script_instance = true;
- if (gchandle.is_valid()) {
+ // Must make sure event signals are not left dangling
+ disconnect_event_signals();
+
+ if (!gchandle.is_released()) {
if (!predelete_notified && !ref_dying) {
// This destructor is not called from the owners destructor.
// This could be being called from the owner's set_script_instance method,
// meaning this script is being replaced with another one. If this is the case,
- // we must call Dispose here, because Dispose calls owner->set_script_instance(NULL)
+ // we must call Dispose here, because Dispose calls owner->set_script_instance(nullptr)
// and that would mess up with the new script instance if called later.
- MonoObject *mono_object = gchandle->get_target();
+ MonoObject *mono_object = gchandle.get_target();
if (mono_object) {
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
GDMonoUtils::dispose(mono_object, &exc);
if (exc) {
@@ -2184,33 +2245,33 @@ CSharpInstance::~CSharpInstance() {
}
}
- gchandle->release(); // Make sure the gchandle is released
+ gchandle.release(); // Make sure the gchandle is released
}
// If not being called from the owner's destructor, and we still hold a reference to the owner
- if (base_ref && !ref_dying && owner && unsafe_referenced) {
+ if (base_ref_counted && !ref_dying && owner && unsafe_referenced) {
// The owner's script or script instance is being replaced (or removed)
// Transfer ownership to an "instance binding"
- Reference *ref_owner = static_cast<Reference *>(owner);
+ RefCounted *rc_owner = static_cast<RefCounted *>(owner);
// We will unreference the owner before referencing it again, so we need to keep it alive
- Ref<Reference> scope_keep_owner_alive(ref_owner);
+ Ref<RefCounted> scope_keep_owner_alive(rc_owner);
(void)scope_keep_owner_alive;
// Unreference the owner here, before the new "instance binding" references it.
// Otherwise, the unsafe reference debug checks will incorrectly detect a bug.
bool die = _unreference_owner_unsafe();
- CRASH_COND(die == true); // `owner_keep_alive` holds a reference, so it can't die
+ CRASH_COND(die); // `owner_keep_alive` holds a reference, so it can't die
void *data = owner->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
- CRASH_COND(data == NULL);
+ CRASH_COND(data == nullptr);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
if (!script_binding.inited) {
- SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->get_language_bind_mutex());
+ MutexLock lock(CSharpLanguage::get_singleton()->get_language_bind_mutex());
if (!script_binding.inited) { // Other thread may have set it up
// Already had a binding that needs to be setup
@@ -2221,12 +2282,12 @@ 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(ref_owner->reference_get_count() <= 1);
+ CRASH_COND(rc_owner->reference_get_count() <= 1);
#endif
}
if (script.is_valid() && owner) {
- SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+ MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
#ifdef DEBUG_ENABLED
// CSharpInstance must not be created unless it's going to be added to the list for sure
@@ -2241,14 +2302,12 @@ CSharpInstance::~CSharpInstance() {
#ifdef TOOLS_ENABLED
void CSharpScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) {
-
placeholders.erase(p_placeholder);
}
#endif
#ifdef TOOLS_ENABLED
void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames) {
-
if (base_cache.is_valid()) {
base_cache->_update_exports_values(values, propnames);
}
@@ -2263,7 +2322,6 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List
}
void CSharpScript::_update_member_info_no_exports() {
-
if (exports_invalidated) {
GD_MONO_ASSERT_THREAD_ATTACHED;
@@ -2311,61 +2369,72 @@ void CSharpScript::_update_member_info_no_exports() {
}
#endif
-bool CSharpScript::_update_exports() {
-
+bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_update) {
#ifdef TOOLS_ENABLED
- if (!Engine::get_singleton()->is_editor_hint())
- return false;
-
- placeholder_fallback_enabled = true; // until proven otherwise
-
- if (!valid)
+ bool is_editor = Engine::get_singleton()->is_editor_hint();
+ if (is_editor) {
+ placeholder_fallback_enabled = true; // until proven otherwise
+ }
+#endif
+ if (!valid) {
return false;
+ }
bool changed = false;
- if (exports_invalidated) {
+#ifdef TOOLS_ENABLED
+ if (exports_invalidated)
+#endif
+ {
GD_MONO_SCOPE_THREAD_ATTACH;
- exports_invalidated = false;
-
changed = true;
member_info.clear();
- exported_members_cache.clear();
- exported_members_defval_cache.clear();
- // Here we create a temporary managed instance of the class to get the initial values
+#ifdef TOOLS_ENABLED
+ MonoObject *tmp_object = nullptr;
+ Object *tmp_native = nullptr;
+ uint32_t tmp_pinned_gchandle = 0;
- MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
+ if (is_editor) {
+ exports_invalidated = false;
- if (!tmp_object) {
- ERR_PRINT("Failed to allocate temporary MonoObject.");
- return false;
- }
+ exported_members_cache.clear();
+ exported_members_defval_cache.clear();
- uint32_t tmp_pinned_gchandle = MonoGCHandle::new_strong_handle_pinned(tmp_object); // pin it (not sure if needed)
+ // Here we create a temporary managed instance of the class to get the initial values
+ tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
- GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
+ if (!tmp_object) {
+ ERR_PRINT("Failed to allocate temporary MonoObject.");
+ return false;
+ }
- ERR_FAIL_NULL_V_MSG(ctor, NULL,
- "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'.");
+ tmp_pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(tmp_object); // pin it (not sure if needed)
- MonoException *ctor_exc = NULL;
- ctor->invoke(tmp_object, NULL, &ctor_exc);
+ GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0);
- Object *tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object));
+ ERR_FAIL_NULL_V_MSG(ctor, false,
+ "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'.");
- if (ctor_exc) {
- // TODO: Should we free 'tmp_native' if the exception was thrown after its creation?
+ MonoException *ctor_exc = nullptr;
+ ctor->invoke(tmp_object, nullptr, &ctor_exc);
- MonoGCHandle::free_handle(tmp_pinned_gchandle);
- tmp_object = NULL;
+ tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object));
- ERR_PRINT("Exception thrown from constructor of temporary MonoObject:");
- GDMonoUtils::debug_print_unhandled_exception(ctor_exc);
- return false;
+ if (ctor_exc) {
+ // TODO: Should we free 'tmp_native' if the exception was thrown after its creation?
+
+ GDMonoUtils::free_gchandle(tmp_pinned_gchandle);
+ tmp_object = nullptr;
+
+ ERR_PRINT("Exception thrown from constructor of temporary MonoObject:");
+ GDMonoUtils::debug_print_unhandled_exception(ctor_exc);
+ return false;
+ }
}
+#endif
GDMonoClass *top = script_class;
@@ -2381,15 +2450,22 @@ bool CSharpScript::_update_exports() {
if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) {
StringName member_name = field->get_name();
+ member_info[member_name] = prop_info;
+
if (exported) {
- member_info[member_name] = prop_info;
- exported_members_cache.push_front(prop_info);
+#ifdef TOOLS_ENABLED
+ if (is_editor) {
+ exported_members_cache.push_front(prop_info);
- if (tmp_object) {
- exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
+ if (tmp_object) {
+ exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
+ }
}
- } else {
- member_info[member_name] = prop_info;
+#endif
+
+#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED)
+ exported_members_names.insert(member_name);
+#endif
}
}
}
@@ -2402,22 +2478,28 @@ bool CSharpScript::_update_exports() {
if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) {
StringName member_name = property->get_name();
+ member_info[member_name] = prop_info;
+
if (exported) {
- member_info[member_name] = prop_info;
- exported_members_cache.push_front(prop_info);
-
- if (tmp_object) {
- MonoException *exc = NULL;
- MonoObject *ret = property->get_value(tmp_object, &exc);
- if (exc) {
- exported_members_defval_cache[member_name] = Variant();
- GDMonoUtils::debug_print_unhandled_exception(exc);
- } else {
- exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret);
+#ifdef TOOLS_ENABLED
+ if (is_editor) {
+ exported_members_cache.push_front(prop_info);
+ if (tmp_object) {
+ MonoException *exc = nullptr;
+ MonoObject *ret = property->get_value(tmp_object, &exc);
+ if (exc) {
+ exported_members_defval_cache[member_name] = Variant();
+ GDMonoUtils::debug_print_unhandled_exception(exc);
+ } else {
+ exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret);
+ }
}
}
- } else {
- member_info[member_name] = prop_info;
+#endif
+
+#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED)
+ exported_members_names.insert(member_name);
+#endif
}
}
}
@@ -2425,52 +2507,61 @@ bool CSharpScript::_update_exports() {
top = top->get_parent_class();
}
- // Need to check this here, before disposal
- bool base_ref = Object::cast_to<Reference>(tmp_native) != NULL;
+#ifdef TOOLS_ENABLED
+ if (is_editor) {
+ // Need to check this here, before disposal
+ bool base_ref_counted = Object::cast_to<RefCounted>(tmp_native) != nullptr;
- // Dispose the temporary managed instance
+ // Dispose the temporary managed instance
- MonoException *exc = NULL;
- GDMonoUtils::dispose(tmp_object, &exc);
+ MonoException *exc = nullptr;
+ GDMonoUtils::dispose(tmp_object, &exc);
- if (exc) {
- ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:");
- GDMonoUtils::debug_print_unhandled_exception(exc);
- }
+ if (exc) {
+ ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:");
+ GDMonoUtils::debug_print_unhandled_exception(exc);
+ }
- MonoGCHandle::free_handle(tmp_pinned_gchandle);
- tmp_object = NULL;
+ GDMonoUtils::free_gchandle(tmp_pinned_gchandle);
+ tmp_object = nullptr;
- if (tmp_native && !base_ref) {
- Node *node = Object::cast_to<Node>(tmp_native);
- if (node && node->is_inside_tree()) {
- ERR_PRINTS("Temporary instance was added to the scene tree.");
- } else {
- memdelete(tmp_native);
+ if (tmp_native && !base_ref_counted) {
+ Node *node = Object::cast_to<Node>(tmp_native);
+ if (node && node->is_inside_tree()) {
+ ERR_PRINT("Temporary instance was added to the scene tree.");
+ } else {
+ memdelete(tmp_native);
+ }
}
}
+#endif
}
- placeholder_fallback_enabled = false;
-
- if (placeholders.size()) {
- // Update placeholders if any
- Map<StringName, Variant> values;
- List<PropertyInfo> propnames;
- _update_exports_values(values, propnames);
-
- for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) {
- E->get()->update(propnames, values);
+#ifdef TOOLS_ENABLED
+ if (is_editor) {
+ placeholder_fallback_enabled = false;
+
+ if ((changed || p_instance_to_update) && placeholders.size()) {
+ // Update placeholders if any
+ Map<StringName, Variant> values;
+ List<PropertyInfo> propnames;
+ _update_exports_values(values, propnames);
+
+ if (changed) {
+ for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) {
+ E->get()->update(propnames, values);
+ }
+ } else {
+ p_instance_to_update->update(propnames, values);
+ }
}
}
+#endif
return changed;
-#endif
- return false;
}
void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class) {
-
// no need to load the script's signals more than once
if (!signals_invalidated) {
return;
@@ -2478,6 +2569,7 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati
// make sure this classes signals are empty when loading for the first time
_signals.clear();
+ event_signals.clear();
GD_MONO_SCOPE_THREAD_ATTACH;
@@ -2485,65 +2577,100 @@ void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_nati
while (top && top != p_native_class) {
const Vector<GDMonoClass *> &delegates = top->get_all_delegates();
for (int i = delegates.size() - 1; i >= 0; --i) {
- Vector<Argument> parameters;
-
GDMonoClass *delegate = delegates[i];
- if (_get_signal(top, delegate, parameters)) {
+ if (!delegate->has_attribute(CACHED_CLASS(SignalAttribute))) {
+ continue;
+ }
+
+ // Arguments are accessibles as arguments of .Invoke method
+ GDMonoMethod *invoke_method = delegate->get_method(mono_get_delegate_invoke(delegate->get_mono_ptr()));
+
+ Vector<SignalParameter> parameters;
+ if (_get_signal(top, invoke_method, parameters)) {
_signals[delegate->get_name()] = parameters;
}
}
+ List<StringName> found_event_signals;
+
+ void *iter = nullptr;
+ MonoEvent *raw_event = nullptr;
+ while ((raw_event = mono_class_get_events(top->get_mono_ptr(), &iter)) != nullptr) {
+ MonoCustomAttrInfo *event_attrs = mono_custom_attrs_from_event(top->get_mono_ptr(), raw_event);
+ if (event_attrs) {
+ if (mono_custom_attrs_has_attr(event_attrs, CACHED_CLASS(SignalAttribute)->get_mono_ptr())) {
+ String event_name = String::utf8(mono_event_get_name(raw_event));
+ found_event_signals.push_back(StringName(event_name));
+ }
+
+ mono_custom_attrs_free(event_attrs);
+ }
+ }
+
+ const Vector<GDMonoField *> &fields = top->get_all_fields();
+ for (int i = 0; i < fields.size(); i++) {
+ GDMonoField *field = fields[i];
+
+ GDMonoClass *field_class = field->get_type().type_class;
+
+ if (!mono_class_is_delegate(field_class->get_mono_ptr())) {
+ continue;
+ }
+
+ if (!found_event_signals.find(field->get_name())) {
+ continue;
+ }
+
+ GDMonoMethod *invoke_method = field_class->get_method(mono_get_delegate_invoke(field_class->get_mono_ptr()));
+
+ Vector<SignalParameter> parameters;
+ if (_get_signal(top, invoke_method, parameters)) {
+ event_signals[field->get_name()] = { field, invoke_method, parameters };
+ }
+ }
+
top = top->get_parent_class();
}
signals_invalidated = false;
}
-bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> &params) {
+bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> &params) {
GD_MONO_ASSERT_THREAD_ATTACHED;
- if (p_delegate->has_attribute(CACHED_CLASS(SignalAttribute))) {
- MonoType *raw_type = p_delegate->get_mono_type();
+ Vector<StringName> names;
+ Vector<ManagedType> types;
+ p_delegate_invoke->get_parameter_names(names);
+ p_delegate_invoke->get_parameter_types(types);
- if (mono_type_get_type(raw_type) == MONO_TYPE_CLASS) {
- // Arguments are accessibles as arguments of .Invoke method
- GDMonoMethod *invoke = p_delegate->get_method("Invoke", -1);
-
- Vector<StringName> names;
- Vector<ManagedType> types;
- invoke->get_parameter_names(names);
- invoke->get_parameter_types(types);
-
- if (names.size() == types.size()) {
- for (int i = 0; i < names.size(); ++i) {
- Argument arg;
- arg.name = names[i];
- arg.type = GDMonoMarshal::managed_to_variant_type(types[i]);
-
- if (arg.type == Variant::NIL) {
- ERR_PRINTS("Unknown type of signal parameter: '" + arg.name + "' in '" + p_class->get_full_name() + "'.");
- return false;
- }
+ for (int i = 0; i < names.size(); ++i) {
+ SignalParameter arg;
+ arg.name = names[i];
- params.push_back(arg);
- }
+ bool nil_is_variant = false;
+ arg.type = GDMonoMarshal::managed_to_variant_type(types[i], &nil_is_variant);
- return true;
+ if (arg.type == Variant::NIL) {
+ if (nil_is_variant) {
+ arg.nil_is_variant = true;
+ } else {
+ ERR_PRINT("Unknown type of signal parameter: '" + arg.name + "' in '" + p_class->get_full_name() + "'.");
+ return false;
}
}
+
+ params.push_back(arg);
}
- return false;
+ return true;
}
-#ifdef TOOLS_ENABLED
/**
* Returns false if there was an error, otherwise true.
* If there was an error, r_prop_info and r_exported are not assigned any value.
*/
bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported) {
-
GD_MONO_ASSERT_THREAD_ATTACHED;
// Goddammit, C++. All I wanted was some nested functions.
@@ -2551,13 +2678,17 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
(m_member->get_enclosing_class()->get_full_name() + "." + (String)m_member->get_name())
if (p_member->is_static()) {
- if (p_member->has_attribute(CACHED_CLASS(ExportAttribute)))
- ERR_PRINTS("Cannot export member because it is static: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+#ifdef TOOLS_ENABLED
+ if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) {
+ ERR_PRINT("Cannot export member because it is static: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+ }
+#endif
return false;
}
- if (member_info.has(p_member->get_name()))
+ if (member_info.has(p_member->get_name())) {
return false;
+ }
ManagedType type;
@@ -2574,18 +2705,25 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) {
GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member);
if (!property->has_getter()) {
- if (exported)
- ERR_PRINTS("Read-only property cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+#ifdef TOOLS_ENABLED
+ if (exported) {
+ ERR_PRINT("Cannot export a property without a getter: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+ }
+#endif
return false;
}
if (!property->has_setter()) {
- if (exported)
- ERR_PRINTS("Write-only property (without getter) cannot be exported: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+#ifdef TOOLS_ENABLED
+ if (exported) {
+ ERR_PRINT("Cannot export a property without a setter: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+ }
+#endif
return false;
}
}
- Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type);
+ bool nil_is_variant = false;
+ Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type, &nil_is_variant);
if (!p_inspect_export || !exported) {
r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
@@ -2593,16 +2731,21 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
return true;
}
+#ifdef TOOLS_ENABLED
MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute));
+#endif
PropertyHint hint = PROPERTY_HINT_NONE;
String hint_string;
- if (variant_type == Variant::NIL) {
- ERR_PRINTS("Unknown exported member type: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+ if (variant_type == Variant::NIL && !nil_is_variant) {
+#ifdef TOOLS_ENABLED
+ ERR_PRINT("Unknown exported member type: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'.");
+#endif
return false;
}
+#ifdef TOOLS_ENABLED
int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string);
ERR_FAIL_COND_V_MSG(hint_res == -1, false,
@@ -2613,8 +2756,16 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr));
hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr);
}
+#endif
+
+ uint32_t prop_usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE;
+
+ if (variant_type == Variant::NIL) {
+ // System.Object (Variant)
+ prop_usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
- r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), hint, hint_string, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE);
+ r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), hint, hint_string, prop_usage);
r_exported = true;
return true;
@@ -2622,7 +2773,12 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect
#undef MEMBER_FULL_QUALIFIED_NAME
}
+#ifdef TOOLS_ENABLED
int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) {
+ if (p_variant_type == Variant::NIL) {
+ // System.Object (Variant)
+ return 1;
+ }
GD_MONO_ASSERT_THREAD_ATTACHED;
@@ -2648,7 +2804,7 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage
name_only_hint_string += ",";
}
- String enum_field_name = mono_field_get_name(field);
+ String enum_field_name = String::utf8(mono_field_get_name(field));
r_hint_string += enum_field_name;
name_only_hint_string += enum_field_name;
@@ -2656,7 +2812,7 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage
// Instead of using mono_field_get_value_object, we can do this without boxing. Check the
// internal mono functions: ves_icall_System_Enum_GetEnumValuesAndNames and the get_enum_field.
- MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, NULL);
+ MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, nullptr);
ERR_FAIL_NULL_V_MSG(val_obj, -1, "Failed to get '" + enum_field_name + "' constant enum value.");
@@ -2680,17 +2836,18 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage
}
} else if (p_variant_type == Variant::OBJECT && CACHED_CLASS(GodotResource)->is_assignable_from(p_type.type_class)) {
GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(p_type.type_class);
- CRASH_COND(field_native_class == NULL);
+ CRASH_COND(field_native_class == nullptr);
r_hint = PROPERTY_HINT_RESOURCE_TYPE;
- r_hint_string = NATIVE_GDMONOCLASS_NAME(field_native_class);
+ r_hint_string = String(NATIVE_GDMONOCLASS_NAME(field_native_class));
} else if (p_allow_generics && p_variant_type == Variant::ARRAY) {
// Nested arrays are not supported in the inspector
ManagedType elem_type;
- if (!GDMonoMarshal::try_get_array_element_type(p_type, elem_type))
+ if (!GDMonoMarshal::try_get_array_element_type(p_type, elem_type)) {
return 0;
+ }
Variant::Type elem_variant_type = GDMonoMarshal::managed_to_variant_type(elem_type);
@@ -2717,21 +2874,10 @@ int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, Manage
}
#endif
-void CSharpScript::_clear() {
-
- tool = false;
- valid = false;
-
- base = NULL;
- native = NULL;
- script_class = NULL;
-}
-
-Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
-
- if (unlikely(GDMono::get_singleton() == NULL)) {
+Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
+ if (unlikely(GDMono::get_singleton() == nullptr)) {
// Probably not the best error but eh.
- r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL;
+ r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
return Variant();
}
@@ -2743,7 +2889,7 @@ Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, i
GDMonoMethod *method = top->get_method(p_method, p_argcount);
if (method && method->is_static()) {
- MonoObject *result = method->invoke(NULL, p_args);
+ MonoObject *result = method->invoke(nullptr, p_args);
if (result) {
return GDMonoMarshal::mono_object_to_variant(result);
@@ -2760,18 +2906,11 @@ Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, i
}
void CSharpScript::_resource_path_changed() {
-
- String path = get_path();
-
- if (!path.empty()) {
- name = get_path().get_file().get_basename();
- }
+ _update_name();
}
bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const {
-
if (p_name == CSharpLanguage::singleton->string_names._script_source) {
-
r_ret = get_source_code();
return true;
}
@@ -2780,9 +2919,7 @@ bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const {
}
bool CSharpScript::_set(const StringName &p_name, const Variant &p_value) {
-
if (p_name == CSharpLanguage::singleton->string_names._script_source) {
-
set_source_code(p_value);
reload();
return true;
@@ -2792,20 +2929,17 @@ bool CSharpScript::_set(const StringName &p_name, const Variant &p_value) {
}
void CSharpScript::_get_property_list(List<PropertyInfo> *p_properties) const {
-
p_properties->push_back(PropertyInfo(Variant::STRING, CSharpLanguage::singleton->string_names._script_source, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL));
}
void CSharpScript::_bind_methods() {
-
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo("new"));
}
Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) {
-
// This method should not fail, only assertions allowed
- CRASH_COND(p_class == NULL);
+ CRASH_COND(p_class == nullptr);
// TODO OPTIMIZE: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time
Ref<CSharpScript> script = memnew(CSharpScript);
@@ -2816,23 +2950,34 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
}
void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native) {
-
// This method should not fail, only assertions allowed
- CRASH_COND(p_class == NULL);
+ CRASH_COND(p_class == nullptr);
p_script->name = p_class->get_name();
p_script->script_class = p_class;
p_script->native = p_native;
- CRASH_COND(p_script->native == NULL);
+ CRASH_COND(p_script->native == nullptr);
+
+ p_script->valid = true;
+
+ update_script_class_info(p_script);
+
+#ifdef TOOLS_ENABLED
+ p_script->_update_member_info_no_exports();
+#endif
+}
+// Extract information about the script using the mono class.
+void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
GDMonoClass *base = p_script->script_class->get_parent_class();
- if (base != p_script->native)
+ // `base` should only be set if the script is a user defined type.
+ if (base != p_script->native) {
p_script->base = base;
+ }
- p_script->valid = true;
p_script->tool = p_script->script_class->has_attribute(CACHED_CLASS(ToolAttribute));
if (!p_script->tool) {
@@ -2840,7 +2985,7 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon
p_script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
}
-#if TOOLS_ENABLED
+#ifdef TOOLS_ENABLED
if (!p_script->tool) {
p_script->tool = p_script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly();
}
@@ -2856,8 +3001,9 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon
while (native_top) {
native_top->fetch_methods_with_godot_api_checks(p_script->native);
- if (native_top == CACHED_CLASS(GodotObject))
+ if (native_top == CACHED_CLASS(GodotObject)) {
break;
+ }
native_top = native_top->get_parent_class();
}
@@ -2866,37 +3012,44 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMon
p_script->script_class->fetch_methods_with_godot_api_checks(p_script->native);
- // Need to fetch method from base classes as well
+ p_script->rpc_functions.clear();
+
GDMonoClass *top = p_script->script_class;
while (top && top != p_script->native) {
+ // Fetch methods from base classes as well
top->fetch_methods_with_godot_api_checks(p_script->native);
+
+ // Update RPC info
+ {
+ Vector<GDMonoMethod *> methods = top->get_all_methods();
+ for (int i = 0; i < methods.size(); i++) {
+ if (!methods[i]->is_static()) {
+ MultiplayerAPI::RPCMode mode = p_script->_member_get_rpc_mode(methods[i]);
+ if (MultiplayerAPI::RPC_MODE_DISABLED != mode) {
+ MultiplayerAPI::RPCConfig nd;
+ nd.name = methods[i]->get_name();
+ nd.rpc_mode = mode;
+ // TODO Transfer mode, channel
+ nd.transfer_mode = NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE;
+ nd.channel = 0;
+ if (-1 == p_script->rpc_functions.find(nd)) {
+ p_script->rpc_functions.push_back(nd);
+ }
+ }
+ }
+ }
+ }
+
top = top->get_parent_class();
}
+ // Sort so we are 100% that they are always the same.
+ p_script->rpc_functions.sort_custom<MultiplayerAPI::SortRPCConfig>();
+
p_script->load_script_signals(p_script->script_class, p_script->native);
-#ifdef TOOLS_ENABLED
- p_script->_update_member_info_no_exports();
-#endif
}
-bool CSharpScript::can_instance() const {
-
-#ifdef TOOLS_ENABLED
- if (Engine::get_singleton()->is_editor_hint()) {
-
- // Hack to lower the risk of attached scripts not being added to the C# project
- if (!get_path().empty() && get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted...
- if (_create_project_solution_if_needed()) {
- CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(),
- "Compile",
- ProjectSettings::get_singleton()->globalize_path(get_path()));
- } else {
- ERR_PRINTS("C# project could not be created; cannot add file: '" + get_path() + "'.");
- }
- }
- }
-#endif
-
+bool CSharpScript::can_instantiate() const {
#ifdef TOOLS_ENABLED
bool extra_cond = tool || ScriptServer::is_scripting_enabled();
#else
@@ -2907,12 +3060,12 @@ bool CSharpScript::can_instance() const {
// For tool scripts, this will never fire if the class is not found. That's because we
// don't know if it's a tool script if we can't find the class to access the attributes.
if (extra_cond && !script_class) {
- if (GDMono::get_singleton()->get_project_assembly() == NULL) {
+ if (GDMono::get_singleton()->get_project_assembly() == nullptr) {
// The project assembly is not loaded
- ERR_FAIL_V_MSG(NULL, "Cannot instance script because the project assembly is not loaded. Script: '" + get_path() + "'.");
+ ERR_FAIL_V_MSG(false, "Cannot instance script because the project assembly is not loaded. Script: '" + get_path() + "'.");
} else {
// The project assembly is loaded, but the class could not found
- ERR_FAIL_V_MSG(NULL, "Cannot instance script because the class '" + name + "' could not be found. Script: '" + get_path() + "'.");
+ ERR_FAIL_V_MSG(false, "Cannot instance script because the class '" + name + "' could not be found. Script: '" + get_path() + "'.");
}
}
@@ -2920,46 +3073,45 @@ bool CSharpScript::can_instance() const {
}
StringName CSharpScript::get_instance_base_type() const {
-
- if (native)
+ if (native) {
return native->get_name();
- else
+ } else {
return StringName();
+ }
}
-CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error) {
-
+CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) {
GD_MONO_ASSERT_THREAD_ATTACHED;
/* STEP 1, CREATE */
// Search the constructor first, to fail with an error if it's not found before allocating anything else.
GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount);
- if (ctor == NULL) {
- ERR_FAIL_COND_V_MSG(p_argcount == 0, NULL,
+ if (ctor == nullptr) {
+ ERR_FAIL_COND_V_MSG(p_argcount == 0, nullptr,
"Cannot create script instance. The class '" + script_class->get_full_name() +
"' does not define a parameterless constructor." +
- (get_path().empty() ? String() : " Path: '" + get_path() + "'."));
+ (get_path().is_empty() ? String() : " Path: '" + get_path() + "'."));
- ERR_FAIL_V_MSG(NULL, "Constructor not found.");
+ ERR_FAIL_V_MSG(nullptr, "Constructor not found.");
}
- Ref<Reference> ref;
- if (p_isref) {
+ Ref<RefCounted> ref;
+ if (p_is_ref_counted) {
// Hold it alive. Important if we have to dispose a script instance binding before creating the CSharpInstance.
- ref = Ref<Reference>(static_cast<Reference *>(p_owner));
+ ref = Ref<RefCounted>(static_cast<RefCounted *>(p_owner));
}
// If the object had a script instance binding, dispose it before adding the CSharpInstance
if (p_owner->has_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index())) {
void *data = p_owner->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
- CRASH_COND(data == NULL);
+ CRASH_COND(data == nullptr);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
- if (script_binding.inited && script_binding.gchandle.is_valid()) {
- MonoObject *mono_object = script_binding.gchandle->get_target();
+ if (script_binding.inited && !script_binding.gchandle.is_released()) {
+ MonoObject *mono_object = script_binding.gchandle.get_target();
if (mono_object) {
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
GDMonoUtils::dispose(mono_object, &exc);
if (exc) {
@@ -2967,13 +3119,13 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
}
}
+ script_binding.gchandle.release(); // Just in case
script_binding.inited = false;
}
}
- CSharpInstance *instance = memnew(CSharpInstance);
- instance->base_ref = p_isref;
- instance->script = Ref<CSharpScript>(this);
+ CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(this)));
+ instance->base_ref_counted = p_is_ref_counted;
instance->owner = p_owner;
instance->owner->set_script_instance(instance);
@@ -2984,25 +3136,26 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
if (!mono_object) {
// Important to clear this before destroying the script instance here
instance->script = Ref<CSharpScript>();
- instance->owner = NULL;
+ instance->owner = nullptr;
bool die = instance->_unreference_owner_unsafe();
// Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug.
- CRASH_COND(die == true);
+ CRASH_COND(die);
- p_owner->set_script_instance(NULL);
- r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL;
- ERR_FAIL_V_MSG(NULL, "Failed to allocate memory for the object.");
+ p_owner->set_script_instance(nullptr);
+ r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
+ ERR_FAIL_V_MSG(nullptr, "Failed to allocate memory for the object.");
}
// Tie managed to unmanaged
- instance->gchandle = MonoGCHandle::create_strong(mono_object);
+ instance->gchandle = MonoGCHandleData::new_strong_handle(mono_object);
- if (instance->base_ref)
+ if (instance->base_ref_counted) {
instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback)
+ }
{
- SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+ MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
instances.insert(instance->owner);
}
@@ -3017,28 +3170,27 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
return instance;
}
-Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
-
+Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
if (!valid) {
- r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD;
+ r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
return Variant();
}
- r_error.error = Variant::CallError::CALL_OK;
+ r_error.error = Callable::CallError::CALL_OK;
ERR_FAIL_NULL_V(native, Variant());
GD_MONO_SCOPE_THREAD_ATTACH;
- Object *owner = ClassDB::instance(NATIVE_GDMONOCLASS_NAME(native));
+ Object *owner = ClassDB::instantiate(NATIVE_GDMONOCLASS_NAME(native));
REF ref;
- Reference *r = Object::cast_to<Reference>(owner);
+ RefCounted *r = Object::cast_to<RefCounted>(owner);
if (r) {
ref = REF(r);
}
- CSharpInstance *instance = _create_instance(p_args, p_argcount, owner, r != NULL, r_error);
+ CSharpInstance *instance = _create_instance(p_args, p_argcount, owner, r != nullptr, r_error);
if (!instance) {
if (ref.is_null()) {
memdelete(owner); //no owner, sorry
@@ -3054,60 +3206,57 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::Call
}
ScriptInstance *CSharpScript::instance_create(Object *p_this) {
-
#ifdef DEBUG_ENABLED
CRASH_COND(!valid);
#endif
if (native) {
- String native_name = NATIVE_GDMONOCLASS_NAME(native);
+ StringName native_name = NATIVE_GDMONOCLASS_NAME(native);
if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) {
- if (ScriptDebugger::get_singleton()) {
- CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'");
+ if (EngineDebugger::is_active()) {
+ CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0,
+ "Script inherits from native type '" + String(native_name) +
+ "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'");
}
- ERR_FAIL_V_MSG(NULL, "Script inherits from native type '" + native_name +
- "', so it can't be instanced in object of type: '" + p_this->get_class() + "'.");
+ ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) +
+ "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'.");
}
}
GD_MONO_SCOPE_THREAD_ATTACH;
- Variant::CallError unchecked_error;
- return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this) != NULL, unchecked_error);
+ Callable::CallError unchecked_error;
+ return _create_instance(nullptr, 0, p_this, Object::cast_to<RefCounted>(p_this) != nullptr, unchecked_error);
}
PlaceHolderScriptInstance *CSharpScript::placeholder_instance_create(Object *p_this) {
-
#ifdef TOOLS_ENABLED
PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(CSharpLanguage::get_singleton(), Ref<Script>(this), p_this));
placeholders.insert(si);
- _update_exports();
+ _update_exports(si);
return si;
#else
- return NULL;
+ return nullptr;
#endif
}
bool CSharpScript::instance_has(const Object *p_this) const {
-
- SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+ MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
return instances.has((Object *)p_this);
}
bool CSharpScript::has_source_code() const {
-
- return !source.empty();
+ return !source.is_empty();
}
String CSharpScript::get_source_code() const {
-
return source;
}
void CSharpScript::set_source_code(const String &p_code) {
-
- if (source == p_code)
+ if (source == p_code) {
return;
+ }
source = p_code;
#ifdef TOOLS_ENABLED
source_changed_cache = true;
@@ -3115,9 +3264,9 @@ void CSharpScript::set_source_code(const String &p_code) {
}
void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const {
-
- if (!script_class)
+ if (!script_class) {
return;
+ }
GD_MONO_SCOPE_THREAD_ATTACH;
@@ -3129,9 +3278,9 @@ void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const {
}
bool CSharpScript::has_method(const StringName &p_method) const {
-
- if (!script_class)
+ if (!script_class) {
return false;
+ }
GD_MONO_SCOPE_THREAD_ATTACH;
@@ -3139,9 +3288,9 @@ bool CSharpScript::has_method(const StringName &p_method) const {
}
MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
-
- if (!script_class)
+ if (!script_class) {
return MethodInfo();
+ }
GD_MONO_SCOPE_THREAD_ATTACH;
@@ -3160,10 +3309,9 @@ MethodInfo CSharpScript::get_method_info(const StringName &p_method) const {
}
Error CSharpScript::reload(bool p_keep_state) {
-
bool has_instances;
{
- SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+ MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
has_instances = instances.size();
}
@@ -3171,100 +3319,41 @@ Error CSharpScript::reload(bool p_keep_state) {
GD_MONO_SCOPE_THREAD_ATTACH;
- GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly();
-
- if (project_assembly) {
- const Variant *script_metadata_var = CSharpLanguage::get_singleton()->get_scripts_metadata().getptr(get_path());
- if (script_metadata_var) {
- Dictionary script_metadata = script_metadata_var->operator Dictionary()["class"];
- const Variant *namespace_ = script_metadata.getptr("namespace");
- const Variant *class_name = script_metadata.getptr("class_name");
- ERR_FAIL_NULL_V(namespace_, ERR_BUG);
- ERR_FAIL_NULL_V(class_name, ERR_BUG);
- GDMonoClass *klass = project_assembly->get_class(namespace_->operator String(), class_name->operator String());
- if (klass) {
- bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(klass);
- ERR_FAIL_COND_V(!obj_type, ERR_BUG);
- script_class = klass;
- }
- } else {
- // Missing script metadata. Fallback to legacy method
- script_class = project_assembly->get_object_derived_class(name);
- }
-
- valid = script_class != NULL;
-
- if (script_class) {
-#ifdef DEBUG_ENABLED
- print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
-#endif
-
- tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute));
-
- if (!tool) {
- GDMonoClass *nesting_class = script_class->get_nesting_class();
- tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
- }
-
-#if TOOLS_ENABLED
- if (!tool) {
- tool = script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly();
- }
-#endif
-
- native = GDMonoUtils::get_class_native_base(script_class);
+ const DotNetScriptLookupInfo *lookup_info =
+ CSharpLanguage::get_singleton()->lookup_dotnet_script(get_path());
- CRASH_COND(native == NULL);
-
- GDMonoClass *base_class = script_class->get_parent_class();
+ if (lookup_info) {
+ GDMonoClass *klass = lookup_info->script_class;
+ if (klass) {
+ ERR_FAIL_COND_V(!CACHED_CLASS(GodotObject)->is_assignable_from(klass), FAILED);
+ script_class = klass;
+ }
+ }
- if (base_class != native)
- base = base_class;
+ valid = script_class != nullptr;
+ if (script_class) {
#ifdef DEBUG_ENABLED
- // For debug builds, we must fetch from all native base methods as well.
- // Native base methods must be fetched before the current class.
- // Not needed if the script class itself is a native class.
-
- if (script_class != native) {
- GDMonoClass *native_top = native;
- while (native_top) {
- native_top->fetch_methods_with_godot_api_checks(native);
-
- if (native_top == CACHED_CLASS(GodotObject))
- break;
-
- native_top = native_top->get_parent_class();
- }
- }
+ print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
#endif
- script_class->fetch_methods_with_godot_api_checks(native);
+ native = GDMonoUtils::get_class_native_base(script_class);
- // Need to fetch method from base classes as well
- GDMonoClass *top = script_class;
- while (top && top != native) {
- top->fetch_methods_with_godot_api_checks(native);
- top = top->get_parent_class();
- }
+ CRASH_COND(native == nullptr);
- load_script_signals(script_class, native);
- _update_exports();
- }
+ update_script_class_info(this);
- return OK;
+ _update_exports();
}
- return ERR_FILE_MISSING_DEPENDENCIES;
+ return OK;
}
ScriptLanguage *CSharpScript::get_language() const {
-
return CSharpLanguage::get_singleton();
}
bool CSharpScript::get_property_default_value(const StringName &p_property, Variant &r_value) const {
-
#ifdef TOOLS_ENABLED
const Map<StringName, Variant>::Element *E = exported_members_defval_cache.find(p_property);
@@ -3282,58 +3371,124 @@ bool CSharpScript::get_property_default_value(const StringName &p_property, Vari
}
void CSharpScript::update_exports() {
-
#ifdef TOOLS_ENABLED
_update_exports();
#endif
}
bool CSharpScript::has_script_signal(const StringName &p_signal) const {
- return _signals.has(p_signal);
+ return event_signals.has(p_signal) || _signals.has(p_signal);
}
void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
- for (const Map<StringName, Vector<Argument> >::Element *E = _signals.front(); E; E = E->next()) {
+ for (const Map<StringName, Vector<SignalParameter>>::Element *E = _signals.front(); E; E = E->next()) {
MethodInfo mi;
+ mi.name = E->key();
+
+ const Vector<SignalParameter> &params = E->value();
+ for (int i = 0; i < params.size(); i++) {
+ const SignalParameter &param = params[i];
+
+ PropertyInfo arg_info = PropertyInfo(param.type, param.name);
+ if (param.type == Variant::NIL && param.nil_is_variant) {
+ arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
+
+ mi.arguments.push_back(arg_info);
+ }
+
+ r_signals->push_back(mi);
+ }
+ for (const Map<StringName, EventSignal>::Element *E = event_signals.front(); E; E = E->next()) {
+ MethodInfo mi;
mi.name = E->key();
- for (int i = 0; i < E->get().size(); i++) {
- PropertyInfo arg;
- arg.name = E->get()[i].name;
- mi.arguments.push_back(arg);
+
+ const EventSignal &event_signal = E->value();
+ const Vector<SignalParameter> &params = event_signal.parameters;
+ for (int i = 0; i < params.size(); i++) {
+ const SignalParameter &param = params[i];
+
+ PropertyInfo arg_info = PropertyInfo(param.type, param.name);
+ if (param.type == Variant::NIL && param.nil_is_variant) {
+ arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
+
+ mi.arguments.push_back(arg_info);
}
+
r_signals->push_back(mi);
}
}
-Ref<Script> CSharpScript::get_base_script() const {
+bool CSharpScript::inherits_script(const Ref<Script> &p_script) const {
+ Ref<CSharpScript> cs = p_script;
+ if (cs.is_null()) {
+ return false;
+ }
+ if (script_class == nullptr || cs->script_class == nullptr) {
+ return false;
+ }
+
+ if (script_class == cs->script_class) {
+ return true;
+ }
+
+ return cs->script_class->is_assignable_from(script_class);
+}
+
+Ref<Script> CSharpScript::get_base_script() const {
// TODO search in metadata file once we have it, not important any way?
return Ref<Script>();
}
void CSharpScript::get_script_property_list(List<PropertyInfo> *p_list) const {
-
for (Map<StringName, PropertyInfo>::Element *E = member_info.front(); E; E = E->next()) {
p_list->push_back(E->value());
}
}
int CSharpScript::get_member_line(const StringName &p_member) const {
-
// TODO omnisharp
return -1;
}
-Error CSharpScript::load_source_code(const String &p_path) {
+MultiplayerAPI::RPCMode CSharpScript::_member_get_rpc_mode(IMonoClassMember *p_member) const {
+ if (p_member->has_attribute(CACHED_CLASS(RemoteAttribute))) {
+ return MultiplayerAPI::RPC_MODE_REMOTE;
+ }
+ if (p_member->has_attribute(CACHED_CLASS(MasterAttribute))) {
+ return MultiplayerAPI::RPC_MODE_MASTER;
+ }
+ if (p_member->has_attribute(CACHED_CLASS(PuppetAttribute))) {
+ return MultiplayerAPI::RPC_MODE_PUPPET;
+ }
+ if (p_member->has_attribute(CACHED_CLASS(RemoteSyncAttribute))) {
+ return MultiplayerAPI::RPC_MODE_REMOTESYNC;
+ }
+ if (p_member->has_attribute(CACHED_CLASS(MasterSyncAttribute))) {
+ return MultiplayerAPI::RPC_MODE_MASTERSYNC;
+ }
+ if (p_member->has_attribute(CACHED_CLASS(PuppetSyncAttribute))) {
+ return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
+ }
+
+ return MultiplayerAPI::RPC_MODE_DISABLED;
+}
+const Vector<MultiplayerAPI::RPCConfig> CSharpScript::get_rpc_methods() const {
+ return rpc_functions;
+}
+
+Error CSharpScript::load_source_code(const String &p_path) {
Error ferr = read_all_file_utf8(p_path, source);
ERR_FAIL_COND_V_MSG(ferr != OK, ferr,
ferr == ERR_INVALID_DATA ?
- "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded."
+ "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded."
" Please ensure that scripts are saved in valid UTF-8 unicode." :
- "Failed to read file: '" + p_path + "'.");
+ "Failed to read file: '" + p_path + "'.");
#ifdef TOOLS_ENABLED
source_changed_cache = true;
@@ -3342,48 +3497,59 @@ Error CSharpScript::load_source_code(const String &p_path) {
return OK;
}
-StringName CSharpScript::get_script_name() const {
+void CSharpScript::_update_name() {
+ String path = get_path();
- return name;
+ if (!path.is_empty()) {
+ name = get_path().get_file().get_basename();
+ }
}
-CSharpScript::CSharpScript() :
- script_list(this) {
-
- _clear();
+void CSharpScript::_clear() {
+ tool = false;
+ valid = false;
-#ifdef TOOLS_ENABLED
- source_changed_cache = false;
- placeholder_fallback_enabled = false;
- exports_invalidated = true;
-#endif
+ base = nullptr;
+ native = nullptr;
+ script_class = nullptr;
+}
- signals_invalidated = true;
+CSharpScript::CSharpScript() {
+ _clear();
- _resource_path_changed();
+ _update_name();
#ifdef DEBUG_ENABLED
{
- SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+ MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
CSharpLanguage::get_singleton()->script_list.add(&this->script_list);
}
#endif
}
CSharpScript::~CSharpScript() {
-
#ifdef DEBUG_ENABLED
- SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->script_instances_mutex);
+ MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex);
CSharpLanguage::get_singleton()->script_list.remove(&this->script_list);
#endif
}
-/*************** RESOURCE ***************/
+void CSharpScript::get_members(Set<StringName> *p_members) {
+#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED)
+ if (p_members) {
+ for (Set<StringName>::Element *E = exported_members_names.front(); E; E = E->next()) {
+ p_members->insert(E->get());
+ }
+ }
+#endif
+}
-RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p_original_path, Error *r_error) {
+/*************** RESOURCE ***************/
- if (r_error)
+RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
+ if (r_error) {
*r_error = ERR_FILE_CANT_OPEN;
+ }
// TODO ignore anything inside bin/ and obj/ in tools builds?
@@ -3400,29 +3566,26 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
script->reload();
- if (r_error)
+ if (r_error) {
*r_error = OK;
+ }
return scriptres;
}
void ResourceFormatLoaderCSharpScript::get_recognized_extensions(List<String> *p_extensions) const {
-
p_extensions->push_back("cs");
}
bool ResourceFormatLoaderCSharpScript::handles_type(const String &p_type) const {
-
return p_type == "Script" || p_type == CSharpLanguage::get_singleton()->get_type();
}
String ResourceFormatLoaderCSharpScript::get_resource_type(const String &p_path) const {
-
return p_path.get_extension().to_lower() == "cs" ? CSharpLanguage::get_singleton()->get_type() : "";
}
Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_resource, uint32_t p_flags) {
-
Ref<CSharpScript> sqscr = p_resource;
ERR_FAIL_COND_V(sqscr.is_null(), ERR_INVALID_PARAMETER);
@@ -3430,14 +3593,10 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r
#ifdef TOOLS_ENABLED
if (!FileAccess::exists(p_path)) {
- // The file does not yet exists, let's assume the user just created this script
-
- if (_create_project_solution_if_needed()) {
- CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(),
- "Compile",
- ProjectSettings::get_singleton()->globalize_path(p_path));
- } else {
- ERR_PRINTS("C# project could not be created; cannot add file: '" + p_path + "'.");
+ // The file does not yet exist, let's assume the user just created this script. In such
+ // cases we need to check whether the solution and csproj were already created or not.
+ if (!_create_project_solution_if_needed()) {
+ ERR_PRINT("C# project could not be created; cannot add file: '" + p_path + "'.");
}
}
#endif
@@ -3466,19 +3625,16 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r
}
void ResourceFormatSaverCSharpScript::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const {
-
if (Object::cast_to<CSharpScript>(p_resource.ptr())) {
p_extensions->push_back("cs");
}
}
bool ResourceFormatSaverCSharpScript::recognize(const RES &p_resource) const {
-
- return Object::cast_to<CSharpScript>(p_resource.ptr()) != NULL;
+ return Object::cast_to<CSharpScript>(p_resource.ptr()) != nullptr;
}
CSharpLanguage::StringNameCache::StringNameCache() {
-
_signal_callback = StaticCString::create("_signal_callback");
_set = StaticCString::create("_set");
_get = StaticCString::create("_get");
@@ -3488,4 +3644,5 @@ CSharpLanguage::StringNameCache::StringNameCache() {
on_before_serialize = StaticCString::create("OnBeforeSerialize");
on_after_deserialize = StaticCString::create("OnAfterDeserialize");
dotctor = StaticCString::create(".ctor");
+ delegate_invoke_method_name = StaticCString::create("Invoke");
}
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index f244bc4119..da6b60aee2 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,10 +31,11 @@
#ifndef CSHARP_SCRIPT_H
#define CSHARP_SCRIPT_H
+#include "core/doc_data.h"
#include "core/io/resource_loader.h"
#include "core/io/resource_saver.h"
-#include "core/script_language.h"
-#include "core/self_list.h"
+#include "core/object/script_language.h"
+#include "core/templates/self_list.h"
#include "mono_gc_handle.h"
#include "mono_gd/gd_mono.h"
@@ -53,8 +54,8 @@ class CSharpLanguage;
template <typename TScriptInstance, typename TScriptLanguage>
TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
if (!p_inst)
- return NULL;
- return p_inst->get_language() == TScriptLanguage::get_singleton() ? static_cast<TScriptInstance *>(p_inst) : NULL;
+ return nullptr;
+ return p_inst->get_language() == TScriptLanguage::get_singleton() ? static_cast<TScriptInstance *>(p_inst) : nullptr;
}
#else
template <typename TScriptInstance, typename TScriptLanguage>
@@ -65,22 +66,47 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) {
#define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst))
-class CSharpScript : public Script {
+struct DotNetScriptLookupInfo {
+ String class_namespace;
+ String class_name;
+ GDMonoClass *script_class = nullptr;
+
+ DotNetScriptLookupInfo() {} // Required by HashMap...
+ DotNetScriptLookupInfo(const String &p_class_namespace, const String &p_class_name, GDMonoClass *p_script_class) :
+ class_namespace(p_class_namespace), class_name(p_class_name), script_class(p_script_class) {
+ }
+};
+
+class CSharpScript : public Script {
GDCLASS(CSharpScript, Script);
+public:
+ struct SignalParameter {
+ String name;
+ Variant::Type type;
+ bool nil_is_variant = false;
+ };
+
+ struct EventSignal {
+ GDMonoField *field = nullptr;
+ GDMonoMethod *invoke_method = nullptr;
+ Vector<SignalParameter> parameters;
+ };
+
+private:
friend class CSharpInstance;
friend class CSharpLanguage;
friend struct CSharpScriptDepSort;
- bool tool;
- bool valid;
+ bool tool = false;
+ bool valid = false;
bool builtin;
- GDMonoClass *base;
- GDMonoClass *native;
- GDMonoClass *script_class;
+ GDMonoClass *base = nullptr;
+ GDMonoClass *native = nullptr;
+ GDMonoClass *script_class = nullptr;
Ref<CSharpScript> base_cache; // TODO what's this for?
@@ -91,7 +117,8 @@ class CSharpScript : public Script {
// TODO
// Replace with buffer containing the serialized state of managed scripts.
// Keep variant state backup to use only with script instance placeholders.
- List<Pair<StringName, Variant> > properties;
+ List<Pair<StringName, Variant>> properties;
+ List<Pair<StringName, Array>> event_signals;
};
Set<ObjectID> pending_reload_instances;
@@ -103,116 +130,137 @@ class CSharpScript : public Script {
String source;
StringName name;
- SelfList<CSharpScript> script_list;
+ SelfList<CSharpScript> script_list = this;
- struct Argument {
- String name;
- Variant::Type type;
- };
+ Map<StringName, Vector<SignalParameter>> _signals;
+ Map<StringName, EventSignal> event_signals;
+ bool signals_invalidated = true;
- Map<StringName, Vector<Argument> > _signals;
- bool signals_invalidated;
+ Vector<MultiplayerAPI::RPCConfig> rpc_functions;
#ifdef TOOLS_ENABLED
List<PropertyInfo> exported_members_cache; // members_cache
Map<StringName, Variant> exported_members_defval_cache; // member_default_values_cache
Set<PlaceHolderScriptInstance *> placeholders;
- bool source_changed_cache;
- bool placeholder_fallback_enabled;
- bool exports_invalidated;
+ bool source_changed_cache = false;
+ bool placeholder_fallback_enabled = false;
+ bool exports_invalidated = true;
void _update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames);
void _update_member_info_no_exports();
- virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder);
+ void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) override;
+#endif
+
+#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED)
+ Set<StringName> exported_members_names;
#endif
Map<StringName, PropertyInfo> member_info;
void _clear();
+ void _update_name();
+
void load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class);
- bool _get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> &params);
+ bool _get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> &params);
+
+ bool _update_exports(PlaceHolderScriptInstance *p_instance_to_update = nullptr);
- bool _update_exports();
-#ifdef TOOLS_ENABLED
bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported);
+#ifdef TOOLS_ENABLED
static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string);
#endif
- CSharpInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error);
- Variant _new(const Variant **p_args, int p_argcount, Variant::CallError &r_error);
+ CSharpInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error);
+ Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
// Do not use unless you know what you are doing
friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
static Ref<CSharpScript> create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native);
+ static void update_script_class_info(Ref<CSharpScript> p_script);
static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native);
+ MultiplayerAPI::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const;
+
protected:
static void _bind_methods();
- Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error);
- virtual void _resource_path_changed();
+ Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
+ void _resource_path_changed() override;
bool _get(const StringName &p_name, Variant &r_ret) const;
bool _set(const StringName &p_name, const Variant &p_value);
void _get_property_list(List<PropertyInfo> *p_properties) const;
public:
- virtual bool can_instance() const;
- virtual StringName get_instance_base_type() const;
- virtual ScriptInstance *instance_create(Object *p_this);
- virtual PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this);
- virtual bool instance_has(const Object *p_this) const;
+ bool can_instantiate() const override;
+ StringName get_instance_base_type() const override;
+ ScriptInstance *instance_create(Object *p_this) override;
+ PlaceHolderScriptInstance *placeholder_instance_create(Object *p_this) override;
+ bool instance_has(const Object *p_this) const override;
+
+ bool has_source_code() const override;
+ String get_source_code() const override;
+ void set_source_code(const String &p_code) override;
- virtual bool has_source_code() const;
- virtual String get_source_code() const;
- virtual void set_source_code(const String &p_code);
+#ifdef TOOLS_ENABLED
+ virtual const Vector<DocData::ClassDoc> &get_documentation() const override {
+ // TODO
+ static Vector<DocData::ClassDoc> docs;
+ return docs;
+ }
+#endif // TOOLS_ENABLED
+
+ Error reload(bool p_keep_state = false) override;
- virtual Error reload(bool p_keep_state = false);
+ bool has_script_signal(const StringName &p_signal) const override;
+ void get_script_signal_list(List<MethodInfo> *r_signals) const override;
- virtual bool has_script_signal(const StringName &p_signal) const;
- virtual void get_script_signal_list(List<MethodInfo> *r_signals) const;
+ bool get_property_default_value(const StringName &p_property, Variant &r_value) const override;
+ void get_script_property_list(List<PropertyInfo> *p_list) const override;
+ void update_exports() override;
- virtual bool get_property_default_value(const StringName &p_property, Variant &r_value) const;
- virtual void get_script_property_list(List<PropertyInfo> *p_list) const;
- virtual void update_exports();
+ void get_members(Set<StringName> *p_members) override;
- virtual bool is_tool() const { return tool; }
- virtual bool is_valid() const { return valid; }
+ bool is_tool() const override { return tool; }
+ bool is_valid() const override { return valid; }
- virtual Ref<Script> get_base_script() const;
- virtual ScriptLanguage *get_language() const;
+ bool inherits_script(const Ref<Script> &p_script) const override;
- virtual void get_script_method_list(List<MethodInfo> *p_list) const;
- bool has_method(const StringName &p_method) const;
- MethodInfo get_method_info(const StringName &p_method) const;
+ Ref<Script> get_base_script() const override;
+ ScriptLanguage *get_language() const override;
- virtual int get_member_line(const StringName &p_member) const;
+ void get_script_method_list(List<MethodInfo> *p_list) const override;
+ bool has_method(const StringName &p_method) const override;
+ MethodInfo get_method_info(const StringName &p_method) const override;
+
+ int get_member_line(const StringName &p_member) const override;
+
+ const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override;
#ifdef TOOLS_ENABLED
- virtual bool is_placeholder_fallback_enabled() const { return placeholder_fallback_enabled; }
+ bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; }
#endif
Error load_source_code(const String &p_path);
- StringName get_script_name() const;
-
CSharpScript();
~CSharpScript();
};
class CSharpInstance : public ScriptInstance {
-
friend class CSharpScript;
friend class CSharpLanguage;
- Object *owner;
- bool base_ref;
- bool ref_dying;
- bool unsafe_referenced;
- bool predelete_notified;
- bool destructing_script_instance;
+ Object *owner = nullptr;
+ bool base_ref_counted = false;
+ bool ref_dying = false;
+ bool unsafe_referenced = false;
+ bool predelete_notified = false;
+ bool destructing_script_instance = false;
Ref<CSharpScript> script;
- Ref<MonoGCHandle> gchandle;
+ MonoGCHandleData gchandle;
+
+ List<Callable> connected_event_signals;
bool _reference_owner_unsafe();
@@ -222,37 +270,32 @@ class CSharpInstance : public ScriptInstance {
bool _unreference_owner_unsafe();
/*
- * If NULL is returned, the caller must destroy the script instance by removing it from its owner.
+ * If nullptr is returned, the caller must destroy the script instance by removing it from its owner.
*/
MonoObject *_internal_new_managed();
// Do not use unless you know what you are doing
friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
- static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle);
-
- void _call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount);
+ static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle);
- MultiplayerAPI::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const;
-
- void get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state);
+ void get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state);
+ void get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state);
public:
MonoObject *get_mono_object() const;
_FORCE_INLINE_ bool is_destructing_script_instance() { return destructing_script_instance; }
- virtual Object *get_owner();
+ Object *get_owner() override;
- virtual bool set(const StringName &p_name, const Variant &p_value);
- virtual bool get(const StringName &p_name, Variant &r_ret) const;
- virtual void get_property_list(List<PropertyInfo> *p_properties) const;
- virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const;
+ bool set(const StringName &p_name, const Variant &p_value) override;
+ bool get(const StringName &p_name, Variant &r_ret) const override;
+ void get_property_list(List<PropertyInfo> *p_properties) const override;
+ Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const override;
- /* TODO */ virtual void get_method_list(List<MethodInfo> *p_list) const {}
- virtual bool has_method(const StringName &p_method) const;
- virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error);
- virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount);
- virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount);
+ /* TODO */ void get_method_list(List<MethodInfo> *p_list) const override {}
+ bool has_method(const StringName &p_method) const override;
+ Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
void mono_object_disposed(MonoObject *p_obj);
@@ -262,59 +305,68 @@ public:
*/
void mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance);
- virtual void refcount_incremented();
- virtual bool refcount_decremented();
+ void connect_event_signals();
+ void disconnect_event_signals();
+
+ void refcount_incremented() override;
+ bool refcount_decremented() override;
- virtual MultiplayerAPI::RPCMode get_rpc_mode(const StringName &p_method) const;
- virtual MultiplayerAPI::RPCMode get_rset_mode(const StringName &p_variable) const;
+ const Vector<MultiplayerAPI::RPCConfig> get_rpc_methods() const override;
- virtual void notification(int p_notification);
+ void notification(int p_notification) override;
void _call_notification(int p_notification);
- virtual String to_string(bool *r_valid);
+ String to_string(bool *r_valid) override;
- virtual Ref<Script> get_script() const;
+ Ref<Script> get_script() const override;
- virtual ScriptLanguage *get_language();
+ ScriptLanguage *get_language() override;
- CSharpInstance();
+ CSharpInstance(const Ref<CSharpScript> &p_script);
~CSharpInstance();
};
struct CSharpScriptBinding {
- bool inited;
+ bool inited = false;
StringName type_name;
- GDMonoClass *wrapper_class;
- Ref<MonoGCHandle> gchandle;
- Object *owner;
+ GDMonoClass *wrapper_class = nullptr;
+ MonoGCHandleData gchandle;
+ Object *owner = nullptr;
+
+ CSharpScriptBinding() {}
};
-class CSharpLanguage : public ScriptLanguage {
+class ManagedCallableMiddleman : public Object {
+ GDCLASS(ManagedCallableMiddleman, Object);
+};
+class CSharpLanguage : public ScriptLanguage {
friend class CSharpScript;
friend class CSharpInstance;
static CSharpLanguage *singleton;
- bool finalizing;
+ bool finalizing = false;
+ bool finalized = false;
- GDMono *gdmono;
+ GDMono *gdmono = nullptr;
SelfList<CSharpScript>::List script_list;
- Mutex *script_instances_mutex;
- Mutex *script_gchandle_release_mutex;
- Mutex *language_bind_mutex;
+ Mutex script_instances_mutex;
+ Mutex script_gchandle_release_mutex;
+ Mutex language_bind_mutex;
Map<Object *, CSharpScriptBinding> script_bindings;
#ifdef DEBUG_ENABLED
// List of unsafe object references
Map<ObjectID, int> unsafe_object_references;
- Mutex *unsafe_object_references_lock;
+ Mutex unsafe_object_references_lock;
#endif
- struct StringNameCache {
+ ManagedCallableMiddleman *managed_callable_middleman = memnew(ManagedCallableMiddleman);
+ struct StringNameCache {
StringName _signal_callback;
StringName _set;
StringName _get;
@@ -324,27 +376,27 @@ class CSharpLanguage : public ScriptLanguage {
StringName dotctor; // .ctor
StringName on_before_serialize; // OnBeforeSerialize
StringName on_after_deserialize; // OnAfterDeserialize
+ StringName delegate_invoke_method_name;
StringNameCache();
};
- int lang_idx;
+ int lang_idx = -1;
- Dictionary scripts_metadata;
- bool scripts_metadata_invalidated;
+ HashMap<String, DotNetScriptLookupInfo> dotnet_script_lookup_map;
+
+ void lookup_script_for_class(GDMonoClass *p_class);
// For debug_break and debug_break_parse
- int _debug_parse_err_line;
+ int _debug_parse_err_line = -1;
String _debug_parse_err_file;
String _debug_error;
- void _load_scripts_metadata();
-
friend class GDMono;
void _on_scripts_domain_unloaded();
#ifdef TOOLS_ENABLED
- EditorPlugin *godotsharp_editor;
+ EditorPlugin *godotsharp_editor = nullptr;
static void _editor_init_callback();
#endif
@@ -352,7 +404,7 @@ class CSharpLanguage : public ScriptLanguage {
public:
StringNameCache string_names;
- Mutex *get_language_bind_mutex() { return language_bind_mutex; }
+ const Mutex &get_language_bind_mutex() { return language_bind_mutex; }
_FORCE_INLINE_ int get_language_index() { return lang_idx; }
void set_language_index(int p_idx);
@@ -365,8 +417,8 @@ public:
_FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; }
#endif
- static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle);
- static void release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle);
+ static void release_script_gchandle(MonoGCHandleData &p_gchandle);
+ static void release_script_gchandle(MonoObject *p_expected_obj, MonoGCHandleData &p_gchandle);
bool debug_break(const String &p_error, bool p_allow_continue = true);
bool debug_break_parse(const String &p_file, int p_line, const String &p_error);
@@ -376,86 +428,90 @@ public:
void reload_assemblies(bool p_soft_reload);
#endif
- _FORCE_INLINE_ Dictionary get_scripts_metadata_or_nothing() {
- return scripts_metadata_invalidated ? Dictionary() : scripts_metadata;
- }
+ _FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; }
- _FORCE_INLINE_ const Dictionary &get_scripts_metadata() {
- if (scripts_metadata_invalidated)
- _load_scripts_metadata();
- return scripts_metadata;
+ void lookup_scripts_in_assembly(GDMonoAssembly *p_assembly);
+
+ const DotNetScriptLookupInfo *lookup_dotnet_script(const String &p_script_path) const {
+ return dotnet_script_lookup_map.getptr(p_script_path);
}
- virtual String get_name() const;
+ String get_name() const override;
/* LANGUAGE FUNCTIONS */
- virtual String get_type() const;
- virtual String get_extension() const;
- virtual Error execute_file(const String &p_path);
- virtual void init();
- virtual void finish();
+ String get_type() const override;
+ String get_extension() const override;
+ Error execute_file(const String &p_path) override;
+ void init() override;
+ void finish() override;
+
+ void finalize();
/* EDITOR FUNCTIONS */
- virtual void get_reserved_words(List<String> *p_words) const;
- virtual void get_comment_delimiters(List<String> *p_delimiters) const;
- virtual void get_string_delimiters(List<String> *p_delimiters) const;
- virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const;
- virtual bool is_using_templates();
- virtual void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script);
- /* TODO */ virtual bool validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings = NULL, Set<int> *r_safe_lines = NULL) const { return true; }
- virtual String validate_path(const String &p_path) const;
- virtual Script *create_script() const;
- virtual bool has_named_classes() const;
- virtual bool supports_builtin_mode() const;
- /* TODO? */ virtual int find_function(const String &p_function, const String &p_code) const { return -1; }
- virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const;
+ void get_reserved_words(List<String> *p_words) const override;
+ bool is_control_flow_keyword(String p_keyword) const override;
+ void get_comment_delimiters(List<String> *p_delimiters) const override;
+ void get_string_delimiters(List<String> *p_delimiters) const override;
+ Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const override;
+ bool is_using_templates() override;
+ void make_template(const String &p_class_name, const String &p_base_class_name, Ref<Script> &p_script) override;
+ /* TODO */ bool validate(const String &p_script, const String &p_path, List<String> *r_functions,
+ List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, Set<int> *r_safe_lines = nullptr) const override {
+ return true;
+ }
+ String validate_path(const String &p_path) const override;
+ Script *create_script() const override;
+ bool has_named_classes() const override;
+ bool supports_builtin_mode() const override;
+ /* TODO? */ int find_function(const String &p_function, const String &p_code) const override { return -1; }
+ String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const override;
virtual String _get_indentation() const;
- /* TODO? */ virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const {}
- /* TODO */ virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) {}
+ /* TODO? */ void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override {}
+ /* TODO */ void add_global_constant(const StringName &p_variable, const Variant &p_value) override {}
/* DEBUGGER FUNCTIONS */
- virtual String debug_get_error() const;
- virtual int debug_get_stack_level_count() const;
- virtual int debug_get_stack_level_line(int p_level) const;
- virtual String debug_get_stack_level_function(int p_level) const;
- virtual String debug_get_stack_level_source(int p_level) const;
- /* TODO */ virtual void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {}
- /* TODO */ virtual void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {}
- /* TODO */ virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {}
- /* TODO */ virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) { return ""; }
- virtual Vector<StackInfo> debug_get_current_stack_info();
+ String debug_get_error() const override;
+ int debug_get_stack_level_count() const override;
+ int debug_get_stack_level_line(int p_level) const override;
+ String debug_get_stack_level_function(int p_level) const override;
+ String debug_get_stack_level_source(int p_level) const override;
+ /* TODO */ void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {}
+ /* TODO */ void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {}
+ /* TODO */ void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {}
+ /* TODO */ String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) override { return ""; }
+ Vector<StackInfo> debug_get_current_stack_info() override;
/* PROFILING FUNCTIONS */
- /* TODO */ virtual void profiling_start() {}
- /* TODO */ virtual void profiling_stop() {}
- /* TODO */ virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) { return 0; }
- /* TODO */ virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) { return 0; }
+ /* TODO */ void profiling_start() override {}
+ /* TODO */ void profiling_stop() override {}
+ /* TODO */ int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override { return 0; }
+ /* TODO */ int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override { return 0; }
- virtual void frame();
+ void frame() override;
- /* TODO? */ virtual void get_public_functions(List<MethodInfo> *p_functions) const {}
- /* TODO? */ virtual void get_public_constants(List<Pair<String, Variant> > *p_constants) const {}
+ /* TODO? */ void get_public_functions(List<MethodInfo> *p_functions) const override {}
+ /* TODO? */ void get_public_constants(List<Pair<String, Variant>> *p_constants) const override {}
- virtual void reload_all_scripts();
- virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload);
+ void reload_all_scripts() override;
+ void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) override;
/* LOADER FUNCTIONS */
- virtual void get_recognized_extensions(List<String> *p_extensions) const;
+ void get_recognized_extensions(List<String> *p_extensions) const override;
#ifdef TOOLS_ENABLED
- virtual Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col);
- virtual bool overrides_external_editor();
+ Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) override;
+ bool overrides_external_editor() override;
#endif
/* THREAD ATTACHING */
- virtual void thread_enter();
- virtual void thread_exit();
+ void thread_enter() override;
+ void thread_exit() override;
// Don't use these. I'm watching you
- virtual void *alloc_instance_binding_data(Object *p_object);
- virtual void free_instance_binding_data(void *p_data);
- virtual void refcount_incremented_instance_binding(Object *p_object);
- virtual bool refcount_decremented_instance_binding(Object *p_object);
+ void *alloc_instance_binding_data(Object *p_object) override;
+ void free_instance_binding_data(void *p_data) override;
+ void refcount_incremented_instance_binding(Object *p_object) override;
+ bool refcount_decremented_instance_binding(Object *p_object) override;
Map<Object *, CSharpScriptBinding>::Element *insert_script_binding(Object *p_object, const CSharpScriptBinding &p_script_binding);
bool setup_csharp_script_binding(CSharpScriptBinding &r_script_binding, Object *p_object);
@@ -473,17 +529,17 @@ public:
class ResourceFormatLoaderCSharpScript : public ResourceFormatLoader {
public:
- virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL);
- virtual void get_recognized_extensions(List<String> *p_extensions) const;
- virtual bool handles_type(const String &p_type) const;
- virtual String get_resource_type(const String &p_path) const;
+ RES load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
+ void get_recognized_extensions(List<String> *p_extensions) const override;
+ bool handles_type(const String &p_type) const override;
+ String get_resource_type(const String &p_path) const override;
};
class ResourceFormatSaverCSharpScript : public ResourceFormatSaver {
public:
- virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0);
- virtual void get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const;
- virtual bool recognize(const RES &p_resource) const;
+ Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0) override;
+ void get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const override;
+ bool recognize(const RES &p_resource) const override;
};
#endif // CSHARP_SCRIPT_H
diff --git a/modules/mono/doc_classes/@C#.xml b/modules/mono/doc_classes/@C#.xml
deleted file mode 100644
index 826c106d7e..0000000000
--- a/modules/mono/doc_classes/@C#.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<class name="@C#" category="Core" version="3.2">
- <brief_description>
- </brief_description>
- <description>
- </description>
- <tutorials>
- </tutorials>
- <methods>
- </methods>
- <constants>
- </constants>
-</class>
diff --git a/modules/mono/doc_classes/CSharpScript.xml b/modules/mono/doc_classes/CSharpScript.xml
index de2e246ea9..45a6f991bf 100644
--- a/modules/mono/doc_classes/CSharpScript.xml
+++ b/modules/mono/doc_classes/CSharpScript.xml
@@ -1,16 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<class name="CSharpScript" inherits="Script" category="Core" version="3.2">
+<class name="CSharpScript" inherits="Script" version="4.0">
<brief_description>
+ A script implemented in the C# programming language (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.
+ See also [GodotSharp].
</description>
<tutorials>
+ <link title="C# tutorial index">https://docs.godotengine.org/en/latest/getting_started/scripting/c_sharp/index.html</link>
</tutorials>
<methods>
<method name="new" qualifiers="vararg">
- <return type="Object">
+ <return type="Variant">
</return>
<description>
+ Returns a new instance of the script.
</description>
</method>
</methods>
diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml
index 18556a84ba..417f8ac704 100644
--- a/modules/mono/doc_classes/GodotSharp.xml
+++ b/modules/mono/doc_classes/GodotSharp.xml
@@ -1,8 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<class name="GodotSharp" inherits="Object" category="Core" version="3.2">
+<class name="GodotSharp" inherits="Object" version="4.0">
<brief_description>
+ Bridge between Godot and the Mono runtime (Mono-enabled builds only).
</brief_description>
<description>
+ This class is a bridge between Godot and the Mono runtime. It exposes several low-level operations and is only available in Mono-enabled Godot builds.
+ See also [CSharpScript].
</description>
<tutorials>
</tutorials>
@@ -11,26 +14,30 @@
<return type="void">
</return>
<description>
- Attaches the current thread to the mono runtime.
+ Attaches the current thread to the Mono runtime.
</description>
</method>
<method name="detach_thread">
<return type="void">
</return>
<description>
- Detaches the current thread from the mono runtime.
+ Detaches the current thread from the Mono runtime.
</description>
</method>
<method name="get_domain_id">
<return type="int">
</return>
<description>
+ Returns the current MonoDomain ID.
+ [b]Note:[/b] The Mono runtime must be initialized for this method to work (use [method is_runtime_initialized] to check). If the Mono runtime isn't initialized at the time this method is called, the engine will crash.
</description>
</method>
<method name="get_scripts_domain_id">
<return type="int">
</return>
<description>
+ Returns the scripts MonoDomain's ID. This will be the same MonoDomain ID as [method get_domain_id], unless the scripts domain isn't loaded.
+ [b]Note:[/b] The Mono runtime must be initialized for this method to work (use [method is_runtime_initialized] to check). If the Mono runtime isn't initialized at the time this method is called, the engine will crash.
</description>
</method>
<method name="is_domain_finalizing_for_unload">
@@ -39,26 +46,28 @@
<argument index="0" name="domain_id" type="int">
</argument>
<description>
- Returns whether the domain is being finalized.
+ Returns [code]true[/code] if the domain is being finalized, [code]false[/code] otherwise.
</description>
</method>
<method name="is_runtime_initialized">
<return type="bool">
</return>
<description>
+ Returns [code]true[/code] if the Mono runtime is initialized, [code]false[/code] otherwise.
</description>
</method>
<method name="is_runtime_shutting_down">
<return type="bool">
</return>
<description>
+ Returns [code]true[/code] if the Mono runtime is shutting down, [code]false[/code] otherwise.
</description>
</method>
<method name="is_scripts_domain_loaded">
<return type="bool">
</return>
<description>
- Returns whether the scripts domain is loaded.
+ Returns [code]true[/code] if the scripts domain is loaded, [code]false[/code] otherwise.
</description>
</method>
</methods>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
new file mode 100644
index 0000000000..d1868f52ef
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
@@ -0,0 +1,34 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "Godot.SourceGenerators\Godot.SourceGenerators.csproj", "{32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Sample", "Godot.SourceGenerators.Sample\Godot.SourceGenerators.Sample.csproj", "{7297A614-8DF5-43DE-9EAD-99671B26BD1F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj", "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {32D31B23-2A45-4099-B4F5-95B4C8FF7D9F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7297A614-8DF5-43DE-9EAD-99671B26BD1F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
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
new file mode 100644
index 0000000000..4e9e7184da
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
@@ -0,0 +1,41 @@
+<Project Sdk="Microsoft.Build.NoTargets/2.0.1">
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+
+ <Description>MSBuild .NET Sdk for Godot projects.</Description>
+ <Authors>Godot Engine contributors</Authors>
+
+ <PackageId>Godot.NET.Sdk</PackageId>
+ <Version>4.0.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>
+ <PackageType>MSBuildSdk</PackageType>
+ <PackageTags>MSBuildSdk</PackageTags>
+ <PackageLicenseExpression>MIT</PackageLicenseExpression>
+ <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+
+ <!-- Exclude target framework from the package dependencies as we don't include the build output -->
+ <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
+ <IncludeBuildOutput>false</IncludeBuildOutput>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <!-- Package Sdk\Sdk.props and Sdk\Sdk.targets file -->
+ <None Include="Sdk\Sdk.props" Pack="true" PackagePath="Sdk" />
+ <None Include="Sdk\Sdk.targets" Pack="true" PackagePath="Sdk" />
+ <!-- SdkPackageVersions.props -->
+ <None Include="..\..\..\SdkPackageVersions.props" Pack="true" PackagePath="Sdk">
+ <Link>Sdk\SdkPackageVersions.props</Link>
+ </None>
+ </ItemGroup>
+
+ <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
+ <PropertyGroup>
+ <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
+ <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
+ </PropertyGroup>
+ <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
+ </Target>
+</Project>
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
new file mode 100644
index 0000000000..0128f5c706
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
@@ -0,0 +1,115 @@
+<Project>
+ <Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" />
+
+ <PropertyGroup>
+ <!-- Determines if we should import Microsoft.NET.Sdk, if it wasn't already imported. -->
+ <GodotSdkImportsMicrosoftNetSdk Condition=" '$(UsingMicrosoftNETSdk)' != 'true' ">true</GodotSdkImportsMicrosoftNetSdk>
+
+ <GodotProjectTypeGuid>{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}</GodotProjectTypeGuid>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <Configurations>Debug;ExportDebug;ExportRelease</Configurations>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+
+ <GodotProjectDir Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)</GodotProjectDir>
+ <GodotProjectDir Condition=" '$(SolutionDir)' == '' ">$(MSBuildProjectDirectory)</GodotProjectDir>
+ <GodotProjectDir>$([MSBuild]::EnsureTrailingSlash('$(GodotProjectDir)'))</GodotProjectDir>
+
+ <!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.godot\mono\temp\'. -->
+ <BaseOutputPath>$(GodotProjectDir).godot\mono\temp\bin\</BaseOutputPath>
+ <OutputPath>$(GodotProjectDir).godot\mono\temp\bin\$(Configuration)\</OutputPath>
+ <!--
+ Use custom IntermediateOutputPath and BaseIntermediateOutputPath only if it wasn't already set.
+ Otherwise the old values may have already been changed by MSBuild which can cause problems with NuGet.
+ -->
+ <IntermediateOutputPath Condition=" '$(IntermediateOutputPath)' == '' ">$(GodotProjectDir).godot\mono\temp\obj\$(Configuration)\</IntermediateOutputPath>
+ <BaseIntermediateOutputPath Condition=" '$(BaseIntermediateOutputPath)' == '' ">$(GodotProjectDir).godot\mono\temp\obj\</BaseIntermediateOutputPath>
+
+ <!-- Do not append the target framework name to the output path. -->
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+ </PropertyGroup>
+
+ <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " />
+
+ <PropertyGroup>
+ <EnableDefaultNoneItems>false</EnableDefaultNoneItems>
+ </PropertyGroup>
+
+ <!--
+ The Microsoft.NET.Sdk only understands of the Debug and Release configurations.
+ We need to set the following properties manually for ExportDebug and ExportRelease.
+ -->
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' or '$(Configuration)' == 'ExportDebug' ">
+ <DebugSymbols Condition=" '$(DebugSymbols)' == '' ">true</DebugSymbols>
+ <Optimize Condition=" '$(Optimize)' == '' ">false</Optimize>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'ExportRelease' ">
+ <Optimize Condition=" '$(Optimize)' == '' ">true</Optimize>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <GodotApiConfiguration Condition=" '$(Configuration)' != 'ExportRelease' ">Debug</GodotApiConfiguration>
+ <GodotApiConfiguration Condition=" '$(Configuration)' == 'ExportRelease' ">Release</GodotApiConfiguration>
+ </PropertyGroup>
+
+ <!-- Auto-detect the target Godot platform if it was not specified. -->
+ <PropertyGroup Condition=" '$(GodotTargetPlatform)' == '' ">
+ <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Linux))' ">linuxbsd</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(FreeBSD))' ">linuxbsd</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(OSX))' ">osx</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Windows))' ">windows</GodotTargetPlatform>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <GodotRealTIsDouble Condition=" '$(GodotRealTIsDouble)' == '' ">false</GodotRealTIsDouble>
+ </PropertyGroup>
+
+ <!-- Godot DefineConstants. -->
+ <PropertyGroup>
+ <!-- Define constant to identify Godot builds. -->
+ <GodotDefineConstants>GODOT</GodotDefineConstants>
+
+ <!--
+ Define constant to determine the target Godot platform. This includes the
+ recognized platform names and the platform category (PC, MOBILE or WEB).
+ -->
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'windows' ">GODOT_WINDOWS;GODOT_PC</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'linuxbsd' ">GODOT_LINUXBSD;GODOT_PC</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'osx' ">GODOT_OSX;GODOT_MACOS;GODOT_PC</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'server' ">GODOT_SERVER;GODOT_PC</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'uwp' ">GODOT_UWP;GODOT_PC</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'haiku' ">GODOT_HAIKU;GODOT_PC</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'android' ">GODOT_ANDROID;GODOT_MOBILE</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'iphone' ">GODOT_IPHONE;GODOT_IOS;GODOT_MOBILE</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'javascript' ">GODOT_JAVASCRIPT;GODOT_HTML5;GODOT_WASM;GODOT_WEB</GodotPlatformConstants>
+
+ <GodotDefineConstants>$(GodotDefineConstants);$(GodotPlatformConstants)</GodotDefineConstants>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <!-- ExportDebug also defines DEBUG like Debug does. -->
+ <DefineConstants Condition=" '$(Configuration)' == 'ExportDebug' ">$(DefineConstants);DEBUG</DefineConstants>
+ <!-- Debug defines TOOLS to differentiate between Debug and ExportDebug configurations. -->
+ <DefineConstants Condition=" '$(Configuration)' == 'Debug' ">$(DefineConstants);TOOLS</DefineConstants>
+
+ <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
+ </PropertyGroup>
+
+ <!-- Godot API references -->
+ <ItemGroup>
+ <!--
+ TODO:
+ We should consider a nuget package for reference assemblies. This is difficult because the
+ Godot scripting API is continuaslly breaking backwards compatibility even in patch releases.
+ -->
+ <Reference Include="GodotSharp">
+ <Private>false</Private>
+ <HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath>
+ </Reference>
+ <Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' ">
+ <Private>false</Private>
+ <HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+</Project>
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
new file mode 100644
index 0000000000..92e299d2f3
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
@@ -0,0 +1,22 @@
+<Project>
+ <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " />
+
+ <PropertyGroup>
+ <EnableGodotProjectTypeGuid Condition=" '$(EnableGodotProjectTypeGuid)' == '' ">true</EnableGodotProjectTypeGuid>
+ <ProjectTypeGuids Condition=" '$(EnableGodotProjectTypeGuid)' == 'true' ">$(GodotProjectTypeGuid);$(DefaultProjectTypeGuid)</ProjectTypeGuids>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <!--
+ Define constant to determine whether the real_t type in Godot is double precision or not.
+ By default this is false, like the official Godot builds. If someone is using a custom
+ Godot build where real_t is double, they can override the GodotRealTIsDouble property.
+ -->
+ <DefineConstants Condition=" '$(GodotRealTIsDouble)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants>
+ </PropertyGroup>
+
+ <!-- C# source generators -->
+ <ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' ">
+ <PackageReference Include="Godot.SourceGenerators" Version="$(PackageVersion_Godot_SourceGenerators)" />
+ </ItemGroup>
+</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs
new file mode 100644
index 0000000000..5eaebc4474
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Bar.cs
@@ -0,0 +1,15 @@
+namespace Godot.SourceGenerators.Sample
+{
+ partial class Bar : Godot.Object
+ {
+ }
+
+ // Foo in another file
+ partial class Foo
+ {
+ }
+
+ partial class NotSameNameAsFile : Godot.Object
+ {
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs
new file mode 100644
index 0000000000..21a5bfe560
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Foo.cs
@@ -0,0 +1,11 @@
+namespace Godot.SourceGenerators.Sample
+{
+ partial class Foo : Godot.Object
+ {
+ }
+
+ // Foo again in the same file
+ partial class Foo
+ {
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
new file mode 100644
index 0000000000..24f7909861
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj
@@ -0,0 +1,31 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netstandard2.1</TargetFramework>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <!-- $(GodotProjectDir) would normally be defined by the Godot.NET.Sdk -->
+ <GodotProjectDir>$(MSBuildProjectDirectory)</GodotProjectDir>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <!-- The emitted files are not part of the compilation nor design.
+ They're only for peeking at the generated sources. Sometimes the
+ emitted files get corrupted, but that won't break anything. -->
+ <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
+ <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GeneratedFiles</CompilerGeneratedFilesOutputPath>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\glue\GodotSharp\GodotSharp\GodotSharp.csproj">
+ <Private>False</Private>
+ </ProjectReference>
+ <ProjectReference Include="..\Godot.SourceGenerators\Godot.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
+ </ItemGroup>
+
+ <!-- This file is imported automatically when using PackageReference to
+ reference Godot.SourceGenerators, but not when using ProjectReference -->
+ <Import Project="..\Godot.SourceGenerators\Godot.SourceGenerators.props" />
+
+</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
new file mode 100644
index 0000000000..4867c986e6
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
@@ -0,0 +1,33 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Godot.SourceGenerators
+{
+ public static class Common
+ {
+ public static void ReportNonPartialGodotScriptClass(
+ GeneratorExecutionContext context,
+ ClassDeclarationSyntax cds, INamedTypeSymbol symbol
+ )
+ {
+ string message =
+ "Missing partial modifier on declaration of type '" +
+ $"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'";
+
+ string description = $"{message}. Subclasses of '{GodotClasses.Object}' must be " +
+ "declared with the partial modifier or annotated with the " +
+ $"attribute '{GodotClasses.DisableGodotGeneratorsAttr}'.";
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ new DiagnosticDescriptor(id: "GODOT-G0001",
+ title: message,
+ messageFormat: message,
+ category: "Usage",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description),
+ cds.GetLocation(),
+ cds.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
new file mode 100644
index 0000000000..e16f72f43a
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
@@ -0,0 +1,89 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Godot.SourceGenerators
+{
+ static class ExtensionMethods
+ {
+ public static bool TryGetGlobalAnalyzerProperty(
+ this GeneratorExecutionContext context, string property, out string? value
+ ) => context.AnalyzerConfigOptions.GlobalOptions
+ .TryGetValue("build_property." + property, out value);
+
+ private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName)
+ {
+ if (symbol == null)
+ return false;
+
+ while (true)
+ {
+ if (symbol.ToString() == baseName)
+ {
+ return true;
+ }
+
+ if (symbol.BaseType != null)
+ {
+ symbol = symbol.BaseType;
+ continue;
+ }
+
+ break;
+ }
+
+ return false;
+ }
+
+ private static bool IsGodotScriptClass(
+ this ClassDeclarationSyntax cds, Compilation compilation,
+ out INamedTypeSymbol? symbol
+ )
+ {
+ var sm = compilation.GetSemanticModel(cds.SyntaxTree);
+
+ var classTypeSymbol = sm.GetDeclaredSymbol(cds);
+
+ if (classTypeSymbol?.BaseType == null
+ || !classTypeSymbol.BaseType.InheritsFrom(GodotClasses.Object))
+ {
+ symbol = null;
+ return false;
+ }
+
+ symbol = classTypeSymbol;
+ return true;
+ }
+
+ public static IEnumerable<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol)> SelectGodotScriptClasses(
+ this IEnumerable<ClassDeclarationSyntax> source,
+ Compilation compilation
+ )
+ {
+ foreach (var cds in source)
+ {
+ if (cds.IsGodotScriptClass(compilation, out var symbol))
+ yield return (cds, symbol!);
+ }
+ }
+
+ public static bool IsPartial(this ClassDeclarationSyntax cds)
+ => cds.Modifiers.Any(SyntaxKind.PartialKeyword);
+
+ public static bool HasDisableGeneratorsAttribute(this INamedTypeSymbol symbol)
+ => symbol.GetAttributes().Any(attr =>
+ attr.AttributeClass?.ToString() == GodotClasses.DisableGodotGeneratorsAttr);
+
+ private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } =
+ SymbolDisplayFormat.FullyQualifiedFormat
+ .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted);
+
+ public static string FullQualifiedName(this INamedTypeSymbol symbol)
+ => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal);
+
+ public static string FullQualifiedName(this INamespaceSymbol namespaceSymbol)
+ => namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal);
+ }
+}
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
new file mode 100644
index 0000000000..224d7e5b5a
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj
@@ -0,0 +1,40 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <LangVersion>8.0</LangVersion>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+ <PropertyGroup>
+ <Description>Core C# source generator for Godot projects.</Description>
+ <Authors>Godot Engine contributors</Authors>
+
+ <PackageId>Godot.SourceGenerators</PackageId>
+ <Version>4.0.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>
+ <PackageLicenseExpression>MIT</PackageLicenseExpression>
+
+ <GeneratePackageOnBuild>true</GeneratePackageOnBuild> <!-- Generates a package at build -->
+ <IncludeBuildOutput>false</IncludeBuildOutput> <!-- Do not include the generator as a lib dependency -->
+ </PropertyGroup>
+ <ItemGroup>
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
+ <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1" PrivateAssets="all" />
+ </ItemGroup>
+ <ItemGroup>
+ <!-- Package the generator in the analyzer directory of the nuget package -->
+ <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
+
+ <!-- Package the props file -->
+ <None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="false" />
+ </ItemGroup>
+
+ <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack">
+ <PropertyGroup>
+ <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath>
+ <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir>
+ </PropertyGroup>
+ <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" />
+ </Target>
+</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props
new file mode 100644
index 0000000000..f9b47ad5b1
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props
@@ -0,0 +1,7 @@
+<Project>
+ <ItemGroup>
+ <!-- $(GodotProjectDir) is defined by Godot.NET.Sdk -->
+ <CompilerVisibleProperty Include="GodotProjectDir" />
+ <CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" />
+ </ItemGroup>
+</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
new file mode 100644
index 0000000000..29e41d155a
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
@@ -0,0 +1,9 @@
+namespace Godot.SourceGenerators
+{
+ public static class GodotClasses
+ {
+ public const string Object = "Godot.Object";
+ public const string DisableGodotGeneratorsAttr = "Godot.DisableGodotGeneratorsAttribute";
+ public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute";
+ }
+}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
new file mode 100644
index 0000000000..a51728e221
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Godot.SourceGenerators
+{
+ [Generator]
+ public class ScriptPathAttributeGenerator : ISourceGenerator
+ {
+ public void Execute(GeneratorExecutionContext context)
+ {
+ if (context.TryGetGlobalAnalyzerProperty("GodotScriptPathAttributeGenerator", out string? toggle)
+ && toggle == "disabled")
+ {
+ return;
+ }
+
+ // NOTE: IsNullOrEmpty doesn't work well with nullable checks
+ // ReSharper disable once ReplaceWithStringIsNullOrEmpty
+ if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir)
+ || godotProjectDir!.Length == 0)
+ {
+ throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty.");
+ }
+
+ var godotClasses = context.Compilation.SyntaxTrees
+ .SelectMany(tree =>
+ tree.GetRoot().DescendantNodes()
+ .OfType<ClassDeclarationSyntax>()
+ // Ignore inner classes
+ .Where(cds => !(cds.Parent is ClassDeclarationSyntax))
+ .SelectGodotScriptClasses(context.Compilation)
+ // Report and skip non-partial classes
+ .Where(x =>
+ {
+ if (x.cds.IsPartial() || x.symbol.HasDisableGeneratorsAttribute())
+ return true;
+ Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol);
+ return false;
+ })
+ )
+ // Ignore classes whose name is not the same as the file name
+ .Where(x => Path.GetFileNameWithoutExtension(x.cds.SyntaxTree.FilePath) == x.symbol.Name)
+ .GroupBy(x => x.symbol)
+ .ToDictionary(g => g.Key, g => g.Select(x => x.cds));
+
+ foreach (var godotClass in godotClasses)
+ {
+ VisitGodotScriptClass(context, godotProjectDir,
+ symbol: godotClass.Key,
+ classDeclarations: godotClass.Value);
+ }
+
+ if (godotClasses.Count <= 0)
+ return;
+
+ AddScriptTypesAssemblyAttr(context, godotClasses);
+ }
+
+ private static void VisitGodotScriptClass(
+ GeneratorExecutionContext context,
+ string godotProjectDir,
+ INamedTypeSymbol symbol,
+ IEnumerable<ClassDeclarationSyntax> classDeclarations
+ )
+ {
+ var attributes = new StringBuilder();
+
+ // Remember syntax trees for which we already added an attribute, to prevent unnecessary duplicates.
+ var attributedTrees = new List<SyntaxTree>();
+
+ foreach (var cds in classDeclarations)
+ {
+ if (attributedTrees.Contains(cds.SyntaxTree))
+ continue;
+
+ attributedTrees.Add(cds.SyntaxTree);
+
+ if (attributes.Length != 0)
+ attributes.Append("\n");
+
+ attributes.Append(@"[ScriptPathAttribute(""res://");
+ attributes.Append(RelativeToDir(cds.SyntaxTree.FilePath, godotProjectDir));
+ attributes.Append(@""")]");
+ }
+
+ string className = symbol.Name;
+
+ INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
+ string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
+ namespaceSymbol.FullQualifiedName() :
+ string.Empty;
+ bool hasNamespace = classNs.Length != 0;
+
+ string uniqueName = hasNamespace ?
+ classNs + "." + className + "_ScriptPath_Generated" :
+ className + "_ScriptPath_Generated";
+
+ var source = new StringBuilder();
+
+ // using Godot;
+ // namespace {classNs} {
+ // {attributesBuilder}
+ // partial class {className} { }
+ // }
+
+ source.Append("using Godot;\n");
+
+ if (hasNamespace)
+ {
+ source.Append("namespace ");
+ source.Append(classNs);
+ source.Append(" {\n\n");
+ }
+
+ source.Append(attributes);
+ source.Append("\n partial class ");
+ source.Append(className);
+ source.Append("\n{\n}\n");
+
+ if (hasNamespace)
+ {
+ source.Append("\n}\n");
+ }
+
+ context.AddSource(uniqueName, SourceText.From(source.ToString(), Encoding.UTF8));
+ }
+
+ private static void AddScriptTypesAssemblyAttr(GeneratorExecutionContext context,
+ Dictionary<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> godotClasses)
+ {
+ var sourceBuilder = new StringBuilder();
+
+ sourceBuilder.Append("[assembly:");
+ sourceBuilder.Append(GodotClasses.AssemblyHasScriptsAttr);
+ sourceBuilder.Append("(new System.Type[] {");
+
+ bool first = true;
+
+ foreach (var godotClass in godotClasses)
+ {
+ var qualifiedName = godotClass.Key.ToDisplayString(
+ NullableFlowState.NotNull, SymbolDisplayFormat.FullyQualifiedFormat);
+ if (!first)
+ sourceBuilder.Append(", ");
+ first = false;
+ sourceBuilder.Append("typeof(");
+ sourceBuilder.Append(qualifiedName);
+ sourceBuilder.Append(")");
+ }
+
+ sourceBuilder.Append("})]\n");
+
+ context.AddSource("AssemblyScriptTypes_Generated",
+ SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
+ }
+
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ }
+
+ private static string RelativeToDir(string path, string dir)
+ {
+ // Make sure the directory ends with a path separator
+ dir = Path.Combine(dir, " ").TrimEnd();
+
+ if (Path.DirectorySeparatorChar == '\\')
+ dir = dir.Replace("/", "\\") + "\\";
+
+ var fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute);
+ var relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute);
+
+ // MakeRelativeUri converts spaces to %20, hence why we need UnescapeDataString
+ return Uri.UnescapeDataString(relRoot.MakeRelativeUri(fullPath).ToString());
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
index 6015cb22b6..9b7b422276 100644
--- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs
@@ -2,7 +2,6 @@ using System;
using System.IO;
using System.Security;
using Microsoft.Build.Framework;
-using GodotTools.Core;
namespace GodotTools.BuildLogger
{
@@ -16,14 +15,14 @@ namespace GodotTools.BuildLogger
public void Initialize(IEventSource eventSource)
{
if (null == Parameters)
- throw new LoggerException("Log directory was not set.");
+ throw new LoggerException("Log directory parameter not specified.");
var parameters = Parameters.Split(new[] { ';' });
string logDir = parameters[0];
if (string.IsNullOrEmpty(logDir))
- throw new LoggerException("Log directory was not set.");
+ throw new LoggerException("Log directory parameter is empty.");
if (parameters.Length > 1)
throw new LoggerException("Too many parameters passed.");
@@ -52,36 +51,46 @@ namespace GodotTools.BuildLogger
{
throw new LoggerException("Failed to create log file: " + ex.Message);
}
- else
- {
- // Unexpected failure
- throw;
- }
+
+ // Unexpected failure
+ throw;
}
eventSource.ProjectStarted += eventSource_ProjectStarted;
- eventSource.TaskStarted += eventSource_TaskStarted;
+ eventSource.ProjectFinished += eventSource_ProjectFinished;
eventSource.MessageRaised += eventSource_MessageRaised;
eventSource.WarningRaised += eventSource_WarningRaised;
eventSource.ErrorRaised += eventSource_ErrorRaised;
- eventSource.ProjectFinished += eventSource_ProjectFinished;
}
- void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
+ private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
+ {
+ WriteLine(e.Message);
+ indent++;
+ }
+
+ private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
+ {
+ indent--;
+ WriteLine(e.Message);
+ }
+
+ private void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
{
string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): error {e.Code}: {e.Message}";
- if (e.ProjectFile.Length > 0)
+ if (!string.IsNullOrEmpty(e.ProjectFile))
line += $" [{e.ProjectFile}]";
WriteLine(line);
string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
- $@"{e.Code.CsvEscape()},{e.Message.CsvEscape()},{e.ProjectFile.CsvEscape()}";
+ $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," +
+ $"{e.ProjectFile?.CsvEscape() ?? string.Empty}";
issuesStreamWriter.WriteLine(errorLine);
}
- void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
+ private void eventSource_WarningRaised(object sender, BuildWarningEventArgs e)
{
string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): warning {e.Code}: {e.Message}";
@@ -90,8 +99,9 @@ namespace GodotTools.BuildLogger
WriteLine(line);
- string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber},{e.Code.CsvEscape()}," +
- $@"{e.Message.CsvEscape()},{(e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty)}";
+ string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," +
+ $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," +
+ $"{e.ProjectFile?.CsvEscape() ?? string.Empty}";
issuesStreamWriter.WriteLine(warningLine);
}
@@ -107,40 +117,6 @@ namespace GodotTools.BuildLogger
}
}
- private void eventSource_TaskStarted(object sender, TaskStartedEventArgs e)
- {
- // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName
- // To keep this log clean, this logger will ignore these events.
- }
-
- private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
- {
- WriteLine(e.Message);
- indent++;
- }
-
- private void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e)
- {
- indent--;
- WriteLine(e.Message);
- }
-
- /// <summary>
- /// Write a line to the log, adding the SenderName
- /// </summary>
- private void WriteLineWithSender(string line, BuildEventArgs e)
- {
- if (0 == string.Compare(e.SenderName, "MSBuild", StringComparison.OrdinalIgnoreCase))
- {
- // Well, if the sender name is MSBuild, let's leave it out for prettiness
- WriteLine(line);
- }
- else
- {
- WriteLine(e.SenderName + ": " + line);
- }
- }
-
/// <summary>
/// Write a line to the log, adding the SenderName and Message
/// (these parameters are on all MSBuild event argument objects)
@@ -183,4 +159,17 @@ namespace GodotTools.BuildLogger
private StreamWriter issuesStreamWriter;
private int indent;
}
+
+ internal static class StringExtensions
+ {
+ public static string CsvEscape(this string value, char delimiter = ',')
+ {
+ bool hasSpecialChar = value.IndexOfAny(new[] { '\"', '\n', '\r', delimiter }) != -1;
+
+ if (hasSpecialChar)
+ return "\"" + value.Replace("\"", "\"\"") + "\"";
+
+ return value;
+ }
+ }
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
index 8fdd485209..0afec970c6 100644
--- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj
@@ -1,60 +1,10 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid>
- <OutputType>Library</OutputType>
- <AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>GodotTools.BuildLogger</RootNamespace>
- <AssemblyName>GodotTools.BuildLogger</AssemblyName>
- <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
- <FileAlignment>512</FileAlignment>
- <LangVersion>7</LangVersion>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <LangVersion>7.2</LangVersion>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <PlatformTarget>AnyCPU</PlatformTarget>
- <DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug\</OutputPath>
- <DefineConstants>DEBUG;TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <PlatformTarget>AnyCPU</PlatformTarget>
- <DebugType>portable</DebugType>
- <Optimize>true</Optimize>
- <OutputPath>bin\Release\</OutputPath>
- <DefineConstants>TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="Microsoft.Build.Framework" />
- <Reference Include="System" />
- <Reference Include="System.Core" />
- <Reference Include="System.Data" />
- <Reference Include="System.Xml" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="GodotBuildLogger.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- </ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
- <Project>{639e48bd-44e5-4091-8edd-22d36dc0768d}</Project>
- <Name>GodotTools.Core</Name>
- </ProjectReference>
+ <PackageReference Include="Microsoft.Build.Framework" Version="16.5.0" />
</ItemGroup>
- <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
- <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
- Other similar extension points exist, see Microsoft.Common.targets.
- <Target Name="BeforeBuild">
- </Target>
- <Target Name="AfterBuild">
- </Target>
- -->
</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs
deleted file mode 100644
index 8717c4901e..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("GodotTools.BuildLogger")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("Godot Engine contributors")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("6CE9A984-37B1-4F8A-8FE9-609F05F071B3")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs b/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs
new file mode 100644
index 0000000000..e1ccf0454a
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs
@@ -0,0 +1,30 @@
+using System.IO;
+
+namespace GodotTools.Core
+{
+ public static class FileUtils
+ {
+ public static void SaveBackupCopy(string filePath)
+ {
+ string backupPathBase = filePath + ".old";
+ string backupPath = backupPathBase;
+
+ const int maxAttempts = 5;
+ int attempt = 1;
+
+ while (File.Exists(backupPath) && attempt <= maxAttempts)
+ {
+ backupPath = backupPathBase + "." + (attempt);
+ attempt++;
+ }
+
+ if (attempt > maxAttempts + 1)
+ {
+ // Overwrite the oldest one
+ backupPath = backupPathBase;
+ }
+
+ File.Copy(filePath, backupPath, overwrite: true);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
index 2c35ef540a..d6d8962f90 100644
--- a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj
@@ -1,39 +1,7 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid>
- <OutputType>Library</OutputType>
- <RootNamespace>GodotTools.Core</RootNamespace>
- <AssemblyName>GodotTools.Core</AssemblyName>
- <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
- <LangVersion>7</LangVersion>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <LangVersion>7.2</LangVersion>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>full</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug</OutputPath>
- <DefineConstants>DEBUG;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <Optimize>true</Optimize>
- <OutputPath>bin\Release</OutputPath>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="System" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="ProcessExtensions.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="StringExtensions.cs" />
- </ItemGroup>
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs
deleted file mode 100644
index 699ae6e741..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.Core/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle("GodotTools.Core")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("Godot Engine contributors")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-
-[assembly: AssemblyVersion("1.0.*")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
index b531b6aeee..b217ae4bf7 100644
--- a/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/StringExtensions.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Runtime.InteropServices;
namespace GodotTools.Core
{
@@ -14,47 +15,52 @@ namespace GodotTools.Core
if (Path.DirectorySeparatorChar == '\\')
dir = dir.Replace("/", "\\") + "\\";
- Uri fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute);
- Uri relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute);
+ var fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute);
+ var relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute);
- return relRoot.MakeRelativeUri(fullPath).ToString();
+ // MakeRelativeUri converts spaces to %20, hence why we need UnescapeDataString
+ return Uri.UnescapeDataString(relRoot.MakeRelativeUri(fullPath).ToString());
}
public static string NormalizePath(this string path)
{
+ if (string.IsNullOrEmpty(path))
+ return path;
+
bool rooted = path.IsAbsolutePath();
path = path.Replace('\\', '/');
+ path = path[path.Length - 1] == '/' ? path.Substring(0, path.Length - 1) : path;
- string[] parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
+ string[] parts = path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim();
- return rooted ? Path.DirectorySeparatorChar.ToString() + path : path;
+ if (!rooted)
+ return path;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ string maybeDrive = parts[0];
+ if (maybeDrive.Length == 2 && maybeDrive[1] == ':')
+ return path; // Already has drive letter
+ }
+
+ return Path.DirectorySeparatorChar + path;
}
- private static readonly string driveRoot = Path.GetPathRoot(Environment.CurrentDirectory);
+ private static readonly string DriveRoot = Path.GetPathRoot(Environment.CurrentDirectory);
public static bool IsAbsolutePath(this string path)
{
return path.StartsWith("/", StringComparison.Ordinal) ||
path.StartsWith("\\", StringComparison.Ordinal) ||
- path.StartsWith(driveRoot, StringComparison.Ordinal);
- }
-
- public static string CsvEscape(this string value, char delimiter = ',')
- {
- bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1;
-
- if (hasSpecialChar)
- return "\"" + value.Replace("\"", "\"\"") + "\"";
-
- return value;
+ path.StartsWith(DriveRoot, StringComparison.Ordinal);
}
- public static string ToSafeDirName(this string dirName, bool allowDirSeparator)
+ public static string ToSafeDirName(this string dirName, bool allowDirSeparator = false)
{
- var invalidChars = new List<string> { ":", "*", "?", "\"", "<", ">", "|" };
+ var invalidChars = new List<string> {":", "*", "?", "\"", "<", ">", "|"};
if (allowDirSeparator)
{
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs
deleted file mode 100644
index 7a2ff2ca56..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ConsoleLogger.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-
-namespace GodotTools.IdeConnection
-{
- public class ConsoleLogger : ILogger
- {
- public void LogDebug(string message)
- {
- Console.WriteLine("DEBUG: " + message);
- }
-
- public void LogInfo(string message)
- {
- Console.WriteLine("INFO: " + message);
- }
-
- public void LogWarning(string message)
- {
- Console.WriteLine("WARN: " + message);
- }
-
- public void LogError(string message)
- {
- Console.WriteLine("ERROR: " + message);
- }
-
- public void LogError(string message, Exception e)
- {
- Console.WriteLine("EXCEPTION: " + message);
- Console.WriteLine(e);
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs
deleted file mode 100644
index be89638241..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeBase.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System;
-using Path = System.IO.Path;
-
-namespace GodotTools.IdeConnection
-{
- public class GodotIdeBase : IDisposable
- {
- private ILogger logger;
-
- public ILogger Logger
- {
- get => logger ?? (logger = new ConsoleLogger());
- set => logger = value;
- }
-
- private readonly string projectMetadataDir;
-
- protected const string MetaFileName = "ide_server_meta.txt";
- protected string MetaFilePath => Path.Combine(projectMetadataDir, MetaFileName);
-
- private GodotIdeConnection connection;
- protected readonly object ConnectionLock = new object();
-
- public bool IsDisposed { get; private set; } = false;
-
- public bool IsConnected => connection != null && !connection.IsDisposed && connection.IsConnected;
-
- public event Action Connected
- {
- add
- {
- if (connection != null && !connection.IsDisposed)
- connection.Connected += value;
- }
- remove
- {
- if (connection != null && !connection.IsDisposed)
- connection.Connected -= value;
- }
- }
-
- protected GodotIdeConnection Connection
- {
- get => connection;
- set
- {
- connection?.Dispose();
- connection = value;
- }
- }
-
- protected GodotIdeBase(string projectMetadataDir)
- {
- this.projectMetadataDir = projectMetadataDir;
- }
-
- protected void DisposeConnection()
- {
- lock (ConnectionLock)
- {
- connection?.Dispose();
- }
- }
-
- ~GodotIdeBase()
- {
- Dispose(disposing: false);
- }
-
- public void Dispose()
- {
- if (IsDisposed)
- return;
-
- lock (ConnectionLock)
- {
- if (IsDisposed) // lock may not be fair
- return;
- IsDisposed = true;
- }
-
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- {
- connection?.Dispose();
- }
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs
deleted file mode 100644
index 2bf3b83c75..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeClient.cs
+++ /dev/null
@@ -1,219 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Net;
-using System.Net.Sockets;
-using System.Threading;
-
-namespace GodotTools.IdeConnection
-{
- public abstract class GodotIdeClient : GodotIdeBase
- {
- protected GodotIdeMetadata GodotIdeMetadata;
-
- private readonly FileSystemWatcher fsWatcher;
-
- protected GodotIdeClient(string projectMetadataDir) : base(projectMetadataDir)
- {
- messageHandlers = InitializeMessageHandlers();
-
- // FileSystemWatcher requires an existing directory
- if (!File.Exists(projectMetadataDir))
- Directory.CreateDirectory(projectMetadataDir);
-
- fsWatcher = new FileSystemWatcher(projectMetadataDir, MetaFileName);
- }
-
- private void OnMetaFileChanged(object sender, FileSystemEventArgs e)
- {
- if (IsDisposed)
- return;
-
- lock (ConnectionLock)
- {
- if (IsDisposed)
- return;
-
- if (!File.Exists(MetaFilePath))
- return;
-
- var metadata = ReadMetadataFile();
-
- if (metadata != null && metadata != GodotIdeMetadata)
- {
- GodotIdeMetadata = metadata.Value;
- ConnectToServer();
- }
- }
- }
-
- private void OnMetaFileDeleted(object sender, FileSystemEventArgs e)
- {
- if (IsDisposed)
- return;
-
- if (IsConnected)
- DisposeConnection();
-
- // The file may have been re-created
-
- lock (ConnectionLock)
- {
- if (IsDisposed)
- return;
-
- if (IsConnected || !File.Exists(MetaFilePath))
- return;
-
- var metadata = ReadMetadataFile();
-
- if (metadata != null)
- {
- GodotIdeMetadata = metadata.Value;
- ConnectToServer();
- }
- }
- }
-
- private GodotIdeMetadata? ReadMetadataFile()
- {
- using (var reader = File.OpenText(MetaFilePath))
- {
- string portStr = reader.ReadLine();
-
- if (portStr == null)
- return null;
-
- string editorExecutablePath = reader.ReadLine();
-
- if (editorExecutablePath == null)
- return null;
-
- if (!int.TryParse(portStr, out int port))
- return null;
-
- return new GodotIdeMetadata(port, editorExecutablePath);
- }
- }
-
- private void ConnectToServer()
- {
- var tcpClient = new TcpClient();
-
- Connection = new GodotIdeConnectionClient(tcpClient, HandleMessage);
- Connection.Logger = Logger;
-
- try
- {
- Logger.LogInfo("Connecting to Godot Ide Server");
-
- tcpClient.Connect(IPAddress.Loopback, GodotIdeMetadata.Port);
-
- Logger.LogInfo("Connection open with Godot Ide Server");
-
- var clientThread = new Thread(Connection.Start)
- {
- IsBackground = true,
- Name = "Godot Ide Connection Client"
- };
- clientThread.Start();
- }
- catch (SocketException e)
- {
- if (e.SocketErrorCode == SocketError.ConnectionRefused)
- Logger.LogError("The connection to the Godot Ide Server was refused");
- else
- throw;
- }
- }
-
- public void Start()
- {
- Logger.LogInfo("Starting Godot Ide Client");
-
- fsWatcher.Changed += OnMetaFileChanged;
- fsWatcher.Deleted += OnMetaFileDeleted;
- fsWatcher.EnableRaisingEvents = true;
-
- lock (ConnectionLock)
- {
- if (IsDisposed)
- return;
-
- if (!File.Exists(MetaFilePath))
- {
- Logger.LogInfo("There is no Godot Ide Server running");
- return;
- }
-
- var metadata = ReadMetadataFile();
-
- if (metadata != null)
- {
- GodotIdeMetadata = metadata.Value;
- ConnectToServer();
- }
- else
- {
- Logger.LogError("Failed to read Godot Ide metadata file");
- }
- }
- }
-
- public bool WriteMessage(Message message)
- {
- return Connection.WriteMessage(message);
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- fsWatcher?.Dispose();
- }
- }
-
- protected virtual bool HandleMessage(Message message)
- {
- if (messageHandlers.TryGetValue(message.Id, out var action))
- {
- action(message.Arguments);
- return true;
- }
-
- return false;
- }
-
- private readonly Dictionary<string, Action<string[]>> messageHandlers;
-
- private Dictionary<string, Action<string[]>> InitializeMessageHandlers()
- {
- return new Dictionary<string, Action<string[]>>
- {
- ["OpenFile"] = args =>
- {
- switch (args.Length)
- {
- case 1:
- OpenFile(file: args[0]);
- return;
- case 2:
- OpenFile(file: args[0], line: int.Parse(args[1]));
- return;
- case 3:
- OpenFile(file: args[0], line: int.Parse(args[1]), column: int.Parse(args[2]));
- return;
- default:
- throw new ArgumentException();
- }
- }
- };
- }
-
- protected abstract void OpenFile(string file);
- protected abstract void OpenFile(string file, int line);
- protected abstract void OpenFile(string file, int line, int column);
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs
deleted file mode 100644
index 6441be8d6e..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnection.cs
+++ /dev/null
@@ -1,207 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Net.Sockets;
-using System.Text;
-
-namespace GodotTools.IdeConnection
-{
- public abstract class GodotIdeConnection : IDisposable
- {
- protected const string Version = "1.0";
-
- protected static readonly string ClientHandshake = $"Godot Ide Client Version {Version}";
- protected static readonly string ServerHandshake = $"Godot Ide Server Version {Version}";
-
- private const int ClientWriteTimeout = 8000;
- private readonly TcpClient tcpClient;
-
- private TextReader clientReader;
- private TextWriter clientWriter;
-
- private readonly object writeLock = new object();
-
- private readonly Func<Message, bool> messageHandler;
-
- public event Action Connected;
-
- private ILogger logger;
-
- public ILogger Logger
- {
- get => logger ?? (logger = new ConsoleLogger());
- set => logger = value;
- }
-
- public bool IsDisposed { get; private set; } = false;
-
- public bool IsConnected => tcpClient.Client != null && tcpClient.Client.Connected;
-
- protected GodotIdeConnection(TcpClient tcpClient, Func<Message, bool> messageHandler)
- {
- this.tcpClient = tcpClient;
- this.messageHandler = messageHandler;
- }
-
- public void Start()
- {
- try
- {
- if (!StartConnection())
- return;
-
- string messageLine;
- while ((messageLine = ReadLine()) != null)
- {
- if (!MessageParser.TryParse(messageLine, out Message msg))
- {
- Logger.LogError($"Received message with invalid format: {messageLine}");
- continue;
- }
-
- Logger.LogDebug($"Received message: {msg}");
-
- if (msg.Id == "close")
- {
- Logger.LogInfo("Closing connection");
- return;
- }
-
- try
- {
- try
- {
- Debug.Assert(messageHandler != null);
-
- if (!messageHandler(msg))
- Logger.LogError($"Received unknown message: {msg}");
- }
- catch (Exception e)
- {
- Logger.LogError($"Message handler for '{msg}' failed with exception", e);
- }
- }
- catch (Exception e)
- {
- Logger.LogError($"Exception thrown from message handler. Message: {msg}", e);
- }
- }
- }
- catch (Exception e)
- {
- Logger.LogError($"Unhandled exception in the Godot Ide Connection thread", e);
- }
- finally
- {
- Dispose();
- }
- }
-
- private bool StartConnection()
- {
- NetworkStream clientStream = tcpClient.GetStream();
-
- clientReader = new StreamReader(clientStream, Encoding.UTF8);
-
- lock (writeLock)
- clientWriter = new StreamWriter(clientStream, Encoding.UTF8);
-
- clientStream.WriteTimeout = ClientWriteTimeout;
-
- if (!WriteHandshake())
- {
- Logger.LogError("Could not write handshake");
- return false;
- }
-
- if (!IsValidResponseHandshake(ReadLine()))
- {
- Logger.LogError("Received invalid handshake");
- return false;
- }
-
- Connected?.Invoke();
-
- Logger.LogInfo("Godot Ide connection started");
-
- return true;
- }
-
- private string ReadLine()
- {
- try
- {
- return clientReader?.ReadLine();
- }
- catch (Exception e)
- {
- if (IsDisposed)
- {
- var se = e as SocketException ?? e.InnerException as SocketException;
- if (se != null && se.SocketErrorCode == SocketError.Interrupted)
- return null;
- }
-
- throw;
- }
- }
-
- public bool WriteMessage(Message message)
- {
- Logger.LogDebug($"Sending message {message}");
-
- var messageComposer = new MessageComposer();
-
- messageComposer.AddArgument(message.Id);
- foreach (string argument in message.Arguments)
- messageComposer.AddArgument(argument);
-
- return WriteLine(messageComposer.ToString());
- }
-
- protected bool WriteLine(string text)
- {
- if (clientWriter == null || IsDisposed || !IsConnected)
- return false;
-
- lock (writeLock)
- {
- try
- {
- clientWriter.WriteLine(text);
- clientWriter.Flush();
- }
- catch (Exception e)
- {
- if (!IsDisposed)
- {
- var se = e as SocketException ?? e.InnerException as SocketException;
- if (se != null && se.SocketErrorCode == SocketError.Shutdown)
- Logger.LogInfo("Client disconnected ungracefully");
- else
- Logger.LogError("Exception thrown when trying to write to client", e);
-
- Dispose();
- }
- }
- }
-
- return true;
- }
-
- protected abstract bool WriteHandshake();
- protected abstract bool IsValidResponseHandshake(string handshakeLine);
-
- public void Dispose()
- {
- if (IsDisposed)
- return;
-
- IsDisposed = true;
-
- clientReader?.Dispose();
- clientWriter?.Dispose();
- ((IDisposable)tcpClient)?.Dispose();
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs
deleted file mode 100644
index 1b11a14358..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionClient.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Net.Sockets;
-using System.Threading.Tasks;
-
-namespace GodotTools.IdeConnection
-{
- public class GodotIdeConnectionClient : GodotIdeConnection
- {
- public GodotIdeConnectionClient(TcpClient tcpClient, Func<Message, bool> messageHandler)
- : base(tcpClient, messageHandler)
- {
- }
-
- protected override bool WriteHandshake()
- {
- return WriteLine(ClientHandshake);
- }
-
- protected override bool IsValidResponseHandshake(string handshakeLine)
- {
- return handshakeLine == ServerHandshake;
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs
deleted file mode 100644
index aa98dc7ca3..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeConnectionServer.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Net.Sockets;
-using System.Threading.Tasks;
-
-namespace GodotTools.IdeConnection
-{
- public class GodotIdeConnectionServer : GodotIdeConnection
- {
- public GodotIdeConnectionServer(TcpClient tcpClient, Func<Message, bool> messageHandler)
- : base(tcpClient, messageHandler)
- {
- }
-
- protected override bool WriteHandshake()
- {
- return WriteLine(ServerHandshake);
- }
-
- protected override bool IsValidResponseHandshake(string handshakeLine)
- {
- return handshakeLine == ClientHandshake;
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj
deleted file mode 100644
index 8454535fba..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotTools.IdeConnection.csproj
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
- <PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid>
- <OutputType>Library</OutputType>
- <AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>GodotTools.IdeConnection</RootNamespace>
- <AssemblyName>GodotTools.IdeConnection</AssemblyName>
- <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
- <FileAlignment>512</FileAlignment>
- <LangVersion>7</LangVersion>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <PlatformTarget>AnyCPU</PlatformTarget>
- <DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug\</OutputPath>
- <DefineConstants>DEBUG;TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <PlatformTarget>AnyCPU</PlatformTarget>
- <DebugType>portable</DebugType>
- <Optimize>true</Optimize>
- <OutputPath>bin\Release\</OutputPath>
- <DefineConstants>TRACE</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="System" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="ConsoleLogger.cs" />
- <Compile Include="GodotIdeMetadata.cs" />
- <Compile Include="GodotIdeBase.cs" />
- <Compile Include="GodotIdeClient.cs" />
- <Compile Include="GodotIdeConnection.cs" />
- <Compile Include="GodotIdeConnectionClient.cs" />
- <Compile Include="GodotIdeConnectionServer.cs" />
- <Compile Include="ILogger.cs" />
- <Compile Include="Message.cs" />
- <Compile Include="MessageComposer.cs" />
- <Compile Include="MessageParser.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- </ItemGroup>
- <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
-</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs
deleted file mode 100644
index f24d324ae3..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Message.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Linq;
-
-namespace GodotTools.IdeConnection
-{
- public struct Message
- {
- public string Id { get; set; }
- public string[] Arguments { get; set; }
-
- public Message(string id, params string[] arguments)
- {
- Id = id;
- Arguments = arguments;
- }
-
- public override string ToString()
- {
- return $"(Id: '{Id}', Arguments: '{string.Join(",", Arguments)}')";
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs
deleted file mode 100644
index 30ffe7a06e..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageComposer.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System.Linq;
-using System.Text;
-
-namespace GodotTools.IdeConnection
-{
- public class MessageComposer
- {
- private readonly StringBuilder stringBuilder = new StringBuilder();
-
- private static readonly char[] CharsToEscape = { '\\', '"' };
-
- public void AddArgument(string argument)
- {
- AddArgument(argument, quoted: argument.Contains(","));
- }
-
- public void AddArgument(string argument, bool quoted)
- {
- if (stringBuilder.Length > 0)
- stringBuilder.Append(',');
-
- if (quoted)
- {
- stringBuilder.Append('"');
-
- foreach (char @char in argument)
- {
- if (CharsToEscape.Contains(@char))
- stringBuilder.Append('\\');
- stringBuilder.Append(@char);
- }
-
- stringBuilder.Append('"');
- }
- else
- {
- stringBuilder.Append(argument);
- }
- }
-
- public override string ToString()
- {
- return stringBuilder.ToString();
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs
deleted file mode 100644
index 4365d69989..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/MessageParser.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace GodotTools.IdeConnection
-{
- public static class MessageParser
- {
- public static bool TryParse(string messageLine, out Message message)
- {
- var arguments = new List<string>();
- var stringBuilder = new StringBuilder();
-
- bool expectingArgument = true;
-
- for (int i = 0; i < messageLine.Length; i++)
- {
- char @char = messageLine[i];
-
- if (@char == ',')
- {
- if (expectingArgument)
- arguments.Add(string.Empty);
-
- expectingArgument = true;
- continue;
- }
-
- bool quoted = false;
-
- if (messageLine[i] == '"')
- {
- quoted = true;
- i++;
- }
-
- while (i < messageLine.Length)
- {
- @char = messageLine[i];
-
- if (quoted && @char == '"')
- {
- i++;
- break;
- }
-
- if (@char == '\\')
- {
- i++;
- if (i < messageLine.Length)
- break;
-
- stringBuilder.Append(messageLine[i]);
- }
- else if (!quoted && @char == ',')
- {
- break; // We don't increment the counter to allow the colon to be parsed after this
- }
- else
- {
- stringBuilder.Append(@char);
- }
-
- i++;
- }
-
- arguments.Add(stringBuilder.ToString());
- stringBuilder.Clear();
-
- expectingArgument = false;
- }
-
- if (arguments.Count == 0)
- {
- message = new Message();
- return false;
- }
-
- message = new Message
- {
- Id = arguments[0],
- Arguments = arguments.Skip(1).ToArray()
- };
-
- return true;
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs
deleted file mode 100644
index c7c00e66a2..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("GodotTools.IdeConnection")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("Godot Engine contributors")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("92600954-25F0-4291-8E11-1FEE9FC4BE20")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs
new file mode 100644
index 0000000000..3cb6a6687e
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/ForwarderMessageHandler.cs
@@ -0,0 +1,57 @@
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging.Utils;
+
+namespace GodotTools.IdeMessaging.CLI
+{
+ public class ForwarderMessageHandler : IMessageHandler
+ {
+ private readonly StreamWriter outputWriter;
+ private readonly SemaphoreSlim outputWriteSem = new SemaphoreSlim(1);
+
+ public ForwarderMessageHandler(StreamWriter outputWriter)
+ {
+ this.outputWriter = outputWriter;
+ }
+
+ public async Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger)
+ {
+ await WriteRequestToOutput(id, content);
+ return new MessageContent(MessageStatus.RequestNotSupported, "null");
+ }
+
+ private async Task WriteRequestToOutput(string id, MessageContent content)
+ {
+ using (await outputWriteSem.UseAsync())
+ {
+ await outputWriter.WriteLineAsync("======= Request =======");
+ await outputWriter.WriteLineAsync(id);
+ await outputWriter.WriteLineAsync(content.Body.Count(c => c == '\n').ToString());
+ await outputWriter.WriteLineAsync(content.Body);
+ await outputWriter.WriteLineAsync("=======================");
+ await outputWriter.FlushAsync();
+ }
+ }
+
+ public async Task WriteResponseToOutput(string id, MessageContent content)
+ {
+ using (await outputWriteSem.UseAsync())
+ {
+ await outputWriter.WriteLineAsync("======= Response =======");
+ await outputWriter.WriteLineAsync(id);
+ await outputWriter.WriteLineAsync(content.Body.Count(c => c == '\n').ToString());
+ await outputWriter.WriteLineAsync(content.Body);
+ await outputWriter.WriteLineAsync("========================");
+ await outputWriter.FlushAsync();
+ }
+ }
+
+ public async Task WriteLineToOutput(string eventName)
+ {
+ using (await outputWriteSem.UseAsync())
+ await outputWriter.WriteLineAsync($"======= {eventName} =======");
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj
new file mode 100644
index 0000000000..ae78da27bc
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <ProjectGuid>{B06C2951-C8E3-4F28-80B2-717CF327EB19}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net472</TargetFramework>
+ <LangVersion>7.2</LangVersion>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" />
+ </ItemGroup>
+ <ItemGroup>
+ <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+ </ItemGroup>
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs
new file mode 100644
index 0000000000..4db71500da
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/Program.cs
@@ -0,0 +1,218 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging.Requests;
+using Newtonsoft.Json;
+
+namespace GodotTools.IdeMessaging.CLI
+{
+ internal static class Program
+ {
+ private static readonly ILogger Logger = new CustomLogger();
+
+ public static int Main(string[] args)
+ {
+ try
+ {
+ var mainTask = StartAsync(args, Console.OpenStandardInput(), Console.OpenStandardOutput());
+ mainTask.Wait();
+ return mainTask.Result;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError("Unhandled exception: ", ex);
+ return 1;
+ }
+ }
+
+ private static async Task<int> StartAsync(string[] args, Stream inputStream, Stream outputStream)
+ {
+ var inputReader = new StreamReader(inputStream, Encoding.UTF8);
+ var outputWriter = new StreamWriter(outputStream, Encoding.UTF8);
+
+ try
+ {
+ if (args.Length == 0)
+ {
+ Logger.LogError("Expected at least 1 argument");
+ return 1;
+ }
+
+ string godotProjectDir = args[0];
+
+ if (!Directory.Exists(godotProjectDir))
+ {
+ Logger.LogError($"The specified Godot project directory does not exist: {godotProjectDir}");
+ return 1;
+ }
+
+ var forwarder = new ForwarderMessageHandler(outputWriter);
+
+ using (var fwdClient = new Client("VisualStudioCode", godotProjectDir, forwarder, Logger))
+ {
+ fwdClient.Start();
+
+ // ReSharper disable AccessToDisposedClosure
+ fwdClient.Connected += async () => await forwarder.WriteLineToOutput("Event=Connected");
+ fwdClient.Disconnected += async () => await forwarder.WriteLineToOutput("Event=Disconnected");
+ // ReSharper restore AccessToDisposedClosure
+
+ // TODO: Await connected with timeout
+
+ while (!fwdClient.IsDisposed)
+ {
+ string firstLine = await inputReader.ReadLineAsync();
+
+ if (firstLine == null || firstLine == "QUIT")
+ goto ExitMainLoop;
+
+ string messageId = firstLine;
+
+ string messageArgcLine = await inputReader.ReadLineAsync();
+
+ if (messageArgcLine == null)
+ {
+ Logger.LogInfo("EOF when expecting argument count");
+ goto ExitMainLoop;
+ }
+
+ if (!int.TryParse(messageArgcLine, out int messageArgc))
+ {
+ Logger.LogError("Received invalid line for argument count: " + firstLine);
+ continue;
+ }
+
+ var body = new StringBuilder();
+
+ for (int i = 0; i < messageArgc; i++)
+ {
+ string bodyLine = await inputReader.ReadLineAsync();
+
+ if (bodyLine == null)
+ {
+ Logger.LogInfo($"EOF when expecting body line #{i + 1}");
+ goto ExitMainLoop;
+ }
+
+ body.AppendLine(bodyLine);
+ }
+
+ var response = await SendRequest(fwdClient, messageId, new MessageContent(MessageStatus.Ok, body.ToString()));
+
+ if (response == null)
+ {
+ Logger.LogError($"Failed to write message to the server: {messageId}");
+ }
+ else
+ {
+ var content = new MessageContent(response.Status, JsonConvert.SerializeObject(response));
+ await forwarder.WriteResponseToOutput(messageId, content);
+ }
+ }
+
+ ExitMainLoop:
+
+ await forwarder.WriteLineToOutput("Event=Quit");
+ }
+
+ return 0;
+ }
+ catch (Exception e)
+ {
+ Logger.LogError("Unhandled exception", e);
+ return 1;
+ }
+ }
+
+ private static async Task<Response> SendRequest(Client client, string id, MessageContent content)
+ {
+ var handlers = new Dictionary<string, Func<Task<Response>>>
+ {
+ [PlayRequest.Id] = async () =>
+ {
+ var request = JsonConvert.DeserializeObject<PlayRequest>(content.Body);
+ return await client.SendRequest<PlayResponse>(request);
+ },
+ [DebugPlayRequest.Id] = async () =>
+ {
+ var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body);
+ return await client.SendRequest<DebugPlayResponse>(request);
+ },
+ [ReloadScriptsRequest.Id] = async () =>
+ {
+ var request = JsonConvert.DeserializeObject<ReloadScriptsRequest>(content.Body);
+ return await client.SendRequest<ReloadScriptsResponse>(request);
+ },
+ [CodeCompletionRequest.Id] = async () =>
+ {
+ var request = JsonConvert.DeserializeObject<CodeCompletionRequest>(content.Body);
+ return await client.SendRequest<CodeCompletionResponse>(request);
+ }
+ };
+
+ if (handlers.TryGetValue(id, out var handler))
+ return await handler();
+
+ Console.WriteLine("INVALID REQUEST");
+ return null;
+ }
+
+ private class CustomLogger : ILogger
+ {
+ private static string ThisAppPath => Assembly.GetExecutingAssembly().Location;
+ private static string ThisAppPathWithoutExtension => Path.ChangeExtension(ThisAppPath, null);
+
+ private static readonly string LogPath = $"{ThisAppPathWithoutExtension}.log";
+
+ private static StreamWriter NewWriter() => new StreamWriter(LogPath, append: true, encoding: Encoding.UTF8);
+
+ private static void Log(StreamWriter writer, string message)
+ {
+ writer.WriteLine($"{DateTime.Now:HH:mm:ss.ffffff}: {message}");
+ }
+
+ public void LogDebug(string message)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "DEBUG: " + message);
+ }
+ }
+
+ public void LogInfo(string message)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "INFO: " + message);
+ }
+ }
+
+ public void LogWarning(string message)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "WARN: " + message);
+ }
+ }
+
+ public void LogError(string message)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "ERROR: " + message);
+ }
+ }
+
+ public void LogError(string message, Exception e)
+ {
+ using (var writer = NewWriter())
+ {
+ Log(writer, "EXCEPTION: " + message + '\n' + e);
+ }
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs
new file mode 100644
index 0000000000..0f50c90531
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Client.cs
@@ -0,0 +1,351 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using Newtonsoft.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging.Requests;
+using GodotTools.IdeMessaging.Utils;
+
+namespace GodotTools.IdeMessaging
+{
+ // ReSharper disable once UnusedType.Global
+ public sealed class Client : IDisposable
+ {
+ private readonly ILogger logger;
+
+ private readonly string identity;
+
+ private string MetaFilePath { get; }
+ private DateTime? metaFileModifiedTime;
+ private GodotIdeMetadata godotIdeMetadata;
+ private readonly FileSystemWatcher fsWatcher;
+
+ public string GodotEditorExecutablePath => godotIdeMetadata.EditorExecutablePath;
+
+ private readonly IMessageHandler messageHandler;
+
+ private Peer peer;
+ private readonly SemaphoreSlim connectionSem = new SemaphoreSlim(1);
+
+ private readonly Queue<NotifyAwaiter<bool>> clientConnectedAwaiters = new Queue<NotifyAwaiter<bool>>();
+ private readonly Queue<NotifyAwaiter<bool>> clientDisconnectedAwaiters = new Queue<NotifyAwaiter<bool>>();
+
+ // ReSharper disable once UnusedMember.Global
+ public async Task<bool> AwaitConnected()
+ {
+ var awaiter = new NotifyAwaiter<bool>();
+ clientConnectedAwaiters.Enqueue(awaiter);
+ return await awaiter;
+ }
+
+ // ReSharper disable once UnusedMember.Global
+ public async Task<bool> AwaitDisconnected()
+ {
+ var awaiter = new NotifyAwaiter<bool>();
+ clientDisconnectedAwaiters.Enqueue(awaiter);
+ return await awaiter;
+ }
+
+ // ReSharper disable once MemberCanBePrivate.Global
+ public bool IsDisposed { get; private set; }
+
+ // ReSharper disable once MemberCanBePrivate.Global
+ public bool IsConnected => peer != null && !peer.IsDisposed && peer.IsTcpClientConnected;
+
+ // ReSharper disable once EventNeverSubscribedTo.Global
+ public event Action Connected
+ {
+ add
+ {
+ if (peer != null && !peer.IsDisposed)
+ peer.Connected += value;
+ }
+ remove
+ {
+ if (peer != null && !peer.IsDisposed)
+ peer.Connected -= value;
+ }
+ }
+
+ // ReSharper disable once EventNeverSubscribedTo.Global
+ public event Action Disconnected
+ {
+ add
+ {
+ if (peer != null && !peer.IsDisposed)
+ peer.Disconnected += value;
+ }
+ remove
+ {
+ if (peer != null && !peer.IsDisposed)
+ peer.Disconnected -= value;
+ }
+ }
+
+ ~Client()
+ {
+ Dispose(disposing: false);
+ }
+
+ public async void Dispose()
+ {
+ if (IsDisposed)
+ return;
+
+ using (await connectionSem.UseAsync())
+ {
+ if (IsDisposed) // lock may not be fair
+ return;
+ IsDisposed = true;
+ }
+
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ peer?.Dispose();
+ fsWatcher?.Dispose();
+ }
+ }
+
+ public Client(string identity, string godotProjectDir, IMessageHandler messageHandler, ILogger logger)
+ {
+ this.identity = identity;
+ this.messageHandler = messageHandler;
+ this.logger = logger;
+
+ string projectMetadataDir = Path.Combine(godotProjectDir, ".godot", "mono", "metadata");
+
+ MetaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName);
+
+ // FileSystemWatcher requires an existing directory
+ if (!Directory.Exists(projectMetadataDir))
+ Directory.CreateDirectory(projectMetadataDir);
+
+ fsWatcher = new FileSystemWatcher(projectMetadataDir, GodotIdeMetadata.DefaultFileName);
+ }
+
+ private async void OnMetaFileChanged(object sender, FileSystemEventArgs e)
+ {
+ if (IsDisposed)
+ return;
+
+ using (await connectionSem.UseAsync())
+ {
+ if (IsDisposed)
+ return;
+
+ if (!File.Exists(MetaFilePath))
+ return;
+
+ var lastWriteTime = File.GetLastWriteTime(MetaFilePath);
+
+ if (lastWriteTime == metaFileModifiedTime)
+ return;
+
+ metaFileModifiedTime = lastWriteTime;
+
+ var metadata = ReadMetadataFile();
+
+ if (metadata != null && metadata != godotIdeMetadata)
+ {
+ godotIdeMetadata = metadata.Value;
+ _ = Task.Run(ConnectToServer);
+ }
+ }
+ }
+
+ private async void OnMetaFileDeleted(object sender, FileSystemEventArgs e)
+ {
+ if (IsDisposed)
+ return;
+
+ if (IsConnected)
+ {
+ using (await connectionSem.UseAsync())
+ peer?.Dispose();
+ }
+
+ // The file may have been re-created
+
+ using (await connectionSem.UseAsync())
+ {
+ if (IsDisposed)
+ return;
+
+ if (IsConnected || !File.Exists(MetaFilePath))
+ return;
+
+ var lastWriteTime = File.GetLastWriteTime(MetaFilePath);
+
+ if (lastWriteTime == metaFileModifiedTime)
+ return;
+
+ metaFileModifiedTime = lastWriteTime;
+
+ var metadata = ReadMetadataFile();
+
+ if (metadata != null)
+ {
+ godotIdeMetadata = metadata.Value;
+ _ = Task.Run(ConnectToServer);
+ }
+ }
+ }
+
+ private GodotIdeMetadata? ReadMetadataFile()
+ {
+ using (var fileStream = new FileStream(MetaFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ using (var reader = new StreamReader(fileStream))
+ {
+ string portStr = reader.ReadLine();
+
+ if (portStr == null)
+ return null;
+
+ string editorExecutablePath = reader.ReadLine();
+
+ if (editorExecutablePath == null)
+ return null;
+
+ if (!int.TryParse(portStr, out int port))
+ return null;
+
+ return new GodotIdeMetadata(port, editorExecutablePath);
+ }
+ }
+
+ private async Task AcceptClient(TcpClient tcpClient)
+ {
+ logger.LogDebug("Accept client...");
+
+ using (peer = new Peer(tcpClient, new ClientHandshake(), messageHandler, logger))
+ {
+ // ReSharper disable AccessToDisposedClosure
+ peer.Connected += () =>
+ {
+ logger.LogInfo("Connection open with Ide Client");
+
+ while (clientConnectedAwaiters.Count > 0)
+ clientConnectedAwaiters.Dequeue().SetResult(true);
+ };
+
+ peer.Disconnected += () =>
+ {
+ while (clientDisconnectedAwaiters.Count > 0)
+ clientDisconnectedAwaiters.Dequeue().SetResult(true);
+ };
+ // ReSharper restore AccessToDisposedClosure
+
+ try
+ {
+ if (!await peer.DoHandshake(identity))
+ {
+ logger.LogError("Handshake failed");
+ return;
+ }
+ }
+ catch (Exception e)
+ {
+ logger.LogError("Handshake failed with unhandled exception: ", e);
+ return;
+ }
+
+ await peer.Process();
+
+ logger.LogInfo("Connection closed with Ide Client");
+ }
+ }
+
+ private async Task ConnectToServer()
+ {
+ var tcpClient = new TcpClient();
+
+ try
+ {
+ logger.LogInfo("Connecting to Godot Ide Server");
+
+ await tcpClient.ConnectAsync(IPAddress.Loopback, godotIdeMetadata.Port);
+
+ logger.LogInfo("Connection open with Godot Ide Server");
+
+ await AcceptClient(tcpClient);
+ }
+ catch (SocketException e)
+ {
+ if (e.SocketErrorCode == SocketError.ConnectionRefused)
+ logger.LogError("The connection to the Godot Ide Server was refused");
+ else
+ throw;
+ }
+ }
+
+ // ReSharper disable once UnusedMember.Global
+ public async void Start()
+ {
+ fsWatcher.Created += OnMetaFileChanged;
+ fsWatcher.Changed += OnMetaFileChanged;
+ fsWatcher.Deleted += OnMetaFileDeleted;
+ fsWatcher.EnableRaisingEvents = true;
+
+ using (await connectionSem.UseAsync())
+ {
+ if (IsDisposed)
+ return;
+
+ if (IsConnected)
+ return;
+
+ if (!File.Exists(MetaFilePath))
+ {
+ logger.LogInfo("There is no Godot Ide Server running");
+ return;
+ }
+
+ var metadata = ReadMetadataFile();
+
+ if (metadata != null)
+ {
+ godotIdeMetadata = metadata.Value;
+ _ = Task.Run(ConnectToServer);
+ }
+ else
+ {
+ logger.LogError("Failed to read Godot Ide metadata file");
+ }
+ }
+ }
+
+ public async Task<TResponse> SendRequest<TResponse>(Request request)
+ where TResponse : Response, new()
+ {
+ if (!IsConnected)
+ {
+ logger.LogError("Cannot write request. Not connected to the Godot Ide Server.");
+ return null;
+ }
+
+ string body = JsonConvert.SerializeObject(request);
+ return await peer.SendRequest<TResponse>(request.Id, body);
+ }
+
+ public async Task<TResponse> SendRequest<TResponse>(string id, string body)
+ where TResponse : Response, new()
+ {
+ if (!IsConnected)
+ {
+ logger.LogError("Cannot write request. Not connected to the Godot Ide Server.");
+ return null;
+ }
+
+ return await peer.SendRequest<TResponse>(id, body);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs
new file mode 100644
index 0000000000..43041be7be
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientHandshake.cs
@@ -0,0 +1,44 @@
+using System.Text.RegularExpressions;
+
+namespace GodotTools.IdeMessaging
+{
+ public class ClientHandshake : IHandshake
+ {
+ private static readonly string ClientHandshakeBase = $"{Peer.ClientHandshakeName},Version={Peer.ProtocolVersionMajor}.{Peer.ProtocolVersionMinor}.{Peer.ProtocolVersionRevision}";
+ private static readonly string ServerHandshakePattern = $@"{Regex.Escape(Peer.ServerHandshakeName)},Version=([0-9]+)\.([0-9]+)\.([0-9]+),([_a-zA-Z][_a-zA-Z0-9]{{0,63}})";
+
+ public string GetHandshakeLine(string identity) => $"{ClientHandshakeBase},{identity}";
+
+ public bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger)
+ {
+ identity = null;
+
+ var match = Regex.Match(handshake, ServerHandshakePattern);
+
+ if (!match.Success)
+ return false;
+
+ if (!uint.TryParse(match.Groups[1].Value, out uint serverMajor) || Peer.ProtocolVersionMajor != serverMajor)
+ {
+ logger.LogDebug("Incompatible major version: " + match.Groups[1].Value);
+ return false;
+ }
+
+ if (!uint.TryParse(match.Groups[2].Value, out uint serverMinor) || Peer.ProtocolVersionMinor < serverMinor)
+ {
+ logger.LogDebug("Incompatible minor version: " + match.Groups[2].Value);
+ return false;
+ }
+
+ if (!uint.TryParse(match.Groups[3].Value, out uint _)) // Revision
+ {
+ logger.LogDebug("Incompatible revision build: " + match.Groups[3].Value);
+ return false;
+ }
+
+ identity = match.Groups[4].Value;
+
+ return true;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs
new file mode 100644
index 0000000000..64bcfd824c
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ClientMessageHandler.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging.Requests;
+using Newtonsoft.Json;
+
+namespace GodotTools.IdeMessaging
+{
+ // ReSharper disable once UnusedType.Global
+ public abstract class ClientMessageHandler : IMessageHandler
+ {
+ private readonly Dictionary<string, Peer.RequestHandler> requestHandlers;
+
+ protected ClientMessageHandler()
+ {
+ requestHandlers = InitializeRequestHandlers();
+ }
+
+ public async Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger)
+ {
+ if (!requestHandlers.TryGetValue(id, out var handler))
+ {
+ logger.LogError($"Received unknown request: {id}");
+ return new MessageContent(MessageStatus.RequestNotSupported, "null");
+ }
+
+ try
+ {
+ var response = await handler(peer, content);
+ return new MessageContent(response.Status, JsonConvert.SerializeObject(response));
+ }
+ catch (JsonException)
+ {
+ logger.LogError($"Received request with invalid body: {id}");
+ return new MessageContent(MessageStatus.InvalidRequestBody, "null");
+ }
+ }
+
+ private Dictionary<string, Peer.RequestHandler> InitializeRequestHandlers()
+ {
+ return new Dictionary<string, Peer.RequestHandler>
+ {
+ [OpenFileRequest.Id] = async (peer, content) =>
+ {
+ var request = JsonConvert.DeserializeObject<OpenFileRequest>(content.Body);
+ return await HandleOpenFile(request);
+ }
+ };
+ }
+
+ protected abstract Task<Response> HandleOpenFile(OpenFileRequest request);
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs
index d16daba0e2..686202e81e 100644
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/GodotIdeMetadata.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotIdeMetadata.cs
@@ -1,10 +1,12 @@
-namespace GodotTools.IdeConnection
+namespace GodotTools.IdeMessaging
{
- public struct GodotIdeMetadata
+ public readonly struct GodotIdeMetadata
{
public int Port { get; }
public string EditorExecutablePath { get; }
+ public const string DefaultFileName = "ide_messaging_meta.txt";
+
public GodotIdeMetadata(int port, string editorExecutablePath)
{
Port = port;
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj
new file mode 100644
index 0000000000..dad6b9ae7a
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj
@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <ProjectGuid>{92600954-25F0-4291-8E11-1FEE9FC4BE20}</ProjectGuid>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <LangVersion>7.2</LangVersion>
+ <PackageId>GodotTools.IdeMessaging</PackageId>
+ <Version>1.1.1</Version>
+ <AssemblyVersion>$(Version)</AssemblyVersion>
+ <Authors>Godot Engine contributors</Authors>
+ <Company />
+ <PackageTags>godot</PackageTags>
+ <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/GodotTools/GodotTools.IdeMessaging</RepositoryUrl>
+ <PackageLicenseExpression>MIT</PackageLicenseExpression>
+ <Description>
+This library enables communication with the Godot Engine editor (the version with .NET support).
+It's intended for use in IDEs/editors plugins for a better experience working with Godot C# projects.
+
+A client using this library is only compatible with servers of the same major version and of a lower or equal minor version.
+ </Description>
+ </PropertyGroup>
+ <ItemGroup>
+ <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+ </ItemGroup>
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs
new file mode 100644
index 0000000000..6387145a28
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IHandshake.cs
@@ -0,0 +1,8 @@
+namespace GodotTools.IdeMessaging
+{
+ public interface IHandshake
+ {
+ string GetHandshakeLine(string identity);
+ bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger);
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ILogger.cs
index 614bb30271..d2855f93a1 100644
--- a/modules/mono/editor/GodotTools/GodotTools.IdeConnection/ILogger.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ILogger.cs
@@ -1,6 +1,6 @@
using System;
-namespace GodotTools.IdeConnection
+namespace GodotTools.IdeMessaging
{
public interface ILogger
{
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IMessageHandler.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IMessageHandler.cs
new file mode 100644
index 0000000000..9622fcc96d
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/IMessageHandler.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace GodotTools.IdeMessaging
+{
+ public interface IMessageHandler
+ {
+ Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger);
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Message.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Message.cs
new file mode 100644
index 0000000000..6903ec197b
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Message.cs
@@ -0,0 +1,52 @@
+namespace GodotTools.IdeMessaging
+{
+ public class Message
+ {
+ public MessageKind Kind { get; }
+ public string Id { get; }
+ public MessageContent Content { get; }
+
+ public Message(MessageKind kind, string id, MessageContent content)
+ {
+ Kind = kind;
+ Id = id;
+ Content = content;
+ }
+
+ public override string ToString()
+ {
+ return $"{Kind} | {Id}";
+ }
+ }
+
+ public enum MessageKind
+ {
+ Request,
+ Response
+ }
+
+ public enum MessageStatus
+ {
+ Ok,
+ RequestNotSupported,
+ InvalidRequestBody
+ }
+
+ public readonly struct MessageContent
+ {
+ public MessageStatus Status { get; }
+ public string Body { get; }
+
+ public MessageContent(string body)
+ {
+ Status = MessageStatus.Ok;
+ Body = body;
+ }
+
+ public MessageContent(MessageStatus status, string body)
+ {
+ Status = status;
+ Body = body;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs
new file mode 100644
index 0000000000..a00575a2a1
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/MessageDecoder.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Text;
+
+namespace GodotTools.IdeMessaging
+{
+ public class MessageDecoder
+ {
+ private class DecodedMessage
+ {
+ public MessageKind? Kind;
+ public string Id;
+ public MessageStatus? Status;
+ public readonly StringBuilder Body = new StringBuilder();
+ public uint? PendingBodyLines;
+
+ public void Clear()
+ {
+ Kind = null;
+ Id = null;
+ Status = null;
+ Body.Clear();
+ PendingBodyLines = null;
+ }
+
+ public Message ToMessage()
+ {
+ if (!Kind.HasValue || Id == null || !Status.HasValue ||
+ !PendingBodyLines.HasValue || PendingBodyLines.Value > 0)
+ throw new InvalidOperationException();
+
+ return new Message(Kind.Value, Id, new MessageContent(Status.Value, Body.ToString()));
+ }
+ }
+
+ public enum State
+ {
+ Decoding,
+ Decoded,
+ Errored
+ }
+
+ private readonly DecodedMessage decodingMessage = new DecodedMessage();
+
+ public State Decode(string messageLine, out Message decodedMessage)
+ {
+ decodedMessage = null;
+
+ if (!decodingMessage.Kind.HasValue)
+ {
+ if (!Enum.TryParse(messageLine, ignoreCase: true, out MessageKind kind))
+ {
+ decodingMessage.Clear();
+ return State.Errored;
+ }
+
+ decodingMessage.Kind = kind;
+ }
+ else if (decodingMessage.Id == null)
+ {
+ decodingMessage.Id = messageLine;
+ }
+ else if (decodingMessage.Status == null)
+ {
+ if (!Enum.TryParse(messageLine, ignoreCase: true, out MessageStatus status))
+ {
+ decodingMessage.Clear();
+ return State.Errored;
+ }
+
+ decodingMessage.Status = status;
+ }
+ else if (decodingMessage.PendingBodyLines == null)
+ {
+ if (!uint.TryParse(messageLine, out uint pendingBodyLines))
+ {
+ decodingMessage.Clear();
+ return State.Errored;
+ }
+
+ decodingMessage.PendingBodyLines = pendingBodyLines;
+ }
+ else
+ {
+ if (decodingMessage.PendingBodyLines > 0)
+ {
+ decodingMessage.Body.AppendLine(messageLine);
+ decodingMessage.PendingBodyLines -= 1;
+ }
+ else
+ {
+ decodedMessage = decodingMessage.ToMessage();
+ decodingMessage.Clear();
+ return State.Decoded;
+ }
+ }
+
+ return State.Decoding;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs
new file mode 100644
index 0000000000..10d7e1898e
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Peer.cs
@@ -0,0 +1,298 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Sockets;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging.Requests;
+using GodotTools.IdeMessaging.Utils;
+
+namespace GodotTools.IdeMessaging
+{
+ public sealed class Peer : IDisposable
+ {
+ /// <summary>
+ /// Major version.
+ /// There is no forward nor backward compatibility between different major versions.
+ /// Connection is refused if client and server have different major versions.
+ /// </summary>
+ public static readonly int ProtocolVersionMajor = Assembly.GetAssembly(typeof(Peer)).GetName().Version.Major;
+
+ /// <summary>
+ /// Minor version, which clients must be backward compatible with.
+ /// Connection is refused if the client's minor version is lower than the server's.
+ /// </summary>
+ public static readonly int ProtocolVersionMinor = Assembly.GetAssembly(typeof(Peer)).GetName().Version.Minor;
+
+ /// <summary>
+ /// Revision, which doesn't affect compatibility.
+ /// </summary>
+ public static readonly int ProtocolVersionRevision = Assembly.GetAssembly(typeof(Peer)).GetName().Version.Revision;
+
+ public const string ClientHandshakeName = "GodotIdeClient";
+ public const string ServerHandshakeName = "GodotIdeServer";
+
+ private const int ClientWriteTimeout = 8000;
+
+ public delegate Task<Response> RequestHandler(Peer peer, MessageContent content);
+
+ private readonly TcpClient tcpClient;
+
+ private readonly TextReader clientReader;
+ private readonly TextWriter clientWriter;
+
+ private readonly SemaphoreSlim writeSem = new SemaphoreSlim(1);
+
+ private string remoteIdentity = string.Empty;
+ public string RemoteIdentity => remoteIdentity;
+
+ public event Action Connected;
+ public event Action Disconnected;
+
+ private ILogger Logger { get; }
+
+ public bool IsDisposed { get; private set; }
+
+ public bool IsTcpClientConnected => tcpClient.Client != null && tcpClient.Client.Connected;
+
+ private bool IsConnected { get; set; }
+
+ private readonly IHandshake handshake;
+ private readonly IMessageHandler messageHandler;
+
+ private readonly Dictionary<string, Queue<ResponseAwaiter>> requestAwaiterQueues = new Dictionary<string, Queue<ResponseAwaiter>>();
+ private readonly SemaphoreSlim requestsSem = new SemaphoreSlim(1);
+
+ public Peer(TcpClient tcpClient, IHandshake handshake, IMessageHandler messageHandler, ILogger logger)
+ {
+ this.tcpClient = tcpClient;
+ this.handshake = handshake;
+ this.messageHandler = messageHandler;
+
+ Logger = logger;
+
+ NetworkStream clientStream = tcpClient.GetStream();
+ clientStream.WriteTimeout = ClientWriteTimeout;
+
+ clientReader = new StreamReader(clientStream, Encoding.UTF8);
+ clientWriter = new StreamWriter(clientStream, Encoding.UTF8) {NewLine = "\n"};
+ }
+
+ public async Task Process()
+ {
+ try
+ {
+ var decoder = new MessageDecoder();
+
+ string messageLine;
+ while ((messageLine = await ReadLine()) != null)
+ {
+ var state = decoder.Decode(messageLine, out var msg);
+
+ if (state == MessageDecoder.State.Decoding)
+ continue; // Not finished decoding yet
+
+ if (state == MessageDecoder.State.Errored)
+ {
+ Logger.LogError($"Received message line with invalid format: {messageLine}");
+ continue;
+ }
+
+ Logger.LogDebug($"Received message: {msg}");
+
+ try
+ {
+ if (msg.Kind == MessageKind.Request)
+ {
+ var responseContent = await messageHandler.HandleRequest(this, msg.Id, msg.Content, Logger);
+ await WriteMessage(new Message(MessageKind.Response, msg.Id, responseContent));
+ }
+ else if (msg.Kind == MessageKind.Response)
+ {
+ ResponseAwaiter responseAwaiter;
+
+ using (await requestsSem.UseAsync())
+ {
+ if (!requestAwaiterQueues.TryGetValue(msg.Id, out var queue) || queue.Count <= 0)
+ {
+ Logger.LogError($"Received unexpected response: {msg.Id}");
+ return;
+ }
+
+ responseAwaiter = queue.Dequeue();
+ }
+
+ responseAwaiter.SetResult(msg.Content);
+ }
+ else
+ {
+ throw new IndexOutOfRangeException($"Invalid message kind {msg.Kind}");
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.LogError($"Message handler for '{msg}' failed with exception", e);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ if (!IsDisposed || !(e is SocketException || e.InnerException is SocketException))
+ {
+ Logger.LogError("Unhandled exception in the peer loop", e);
+ }
+ }
+ }
+
+ public async Task<bool> DoHandshake(string identity)
+ {
+ if (!await WriteLine(handshake.GetHandshakeLine(identity)))
+ {
+ Logger.LogError("Could not write handshake");
+ return false;
+ }
+
+ var readHandshakeTask = ReadLine();
+
+ if (await Task.WhenAny(readHandshakeTask, Task.Delay(8000)) != readHandshakeTask)
+ {
+ Logger.LogError("Timeout waiting for the client handshake");
+ return false;
+ }
+
+ string peerHandshake = await readHandshakeTask;
+
+ if (handshake == null || !handshake.IsValidPeerHandshake(peerHandshake, out remoteIdentity, Logger))
+ {
+ Logger.LogError("Received invalid handshake: " + peerHandshake);
+ return false;
+ }
+
+ IsConnected = true;
+ Connected?.Invoke();
+
+ Logger.LogInfo("Peer connection started");
+
+ return true;
+ }
+
+ private async Task<string> ReadLine()
+ {
+ try
+ {
+ return await clientReader.ReadLineAsync();
+ }
+ catch (Exception e)
+ {
+ if (IsDisposed)
+ {
+ var se = e as SocketException ?? e.InnerException as SocketException;
+ if (se != null && se.SocketErrorCode == SocketError.Interrupted)
+ return null;
+ }
+
+ throw;
+ }
+ }
+
+ private Task<bool> WriteMessage(Message message)
+ {
+ Logger.LogDebug($"Sending message: {message}");
+ int bodyLineCount = message.Content.Body.Count(c => c == '\n');
+
+ bodyLineCount += 1; // Extra line break at the end
+
+ var builder = new StringBuilder();
+
+ builder.AppendLine(message.Kind.ToString());
+ builder.AppendLine(message.Id);
+ builder.AppendLine(message.Content.Status.ToString());
+ builder.AppendLine(bodyLineCount.ToString());
+ builder.AppendLine(message.Content.Body);
+
+ return WriteLine(builder.ToString());
+ }
+
+ public async Task<TResponse> SendRequest<TResponse>(string id, string body)
+ where TResponse : Response, new()
+ {
+ ResponseAwaiter responseAwaiter;
+
+ using (await requestsSem.UseAsync())
+ {
+ bool written = await WriteMessage(new Message(MessageKind.Request, id, new MessageContent(body)));
+
+ if (!written)
+ return null;
+
+ if (!requestAwaiterQueues.TryGetValue(id, out var queue))
+ {
+ queue = new Queue<ResponseAwaiter>();
+ requestAwaiterQueues.Add(id, queue);
+ }
+
+ responseAwaiter = new ResponseAwaiter<TResponse>();
+ queue.Enqueue(responseAwaiter);
+ }
+
+ return (TResponse)await responseAwaiter;
+ }
+
+ private async Task<bool> WriteLine(string text)
+ {
+ if (clientWriter == null || IsDisposed || !IsTcpClientConnected)
+ return false;
+
+ using (await writeSem.UseAsync())
+ {
+ try
+ {
+ await clientWriter.WriteLineAsync(text);
+ await clientWriter.FlushAsync();
+ }
+ catch (Exception e)
+ {
+ if (!IsDisposed)
+ {
+ var se = e as SocketException ?? e.InnerException as SocketException;
+ if (se != null && se.SocketErrorCode == SocketError.Shutdown)
+ Logger.LogInfo("Client disconnected ungracefully");
+ else
+ Logger.LogError("Exception thrown when trying to write to client", e);
+
+ Dispose();
+ }
+ }
+ }
+
+ return true;
+ }
+
+ // ReSharper disable once UnusedMember.Global
+ public void ShutdownSocketSend()
+ {
+ tcpClient.Client.Shutdown(SocketShutdown.Send);
+ }
+
+ public void Dispose()
+ {
+ if (IsDisposed)
+ return;
+
+ IsDisposed = true;
+
+ if (IsTcpClientConnected)
+ {
+ if (IsConnected)
+ Disconnected?.Invoke();
+ }
+
+ clientReader?.Dispose();
+ clientWriter?.Dispose();
+ ((IDisposable)tcpClient)?.Dispose();
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs
new file mode 100644
index 0000000000..e93db9377b
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Requests/Requests.cs
@@ -0,0 +1,129 @@
+// ReSharper disable ClassNeverInstantiated.Global
+// ReSharper disable UnusedMember.Global
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+
+using Newtonsoft.Json;
+
+namespace GodotTools.IdeMessaging.Requests
+{
+ public abstract class Request
+ {
+ [JsonIgnore] public string Id { get; }
+
+ protected Request(string id)
+ {
+ Id = id;
+ }
+ }
+
+ public abstract class Response
+ {
+ [JsonIgnore] public MessageStatus Status { get; set; } = MessageStatus.Ok;
+ }
+
+ public sealed class CodeCompletionRequest : Request
+ {
+ public enum CompletionKind
+ {
+ InputActions = 0,
+ NodePaths,
+ ResourcePaths,
+ ScenePaths,
+ ShaderParams,
+ Signals,
+ ThemeColors,
+ ThemeConstants,
+ ThemeFonts,
+ ThemeStyles
+ }
+
+ public CompletionKind Kind { get; set; }
+ public string ScriptFile { get; set; }
+
+ public new const string Id = "CodeCompletion";
+
+ public CodeCompletionRequest() : base(Id)
+ {
+ }
+ }
+
+ public sealed class CodeCompletionResponse : Response
+ {
+ public CodeCompletionRequest.CompletionKind Kind;
+ public string ScriptFile { get; set; }
+ public string[] Suggestions { get; set; }
+ }
+
+ public sealed class PlayRequest : Request
+ {
+ public new const string Id = "Play";
+
+ public PlayRequest() : base(Id)
+ {
+ }
+ }
+
+ public sealed class PlayResponse : Response
+ {
+ }
+
+ public sealed class StopPlayRequest : Request
+ {
+ public new const string Id = "StopPlay";
+
+ public StopPlayRequest() : base(Id)
+ {
+ }
+ }
+
+ public sealed class StopPlayResponse : Response
+ {
+ }
+
+ public sealed class DebugPlayRequest : Request
+ {
+ public string DebuggerHost { get; set; }
+ public int DebuggerPort { get; set; }
+ public bool? BuildBeforePlaying { get; set; }
+
+ public new const string Id = "DebugPlay";
+
+ public DebugPlayRequest() : base(Id)
+ {
+ }
+ }
+
+ public sealed class DebugPlayResponse : Response
+ {
+ }
+
+ public sealed class OpenFileRequest : Request
+ {
+ public string File { get; set; }
+ public int? Line { get; set; }
+ public int? Column { get; set; }
+
+ public new const string Id = "OpenFile";
+
+ public OpenFileRequest() : base(Id)
+ {
+ }
+ }
+
+ public sealed class OpenFileResponse : Response
+ {
+ }
+
+ public sealed class ReloadScriptsRequest : Request
+ {
+ public new const string Id = "ReloadScripts";
+
+ public ReloadScriptsRequest() : base(Id)
+ {
+ }
+ }
+
+ public sealed class ReloadScriptsResponse : Response
+ {
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs
new file mode 100644
index 0000000000..548e7f06ee
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/ResponseAwaiter.cs
@@ -0,0 +1,23 @@
+using GodotTools.IdeMessaging.Requests;
+using GodotTools.IdeMessaging.Utils;
+using Newtonsoft.Json;
+
+namespace GodotTools.IdeMessaging
+{
+ public abstract class ResponseAwaiter : NotifyAwaiter<Response>
+ {
+ public abstract void SetResult(MessageContent content);
+ }
+
+ public class ResponseAwaiter<T> : ResponseAwaiter
+ where T : Response, new()
+ {
+ public override void SetResult(MessageContent content)
+ {
+ if (content.Status == MessageStatus.Ok)
+ SetResult(JsonConvert.DeserializeObject<T>(content.Body));
+ else
+ SetResult(new T {Status = content.Status});
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs
index 700b786752..d84a63c83c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/NotifyAwaiter.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/NotifyAwaiter.cs
@@ -1,9 +1,9 @@
using System;
using System.Runtime.CompilerServices;
-namespace GodotTools.Utils
+namespace GodotTools.IdeMessaging.Utils
{
- public sealed class NotifyAwaiter<T> : INotifyCompletion
+ public class NotifyAwaiter<T> : INotifyCompletion
{
private Action continuation;
private Exception exception;
diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs
new file mode 100644
index 0000000000..9d593fbf8a
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/Utils/SemaphoreExtensions.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace GodotTools.IdeMessaging.Utils
+{
+ public static class SemaphoreExtensions
+ {
+ public static ConfiguredTaskAwaitable<IDisposable> UseAsync(this SemaphoreSlim semaphoreSlim, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ var wrapper = new SemaphoreSlimWaitReleaseWrapper(semaphoreSlim, out Task waitAsyncTask, cancellationToken);
+ return waitAsyncTask.ContinueWith<IDisposable>(t => wrapper, cancellationToken).ConfigureAwait(false);
+ }
+
+ private struct SemaphoreSlimWaitReleaseWrapper : IDisposable
+ {
+ private readonly SemaphoreSlim semaphoreSlim;
+
+ public SemaphoreSlimWaitReleaseWrapper(SemaphoreSlim semaphoreSlim, out Task waitAsyncTask, CancellationToken cancellationToken = default(CancellationToken))
+ {
+ this.semaphoreSlim = semaphoreSlim;
+ waitAsyncTask = this.semaphoreSlim.WaitAsync(cancellationToken);
+ }
+
+ public void Dispose()
+ {
+ semaphoreSlim.Release();
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj
new file mode 100644
index 0000000000..5b3ed0b1b7
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <ProjectGuid>{EAFFF236-FA96-4A4D-BD23-0E51EF988277}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net472</TargetFramework>
+ <LangVersion>7.2</LangVersion>
+ </PropertyGroup>
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
+ <PackageReference Include="EnvDTE" Version="8.0.2" />
+ </ItemGroup>
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs
new file mode 100644
index 0000000000..ce2b378623
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs
@@ -0,0 +1,270 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
+using System.Text.RegularExpressions;
+using EnvDTE;
+
+namespace GodotTools.OpenVisualStudio
+{
+ internal static class Program
+ {
+ [DllImport("ole32.dll")]
+ private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable pprot);
+
+ [DllImport("ole32.dll")]
+ private static extern void CreateBindCtx(int reserved, out IBindCtx ppbc);
+
+ [DllImport("user32.dll")]
+ private static extern bool SetForegroundWindow(IntPtr hWnd);
+
+ private static void ShowHelp()
+ {
+ Console.WriteLine("Opens the file(s) in a Visual Studio instance that is editing the specified solution.");
+ Console.WriteLine("If an existing instance for the solution is not found, a new one is created.");
+ Console.WriteLine();
+ Console.WriteLine("Usage:");
+ Console.WriteLine(@" GodotTools.OpenVisualStudio.exe solution [file[;line[;col]]...]");
+ Console.WriteLine();
+ Console.WriteLine("Lines and columns begin at one. Zero or lower will result in an error.");
+ Console.WriteLine("If a line is specified but a column is not, the line is selected in the text editor.");
+ }
+
+ // STAThread needed, otherwise CoRegisterMessageFilter may return CO_E_NOT_SUPPORTED.
+ [STAThread]
+ private static int Main(string[] args)
+ {
+ if (args.Length == 0 || args[0] == "--help" || args[0] == "-h")
+ {
+ ShowHelp();
+ return 0;
+ }
+
+ string solutionFile = NormalizePath(args[0]);
+
+ var dte = FindInstanceEditingSolution(solutionFile);
+
+ if (dte == null)
+ {
+ // Open a new instance
+
+ var visualStudioDteType = Type.GetTypeFromProgID("VisualStudio.DTE.16.0", throwOnError: true);
+ dte = (DTE)Activator.CreateInstance(visualStudioDteType);
+
+ dte.UserControl = true;
+
+ try
+ {
+ dte.Solution.Open(solutionFile);
+ }
+ catch (ArgumentException)
+ {
+ Console.Error.WriteLine("Solution.Open: Invalid path or file not found");
+ return 1;
+ }
+
+ dte.MainWindow.Visible = true;
+ }
+
+ MessageFilter.Register();
+
+ try
+ {
+ // Open files
+
+ for (int i = 1; i < args.Length; i++)
+ {
+ // Both the line number and the column begin at one
+
+ string[] fileArgumentParts = args[i].Split(';');
+
+ string filePath = NormalizePath(fileArgumentParts[0]);
+
+ try
+ {
+ dte.ItemOperations.OpenFile(filePath);
+ }
+ catch (ArgumentException)
+ {
+ Console.Error.WriteLine("ItemOperations.OpenFile: Invalid path or file not found");
+ return 1;
+ }
+
+ if (fileArgumentParts.Length > 1)
+ {
+ if (int.TryParse(fileArgumentParts[1], out int line))
+ {
+ var textSelection = (TextSelection)dte.ActiveDocument.Selection;
+
+ if (fileArgumentParts.Length > 2)
+ {
+ if (int.TryParse(fileArgumentParts[2], out int column))
+ {
+ textSelection.MoveToLineAndOffset(line, column);
+ }
+ else
+ {
+ Console.Error.WriteLine("The column part of the argument must be a valid integer");
+ return 1;
+ }
+ }
+ else
+ {
+ textSelection.GotoLine(line, Select: true);
+ }
+ }
+ else
+ {
+ Console.Error.WriteLine("The line part of the argument must be a valid integer");
+ return 1;
+ }
+ }
+ }
+ }
+ finally
+ {
+ var mainWindow = dte.MainWindow;
+ mainWindow.Activate();
+ SetForegroundWindow(new IntPtr(mainWindow.HWnd));
+
+ MessageFilter.Revoke();
+ }
+
+ return 0;
+ }
+
+ private static DTE FindInstanceEditingSolution(string solutionPath)
+ {
+ if (GetRunningObjectTable(0, out IRunningObjectTable pprot) != 0)
+ return null;
+
+ try
+ {
+ pprot.EnumRunning(out IEnumMoniker ppenumMoniker);
+ ppenumMoniker.Reset();
+
+ var moniker = new IMoniker[1];
+
+ while (ppenumMoniker.Next(1, moniker, IntPtr.Zero) == 0)
+ {
+ string ppszDisplayName;
+
+ CreateBindCtx(0, out IBindCtx ppbc);
+
+ try
+ {
+ moniker[0].GetDisplayName(ppbc, null, out ppszDisplayName);
+ }
+ finally
+ {
+ Marshal.ReleaseComObject(ppbc);
+ }
+
+ if (ppszDisplayName == null)
+ continue;
+
+ // The digits after the colon are the process ID
+ if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.16.0:[0-9]"))
+ continue;
+
+ if (pprot.GetObject(moniker[0], out object ppunkObject) == 0)
+ {
+ if (ppunkObject is DTE dte && dte.Solution.FullName.Length > 0)
+ {
+ if (NormalizePath(dte.Solution.FullName) == solutionPath)
+ return dte;
+ }
+ }
+ }
+ }
+ finally
+ {
+ Marshal.ReleaseComObject(pprot);
+ }
+
+ return null;
+ }
+
+ static string NormalizePath(string path)
+ {
+ return new Uri(Path.GetFullPath(path)).LocalPath
+ .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
+ .ToUpperInvariant();
+ }
+
+ #region MessageFilter. See: http: //msdn.microsoft.com/en-us/library/ms228772.aspx
+
+ private class MessageFilter : IOleMessageFilter
+ {
+ // Class containing the IOleMessageFilter
+ // thread error-handling functions
+
+ private static IOleMessageFilter _oldFilter;
+
+ // Start the filter
+ public static void Register()
+ {
+ IOleMessageFilter newFilter = new MessageFilter();
+ int ret = CoRegisterMessageFilter(newFilter, out _oldFilter);
+ if (ret != 0)
+ Console.Error.WriteLine($"CoRegisterMessageFilter failed with error code: {ret}");
+ }
+
+ // Done with the filter, close it
+ public static void Revoke()
+ {
+ int ret = CoRegisterMessageFilter(_oldFilter, out _);
+ if (ret != 0)
+ Console.Error.WriteLine($"CoRegisterMessageFilter failed with error code: {ret}");
+ }
+
+ //
+ // IOleMessageFilter functions
+ // Handle incoming thread requests
+ int IOleMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo)
+ {
+ // Return the flag SERVERCALL_ISHANDLED
+ return 0;
+ }
+
+ // Thread call was rejected, so try again.
+ int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType)
+ {
+ if (dwRejectType == 2)
+ // flag = SERVERCALL_RETRYLATER
+ {
+ // Retry the thread call immediately if return >= 0 & < 100
+ return 99;
+ }
+
+ // Too busy; cancel call
+ return -1;
+ }
+
+ int IOleMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType)
+ {
+ // Return the flag PENDINGMSG_WAITDEFPROCESS
+ return 2;
+ }
+
+ // Implement the IOleMessageFilter interface
+ [DllImport("ole32.dll")]
+ private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter);
+ }
+
+ [ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ private interface IOleMessageFilter
+ {
+ [PreserveSig]
+ int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo);
+
+ [PreserveSig]
+ int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType);
+
+ [PreserveSig]
+ int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType);
+ }
+
+ #endregion
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs
index 76cb249acf..cc0da44a13 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/DotNetSolution.cs
@@ -1,6 +1,9 @@
using GodotTools.Core;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
namespace GodotTools.ProjectEditor
{
@@ -86,7 +89,7 @@ namespace GodotTools.ProjectEditor
string solutionPath = Path.Combine(DirectoryPath, Name + ".sln");
string content = string.Format(SolutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg);
- File.WriteAllText(solutionPath, content);
+ File.WriteAllText(solutionPath, content, Encoding.UTF8); // UTF-8 with BOM
}
public DotNetSolution(string name)
@@ -118,5 +121,45 @@ EndProject";
const string ProjectPlatformsConfig =
@" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU
{{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU";
+
+ public static void MigrateFromOldConfigNames(string slnPath)
+ {
+ if (!File.Exists(slnPath))
+ return;
+
+ var input = File.ReadAllText(slnPath);
+
+ if (!Regex.IsMatch(input, Regex.Escape("Tools|Any CPU")))
+ return;
+
+ // This method renames old configurations in solutions to the new ones.
+ //
+ // This is the order configs appear in the solution and what we want to rename them to:
+ // Debug|Any CPU = Debug|Any CPU -> ExportDebug|Any CPU = ExportDebug|Any CPU
+ // Tools|Any CPU = Tools|Any CPU -> Debug|Any CPU = Debug|Any CPU
+ //
+ // But we want to move Tools (now Debug) to the top, so it's easier to rename like this:
+ // Debug|Any CPU = Debug|Any CPU -> Debug|Any CPU = Debug|Any CPU
+ // Release|Any CPU = Release|Any CPU -> ExportDebug|Any CPU = ExportDebug|Any CPU
+ // Tools|Any CPU = Tools|Any CPU -> ExportRelease|Any CPU = ExportRelease|Any CPU
+
+ var dict = new Dictionary<string, string>
+ {
+ {"Debug|Any CPU", "Debug|Any CPU"},
+ {"Release|Any CPU", "ExportDebug|Any CPU"},
+ {"Tools|Any CPU", "ExportRelease|Any CPU"}
+ };
+
+ var regex = new Regex(string.Join("|",dict.Keys.Select(Regex.Escape)));
+ var result = regex.Replace(input,m => dict[m.Value]);
+
+ if (result != input)
+ {
+ // Save a copy of the solution before replacing it
+ FileUtils.SaveBackupCopy(slnPath);
+
+ File.WriteAllText(slnPath, result);
+ }
+ }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
index b60e501beb..37123ba2b2 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj
@@ -1,57 +1,32 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid>
- <OutputType>Library</OutputType>
- <RootNamespace>GodotTools.ProjectEditor</RootNamespace>
- <AssemblyName>GodotTools.ProjectEditor</AssemblyName>
- <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
- <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
- <LangVersion>7</LangVersion>
+ <TargetFramework>net472</TargetFramework>
+ <LangVersion>7.2</LangVersion>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug</OutputPath>
- <DefineConstants>DEBUG;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <Optimize>true</Optimize>
- <OutputPath>bin\Release</OutputPath>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <ItemGroup>
- <Reference Include="System" />
- <Reference Include="Microsoft.Build" />
- <Reference Include="DotNet.Glob, Version=2.1.1.0, Culture=neutral, PublicKeyToken=b68cc888b4f632d1, processorArchitecture=MSIL">
- <HintPath>$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
- </Reference>
- </ItemGroup>
<ItemGroup>
- <Compile Include="ApiAssembliesInfo.cs" />
- <Compile Include="DotNetSolution.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="IdentifierUtils.cs" />
- <Compile Include="ProjectExtensions.cs" />
- <Compile Include="ProjectGenerator.cs" />
- <Compile Include="ProjectUtils.cs" />
+ <PackageReference Include="Microsoft.Build" Version="16.5.0" />
+ <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
- <None Include="packages.config" />
+ <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
+ <ProjectReference Include="..\GodotTools.Shared\GodotTools.Shared.csproj" />
</ItemGroup>
+ <!--
+ The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described
+ here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486
+ We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when
+ searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed.
+ -->
<ItemGroup>
- <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
- <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project>
- <Name>GodotTools.Core</Name>
- </ProjectReference>
+ <None Include="MSBuild.exe" CopyToOutputDirectory="Always" />
</ItemGroup>
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <Target Name="CopyMSBuildStubWindows" AfterTargets="Build" Condition=" '$(GodotPlatform)' == 'windows' Or ( '$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT' ) ">
+ <PropertyGroup>
+ <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
+ <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir>
+ </PropertyGroup>
+ <!-- Need to copy it here as well on Windows -->
+ <Copy SourceFiles="MSBuild.exe" DestinationFiles="$(GodotOutputDataDir)\Mono\lib\mono\v4.0\MSBuild.exe" />
+ </Target>
</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
index f93eb9a1fa..ed77076df3 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
@@ -22,6 +22,37 @@ namespace GodotTools.ProjectEditor
return string.Join(".", identifiers);
}
+ /// <summary>
+ /// Skips invalid identifier characters including decimal digit numbers at the start of the identifier.
+ /// </summary>
+ private static void SkipInvalidCharacters(string source, int startIndex, StringBuilder outputBuilder)
+ {
+ for (int i = startIndex; i < source.Length; i++)
+ {
+ char @char = source[i];
+
+ switch (char.GetUnicodeCategory(@char))
+ {
+ case UnicodeCategory.UppercaseLetter:
+ case UnicodeCategory.LowercaseLetter:
+ case UnicodeCategory.TitlecaseLetter:
+ case UnicodeCategory.ModifierLetter:
+ case UnicodeCategory.LetterNumber:
+ case UnicodeCategory.OtherLetter:
+ outputBuilder.Append(@char);
+ break;
+ case UnicodeCategory.NonSpacingMark:
+ case UnicodeCategory.SpacingCombiningMark:
+ case UnicodeCategory.ConnectorPunctuation:
+ case UnicodeCategory.DecimalDigitNumber:
+ // Identifiers may start with underscore
+ if (outputBuilder.Length > startIndex || @char == '_')
+ outputBuilder.Append(@char);
+ break;
+ }
+ }
+ }
+
public static string SanitizeIdentifier(string identifier, bool allowEmpty)
{
if (string.IsNullOrEmpty(identifier))
@@ -44,30 +75,7 @@ namespace GodotTools.ProjectEditor
startIndex += 1;
}
- for (int i = startIndex; i < identifier.Length; i++)
- {
- char @char = identifier[i];
-
- switch (Char.GetUnicodeCategory(@char))
- {
- case UnicodeCategory.UppercaseLetter:
- case UnicodeCategory.LowercaseLetter:
- case UnicodeCategory.TitlecaseLetter:
- case UnicodeCategory.ModifierLetter:
- case UnicodeCategory.LetterNumber:
- case UnicodeCategory.OtherLetter:
- identifierBuilder.Append(@char);
- break;
- case UnicodeCategory.NonSpacingMark:
- case UnicodeCategory.SpacingCombiningMark:
- case UnicodeCategory.ConnectorPunctuation:
- case UnicodeCategory.DecimalDigitNumber:
- // Identifiers may start with underscore
- if (identifierBuilder.Length > startIndex || @char == '_')
- identifierBuilder.Append(@char);
- break;
- }
- }
+ SkipInvalidCharacters(identifier, startIndex, identifierBuilder);
if (identifierBuilder.Length == startIndex)
{
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
deleted file mode 100644
index 36961eb45e..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using GodotTools.Core;
-using System;
-using DotNet.Globbing;
-using Microsoft.Build.Construction;
-
-namespace GodotTools.ProjectEditor
-{
- public static class ProjectExtensions
- {
- public static bool HasItem(this ProjectRootElement root, string itemType, string include)
- {
- GlobOptions globOptions = new GlobOptions();
- globOptions.Evaluation.CaseInsensitive = false;
-
- string normalizedInclude = include.NormalizePath();
-
- foreach (var itemGroup in root.ItemGroups)
- {
- if (itemGroup.Condition.Length != 0)
- continue;
-
- foreach (var item in itemGroup.Items)
- {
- if (item.ItemType != itemType)
- continue;
-
- var glob = Glob.Parse(item.Include.NormalizePath(), globOptions);
-
- if (glob.IsMatch(normalizedInclude))
- {
- return true;
- }
- }
- }
-
- return false;
- }
-
- public static bool AddItemChecked(this ProjectRootElement root, string itemType, string include)
- {
- if (!root.HasItem(itemType, include))
- {
- root.AddItem(itemType, include);
- return true;
- }
-
- return false;
- }
-
- public static Guid GetGuid(this ProjectRootElement root)
- {
- foreach (var property in root.Properties)
- {
- if (property.Name == "ProjectGuid")
- return Guid.Parse(property.Value);
- }
-
- return Guid.Empty;
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
index 28b7832f90..7d49d251dd 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
@@ -1,162 +1,50 @@
-using GodotTools.Core;
using System;
-using System.Collections.Generic;
using System.IO;
-using System.Reflection;
+using System.Text;
using Microsoft.Build.Construction;
+using Microsoft.Build.Evaluation;
+using GodotTools.Shared;
namespace GodotTools.ProjectEditor
{
public static class ProjectGenerator
{
- private const string CoreApiProjectName = "GodotSharp";
- private const string EditorApiProjectName = "GodotSharpEditor";
+ public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GeneratedGodotNupkgsVersions.GodotNETSdk}";
- public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems)
+ public static ProjectRootElement GenGameProject(string name)
{
- string path = Path.Combine(dir, name + ".csproj");
-
- ProjectPropertyGroupElement mainGroup;
- var root = CreateLibraryProject(name, "Tools", out mainGroup);
-
- mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)"));
- mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj"));
- mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)"));
- mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'Release' ";
- mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'Release' ";
-
- var toolsGroup = root.AddPropertyGroup();
- toolsGroup.Condition = " '$(Configuration)|$(Platform)' == 'Tools|AnyCPU' ";
- toolsGroup.AddProperty("DebugSymbols", "true");
- toolsGroup.AddProperty("DebugType", "portable");
- toolsGroup.AddProperty("Optimize", "false");
- toolsGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;TOOLS;");
- toolsGroup.AddProperty("ErrorReport", "prompt");
- toolsGroup.AddProperty("WarningLevel", "4");
- toolsGroup.AddProperty("ConsolePause", "false");
-
- var coreApiRef = root.AddItem("Reference", CoreApiProjectName);
- coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll"));
- coreApiRef.AddMetadata("Private", "False");
-
- var editorApiRef = root.AddItem("Reference", EditorApiProjectName);
- editorApiRef.Condition = " '$(Configuration)' == 'Tools' ";
- editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll"));
- editorApiRef.AddMetadata("Private", "False");
-
- GenAssemblyInfoFile(root, dir, name);
-
- foreach (var item in compileItems)
- {
- root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\"));
- }
-
- root.Save(path);
-
- return root.GetGuid().ToString().ToUpper();
- }
-
- private static void GenAssemblyInfoFile(ProjectRootElement root, string dir, string name, string[] assemblyLines = null, string[] usingDirectives = null)
- {
- string propertiesDir = Path.Combine(dir, "Properties");
- if (!Directory.Exists(propertiesDir))
- Directory.CreateDirectory(propertiesDir);
-
- string usingDirectivesText = string.Empty;
+ if (name.Length == 0)
+ throw new ArgumentException("Project name is empty", nameof(name));
- if (usingDirectives != null)
- {
- foreach (var usingDirective in usingDirectives)
- usingDirectivesText += "\nusing " + usingDirective + ";";
- }
+ var root = ProjectRootElement.Create(NewProjectFileOptions.None);
- string assemblyLinesText = string.Empty;
+ root.Sdk = GodotSdkAttrValue;
- if (assemblyLines != null)
- assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
+ var mainGroup = root.AddPropertyGroup();
+ mainGroup.AddProperty("TargetFramework", "netstandard2.1");
- string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
+ string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true);
- string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs");
+ // If the name is not a valid namespace, manually set RootNamespace to a sanitized one.
+ if (sanitizedName != name)
+ mainGroup.AddProperty("RootNamespace", sanitizedName);
- File.WriteAllText(assemblyInfoFile, content);
-
- root.AddItem("Compile", assemblyInfoFile.RelativeToPath(dir).Replace("/", "\\"));
+ return root;
}
- public static ProjectRootElement CreateLibraryProject(string name, string defaultConfig, out ProjectPropertyGroupElement mainGroup)
+ public static string GenAndSaveGameProject(string dir, string name)
{
- if (string.IsNullOrEmpty(name))
- throw new ArgumentException($"{nameof(name)} cannot be empty", nameof(name));
-
- var root = ProjectRootElement.Create();
- root.DefaultTargets = "Build";
-
- mainGroup = root.AddPropertyGroup();
- mainGroup.AddProperty("Configuration", defaultConfig).Condition = " '$(Configuration)' == '' ";
- mainGroup.AddProperty("Platform", "AnyCPU").Condition = " '$(Platform)' == '' ";
- mainGroup.AddProperty("ProjectGuid", "{" + Guid.NewGuid().ToString().ToUpper() + "}");
- mainGroup.AddProperty("OutputType", "Library");
- mainGroup.AddProperty("OutputPath", Path.Combine("bin", "$(Configuration)"));
- mainGroup.AddProperty("RootNamespace", IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true));
- mainGroup.AddProperty("AssemblyName", name);
- mainGroup.AddProperty("TargetFrameworkVersion", "v4.7");
- mainGroup.AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString());
+ if (name.Length == 0)
+ throw new ArgumentException("Project name is empty", nameof(name));
- var debugGroup = root.AddPropertyGroup();
- debugGroup.Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ";
- debugGroup.AddProperty("DebugSymbols", "true");
- debugGroup.AddProperty("DebugType", "portable");
- debugGroup.AddProperty("Optimize", "false");
- debugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;");
- debugGroup.AddProperty("ErrorReport", "prompt");
- debugGroup.AddProperty("WarningLevel", "4");
- debugGroup.AddProperty("ConsolePause", "false");
-
- var releaseGroup = root.AddPropertyGroup();
- releaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ";
- releaseGroup.AddProperty("DebugType", "portable");
- releaseGroup.AddProperty("Optimize", "true");
- releaseGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;");
- releaseGroup.AddProperty("ErrorReport", "prompt");
- releaseGroup.AddProperty("WarningLevel", "4");
- releaseGroup.AddProperty("ConsolePause", "false");
+ string path = Path.Combine(dir, name + ".csproj");
- // References
- var referenceGroup = root.AddItemGroup();
- referenceGroup.AddItem("Reference", "System");
+ var root = GenGameProject(name);
- root.AddImport(Path.Combine("$(MSBuildBinPath)", "Microsoft.CSharp.targets").Replace("/", "\\"));
+ // Save (without BOM)
+ root.Save(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
- return root;
+ return Guid.NewGuid().ToString().ToUpper();
}
-
- private const string AssemblyInfoTemplate =
- @"using System.Reflection;{0}
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle(""{1}"")]
-[assembly: AssemblyDescription("""")]
-[assembly: AssemblyConfiguration("""")]
-[assembly: AssemblyCompany("""")]
-[assembly: AssemblyProduct("""")]
-[assembly: AssemblyCopyright("""")]
-[assembly: AssemblyTrademark("""")]
-[assembly: AssemblyCulture("""")]
-
-// The assembly version has the format ""{{Major}}.{{Minor}}.{{Build}}.{{Revision}}"".
-// The form ""{{Major}}.{{Minor}}.*"" will automatically update the build and revision,
-// and ""{{Major}}.{{Minor}}.{{Build}}.*"" will update just the revision.
-
-[assembly: AssemblyVersion(""1.0.*"")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("""")]
-{2}";
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
index 233aab45b3..cdac9acb25 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
@@ -1,175 +1,52 @@
-using GodotTools.Core;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using DotNet.Globbing;
+using System;
using Microsoft.Build.Construction;
namespace GodotTools.ProjectEditor
{
- public static class ProjectUtils
+ public sealed class MSBuildProject
{
- public static void AddItemToProjectChecked(string projectPath, string itemType, string include)
- {
- var dir = Directory.GetParent(projectPath).FullName;
- var root = ProjectRootElement.Open(projectPath);
- Debug.Assert(root != null);
+ internal ProjectRootElement Root { get; set; }
- var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\");
+ public bool HasUnsavedChanges { get; set; }
- if (root.AddItemChecked(itemType, normalizedInclude))
- root.Save();
- }
+ public void Save() => Root.Save();
- private static string[] GetAllFilesRecursive(string rootDirectory, string mask)
+ public MSBuildProject(ProjectRootElement root)
{
- string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories);
-
- // We want relative paths
- for (int i = 0; i < files.Length; i++)
- {
- files[i] = files[i].RelativeToPath(rootDirectory);
- }
-
- return files;
+ Root = root;
}
+ }
- public static string[] GetIncludeFiles(string projectPath, string itemType)
+ public static class ProjectUtils
+ {
+ public static MSBuildProject Open(string path)
{
- var result = new List<string>();
- var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
-
- var globOptions = new GlobOptions();
- globOptions.Evaluation.CaseInsensitive = false;
-
- var root = ProjectRootElement.Open(projectPath);
-
- foreach (var itemGroup in root.ItemGroups)
- {
- if (itemGroup.Condition.Length != 0)
- continue;
-
- foreach (var item in itemGroup.Items)
- {
- if (item.ItemType != itemType)
- continue;
-
- string normalizedInclude = item.Include.NormalizePath();
-
- var glob = Glob.Parse(normalizedInclude, globOptions);
-
- // TODO Check somehow if path has no blob to avoid the following loop...
-
- foreach (var existingFile in existingFiles)
- {
- if (glob.IsMatch(existingFile))
- {
- result.Add(existingFile);
- }
- }
- }
- }
-
- return result.ToArray();
+ var root = ProjectRootElement.Open(path);
+ return root != null ? new MSBuildProject(root) : null;
}
- /// Simple function to make sure the Api assembly references are configured correctly
- public static void FixApiHintPath(string projectPath)
+ public static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName)
{
- var root = ProjectRootElement.Open(projectPath);
- Debug.Assert(root != null);
-
- bool dirty = false;
-
- void AddPropertyIfNotPresent(string name, string condition, string value)
- {
- if (root.PropertyGroups
- .Any(g => (g.Condition == string.Empty || g.Condition == condition) &&
- g.Properties
- .Any(p => p.Name == name &&
- p.Value == value &&
- (p.Condition == condition || g.Condition == condition))))
- {
- return;
- }
-
- root.AddProperty(name, value).Condition = condition;
- dirty = true;
- }
-
- AddPropertyIfNotPresent(name: "ApiConfiguration",
- condition: " '$(Configuration)' != 'Release' ",
- value: "Debug");
- AddPropertyIfNotPresent(name: "ApiConfiguration",
- condition: " '$(Configuration)' == 'Release' ",
- value: "Release");
-
- void SetReferenceHintPath(string referenceName, string condition, string hintPath)
- {
- foreach (var itemGroup in root.ItemGroups.Where(g =>
- g.Condition == string.Empty || g.Condition == condition))
- {
- var references = itemGroup.Items.Where(item =>
- item.ItemType == "Reference" &&
- item.Include == referenceName &&
- (item.Condition == condition || itemGroup.Condition == condition));
-
- var referencesWithHintPath = references.Where(reference =>
- reference.Metadata.Any(m => m.Name == "HintPath"));
+ var origRoot = project.Root;
- if (referencesWithHintPath.Any(reference => reference.Metadata
- .Any(m => m.Name == "HintPath" && m.Value == hintPath)))
- {
- // Found a Reference item with the right HintPath
- return;
- }
+ if (!string.IsNullOrEmpty(origRoot.Sdk))
+ return;
- var referenceWithHintPath = referencesWithHintPath.FirstOrDefault();
- if (referenceWithHintPath != null)
- {
- // Found a Reference item with a wrong HintPath
- foreach (var metadata in referenceWithHintPath.Metadata.ToList()
- .Where(m => m.Name == "HintPath"))
- {
- // Safe to remove as we duplicate with ToList() to loop
- referenceWithHintPath.RemoveChild(metadata);
- }
-
- referenceWithHintPath.AddMetadata("HintPath", hintPath);
- dirty = true;
- return;
- }
-
- var referenceWithoutHintPath = references.FirstOrDefault();
- if (referenceWithoutHintPath != null)
- {
- // Found a Reference item without a HintPath
- referenceWithoutHintPath.AddMetadata("HintPath", hintPath);
- dirty = true;
- return;
- }
- }
-
- // Found no Reference item at all. Add it.
- root.AddItem("Reference", referenceName).Condition = condition;
- dirty = true;
- }
-
- const string coreProjectName = "GodotSharp";
- const string editorProjectName = "GodotSharpEditor";
-
- const string coreCondition = "";
- const string editorCondition = " '$(Configuration)' == 'Tools' ";
+ project.Root = ProjectGenerator.GenGameProject(projectName);
+ project.Root.FullPath = origRoot.FullPath;
+ project.HasUnsavedChanges = true;
+ }
- var coreHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{coreProjectName}.dll";
- var editorHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{editorProjectName}.dll";
+ public static void EnsureGodotSdkIsUpToDate(MSBuildProject project)
+ {
+ var root = project.Root;
+ string godotSdkAttrValue = ProjectGenerator.GodotSdkAttrValue;
- SetReferenceHintPath(coreProjectName, coreCondition, coreHintPath);
- SetReferenceHintPath(editorProjectName, editorCondition, editorHintPath);
+ if (!string.IsNullOrEmpty(root.Sdk) && root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase))
+ return;
- if (dirty)
- root.Save();
+ root.Sdk = godotSdkAttrValue;
+ project.HasUnsavedChanges = true;
}
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs
deleted file mode 100644
index 09333850fc..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle("GodotTools.ProjectEditor")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("Godot Engine contributors")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-
-[assembly: AssemblyVersion("1.0.*")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
-
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config
deleted file mode 100644
index 2db030f9d8..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/packages.config
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
- <package id="DotNet.Glob" version="2.1.1" targetFramework="net45" />
-</packages>
diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets
new file mode 100644
index 0000000000..aab2d73bdd
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets
@@ -0,0 +1,36 @@
+<Project>
+ <!-- Generate C# file with the version of all the nupkgs bundled with Godot -->
+
+ <Target Name="SetPropertiesForGenerateGodotNupkgsVersions">
+ <PropertyGroup>
+ <GeneratedGodotNupkgsVersionsFile>$(IntermediateOutputPath)GodotNupkgsVersions.g.cs</GeneratedGodotNupkgsVersionsFile>
+ </PropertyGroup>
+ </Target>
+
+ <Target Name="GenerateGodotNupkgsVersionsFile"
+ DependsOnTargets="PrepareForBuild;_GenerateGodotNupkgsVersionsFile"
+ BeforeTargets="BeforeCompile;CoreCompile">
+ <ItemGroup>
+ <Compile Include="$(GeneratedGodotNupkgsVersionsFile)" />
+ <FileWrites Include="$(GeneratedGodotNupkgsVersionsFile)" />
+ </ItemGroup>
+ </Target>
+ <Target Name="_GenerateGodotNupkgsVersionsFile"
+ DependsOnTargets="SetPropertiesForGenerateGodotNupkgsVersions"
+ Inputs="$(MSBuildProjectFile);$(MSBuildThisFileDirectory);$(MSBuildProjectFile)\..\..\..\SdkPackageVersions.props"
+ Outputs="$(GeneratedGodotNupkgsVersionsFile)">
+ <PropertyGroup>
+ <GenerateGodotNupkgsVersionsCode><![CDATA[
+namespace $(RootNamespace) {
+ public class GeneratedGodotNupkgsVersions {
+ public const string GodotNETSdk = "$(PackageVersion_Godot_NET_Sdk)"%3b
+ public const string GodotSourceGenerators = "$(PackageVersion_Godot_SourceGenerators)"%3b
+ }
+}
+]]></GenerateGodotNupkgsVersionsCode>
+ </PropertyGroup>
+ <WriteLinesToFile Lines="$(GenerateGodotNupkgsVersionsCode)"
+ File="$(GeneratedGodotNupkgsVersionsFile)"
+ Overwrite="True" WriteOnlyWhenDifferent="True" />
+ </Target>
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj b/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj
new file mode 100644
index 0000000000..3bc1698c15
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj
@@ -0,0 +1,6 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ </PropertyGroup>
+ <Import Project="GenerateGodotNupkgsVersions.targets" />
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln
index a3438ea5f3..d3107a69db 100644
--- a/modules/mono/editor/GodotTools/GodotTools.sln
+++ b/modules/mono/editor/GodotTools/GodotTools.sln
@@ -9,7 +9,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Core", "GodotToo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.BuildLogger", "GodotTools.BuildLogger\GodotTools.BuildLogger.csproj", "{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeConnection", "GodotTools.IdeConnection\GodotTools.IdeConnection.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.IdeMessaging", "GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj", "{92600954-25F0-4291-8E11-1FEE9FC4BE20}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.OpenVisualStudio", "GodotTools.OpenVisualStudio\GodotTools.OpenVisualStudio.csproj", "{EAFFF236-FA96-4A4D-BD23-0E51EF988277}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Shared", "GodotTools.Shared\GodotTools.Shared.csproj", "{2758FFAF-8237-4CF2-B569-66BF8B3587BB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -37,5 +41,13 @@ Global
{92600954-25F0-4291-8E11-1FEE9FC4BE20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92600954-25F0-4291-8E11-1FEE9FC4BE20}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EAFFF236-FA96-4A4D-BD23-0E51EF988277}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
deleted file mode 100644
index 4c76d2abf1..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
+++ /dev/null
@@ -1,343 +0,0 @@
-using Godot;
-using System;
-using System.IO;
-using Godot.Collections;
-using GodotTools.Internals;
-using static GodotTools.Internals.Globals;
-using File = GodotTools.Utils.File;
-using Path = System.IO.Path;
-
-namespace GodotTools
-{
- public class BottomPanel : VBoxContainer
- {
- private EditorInterface editorInterface;
-
- private TabContainer panelTabs;
-
- private VBoxContainer panelBuildsTab;
-
- private ItemList buildTabsList;
- private TabContainer buildTabs;
-
- private ToolButton warningsBtn;
- private ToolButton errorsBtn;
- private Button viewLogBtn;
-
- private void _UpdateBuildTabsList()
- {
- buildTabsList.Clear();
-
- int currentTab = buildTabs.CurrentTab;
-
- bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount();
-
- for (int i = 0; i < buildTabs.GetChildCount(); i++)
- {
- var tab = (BuildTab)buildTabs.GetChild(i);
-
- if (tab == null)
- continue;
-
- string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
- itemName += " [" + tab.BuildInfo.Configuration + "]";
-
- buildTabsList.AddItem(itemName, tab.IconTexture);
-
- string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
- itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
- itemTooltip += "\nStatus: ";
-
- if (tab.BuildExited)
- itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
- else
- itemTooltip += "Running";
-
- if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
- itemTooltip += $"\nErrors: {tab.ErrorCount}";
-
- itemTooltip += $"\nWarnings: {tab.WarningCount}";
-
- buildTabsList.SetItemTooltip(i, itemTooltip);
-
- if (noCurrentTab || currentTab == i)
- {
- buildTabsList.Select(i);
- _BuildTabsItemSelected(i);
- }
- }
- }
-
- public BuildTab GetBuildTabFor(BuildInfo buildInfo)
- {
- foreach (var buildTab in new Array<BuildTab>(buildTabs.GetChildren()))
- {
- if (buildTab.BuildInfo.Equals(buildInfo))
- return buildTab;
- }
-
- var newBuildTab = new BuildTab(buildInfo);
- AddBuildTab(newBuildTab);
-
- return newBuildTab;
- }
-
- private void _BuildTabsItemSelected(int idx)
- {
- if (idx < 0 || idx >= buildTabs.GetTabCount())
- throw new IndexOutOfRangeException();
-
- buildTabs.CurrentTab = idx;
- if (!buildTabs.Visible)
- buildTabs.Visible = true;
-
- warningsBtn.Visible = true;
- errorsBtn.Visible = true;
- viewLogBtn.Visible = true;
- }
-
- private void _BuildTabsNothingSelected()
- {
- if (buildTabs.GetTabCount() != 0)
- {
- // just in case
- buildTabs.Visible = false;
-
- // This callback is called when clicking on the empty space of the list.
- // ItemList won't deselect the items automatically, so we must do it ourselves.
- buildTabsList.UnselectAll();
- }
-
- warningsBtn.Visible = false;
- errorsBtn.Visible = false;
- viewLogBtn.Visible = false;
- }
-
- private void _WarningsToggled(bool pressed)
- {
- int currentTab = buildTabs.CurrentTab;
-
- if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
- throw new InvalidOperationException("No tab selected");
-
- var buildTab = (BuildTab)buildTabs.GetChild(currentTab);
- buildTab.WarningsVisible = pressed;
- buildTab.UpdateIssuesList();
- }
-
- private void _ErrorsToggled(bool pressed)
- {
- int currentTab = buildTabs.CurrentTab;
-
- if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
- throw new InvalidOperationException("No tab selected");
-
- var buildTab = (BuildTab)buildTabs.GetChild(currentTab);
- buildTab.ErrorsVisible = pressed;
- buildTab.UpdateIssuesList();
- }
-
- public void BuildProjectPressed()
- {
- if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
- return; // No solution to build
-
- string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
- string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
-
- CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
-
- if (File.Exists(editorScriptsMetadataPath))
- {
- try
- {
- File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
- }
- catch (IOException e)
- {
- GD.PushError($"Failed to copy scripts metadata file. Exception message: {e.Message}");
- return;
- }
- }
-
- var godotDefines = new[]
- {
- OS.GetName(),
- Internal.GodotIs32Bits() ? "32" : "64"
- };
-
- bool buildSuccess = BuildManager.BuildProjectBlocking("Tools", godotDefines);
-
- if (!buildSuccess)
- return;
-
- // Notify running game for hot-reload
- Internal.ScriptEditorDebuggerReloadScripts();
-
- // Hot-reload in the editor
- GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer();
-
- if (Internal.IsAssembliesReloadingNeeded())
- Internal.ReloadAssemblies(softReload: false);
- }
-
- private void _ViewLogPressed()
- {
- if (!buildTabsList.IsAnythingSelected())
- return;
-
- var selectedItems = buildTabsList.GetSelectedItems();
-
- if (selectedItems.Length != 1)
- throw new InvalidOperationException($"Expected 1 selected item, got {selectedItems.Length}");
-
- int selectedItem = selectedItems[0];
-
- var buildTab = (BuildTab)buildTabs.GetTabControl(selectedItem);
-
- OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, BuildManager.MsBuildLogFileName));
- }
-
- public override void _Notification(int what)
- {
- base._Notification(what);
-
- if (what == EditorSettings.NotificationEditorSettingsChanged)
- {
- var editorBaseControl = editorInterface.GetBaseControl();
- panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles"));
- panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles"));
- panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles"));
- }
- }
-
- public void AddBuildTab(BuildTab buildTab)
- {
- buildTabs.AddChild(buildTab);
- RaiseBuildTab(buildTab);
- }
-
- public void RaiseBuildTab(BuildTab buildTab)
- {
- if (buildTab.GetParent() != buildTabs)
- throw new InvalidOperationException("Build tab is not in the tabs list");
-
- buildTabs.MoveChild(buildTab, 0);
- _UpdateBuildTabsList();
- }
-
- public void ShowBuildTab()
- {
- for (int i = 0; i < panelTabs.GetTabCount(); i++)
- {
- if (panelTabs.GetTabControl(i) == panelBuildsTab)
- {
- panelTabs.CurrentTab = i;
- GodotSharpEditor.Instance.MakeBottomPanelItemVisible(this);
- return;
- }
- }
-
- GD.PushError("Builds tab not found");
- }
-
- public override void _Ready()
- {
- base._Ready();
-
- editorInterface = GodotSharpEditor.Instance.GetEditorInterface();
-
- var editorBaseControl = editorInterface.GetBaseControl();
-
- SizeFlagsVertical = (int)SizeFlags.ExpandFill;
- SetAnchorsAndMarginsPreset(LayoutPreset.Wide);
-
- panelTabs = new TabContainer
- {
- TabAlign = TabContainer.TabAlignEnum.Left,
- RectMinSize = new Vector2(0, 228) * EditorScale,
- SizeFlagsVertical = (int)SizeFlags.ExpandFill
- };
- panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles"));
- panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles"));
- panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles"));
- AddChild(panelTabs);
-
- {
- // Builds tab
- panelBuildsTab = new VBoxContainer
- {
- Name = "Builds".TTR(),
- SizeFlagsHorizontal = (int)SizeFlags.ExpandFill
- };
- panelTabs.AddChild(panelBuildsTab);
-
- var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill };
- panelBuildsTab.AddChild(toolBarHBox);
-
- var buildProjectBtn = new Button
- {
- Text = "Build Project".TTR(),
- FocusMode = FocusModeEnum.None
- };
- buildProjectBtn.Connect("pressed", this, nameof(BuildProjectPressed));
- toolBarHBox.AddChild(buildProjectBtn);
-
- toolBarHBox.AddSpacer(begin: false);
-
- warningsBtn = new ToolButton
- {
- Text = "Warnings".TTR(),
- ToggleMode = true,
- Pressed = true,
- Visible = false,
- FocusMode = FocusModeEnum.None
- };
- warningsBtn.Connect("toggled", this, nameof(_WarningsToggled));
- toolBarHBox.AddChild(warningsBtn);
-
- errorsBtn = new ToolButton
- {
- Text = "Errors".TTR(),
- ToggleMode = true,
- Pressed = true,
- Visible = false,
- FocusMode = FocusModeEnum.None
- };
- errorsBtn.Connect("toggled", this, nameof(_ErrorsToggled));
- toolBarHBox.AddChild(errorsBtn);
-
- toolBarHBox.AddSpacer(begin: false);
-
- viewLogBtn = new Button
- {
- Text = "View log".TTR(),
- FocusMode = FocusModeEnum.None,
- Visible = false
- };
- viewLogBtn.Connect("pressed", this, nameof(_ViewLogPressed));
- toolBarHBox.AddChild(viewLogBtn);
-
- var hsc = new HSplitContainer
- {
- SizeFlagsHorizontal = (int)SizeFlags.ExpandFill,
- SizeFlagsVertical = (int)SizeFlags.ExpandFill
- };
- panelBuildsTab.AddChild(hsc);
-
- buildTabsList = new ItemList { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill };
- buildTabsList.Connect("item_selected", this, nameof(_BuildTabsItemSelected));
- buildTabsList.Connect("nothing_selected", this, nameof(_BuildTabsNothingSelected));
- hsc.AddChild(buildTabsList);
-
- buildTabs = new TabContainer
- {
- TabAlign = TabContainer.TabAlignEnum.Left,
- SizeFlagsHorizontal = (int)SizeFlags.ExpandFill,
- TabsVisible = false
- };
- hsc.AddChild(buildTabs);
- }
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
index 70bd552f2f..27737c3da0 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BuildInfo.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
@@ -4,13 +4,15 @@ using Godot.Collections;
using GodotTools.Internals;
using Path = System.IO.Path;
-namespace GodotTools
+namespace GodotTools.Build
{
[Serializable]
- public sealed class BuildInfo : Reference // TODO Remove Reference once we have proper serialization
+ public sealed class BuildInfo : RefCounted // TODO Remove RefCounted once we have proper serialization
{
public string Solution { get; }
+ public string[] Targets { get; }
public string Configuration { get; }
+ public bool Restore { get; }
public Array<string> CustomProperties { get; } = new Array<string>(); // TODO Use List once we have proper serialization
public string LogsDirPath => Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}");
@@ -18,7 +20,9 @@ namespace GodotTools
public override bool Equals(object obj)
{
if (obj is BuildInfo other)
- return other.Solution == Solution && other.Configuration == Configuration;
+ return other.Solution == Solution && other.Targets == Targets &&
+ other.Configuration == Configuration && other.Restore == Restore &&
+ other.CustomProperties == CustomProperties && other.LogsDirPath == LogsDirPath;
return false;
}
@@ -29,7 +33,11 @@ namespace GodotTools
{
int hash = 17;
hash = hash * 29 + Solution.GetHashCode();
+ hash = hash * 29 + Targets.GetHashCode();
hash = hash * 29 + Configuration.GetHashCode();
+ hash = hash * 29 + Restore.GetHashCode();
+ hash = hash * 29 + CustomProperties.GetHashCode();
+ hash = hash * 29 + LogsDirPath.GetHashCode();
return hash;
}
}
@@ -38,10 +46,12 @@ namespace GodotTools
{
}
- public BuildInfo(string solution, string configuration)
+ public BuildInfo(string solution, string[] targets, string configuration, bool restore)
{
Solution = solution;
+ Targets = targets;
Configuration = configuration;
+ Restore = restore;
}
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
new file mode 100644
index 0000000000..2b6f972529
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -0,0 +1,272 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using GodotTools.Ides.Rider;
+using GodotTools.Internals;
+using JetBrains.Annotations;
+using static GodotTools.Internals.Globals;
+using File = GodotTools.Utils.File;
+using OS = GodotTools.Utils.OS;
+
+namespace GodotTools.Build
+{
+ public static class BuildManager
+ {
+ private static BuildInfo _buildInProgress;
+
+ public const string PropNameMSBuildMono = "MSBuild (Mono)";
+ public const string PropNameMSBuildVs = "MSBuild (VS Build Tools)";
+ public const string PropNameMSBuildJetBrains = "MSBuild (JetBrains Rider)";
+ public const string PropNameDotnetCli = "dotnet CLI";
+
+ public const string MsBuildIssuesFileName = "msbuild_issues.csv";
+ public const string MsBuildLogFileName = "msbuild_log.txt";
+
+ public delegate void BuildLaunchFailedEventHandler(BuildInfo buildInfo, string reason);
+
+ public static event BuildLaunchFailedEventHandler BuildLaunchFailed;
+ public static event Action<BuildInfo> BuildStarted;
+ public static event Action<BuildResult> BuildFinished;
+ public static event Action<string> StdOutputReceived;
+ public static event Action<string> StdErrorReceived;
+
+ private static void RemoveOldIssuesFile(BuildInfo buildInfo)
+ {
+ var issuesFile = GetIssuesFilePath(buildInfo);
+
+ if (!File.Exists(issuesFile))
+ return;
+
+ File.Delete(issuesFile);
+ }
+
+ private static void ShowBuildErrorDialog(string message)
+ {
+ var plugin = GodotSharpEditor.Instance;
+ plugin.ShowErrorDialog(message, "Build error");
+ plugin.MakeBottomPanelItemVisible(plugin.MSBuildPanel);
+ }
+
+ public static void RestartBuild(BuildOutputView buildOutputView) => throw new NotImplementedException();
+ public static void StopBuild(BuildOutputView buildOutputView) => throw new NotImplementedException();
+
+ private static string GetLogFilePath(BuildInfo buildInfo)
+ {
+ return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName);
+ }
+
+ private static string GetIssuesFilePath(BuildInfo buildInfo)
+ {
+ return Path.Combine(buildInfo.LogsDirPath, MsBuildIssuesFileName);
+ }
+
+ private static void PrintVerbose(string text)
+ {
+ if (Godot.OS.IsStdoutVerbose())
+ Godot.GD.Print(text);
+ }
+
+ public static bool Build(BuildInfo buildInfo)
+ {
+ if (_buildInProgress != null)
+ throw new InvalidOperationException("A build is already in progress");
+
+ _buildInProgress = buildInfo;
+
+ try
+ {
+ BuildStarted?.Invoke(buildInfo);
+
+ // Required in order to update the build tasks list
+ Internal.GodotMainIteration();
+
+ try
+ {
+ RemoveOldIssuesFile(buildInfo);
+ }
+ catch (IOException e)
+ {
+ BuildLaunchFailed?.Invoke(buildInfo, $"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
+ Console.Error.WriteLine(e);
+ }
+
+ try
+ {
+ int exitCode = BuildSystem.Build(buildInfo, StdOutputReceived, StdErrorReceived);
+
+ if (exitCode != 0)
+ PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
+
+ BuildFinished?.Invoke(exitCode == 0 ? BuildResult.Success : BuildResult.Error);
+
+ return exitCode == 0;
+ }
+ catch (Exception e)
+ {
+ BuildLaunchFailed?.Invoke(buildInfo, $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
+ Console.Error.WriteLine(e);
+ return false;
+ }
+ }
+ finally
+ {
+ _buildInProgress = null;
+ }
+ }
+
+ public static async Task<bool> BuildAsync(BuildInfo buildInfo)
+ {
+ if (_buildInProgress != null)
+ throw new InvalidOperationException("A build is already in progress");
+
+ _buildInProgress = buildInfo;
+
+ try
+ {
+ BuildStarted?.Invoke(buildInfo);
+
+ try
+ {
+ RemoveOldIssuesFile(buildInfo);
+ }
+ catch (IOException e)
+ {
+ BuildLaunchFailed?.Invoke(buildInfo, $"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
+ Console.Error.WriteLine(e);
+ }
+
+ try
+ {
+ int exitCode = await BuildSystem.BuildAsync(buildInfo, StdOutputReceived, StdErrorReceived);
+
+ if (exitCode != 0)
+ PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
+
+ BuildFinished?.Invoke(exitCode == 0 ? BuildResult.Success : BuildResult.Error);
+
+ return exitCode == 0;
+ }
+ catch (Exception e)
+ {
+ BuildLaunchFailed?.Invoke(buildInfo, $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
+ Console.Error.WriteLine(e);
+ return false;
+ }
+ }
+ finally
+ {
+ _buildInProgress = null;
+ }
+ }
+
+ public static bool BuildProjectBlocking(string config, [CanBeNull] string[] targets = null, [CanBeNull] string platform = null)
+ {
+ var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets ?? new[] {"Build"}, config, restore: true);
+
+ // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it.
+ if (platform != null || OS.PlatformNameMap.TryGetValue(Godot.OS.GetName(), out platform))
+ buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}");
+
+ if (Internal.GodotIsRealTDouble())
+ buildInfo.CustomProperties.Add("GodotRealTIsDouble=true");
+
+ return BuildProjectBlocking(buildInfo);
+ }
+
+ private static bool BuildProjectBlocking(BuildInfo buildInfo)
+ {
+ if (!File.Exists(buildInfo.Solution))
+ return true; // No solution to build
+
+ // Make sure the API assemblies are up to date before building the project.
+ // We may not have had the chance to update the release API assemblies, and the debug ones
+ // may have been deleted by the user at some point after they were loaded by the Godot editor.
+ string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(buildInfo.Configuration == "ExportRelease" ? "Release" : "Debug");
+
+ if (!string.IsNullOrEmpty(apiAssembliesUpdateError))
+ {
+ ShowBuildErrorDialog("Failed to update the Godot API assemblies");
+ return false;
+ }
+
+ using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
+ {
+ pr.Step("Building project solution", 0);
+
+ if (!Build(buildInfo))
+ {
+ ShowBuildErrorDialog("Failed to build project solution");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static bool EditorBuildCallback()
+ {
+ if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
+ return true; // No solution to build
+
+ try
+ {
+ // Make sure our packages are added to the fallback folder
+ NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath);
+ }
+ catch (Exception e)
+ {
+ Godot.GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
+ }
+
+ if (GodotSharpEditor.Instance.SkipBuildBeforePlaying)
+ return true; // Requested play from an external editor/IDE which already built the project
+
+ return BuildProjectBlocking("Debug");
+ }
+
+ public static void Initialize()
+ {
+ // Build tool settings
+ var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+
+ BuildTool msbuildDefault;
+
+ if (OS.IsWindows)
+ {
+ if (RiderPathManager.IsExternalEditorSetToRider(editorSettings))
+ msbuildDefault = BuildTool.JetBrainsMsBuild;
+ else
+ msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildVs;
+ }
+ else
+ {
+ msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildMono;
+ }
+
+ EditorDef("mono/builds/build_tool", msbuildDefault);
+
+ string hintString;
+
+ if (OS.IsWindows)
+ {
+ hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," +
+ $"{PropNameMSBuildVs}:{(int)BuildTool.MsBuildVs}," +
+ $"{PropNameMSBuildJetBrains}:{(int)BuildTool.JetBrainsMsBuild}," +
+ $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}";
+ }
+ else
+ {
+ hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," +
+ $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}";
+ }
+
+ editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
+ {
+ ["type"] = Godot.Variant.Type.Int,
+ ["name"] = "mono/builds/build_tool",
+ ["hint"] = Godot.PropertyHint.Enum,
+ ["hint_string"] = hintString
+ });
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
new file mode 100644
index 0000000000..c380707587
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs
@@ -0,0 +1,417 @@
+using Godot;
+using System;
+using Godot.Collections;
+using GodotTools.Internals;
+using JetBrains.Annotations;
+using File = GodotTools.Utils.File;
+using Path = System.IO.Path;
+
+namespace GodotTools.Build
+{
+ public class BuildOutputView : VBoxContainer, ISerializationListener
+ {
+ [Serializable]
+ private class BuildIssue : RefCounted // TODO Remove RefCounted once we have proper serialization
+ {
+ public bool Warning { get; set; }
+ public string File { get; set; }
+ public int Line { get; set; }
+ public int Column { get; set; }
+ public string Code { get; set; }
+ public string Message { get; set; }
+ public string ProjectFile { get; set; }
+ }
+
+ private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization
+ private ItemList issuesList;
+ private TextEdit buildLog;
+ private PopupMenu issuesListContextMenu;
+
+ private readonly object pendingBuildLogTextLock = new object();
+ [NotNull] private string pendingBuildLogText = string.Empty;
+
+ [Signal] public event Action BuildStateChanged;
+
+ public bool HasBuildExited { get; private set; } = false;
+
+ public BuildResult? BuildResult { get; private set; } = null;
+
+ public int ErrorCount { get; private set; } = 0;
+
+ public int WarningCount { get; private set; } = 0;
+
+ public bool ErrorsVisible { get; set; } = true;
+ public bool WarningsVisible { get; set; } = true;
+
+ public Texture2D BuildStateIcon
+ {
+ get
+ {
+ if (!HasBuildExited)
+ return GetThemeIcon("Stop", "EditorIcons");
+
+ if (BuildResult == Build.BuildResult.Error)
+ return GetThemeIcon("Error", "EditorIcons");
+
+ if (WarningCount > 1)
+ return GetThemeIcon("Warning", "EditorIcons");
+
+ return null;
+ }
+ }
+
+ private BuildInfo BuildInfo { get; set; }
+
+ public bool LogVisible
+ {
+ set => buildLog.Visible = value;
+ }
+
+ private void LoadIssuesFromFile(string csvFile)
+ {
+ using (var file = new Godot.File())
+ {
+ try
+ {
+ Error openError = file.Open(csvFile, Godot.File.ModeFlags.Read);
+
+ if (openError != Error.Ok)
+ 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
+ {
+ file.Close(); // Disposing it is not enough. We need to call Close()
+ }
+ }
+ }
+
+ private void IssueActivated(int idx)
+ {
+ if (idx < 0 || idx >= issuesList.GetItemCount())
+ throw new IndexOutOfRangeException("Item list index out of range");
+
+ // Get correct issue idx from issue list
+ int issueIndex = (int)(long)issuesList.GetItemMetadata(idx);
+
+ if (issueIndex < 0 || issueIndex >= issues.Count)
+ throw new IndexOutOfRangeException("Issue index out of range");
+
+ BuildIssue issue = issues[issueIndex];
+
+ if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File))
+ return;
+
+ string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir();
+
+ string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath());
+
+ if (!File.Exists(file))
+ return;
+
+ file = ProjectSettings.LocalizePath(file);
+
+ if (file.StartsWith("res://"))
+ {
+ var script = (Script)ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType);
+
+ if (script != null && Internal.ScriptEditorEdit(script, issue.Line, issue.Column))
+ Internal.EditorNodeShowScriptScreen();
+ }
+ }
+
+ public void UpdateIssuesList()
+ {
+ issuesList.Clear();
+
+ using (var warningIcon = GetThemeIcon("Warning", "EditorIcons"))
+ using (var errorIcon = GetThemeIcon("Error", "EditorIcons"))
+ {
+ for (int i = 0; i < issues.Count; i++)
+ {
+ BuildIssue issue = issues[i];
+
+ if (!(issue.Warning ? WarningsVisible : ErrorsVisible))
+ continue;
+
+ string tooltip = string.Empty;
+ tooltip += $"Message: {issue.Message}";
+
+ if (!string.IsNullOrEmpty(issue.Code))
+ tooltip += $"\nCode: {issue.Code}";
+
+ tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}";
+
+ string text = string.Empty;
+
+ if (!string.IsNullOrEmpty(issue.File))
+ {
+ text += $"{issue.File}({issue.Line},{issue.Column}): ";
+
+ tooltip += $"\nFile: {issue.File}";
+ tooltip += $"\nLine: {issue.Line}";
+ tooltip += $"\nColumn: {issue.Column}";
+ }
+
+ if (!string.IsNullOrEmpty(issue.ProjectFile))
+ tooltip += $"\nProject: {issue.ProjectFile}";
+
+ text += issue.Message;
+
+ int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal);
+ string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx);
+ issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon);
+
+ int index = issuesList.GetItemCount() - 1;
+ issuesList.SetItemTooltip(index, tooltip);
+ issuesList.SetItemMetadata(index, i);
+ }
+ }
+ }
+
+ private void BuildLaunchFailed(BuildInfo buildInfo, string cause)
+ {
+ HasBuildExited = true;
+ BuildResult = Build.BuildResult.Error;
+
+ issuesList.Clear();
+
+ var issue = new BuildIssue {Message = cause, Warning = false};
+
+ ErrorCount += 1;
+ issues.Add(issue);
+
+ UpdateIssuesList();
+
+ EmitSignal(nameof(BuildStateChanged));
+ }
+
+ private void BuildStarted(BuildInfo buildInfo)
+ {
+ BuildInfo = buildInfo;
+ HasBuildExited = false;
+
+ issues.Clear();
+ WarningCount = 0;
+ ErrorCount = 0;
+ buildLog.Text = string.Empty;
+
+ UpdateIssuesList();
+
+ EmitSignal(nameof(BuildStateChanged));
+ }
+
+ private void BuildFinished(BuildResult result)
+ {
+ HasBuildExited = true;
+ BuildResult = result;
+
+ LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName));
+
+ UpdateIssuesList();
+
+ EmitSignal(nameof(BuildStateChanged));
+ }
+
+ private void UpdateBuildLogText()
+ {
+ lock (pendingBuildLogTextLock)
+ {
+ buildLog.Text += pendingBuildLogText;
+ pendingBuildLogText = string.Empty;
+ ScrollToLastNonEmptyLogLine();
+ }
+ }
+
+ private void StdOutputReceived(string text)
+ {
+ lock (pendingBuildLogTextLock)
+ {
+ if (pendingBuildLogText.Length == 0)
+ CallDeferred(nameof(UpdateBuildLogText));
+ pendingBuildLogText += text + "\n";
+ }
+ }
+
+ private void StdErrorReceived(string text)
+ {
+ lock (pendingBuildLogTextLock)
+ {
+ if (pendingBuildLogText.Length == 0)
+ CallDeferred(nameof(UpdateBuildLogText));
+ pendingBuildLogText += text + "\n";
+ }
+ }
+
+ private void ScrollToLastNonEmptyLogLine()
+ {
+ int line;
+ for (line = buildLog.GetLineCount(); line > 0; line--)
+ {
+ string lineText = buildLog.GetLine(line);
+
+ if (!string.IsNullOrEmpty(lineText) || !string.IsNullOrEmpty(lineText?.Trim()))
+ break;
+ }
+
+ buildLog.CursorSetLine(line);
+ }
+
+ public void RestartBuild()
+ {
+ if (!HasBuildExited)
+ throw new InvalidOperationException("Build already started");
+
+ BuildManager.RestartBuild(this);
+ }
+
+ public void StopBuild()
+ {
+ if (!HasBuildExited)
+ throw new InvalidOperationException("Build is not in progress");
+
+ BuildManager.StopBuild(this);
+ }
+
+ private enum IssuesContextMenuOption
+ {
+ Copy
+ }
+
+ private void IssuesListContextOptionPressed(int id)
+ {
+ switch ((IssuesContextMenuOption)id)
+ {
+ case IssuesContextMenuOption.Copy:
+ {
+ // We don't allow multi-selection but just in case that changes later...
+ string text = null;
+
+ foreach (int issueIndex in issuesList.GetSelectedItems())
+ {
+ if (text != null)
+ text += "\n";
+ text += issuesList.GetItemText(issueIndex);
+ }
+
+ if (text != null)
+ DisplayServer.ClipboardSet(text);
+ break;
+ }
+ default:
+ throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid issue context menu option");
+ }
+ }
+
+ private void IssuesListRmbSelected(int index, Vector2 atPosition)
+ {
+ _ = index; // Unused
+
+ issuesListContextMenu.Clear();
+ issuesListContextMenu.Size = new Vector2i(1, 1);
+
+ if (issuesList.IsAnythingSelected())
+ {
+ // Add menu entries for the selected item
+ issuesListContextMenu.AddIconItem(GetThemeIcon("ActionCopy", "EditorIcons"),
+ label: "Copy Error".TTR(), (int)IssuesContextMenuOption.Copy);
+ }
+
+ if (issuesListContextMenu.GetItemCount() > 0)
+ {
+ issuesListContextMenu.Position = (Vector2i)(issuesList.RectGlobalPosition + atPosition);
+ issuesListContextMenu.Popup();
+ }
+ }
+
+ public override void _Ready()
+ {
+ base._Ready();
+
+ SizeFlagsVertical = (int)SizeFlags.ExpandFill;
+
+ var hsc = new HSplitContainer
+ {
+ SizeFlagsHorizontal = (int)SizeFlags.ExpandFill,
+ SizeFlagsVertical = (int)SizeFlags.ExpandFill
+ };
+ AddChild(hsc);
+
+ issuesList = new ItemList
+ {
+ SizeFlagsVertical = (int)SizeFlags.ExpandFill,
+ SizeFlagsHorizontal = (int)SizeFlags.ExpandFill // Avoid being squashed by the build log
+ };
+ issuesList.ItemActivated += IssueActivated;
+ issuesList.AllowRmbSelect = true;
+ issuesList.ItemRmbSelected += IssuesListRmbSelected;
+ hsc.AddChild(issuesList);
+
+ issuesListContextMenu = new PopupMenu();
+ issuesListContextMenu.IdPressed += IssuesListContextOptionPressed;
+ issuesList.AddChild(issuesListContextMenu);
+
+ buildLog = new TextEdit
+ {
+ Readonly = true,
+ SizeFlagsVertical = (int)SizeFlags.ExpandFill,
+ SizeFlagsHorizontal = (int)SizeFlags.ExpandFill // Avoid being squashed by the issues list
+ };
+ hsc.AddChild(buildLog);
+
+ AddBuildEventListeners();
+ }
+
+ private void AddBuildEventListeners()
+ {
+ BuildManager.BuildLaunchFailed += BuildLaunchFailed;
+ BuildManager.BuildStarted += BuildStarted;
+ BuildManager.BuildFinished += BuildFinished;
+ // StdOutput/Error can be received from different threads, so we need to use CallDeferred
+ BuildManager.StdOutputReceived += StdOutputReceived;
+ BuildManager.StdErrorReceived += StdErrorReceived;
+ }
+
+ public void OnBeforeSerialize()
+ {
+ // In case it didn't update yet. We don't want to have to serialize any pending output.
+ UpdateBuildLogText();
+ }
+
+ public void OnAfterDeserialize()
+ {
+ AddBuildEventListeners(); // Re-add them
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildResult.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildResult.cs
new file mode 100644
index 0000000000..59055b60c3
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildResult.cs
@@ -0,0 +1,8 @@
+namespace GodotTools.Build
+{
+ public enum BuildResult
+ {
+ Error,
+ Success
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
index 43c96d2e30..bac7a2e6db 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
@@ -14,16 +14,6 @@ namespace GodotTools.Build
{
public static class BuildSystem
{
- private static string GetMsBuildPath()
- {
- string msbuildPath = MsBuildFinder.FindMsBuild();
-
- if (msbuildPath == null)
- throw new FileNotFoundException("Cannot find the MSBuild executable.");
-
- return msbuildPath;
- }
-
private static string MonoWindowsBinDir
{
get
@@ -46,35 +36,32 @@ namespace GodotTools.Build
{
if (OS.IsWindows)
{
- return (BuildManager.BuildTool)EditorSettings.GetSetting("mono/builds/build_tool")
- == BuildManager.BuildTool.MsBuildMono;
+ return (BuildTool)EditorSettings.GetSetting("mono/builds/build_tool")
+ == BuildTool.MsBuildMono;
}
return false;
}
}
- private static bool PrintBuildOutput =>
- (bool)EditorSettings.GetSetting("mono/builds/print_build_output");
-
- private static Process LaunchBuild(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
+ private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
{
- var customPropertiesList = new List<string>();
-
- if (customProperties != null)
- customPropertiesList.AddRange(customProperties);
+ (string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild();
- string compilerArgs = BuildArguments(solution, config, loggerOutputDir, customPropertiesList);
+ if (msbuildPath == null)
+ throw new FileNotFoundException("Cannot find the MSBuild executable.");
- var startInfo = new ProcessStartInfo(GetMsBuildPath(), compilerArgs);
+ string compilerArgs = BuildArguments(buildTool, buildInfo);
- bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput;
+ var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs);
- if (!redirectOutput || Godot.OS.IsStdoutVerbose())
- Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}");
+ string launchMessage = $"Running: \"{startInfo.FileName}\" {startInfo.Arguments}";
+ stdOutHandler?.Invoke(launchMessage);
+ if (Godot.OS.IsStdoutVerbose())
+ Console.WriteLine(launchMessage);
- startInfo.RedirectStandardOutput = redirectOutput;
- startInfo.RedirectStandardError = redirectOutput;
+ startInfo.RedirectStandardOutput = true;
+ startInfo.RedirectStandardError = true;
startInfo.UseShellExecute = false;
if (UsingMonoMsBuildOnWindows)
@@ -90,34 +77,24 @@ namespace GodotTools.Build
// Needed when running from Developer Command Prompt for VS
RemovePlatformVariable(startInfo.EnvironmentVariables);
- var process = new Process { StartInfo = startInfo };
+ var process = new Process {StartInfo = startInfo};
+
+ if (stdOutHandler != null)
+ process.OutputDataReceived += (s, e) => stdOutHandler.Invoke(e.Data);
+ if (stdErrHandler != null)
+ process.ErrorDataReceived += (s, e) => stdErrHandler.Invoke(e.Data);
process.Start();
- if (redirectOutput)
- {
- process.BeginOutputReadLine();
- process.BeginErrorReadLine();
- }
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
return process;
}
- public static int Build(BuildInfo buildInfo)
+ public static int Build(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
{
- return Build(buildInfo.Solution, buildInfo.Configuration,
- buildInfo.LogsDirPath, buildInfo.CustomProperties);
- }
-
- public static async Task<int> BuildAsync(BuildInfo buildInfo)
- {
- return await BuildAsync(buildInfo.Solution, buildInfo.Configuration,
- buildInfo.LogsDirPath, buildInfo.CustomProperties);
- }
-
- public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
- {
- using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
+ using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
{
process.WaitForExit();
@@ -125,9 +102,9 @@ namespace GodotTools.Build
}
}
- public static async Task<int> BuildAsync(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
+ public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler)
{
- using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
+ using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler))
{
await process.WaitForExitAsync();
@@ -135,12 +112,23 @@ namespace GodotTools.Build
}
}
- private static string BuildArguments(string solution, string config, string loggerOutputDir, List<string> customProperties)
+ private static string BuildArguments(BuildTool buildTool, BuildInfo buildInfo)
{
- string arguments = $@"""{solution}"" /v:normal /t:Build ""/p:{"Configuration=" + config}"" " +
- $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}""";
+ string arguments = string.Empty;
+
+ if (buildTool == BuildTool.DotnetCli)
+ arguments += "msbuild"; // `dotnet msbuild` command
- foreach (string customProperty in customProperties)
+ arguments += $@" ""{buildInfo.Solution}""";
+
+ if (buildInfo.Restore)
+ arguments += " /restore";
+
+ arguments += $@" /t:{string.Join(",", buildInfo.Targets)} " +
+ $@"""/p:{"Configuration=" + buildInfo.Configuration}"" /v:normal " +
+ $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{buildInfo.LogsDirPath}""";
+
+ foreach (string customProperty in buildInfo.CustomProperties)
{
arguments += " /p:" + customProperty;
}
@@ -163,10 +151,5 @@ namespace GodotTools.Build
foreach (string env in platformEnvironmentVariables)
environmentVariables.Remove(env);
}
-
- private static bool IsDebugMsBuildRequested()
- {
- return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1";
- }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs
new file mode 100644
index 0000000000..837c8adddb
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs
@@ -0,0 +1,10 @@
+namespace GodotTools.Build
+{
+ public enum BuildTool : long
+ {
+ MsBuildMono,
+ MsBuildVs,
+ JetBrainsMsBuild,
+ DotnetCli
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
new file mode 100644
index 0000000000..ed69c2b833
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs
@@ -0,0 +1,181 @@
+using System;
+using Godot;
+using GodotTools.Internals;
+using JetBrains.Annotations;
+using static GodotTools.Internals.Globals;
+using File = GodotTools.Utils.File;
+
+namespace GodotTools.Build
+{
+ public class MSBuildPanel : VBoxContainer
+ {
+ public BuildOutputView BuildOutputView { get; private set; }
+
+ private Button errorsBtn;
+ private Button warningsBtn;
+ private Button viewLogBtn;
+
+ private void WarningsToggled(bool pressed)
+ {
+ BuildOutputView.WarningsVisible = pressed;
+ BuildOutputView.UpdateIssuesList();
+ }
+
+ private void ErrorsToggled(bool pressed)
+ {
+ BuildOutputView.ErrorsVisible = pressed;
+ BuildOutputView.UpdateIssuesList();
+ }
+
+ [UsedImplicitly]
+ public void BuildSolution()
+ {
+ if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
+ return; // No solution to build
+
+ try
+ {
+ // Make sure our packages are added to the fallback folder
+ NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath);
+ }
+ catch (Exception e)
+ {
+ GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
+ }
+
+ if (!BuildManager.BuildProjectBlocking("Debug"))
+ return; // Build failed
+
+ // Notify running game for hot-reload
+ Internal.EditorDebuggerNodeReloadScripts();
+
+ // Hot-reload in the editor
+ GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer();
+
+ if (Internal.IsAssembliesReloadingNeeded())
+ Internal.ReloadAssemblies(softReload: false);
+ }
+
+ [UsedImplicitly]
+ private void RebuildSolution()
+ {
+ if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
+ return; // No solution to build
+
+ try
+ {
+ // Make sure our packages are added to the fallback folder
+ NuGetUtils.AddBundledPackagesToFallbackFolder(NuGetUtils.GodotFallbackFolderPath);
+ }
+ catch (Exception e)
+ {
+ GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message);
+ }
+
+ if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] {"Rebuild"}))
+ return; // Build failed
+
+ // Notify running game for hot-reload
+ Internal.EditorDebuggerNodeReloadScripts();
+
+ // Hot-reload in the editor
+ GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer();
+
+ if (Internal.IsAssembliesReloadingNeeded())
+ Internal.ReloadAssemblies(softReload: false);
+ }
+
+ [UsedImplicitly]
+ private void CleanSolution()
+ {
+ if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
+ return; // No solution to build
+
+ BuildManager.BuildProjectBlocking("Debug", targets: new[] {"Clean"});
+ }
+
+ private void ViewLogToggled(bool pressed) => BuildOutputView.LogVisible = pressed;
+
+ private void BuildMenuOptionPressed(int id)
+ {
+ switch ((BuildMenuOptions)id)
+ {
+ case BuildMenuOptions.BuildSolution:
+ BuildSolution();
+ break;
+ case BuildMenuOptions.RebuildSolution:
+ RebuildSolution();
+ break;
+ case BuildMenuOptions.CleanSolution:
+ CleanSolution();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid build menu option");
+ }
+ }
+
+ private enum BuildMenuOptions
+ {
+ BuildSolution,
+ RebuildSolution,
+ CleanSolution
+ }
+
+ public override void _Ready()
+ {
+ base._Ready();
+
+ RectMinSize = new Vector2(0, 228) * EditorScale;
+ SizeFlagsVertical = (int)SizeFlags.ExpandFill;
+
+ var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill};
+ AddChild(toolBarHBox);
+
+ var buildMenuBtn = new MenuButton {Text = "Build", Icon = GetThemeIcon("Play", "EditorIcons")};
+ 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.IdPressed += BuildMenuOptionPressed;
+
+ errorsBtn = new Button
+ {
+ HintTooltip = "Show Errors".TTR(),
+ Icon = GetThemeIcon("StatusError", "EditorIcons"),
+ ExpandIcon = false,
+ ToggleMode = true,
+ Pressed = true,
+ FocusMode = FocusModeEnum.None
+ };
+ errorsBtn.Toggled += ErrorsToggled;
+ toolBarHBox.AddChild(errorsBtn);
+
+ warningsBtn = new Button
+ {
+ HintTooltip = "Show Warnings".TTR(),
+ Icon = GetThemeIcon("NodeWarning", "EditorIcons"),
+ ExpandIcon = false,
+ ToggleMode = true,
+ Pressed = true,
+ FocusMode = FocusModeEnum.None
+ };
+ warningsBtn.Toggled += WarningsToggled;
+ toolBarHBox.AddChild(warningsBtn);
+
+ viewLogBtn = new Button
+ {
+ Text = "Show Output".TTR(),
+ ToggleMode = true,
+ Pressed = true,
+ FocusMode = FocusModeEnum.None
+ };
+ viewLogBtn.Toggled += ViewLogToggled;
+ toolBarHBox.AddChild(viewLogBtn);
+
+ BuildOutputView = new BuildOutputView();
+ AddChild(BuildOutputView);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
index c3db52aa9e..774c49e705 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using Godot;
+using GodotTools.Ides.Rider;
using GodotTools.Internals;
using Directory = System.IO.Directory;
using Environment = System.Environment;
@@ -16,69 +17,96 @@ namespace GodotTools.Build
private static string _msbuildToolsPath = string.Empty;
private static string _msbuildUnixPath = string.Empty;
- public static string FindMsBuild()
+ public static (string, BuildTool) FindMsBuild()
{
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- var buildTool = (BuildManager.BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
+ var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
if (OS.IsWindows)
{
switch (buildTool)
{
- case BuildManager.BuildTool.MsBuildVs:
+ case BuildTool.DotnetCli:
{
- if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath))
+ string dotnetCliPath = OS.PathWhich("dotnet");
+ if (!string.IsNullOrEmpty(dotnetCliPath))
+ return (dotnetCliPath, BuildTool.DotnetCli);
+ GD.PushError($"Cannot find executable for '{BuildManager.PropNameDotnetCli}'. Fallback to MSBuild from Visual Studio.");
+ goto case BuildTool.MsBuildVs;
+ }
+ case BuildTool.MsBuildVs:
+ {
+ if (string.IsNullOrEmpty(_msbuildToolsPath) || !File.Exists(_msbuildToolsPath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_msbuildToolsPath = FindMsBuildToolsPathOnWindows();
- if (_msbuildToolsPath.Empty())
- {
- throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildVs}'.");
- }
+ if (string.IsNullOrEmpty(_msbuildToolsPath))
+ throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildVs}'.");
}
if (!_msbuildToolsPath.EndsWith("\\"))
_msbuildToolsPath += "\\";
- return Path.Combine(_msbuildToolsPath, "MSBuild.exe");
+ return (Path.Combine(_msbuildToolsPath, "MSBuild.exe"), BuildTool.MsBuildVs);
}
- case BuildManager.BuildTool.MsBuildMono:
+ case BuildTool.MsBuildMono:
{
string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat");
if (!File.Exists(msbuildPath))
- {
- throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMsbuildMono}'. Tried with path: {msbuildPath}");
- }
+ throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildMono}'. Tried with path: {msbuildPath}");
- return msbuildPath;
+ return (msbuildPath, BuildTool.MsBuildMono);
+ }
+ case BuildTool.JetBrainsMsBuild:
+ {
+ var editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName);
+
+ if (!File.Exists(editorPath))
+ throw new FileNotFoundException($"Cannot find Rider executable. Tried with path: {editorPath}");
+
+ var riderDir = new FileInfo(editorPath).Directory?.Parent;
+
+ string msbuildPath = Path.Combine(riderDir.FullName, @"tools\MSBuild\Current\Bin\MSBuild.exe");
+
+ if (!File.Exists(msbuildPath))
+ throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildJetBrains}'. Tried with path: {msbuildPath}");
+
+ return (msbuildPath, BuildTool.JetBrainsMsBuild);
}
default:
throw new IndexOutOfRangeException("Invalid build tool in editor settings");
}
}
- if (OS.IsUnixLike())
+ if (OS.IsUnixLike)
{
- if (buildTool == BuildManager.BuildTool.MsBuildMono)
+ switch (buildTool)
{
- if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath))
+ case BuildTool.DotnetCli:
{
- // Try to search it again if it wasn't found last time or if it was removed from its location
- _msbuildUnixPath = FindBuildEngineOnUnix("msbuild");
+ string dotnetCliPath = FindBuildEngineOnUnix("dotnet");
+ if (!string.IsNullOrEmpty(dotnetCliPath))
+ return (dotnetCliPath, BuildTool.DotnetCli);
+ GD.PushError($"Cannot find executable for '{BuildManager.PropNameDotnetCli}'. Fallback to MSBuild from Mono.");
+ goto case BuildTool.MsBuildMono;
}
-
- if (_msbuildUnixPath.Empty())
+ case BuildTool.MsBuildMono:
{
- throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMsbuildMono}'");
- }
+ if (string.IsNullOrEmpty(_msbuildUnixPath) || !File.Exists(_msbuildUnixPath))
+ {
+ // Try to search it again if it wasn't found last time or if it was removed from its location
+ _msbuildUnixPath = FindBuildEngineOnUnix("msbuild");
+ }
- return _msbuildUnixPath;
- }
- else
- {
- throw new IndexOutOfRangeException("Invalid build tool in editor settings");
+ if (string.IsNullOrEmpty(_msbuildUnixPath))
+ throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMSBuildMono}'");
+
+ return (_msbuildUnixPath, BuildTool.MsBuildMono);
+ }
+ default:
+ throw new IndexOutOfRangeException("Invalid build tool in editor settings");
}
}
@@ -91,10 +119,14 @@ namespace GodotTools.Build
{
var result = new List<string>();
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/");
+ result.Add("/opt/local/bin/");
result.Add("/usr/local/var/homebrew/linked/mono/bin/");
+ result.Add("/usr/local/bin/");
+ result.Add("/usr/local/bin/dotnet/");
+ result.Add("/usr/local/share/dotnet/");
}
result.Add("/opt/novell/mono/bin/");
@@ -107,12 +139,12 @@ namespace GodotTools.Build
{
string ret = OS.PathWhich(name);
- if (!ret.Empty())
+ if (!string.IsNullOrEmpty(ret))
return ret;
string retFallback = OS.PathWhich($"{name}.exe");
- if (!retFallback.Empty())
+ if (!string.IsNullOrEmpty(retFallback))
return retFallback;
foreach (string hintDir in MsBuildHintDirs)
@@ -133,14 +165,27 @@ namespace GodotTools.Build
// Try to find 15.0 with vswhere
- string vsWherePath = Environment.GetEnvironmentVariable(Internal.GodotIs32Bits() ? "ProgramFiles" : "ProgramFiles(x86)");
- vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe";
+ var envNames = Internal.GodotIs32Bits() ? new[] {"ProgramFiles", "ProgramW6432"} : new[] {"ProgramFiles(x86)", "ProgramFiles"};
+
+ string vsWherePath = null;
+ foreach (var envName in envNames)
+ {
+ vsWherePath = Environment.GetEnvironmentVariable(envName);
+ if (!string.IsNullOrEmpty(vsWherePath))
+ {
+ vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe";
+ if (File.Exists(vsWherePath))
+ break;
+ }
+
+ vsWherePath = null;
+ }
- var vsWhereArgs = new[] { "-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild" };
+ var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"};
var outputArray = new Godot.Collections.Array<string>();
int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs,
- blocking: true, output: (Godot.Collections.Array)outputArray);
+ output: (Godot.Collections.Array)outputArray);
if (exitCode != 0)
return string.Empty;
@@ -164,7 +209,7 @@ namespace GodotTools.Build
string value = line.Substring(sepIdx + 1).StripEdges();
- if (value.Empty())
+ if (string.IsNullOrEmpty(value))
throw new FormatException("installationPath value is empty");
if (!value.EndsWith("\\"))
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs
new file mode 100644
index 0000000000..16dd1c8c6b
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs
@@ -0,0 +1,297 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Xml;
+using Godot;
+using GodotTools.Internals;
+using GodotTools.Shared;
+using Directory = GodotTools.Utils.Directory;
+using Environment = System.Environment;
+using File = GodotTools.Utils.File;
+
+namespace GodotTools.Build
+{
+ public static class NuGetUtils
+ {
+ public const string GodotFallbackFolderName = "Godot Offline Packages";
+
+ 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 Exception("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.
+ /// Does not determine whether the returned files exist or not.
+ /// </summary>
+ private static string[] GetAllUserNuGetConfigFilePaths()
+ {
+ // Where to find 'NuGet/NuGet.Config':
+ //
+ // - Mono/.NETFramework (standalone NuGet):
+ // Uses Environment.SpecialFolder.ApplicationData
+ // - Windows: '%APPDATA%'
+ // - Linux/macOS: '$HOME/.config'
+ // - CoreCLR (dotnet CLI NuGet):
+ // - Windows: '%APPDATA%'
+ // - Linux/macOS: '$DOTNET_CLI_HOME/.nuget' otherwise '$HOME/.nuget'
+
+ string applicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+
+ if (Utils.OS.IsWindows)
+ {
+ // %APPDATA% for both
+ return new[] {Path.Combine(applicationData, "NuGet", "NuGet.Config")};
+ }
+
+ var paths = new string[2];
+
+ // CoreCLR (dotnet CLI NuGet)
+
+ string dotnetCliHome = Environment.GetEnvironmentVariable("DOTNET_CLI_HOME");
+ if (!string.IsNullOrEmpty(dotnetCliHome))
+ {
+ paths[0] = Path.Combine(dotnetCliHome, ".nuget", "NuGet", "NuGet.Config");
+ }
+ 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");
+ }
+
+ // Mono/.NETFramework (standalone NuGet)
+
+ // ApplicationData is $HOME/.config on Linux/macOS
+ paths[1] = Path.Combine(applicationData, "NuGet", "NuGet.Config");
+
+ return paths;
+ }
+
+ // nupkg extraction
+ //
+ // Exclude: (NuGet.Client -> NuGet.Packaging.PackageHelper.ExcludePaths)
+ // package/
+ // _rels/
+ // [Content_Types].xml
+ //
+ // Don't ignore files that begin with a dot (.)
+ //
+ // 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,
+ /// for both standalone NuGet (Mono/.NETFramework) and dotnet CLI NuGet.
+ /// </summary>
+ public static void AddFallbackFolderToUserNuGetConfigs(string name, string path)
+ {
+ foreach (string nuGetConfigPath in GetAllUserNuGetConfigFilePaths())
+ {
+ 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""?>
+<configuration>
+ <packageSources>
+ <add key=""nuget.org"" value=""https://api.nuget.org/v3/index.json"" protocolVersion=""3"" />
+ </packageSources>
+</configuration>
+";
+ System.IO.File.WriteAllText(nuGetConfigPath, defaultConfig, Encoding.UTF8); // UTF-8 with BOM
+ }
+
+ AddFallbackFolderToNuGetConfig(nuGetConfigPath, name, path);
+ }
+ }
+
+ private static void AddPackageToFallbackFolder(string fallbackFolder,
+ string nupkgPath, string packageId, string packageVersion)
+ {
+ // dotnet CLI provides no command for this, but we can do it manually.
+ //
+ // - The expected structure is as follows:
+ // fallback_folder/
+ // <package.name>/<version>/
+ // <package.name>.<version>.nupkg
+ // <package.name>.<version>.nupkg.sha512
+ // <package.name>.nuspec
+ // ... extracted nupkg files (check code for excluded files) ...
+ //
+ // - <package.name> and <version> must be in lower case.
+ // - The sha512 of the nupkg is base64 encoded.
+ // - We can get the nuspec from the nupkg which is a Zip file.
+
+ string packageIdLower = packageId.ToLower();
+ string packageVersionLower = packageVersion.ToLower();
+
+ string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower);
+ string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg");
+ string nupkgSha512DestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg.sha512");
+
+ if (File.Exists(nupkgDestPath) && File.Exists(nupkgSha512DestPath))
+ return; // Already added (for speed we don't check if every file is properly extracted)
+
+ Directory.CreateDirectory(destDir);
+
+ // Generate .nupkg.sha512 file
+
+ using (var alg = SHA512.Create())
+ {
+ alg.ComputeHash(File.ReadAllBytes(nupkgPath));
+ string base64Hash = Convert.ToBase64String(alg.Hash);
+ File.WriteAllText(nupkgSha512DestPath, base64Hash);
+ }
+
+ // Extract nupkg
+ ExtractNupkg(destDir, nupkgPath, packageId, packageVersion);
+
+ // Copy .nupkg
+ File.Copy(nupkgPath, nupkgDestPath);
+ }
+
+ private static readonly string[] NupkgExcludePaths =
+ {
+ "_rels/",
+ "package/",
+ "[Content_Types].xml"
+ };
+
+ private static void ExtractNupkg(string destDir, string nupkgPath, string packageId, string packageVersion)
+ {
+ // NOTE: Must use SimplifyGodotPath to make sure we don't extract files outside the destination directory.
+
+ using (var archive = ZipFile.OpenRead(nupkgPath))
+ {
+ // Extract .nuspec manually as it needs to be in lower case
+
+ var nuspecEntry = archive.GetEntry(packageId + ".nuspec");
+
+ if (nuspecEntry == null)
+ throw new InvalidOperationException($"Failed to extract package {packageId}.{packageVersion}. Could not find the nuspec file.");
+
+ nuspecEntry.ExtractToFile(Path.Combine(destDir, nuspecEntry.Name.ToLower().SimplifyGodotPath()));
+
+ // Extract the other package files
+
+ foreach (var entry in archive.Entries)
+ {
+ // NOTE: SimplifyGodotPath() removes trailing slash and backslash,
+ // so we can't use the result to check if the entry is a directory.
+
+ string entryFullName = entry.FullName.Replace('\\', '/');
+
+ // Check if the file must be ignored
+ if ( // Excluded files.
+ NupkgExcludePaths.Any(e => entryFullName.StartsWith(e, StringComparison.OrdinalIgnoreCase)) ||
+ // Nupkg hash files and nupkg metadata files on all directory.
+ 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"))
+ {
+ continue;
+ }
+
+ string entryFullNameSimplified = entryFullName.SimplifyGodotPath();
+ string destFilePath = Path.Combine(destDir, entryFullNameSimplified);
+ bool isDir = entryFullName.EndsWith("/");
+
+ if (isDir)
+ {
+ Directory.CreateDirectory(destFilePath);
+ }
+ else
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(destFilePath));
+ entry.ExtractToFile(destFilePath, overwrite: true);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Copies and extracts all the Godot bundled packages to the Godot NuGet fallback folder.
+ /// Does nothing if the packages were already copied.
+ /// </summary>
+ public static void AddBundledPackagesToFallbackFolder(string fallbackFolder)
+ {
+ GD.Print("Copying Godot Offline Packages...");
+
+ string nupkgsLocation = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "nupkgs");
+
+ void AddPackage(string packageId, string packageVersion)
+ {
+ string nupkgPath = Path.Combine(nupkgsLocation, $"{packageId}.{packageVersion}.nupkg");
+ AddPackageToFallbackFolder(fallbackFolder, nupkgPath, packageId, packageVersion);
+ }
+
+ foreach (var (packageId, packageVersion) in PackagesToAdd)
+ AddPackage(packageId, packageVersion);
+ }
+
+ private static readonly (string packageId, string packageVersion)[] PackagesToAdd =
+ {
+ ("Godot.NET.Sdk", GeneratedGodotNupkgsVersions.GodotNETSdk),
+ ("Godot.SourceGenerators", GeneratedGodotNupkgsVersions.GodotSourceGenerators),
+ };
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
deleted file mode 100644
index fa6bf4dafd..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
+++ /dev/null
@@ -1,266 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Threading.Tasks;
-using GodotTools.Build;
-using GodotTools.Internals;
-using GodotTools.Utils;
-using static GodotTools.Internals.Globals;
-using File = GodotTools.Utils.File;
-
-namespace GodotTools
-{
- public static class BuildManager
- {
- private static readonly List<BuildInfo> BuildsInProgress = new List<BuildInfo>();
-
- public const string PropNameMsbuildMono = "MSBuild (Mono)";
- public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)";
-
- public const string MsBuildIssuesFileName = "msbuild_issues.csv";
- public const string MsBuildLogFileName = "msbuild_log.txt";
-
- public enum BuildTool
- {
- MsBuildMono,
- MsBuildVs
- }
-
- private static void RemoveOldIssuesFile(BuildInfo buildInfo)
- {
- var issuesFile = GetIssuesFilePath(buildInfo);
-
- if (!File.Exists(issuesFile))
- return;
-
- File.Delete(issuesFile);
- }
-
- private static void ShowBuildErrorDialog(string message)
- {
- GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error");
- GodotSharpEditor.Instance.BottomPanel.ShowBuildTab();
- }
-
- public static void RestartBuild(BuildTab buildTab) => throw new NotImplementedException();
- public static void StopBuild(BuildTab buildTab) => throw new NotImplementedException();
-
- private static string GetLogFilePath(BuildInfo buildInfo)
- {
- return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName);
- }
-
- private static string GetIssuesFilePath(BuildInfo buildInfo)
- {
- return Path.Combine(buildInfo.LogsDirPath, MsBuildIssuesFileName);
- }
-
- private static void PrintVerbose(string text)
- {
- if (Godot.OS.IsStdoutVerbose())
- Godot.GD.Print(text);
- }
-
- public static bool Build(BuildInfo buildInfo)
- {
- if (BuildsInProgress.Contains(buildInfo))
- throw new InvalidOperationException("A build is already in progress");
-
- BuildsInProgress.Add(buildInfo);
-
- try
- {
- BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo);
- buildTab.OnBuildStart();
-
- // Required in order to update the build tasks list
- Internal.GodotMainIteration();
-
- try
- {
- RemoveOldIssuesFile(buildInfo);
- }
- catch (IOException e)
- {
- buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
- Console.Error.WriteLine(e);
- }
-
- try
- {
- int exitCode = BuildSystem.Build(buildInfo);
-
- if (exitCode != 0)
- PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
-
- buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error);
-
- return exitCode == 0;
- }
- catch (Exception e)
- {
- buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
- Console.Error.WriteLine(e);
- return false;
- }
- }
- finally
- {
- BuildsInProgress.Remove(buildInfo);
- }
- }
-
- public static async Task<bool> BuildAsync(BuildInfo buildInfo)
- {
- if (BuildsInProgress.Contains(buildInfo))
- throw new InvalidOperationException("A build is already in progress");
-
- BuildsInProgress.Add(buildInfo);
-
- try
- {
- BuildTab buildTab = GodotSharpEditor.Instance.BottomPanel.GetBuildTabFor(buildInfo);
-
- try
- {
- RemoveOldIssuesFile(buildInfo);
- }
- catch (IOException e)
- {
- buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
- Console.Error.WriteLine(e);
- }
-
- try
- {
- int exitCode = await BuildSystem.BuildAsync(buildInfo);
-
- if (exitCode != 0)
- PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
-
- buildTab.OnBuildExit(exitCode == 0 ? BuildTab.BuildResults.Success : BuildTab.BuildResults.Error);
-
- return exitCode == 0;
- }
- catch (Exception e)
- {
- buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
- Console.Error.WriteLine(e);
- return false;
- }
- }
- finally
- {
- BuildsInProgress.Remove(buildInfo);
- }
- }
-
- public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines)
- {
- if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
- return true; // No solution to build
-
- // Make sure the API assemblies are up to date before building the project.
- // We may not have had the chance to update the release API assemblies, and the debug ones
- // may have been deleted by the user at some point after they were loaded by the Godot editor.
- string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(config == "Release" ? "Release" : "Debug");
-
- if (!string.IsNullOrEmpty(apiAssembliesUpdateError))
- {
- ShowBuildErrorDialog("Failed to update the Godot API assemblies");
- return false;
- }
-
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
-
- using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
- {
- pr.Step("Building project solution", 0);
-
- var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, config);
-
- // Add Godot defines
- string constants = buildTool == BuildTool.MsBuildVs ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
-
- foreach (var godotDefine in godotDefines)
- constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};";
-
- if (Internal.GodotIsRealTDouble())
- constants += "GODOT_REAL_T_IS_DOUBLE;";
-
- constants += buildTool == BuildTool.MsBuildVs ? "\"" : "\\\"";
-
- buildInfo.CustomProperties.Add(constants);
-
- if (!Build(buildInfo))
- {
- ShowBuildErrorDialog("Failed to build project solution");
- return false;
- }
- }
-
- return true;
- }
-
- public static bool EditorBuildCallback()
- {
- if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
- return true; // No solution to build
-
- string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
- string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
-
- CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
-
- if (File.Exists(editorScriptsMetadataPath))
- File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
-
- var currentPlayRequest = GodotSharpEditor.Instance.GodotIdeManager.GodotIdeServer.CurrentPlayRequest;
-
- if (currentPlayRequest != null)
- {
- if (currentPlayRequest.Value.HasDebugger)
- {
- // Set the environment variable that will tell the player to connect to the IDE debugger
- // TODO: We should probably add a better way to do this
- Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT",
- "--debugger-agent=transport=dt_socket" +
- $",address={currentPlayRequest.Value.DebuggerHost}:{currentPlayRequest.Value.DebuggerPort}" +
- ",server=n");
- }
-
- return true; // Requested play from an external editor/IDE which already built the project
- }
-
- var godotDefines = new[]
- {
- Godot.OS.GetName(),
- Internal.GodotIs32Bits() ? "32" : "64"
- };
-
- return BuildProjectBlocking("Tools", godotDefines);
- }
-
- public static void Initialize()
- {
- // Build tool settings
-
- EditorDef("mono/builds/build_tool", OS.IsWindows ? BuildTool.MsBuildVs : BuildTool.MsBuildMono);
-
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
-
- editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
- {
- ["type"] = Godot.Variant.Type.Int,
- ["name"] = "mono/builds/build_tool",
- ["hint"] = Godot.PropertyHint.Enum,
- ["hint_string"] = OS.IsWindows ?
- $"{PropNameMsbuildMono},{PropNameMsbuildVs}" :
- $"{PropNameMsbuildMono}"
- });
-
- EditorDef("mono/builds/print_build_output", false);
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs b/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs
deleted file mode 100644
index 727581daab..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/BuildTab.cs
+++ /dev/null
@@ -1,267 +0,0 @@
-using Godot;
-using System;
-using Godot.Collections;
-using GodotTools.Internals;
-using File = GodotTools.Utils.File;
-using Path = System.IO.Path;
-
-namespace GodotTools
-{
- public class BuildTab : VBoxContainer
- {
- public enum BuildResults
- {
- Error,
- Success
- }
-
- [Serializable]
- private class BuildIssue : Reference // TODO Remove Reference once we have proper serialization
- {
- public bool Warning { get; set; }
- public string File { get; set; }
- public int Line { get; set; }
- public int Column { get; set; }
- public string Code { get; set; }
- public string Message { get; set; }
- public string ProjectFile { get; set; }
- }
-
- private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization
- private ItemList issuesList;
-
- public bool BuildExited { get; private set; } = false;
-
- public BuildResults? BuildResult { get; private set; } = null;
-
- public int ErrorCount { get; private set; } = 0;
-
- public int WarningCount { get; private set; } = 0;
-
- public bool ErrorsVisible { get; set; } = true;
- public bool WarningsVisible { get; set; } = true;
-
- public Texture IconTexture
- {
- get
- {
- if (!BuildExited)
- return GetIcon("Stop", "EditorIcons");
-
- if (BuildResult == BuildResults.Error)
- return GetIcon("StatusError", "EditorIcons");
-
- return GetIcon("StatusSuccess", "EditorIcons");
- }
- }
-
- public BuildInfo BuildInfo { get; private set; }
-
- private void _LoadIssuesFromFile(string csvFile)
- {
- using (var file = new Godot.File())
- {
- try
- {
- Error openError = file.Open(csvFile, Godot.File.ModeFlags.Read);
-
- if (openError != Error.Ok)
- return;
-
- while (!file.EofReached())
- {
- string[] csvColumns = file.GetCsvLine();
-
- if (csvColumns.Length == 1 && csvColumns[0].Empty())
- 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
- {
- file.Close(); // Disposing it is not enough. We need to call Close()
- }
- }
- }
-
- private void _IssueActivated(int idx)
- {
- if (idx < 0 || idx >= issuesList.GetItemCount())
- throw new IndexOutOfRangeException("Item list index out of range");
-
- // Get correct issue idx from issue list
- int issueIndex = (int)issuesList.GetItemMetadata(idx);
-
- if (idx < 0 || idx >= issues.Count)
- throw new IndexOutOfRangeException("Issue index out of range");
-
- BuildIssue issue = issues[issueIndex];
-
- if (issue.ProjectFile.Empty() && issue.File.Empty())
- return;
-
- string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir();
-
- string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath());
-
- if (!File.Exists(file))
- return;
-
- file = ProjectSettings.LocalizePath(file);
-
- if (file.StartsWith("res://"))
- {
- var script = (Script)ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType);
-
- if (script != null && Internal.ScriptEditorEdit(script, issue.Line, issue.Column))
- Internal.EditorNodeShowScriptScreen();
- }
- }
-
- public void UpdateIssuesList()
- {
- issuesList.Clear();
-
- using (var warningIcon = GetIcon("Warning", "EditorIcons"))
- using (var errorIcon = GetIcon("Error", "EditorIcons"))
- {
- for (int i = 0; i < issues.Count; i++)
- {
- BuildIssue issue = issues[i];
-
- if (!(issue.Warning ? WarningsVisible : ErrorsVisible))
- continue;
-
- string tooltip = string.Empty;
- tooltip += $"Message: {issue.Message}";
-
- if (!issue.Code.Empty())
- tooltip += $"\nCode: {issue.Code}";
-
- tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}";
-
- string text = string.Empty;
-
- if (!issue.File.Empty())
- {
- text += $"{issue.File}({issue.Line},{issue.Column}): ";
-
- tooltip += $"\nFile: {issue.File}";
- tooltip += $"\nLine: {issue.Line}";
- tooltip += $"\nColumn: {issue.Column}";
- }
-
- if (!issue.ProjectFile.Empty())
- tooltip += $"\nProject: {issue.ProjectFile}";
-
- text += issue.Message;
-
- int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal);
- string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx);
- issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon);
-
- int index = issuesList.GetItemCount() - 1;
- issuesList.SetItemTooltip(index, tooltip);
- issuesList.SetItemMetadata(index, i);
- }
- }
- }
-
- public void OnBuildStart()
- {
- BuildExited = false;
-
- issues.Clear();
- WarningCount = 0;
- ErrorCount = 0;
- UpdateIssuesList();
-
- GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
- }
-
- public void OnBuildExit(BuildResults result)
- {
- BuildExited = true;
- BuildResult = result;
-
- _LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, BuildManager.MsBuildIssuesFileName));
- UpdateIssuesList();
-
- GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
- }
-
- public void OnBuildExecFailed(string cause)
- {
- BuildExited = true;
- BuildResult = BuildResults.Error;
-
- issuesList.Clear();
-
- var issue = new BuildIssue { Message = cause, Warning = false };
-
- ErrorCount += 1;
- issues.Add(issue);
-
- UpdateIssuesList();
-
- GodotSharpEditor.Instance.BottomPanel.RaiseBuildTab(this);
- }
-
- public void RestartBuild()
- {
- if (!BuildExited)
- throw new InvalidOperationException("Build already started");
-
- BuildManager.RestartBuild(this);
- }
-
- public void StopBuild()
- {
- if (!BuildExited)
- throw new InvalidOperationException("Build is not in progress");
-
- BuildManager.StopBuild(this);
- }
-
- public override void _Ready()
- {
- base._Ready();
-
- issuesList = new ItemList { SizeFlagsVertical = (int)SizeFlags.ExpandFill };
- issuesList.Connect("item_activated", this, nameof(_IssueActivated));
- AddChild(issuesList);
- }
-
- private BuildTab()
- {
- }
-
- public BuildTab(BuildInfo buildInfo)
- {
- BuildInfo = buildInfo;
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
index 9abfda4538..e43f10804d 100644
--- a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
@@ -1,11 +1,6 @@
using Godot;
using System;
-using Godot.Collections;
-using GodotTools.Internals;
using GodotTools.ProjectEditor;
-using static GodotTools.Internals.Globals;
-using File = GodotTools.Utils.File;
-using Directory = GodotTools.Utils.Directory;
namespace GodotTools
{
@@ -15,7 +10,7 @@ namespace GodotTools
{
try
{
- return ProjectGenerator.GenGameProject(dir, name, compileItems: new string[] { });
+ return ProjectGenerator.GenAndSaveGameProject(dir, name);
}
catch (Exception e)
{
@@ -23,110 +18,5 @@ namespace GodotTools
return string.Empty;
}
}
-
- public static void AddItem(string projectPath, string itemType, string include)
- {
- if (!(bool)GlobalDef("mono/project/auto_update_project", true))
- return;
-
- ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include);
- }
-
- public static void FixApiHintPath(string projectPath)
- {
- try
- {
- ProjectUtils.FixApiHintPath(projectPath);
- }
- catch (Exception e)
- {
- GD.PushError(e.ToString());
- }
- }
-
- private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
-
- private static ulong ConvertToTimestamp(this DateTime value)
- {
- TimeSpan elapsedTime = value - Epoch;
- return (ulong)elapsedTime.TotalSeconds;
- }
-
- public static void GenerateScriptsMetadata(string projectPath, string outputPath)
- {
- if (File.Exists(outputPath))
- File.Delete(outputPath);
-
- var oldDict = Internal.GetScriptsMetadataOrNothing();
- var newDict = new Godot.Collections.Dictionary<string, object>();
-
- foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile"))
- {
- string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath();
-
- ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp();
-
- if (oldDict.TryGetValue(projectIncludeFile, out var oldFileVar))
- {
- var oldFileDict = (Dictionary)oldFileVar;
-
- if (ulong.TryParse(oldFileDict["modified_time"] as string, out ulong storedModifiedTime))
- {
- if (storedModifiedTime == modifiedTime)
- {
- // No changes so no need to parse again
- newDict[projectIncludeFile] = oldFileDict;
- continue;
- }
- }
- }
-
- Error parseError = ScriptClassParser.ParseFile(projectIncludeFile, out var classes, out string errorStr);
- if (parseError != Error.Ok)
- {
- GD.PushError($"Failed to determine namespace and class for script: {projectIncludeFile}. Parse error: {errorStr ?? parseError.ToString()}");
- continue;
- }
-
- string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile);
-
- var classDict = new Dictionary();
-
- foreach (var classDecl in classes)
- {
- if (classDecl.BaseCount == 0)
- continue; // Does not inherit nor implement anything, so it can't be a script class
-
- string classCmp = classDecl.Nested ?
- classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
- classDecl.Name;
-
- if (classCmp != searchName)
- continue;
-
- classDict["namespace"] = classDecl.Namespace;
- classDict["class_name"] = classDecl.Name;
- classDict["nested"] = classDecl.Nested;
- break;
- }
-
- if (classDict.Count == 0)
- continue; // Not found
-
- newDict[projectIncludeFile] = new Dictionary { ["modified_time"] = $"{modifiedTime}", ["class"] = classDict };
- }
-
- if (newDict.Count > 0)
- {
- string json = JSON.Print(newDict);
-
- string baseDir = outputPath.GetBaseDir();
-
- if (!Directory.Exists(baseDir))
- Directory.CreateDirectory(baseDir);
-
- File.WriteAllText(outputPath, json);
- }
- }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
new file mode 100644
index 0000000000..5bb8d444c2
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs
@@ -0,0 +1,618 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using GodotTools.Internals;
+using Directory = GodotTools.Utils.Directory;
+using File = GodotTools.Utils.File;
+using OS = GodotTools.Utils.OS;
+using Path = System.IO.Path;
+
+namespace GodotTools.Export
+{
+ public struct AotOptions
+ {
+ public bool EnableLLVM;
+ public bool LLVMOnly;
+ public string LLVMPath;
+ public string LLVMOutputPath;
+
+ public bool FullAot;
+
+ private bool _useInterpreter;
+ public bool UseInterpreter { get => _useInterpreter && !LLVMOnly; set => _useInterpreter = value; }
+
+ public string[] ExtraAotOptions;
+ public string[] ExtraOptimizerOptions;
+
+ public string ToolchainPath;
+ }
+
+ public static class AotBuilder
+ {
+ public static void CompileAssemblies(ExportPlugin exporter, AotOptions aotOpts, string[] features, string platform, bool isDebug, string bclDir, string outputDir, string outputDataDir, IDictionary<string, string> assemblies)
+ {
+ // TODO: WASM
+
+ string aotTempDir = Path.Combine(Path.GetTempPath(), $"godot-aot-{Process.GetCurrentProcess().Id}");
+
+ if (!Directory.Exists(aotTempDir))
+ Directory.CreateDirectory(aotTempDir);
+
+ var assembliesPrepared = new Dictionary<string, string>();
+
+ foreach (var dependency in assemblies)
+ {
+ string assemblyName = dependency.Key;
+ string assemblyPath = dependency.Value;
+
+ string assemblyPathInBcl = Path.Combine(bclDir, assemblyName + ".dll");
+
+ if (File.Exists(assemblyPathInBcl))
+ {
+ // Don't create teporaries for assemblies from the BCL
+ assembliesPrepared.Add(assemblyName, assemblyPathInBcl);
+ }
+ else
+ {
+ string tempAssemblyPath = Path.Combine(aotTempDir, assemblyName + ".dll");
+ File.Copy(assemblyPath, tempAssemblyPath);
+ assembliesPrepared.Add(assemblyName, tempAssemblyPath);
+ }
+ }
+
+ if (platform == OS.Platforms.iOS)
+ {
+ var architectures = GetEnablediOSArchs(features).ToArray();
+ CompileAssembliesForiOS(exporter, isDebug, architectures, aotOpts, aotTempDir, assembliesPrepared, bclDir);
+ }
+ else if (platform == OS.Platforms.Android)
+ {
+ var abis = GetEnabledAndroidAbis(features).ToArray();
+ CompileAssembliesForAndroid(exporter, isDebug, abis, aotOpts, aotTempDir, assembliesPrepared, bclDir);
+ }
+ else
+ {
+ string bits = features.Contains("64") ? "64" : features.Contains("32") ? "32" : null;
+ CompileAssembliesForDesktop(exporter, platform, isDebug, bits, aotOpts, aotTempDir, outputDataDir, assembliesPrepared, bclDir);
+ }
+ }
+
+ public static void CompileAssembliesForAndroid(ExportPlugin exporter, bool isDebug, string[] abis, AotOptions aotOpts, string aotTempDir, IDictionary<string, string> assemblies, string bclDir)
+ {
+
+ foreach (var assembly in assemblies)
+ {
+ string assemblyName = assembly.Key;
+ string assemblyPath = assembly.Value;
+
+ // Not sure if the 'lib' prefix is an Android thing or just Godot being picky,
+ // but we use '-aot-' as well just in case to avoid conflicts with other libs.
+ string outputFileName = "lib-aot-" + assemblyName + ".dll.so";
+
+ foreach (string abi in abis)
+ {
+ string aotAbiTempDir = Path.Combine(aotTempDir, abi);
+ string soFilePath = Path.Combine(aotAbiTempDir, outputFileName);
+
+ var compilerArgs = GetAotCompilerArgs(OS.Platforms.Android, isDebug, abi, aotOpts, assemblyPath, soFilePath);
+
+ // Make sure the output directory exists
+ Directory.CreateDirectory(aotAbiTempDir);
+
+ string compilerDirPath = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", $"{OS.Platforms.Android}-{abi}");
+
+ ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir);
+
+ // The Godot exporter expects us to pass the abi in the tags parameter
+ exporter.AddSharedObject(soFilePath, tags: new[] { abi });
+ }
+ }
+ }
+
+ public static void CompileAssembliesForDesktop(ExportPlugin exporter, string platform, bool isDebug, string bits, AotOptions aotOpts, string aotTempDir, string outputDataDir, IDictionary<string, string> assemblies, string bclDir)
+ {
+ foreach (var assembly in assemblies)
+ {
+ string assemblyName = assembly.Key;
+ string assemblyPath = assembly.Value;
+
+ string outputFileExtension = platform == OS.Platforms.Windows ? ".dll" :
+ platform == OS.Platforms.MacOS ? ".dylib" :
+ ".so";
+
+ string outputFileName = assemblyName + ".dll" + outputFileExtension;
+ string tempOutputFilePath = Path.Combine(aotTempDir, outputFileName);
+
+ var compilerArgs = GetAotCompilerArgs(platform, isDebug, bits, aotOpts, assemblyPath, tempOutputFilePath);
+
+ string compilerDirPath = GetMonoCrossDesktopDirName(platform, bits);
+
+ ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir);
+
+ if (platform == OS.Platforms.MacOS)
+ {
+ exporter.AddSharedObject(tempOutputFilePath, tags: null);
+ }
+ else
+ {
+ string outputDataLibDir = Path.Combine(outputDataDir, "Mono", platform == OS.Platforms.Windows ? "bin" : "lib");
+ File.Copy(tempOutputFilePath, Path.Combine(outputDataLibDir, outputFileName));
+ }
+ }
+ }
+
+ public static void CompileAssembliesForiOS(ExportPlugin exporter, bool isDebug, string[] architectures, AotOptions aotOpts, string aotTempDir, IDictionary<string, string> assemblies, string bclDir)
+ {
+ var cppCode = new StringBuilder();
+ var aotModuleInfoSymbols = new List<string>(assemblies.Count);
+
+ // {arch: paths}
+ var objFilePathsForiOSArch = architectures.ToDictionary(arch => arch, arch => new List<string>(assemblies.Count));
+
+ foreach (var assembly in assemblies)
+ {
+ string assemblyName = assembly.Key;
+ string assemblyPath = assembly.Value;
+
+ string asmFileName = assemblyName + ".dll.S";
+ string objFileName = assemblyName + ".dll.o";
+
+ foreach (string arch in architectures)
+ {
+ string aotArchTempDir = Path.Combine(aotTempDir, arch);
+ string asmFilePath = Path.Combine(aotArchTempDir, asmFileName);
+
+ var compilerArgs = GetAotCompilerArgs(OS.Platforms.iOS, isDebug, arch, aotOpts, assemblyPath, asmFilePath);
+
+ // Make sure the output directory exists
+ Directory.CreateDirectory(aotArchTempDir);
+
+ string compilerDirPath = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", $"{OS.Platforms.iOS}-{arch}");
+
+ ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir);
+
+ // Assembling
+ bool isSim = arch == "i386" || arch == "x86_64"; // Shouldn't really happen as we don't do AOT for the simulator
+ string versionMinName = isSim ? "iphonesimulator" : "iphoneos";
+ string iOSPlatformName = isSim ? "iPhoneSimulator" : "iPhoneOS";
+ const string versionMin = "10.0"; // TODO: Turn this hard-coded version into an exporter setting
+ string iOSSdkPath = Path.Combine(XcodeHelper.XcodePath,
+ $"Contents/Developer/Platforms/{iOSPlatformName}.platform/Developer/SDKs/{iOSPlatformName}.sdk");
+
+ string objFilePath = Path.Combine(aotArchTempDir, objFileName);
+
+ var clangArgs = new List<string>()
+ {
+ "-isysroot", iOSSdkPath,
+ "-Qunused-arguments",
+ $"-m{versionMinName}-version-min={versionMin}",
+ "-arch", arch,
+ "-c",
+ "-o", objFilePath,
+ "-x", "assembler"
+ };
+
+ if (isDebug)
+ clangArgs.Add("-DDEBUG");
+
+ clangArgs.Add(asmFilePath);
+
+ int clangExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("clang"), clangArgs);
+ if (clangExitCode != 0)
+ throw new Exception($"Command 'clang' exited with code: {clangExitCode}");
+
+ objFilePathsForiOSArch[arch].Add(objFilePath);
+ }
+
+ aotModuleInfoSymbols.Add($"mono_aot_module_{AssemblyNameToAotSymbol(assemblyName)}_info");
+ }
+
+ // Generate driver code
+ cppCode.AppendLine("#if defined(__arm__) || defined(__arm64__) || defined(__aarch64__)");
+ cppCode.AppendLine("#define IOS_DEVICE");
+ cppCode.AppendLine("#endif");
+
+ cppCode.AppendLine("#ifdef IOS_DEVICE");
+ cppCode.AppendLine("extern \"C\" {");
+ cppCode.AppendLine("// Mono API");
+ cppCode.AppendLine(@"
+typedef enum {
+MONO_AOT_MODE_NONE,
+MONO_AOT_MODE_NORMAL,
+MONO_AOT_MODE_HYBRID,
+MONO_AOT_MODE_FULL,
+MONO_AOT_MODE_LLVMONLY,
+MONO_AOT_MODE_INTERP,
+MONO_AOT_MODE_INTERP_LLVMONLY,
+MONO_AOT_MODE_LLVMONLY_INTERP,
+MONO_AOT_MODE_LAST = 1000,
+} MonoAotMode;");
+ cppCode.AppendLine("void mono_jit_set_aot_mode(MonoAotMode);");
+ cppCode.AppendLine("void mono_aot_register_module(void *);");
+
+ if (aotOpts.UseInterpreter)
+ {
+ cppCode.AppendLine("void mono_ee_interp_init(const char *);");
+ cppCode.AppendLine("void mono_icall_table_init();");
+ cppCode.AppendLine("void mono_marshal_ilgen_init();");
+ cppCode.AppendLine("void mono_method_builder_ilgen_init();");
+ cppCode.AppendLine("void mono_sgen_mono_ilgen_init();");
+ }
+
+ foreach (string symbol in aotModuleInfoSymbols)
+ cppCode.AppendLine($"extern void *{symbol};");
+
+ cppCode.AppendLine("void gd_mono_setup_aot() {");
+
+ foreach (string symbol in aotModuleInfoSymbols)
+ cppCode.AppendLine($"\tmono_aot_register_module({symbol});");
+
+ if (aotOpts.UseInterpreter)
+ {
+ cppCode.AppendLine("\tmono_icall_table_init();");
+ cppCode.AppendLine("\tmono_marshal_ilgen_init();");
+ cppCode.AppendLine("\tmono_method_builder_ilgen_init();");
+ cppCode.AppendLine("\tmono_sgen_mono_ilgen_init();");
+ cppCode.AppendLine("\tmono_ee_interp_init(0);");
+ }
+
+ string aotModeStr = null;
+
+ if (aotOpts.LLVMOnly)
+ {
+ aotModeStr = "MONO_AOT_MODE_LLVMONLY"; // --aot=llvmonly
+ }
+ else
+ {
+ if (aotOpts.UseInterpreter)
+ aotModeStr = "MONO_AOT_MODE_INTERP"; // --aot=interp or --aot=interp,full
+ else if (aotOpts.FullAot)
+ aotModeStr = "MONO_AOT_MODE_FULL"; // --aot=full
+ }
+
+ // One of the options above is always set for iOS
+ Debug.Assert(aotModeStr != null);
+
+ cppCode.AppendLine($"\tmono_jit_set_aot_mode({aotModeStr});");
+
+ cppCode.AppendLine("} // gd_mono_setup_aot");
+ cppCode.AppendLine("} // extern \"C\"");
+ cppCode.AppendLine("#endif // IOS_DEVICE");
+
+ // Add the driver code to the Xcode project
+ exporter.AddIosCppCode(cppCode.ToString());
+
+ // Archive the AOT object files into a static library
+
+ var arFilePathsForAllArchs = new List<string>();
+ string projectAssemblyName = GodotSharpEditor.ProjectAssemblyName;
+
+ foreach (var archPathsPair in objFilePathsForiOSArch)
+ {
+ string arch = archPathsPair.Key;
+ var objFilePaths = archPathsPair.Value;
+
+ string arOutputFilePath = Path.Combine(aotTempDir, $"lib-aot-{projectAssemblyName}.{arch}.a");
+
+ var arArgs = new List<string>()
+ {
+ "cr",
+ arOutputFilePath
+ };
+
+ foreach (string objFilePath in objFilePaths)
+ arArgs.Add(objFilePath);
+
+ int arExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("ar"), arArgs);
+ if (arExitCode != 0)
+ throw new Exception($"Command 'ar' exited with code: {arExitCode}");
+
+ arFilePathsForAllArchs.Add(arOutputFilePath);
+ }
+
+ // It's lipo time
+
+ string fatOutputFileName = $"lib-aot-{projectAssemblyName}.fat.a";
+ string fatOutputFilePath = Path.Combine(aotTempDir, fatOutputFileName);
+
+ var lipoArgs = new List<string>();
+ lipoArgs.Add("-create");
+ lipoArgs.AddRange(arFilePathsForAllArchs);
+ lipoArgs.Add("-output");
+ lipoArgs.Add(fatOutputFilePath);
+
+ int lipoExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("lipo"), lipoArgs);
+ if (lipoExitCode != 0)
+ throw new Exception($"Command 'lipo' exited with code: {lipoExitCode}");
+
+ // TODO: Add the AOT lib and interpreter libs as device only to suppress warnings when targeting the simulator
+
+ // Add the fat AOT static library to the Xcode project
+ exporter.AddIosProjectStaticLib(fatOutputFilePath);
+
+ // Add the required Mono libraries to the Xcode project
+
+ string MonoLibFile(string libFileName) => libFileName + ".iphone.fat.a";
+
+ string MonoLibFromTemplate(string libFileName) =>
+ Path.Combine(Internal.FullTemplatesDir, "iphone-mono-libs", MonoLibFile(libFileName));
+
+ exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmonosgen-2.0"));
+
+ exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-native"));
+
+ if (aotOpts.UseInterpreter)
+ {
+ exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-ee-interp"));
+ exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-icall-table"));
+ exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-ilgen"));
+ }
+
+ // TODO: Turn into an exporter option
+ bool enableProfiling = false;
+ if (enableProfiling)
+ exporter.AddIosProjectStaticLib(MonoLibFromTemplate("libmono-profiler-log"));
+
+ // Add frameworks required by Mono to the Xcode project
+ exporter.AddIosFramework("libiconv.tbd");
+ exporter.AddIosFramework("GSS.framework");
+ exporter.AddIosFramework("CFNetwork.framework");
+
+ // Force load and export dynamic are needed for the linker to not strip required symbols.
+ // In theory we shouldn't be relying on this for P/Invoked functions (as is the case with
+ // functions in System.Native/libmono-native). Instead, we should use cecil to search for
+ // DllImports in assemblies and pass them to 'ld' as '-u/--undefined {pinvoke_symbol}'.
+ exporter.AddIosLinkerFlags("-rdynamic");
+ exporter.AddIosLinkerFlags($"-force_load \"$(SRCROOT)/{MonoLibFile("libmono-native")}\"");
+ }
+
+ /// Converts an assembly name to a valid symbol name in the same way the AOT compiler does
+ private static string AssemblyNameToAotSymbol(string assemblyName)
+ {
+ var builder = new StringBuilder();
+
+ foreach (var charByte in Encoding.UTF8.GetBytes(assemblyName))
+ {
+ char @char = (char)charByte;
+ builder.Append(Char.IsLetterOrDigit(@char) || @char == '_' ? @char : '_');
+ }
+
+ return builder.ToString();
+ }
+
+ private static IEnumerable<string> GetAotCompilerArgs(string platform, bool isDebug, string target, AotOptions aotOpts, string assemblyPath, string outputFilePath)
+ {
+ // TODO: LLVM
+
+ bool aotSoftDebug = isDebug && !aotOpts.EnableLLVM;
+ bool aotDwarfDebug = platform == OS.Platforms.iOS;
+
+ var aotOptions = new List<string>();
+ var optimizerOptions = new List<string>();
+
+ if (aotOpts.LLVMOnly)
+ {
+ aotOptions.Add("llvmonly");
+ }
+ else
+ {
+ // Can be both 'interp' and 'full'
+ if (aotOpts.UseInterpreter)
+ aotOptions.Add("interp");
+ if (aotOpts.FullAot)
+ aotOptions.Add("full");
+ }
+
+ aotOptions.Add(aotSoftDebug ? "soft-debug" : "nodebug");
+
+ if (aotDwarfDebug)
+ aotOptions.Add("dwarfdebug");
+
+ if (platform == OS.Platforms.Android)
+ {
+ string abi = target;
+
+ string androidToolchain = aotOpts.ToolchainPath;
+
+ if (string.IsNullOrEmpty(androidToolchain))
+ {
+ androidToolchain = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "android-toolchains", $"{abi}"); // TODO: $"{abi}-{apiLevel}{(clang?"clang":"")}"
+
+ if (!Directory.Exists(androidToolchain))
+ throw new FileNotFoundException("Missing android toolchain. Specify one in the AOT export settings.");
+ }
+ else if (!Directory.Exists(androidToolchain))
+ {
+ throw new FileNotFoundException("Android toolchain not found: " + androidToolchain);
+ }
+
+ var androidToolPrefixes = new Dictionary<string, string>
+ {
+ ["armeabi-v7a"] = "arm-linux-androideabi-",
+ ["arm64-v8a"] = "aarch64-linux-android-",
+ ["x86"] = "i686-linux-android-",
+ ["x86_64"] = "x86_64-linux-android-"
+ };
+
+ aotOptions.Add("tool-prefix=" + Path.Combine(androidToolchain, "bin", androidToolPrefixes[abi]));
+
+ string triple = GetAndroidTriple(abi);
+ aotOptions.Add($"mtriple={triple}");
+ }
+ else if (platform == OS.Platforms.iOS)
+ {
+ if (!aotOpts.LLVMOnly && !aotOpts.UseInterpreter)
+ optimizerOptions.Add("gsharedvt");
+
+ aotOptions.Add("static");
+
+ // I couldn't get the Mono cross-compiler to do assembling, so we'll have to do it ourselves
+ aotOptions.Add("asmonly");
+
+ aotOptions.Add("direct-icalls");
+
+ if (aotSoftDebug)
+ aotOptions.Add("no-direct-calls");
+
+ if (aotOpts.LLVMOnly || !aotOpts.UseInterpreter)
+ aotOptions.Add("direct-pinvoke");
+
+ string arch = target;
+ aotOptions.Add($"mtriple={arch}-ios");
+ }
+
+ aotOptions.Add($"outfile={outputFilePath}");
+
+ if (aotOpts.EnableLLVM)
+ {
+ aotOptions.Add($"llvm-path={aotOpts.LLVMPath}");
+ aotOptions.Add($"llvm-outfile={aotOpts.LLVMOutputPath}");
+ }
+
+ if (aotOpts.ExtraAotOptions.Length > 0)
+ aotOptions.AddRange(aotOpts.ExtraAotOptions);
+
+ if (aotOpts.ExtraOptimizerOptions.Length > 0)
+ optimizerOptions.AddRange(aotOpts.ExtraOptimizerOptions);
+
+ string EscapeOption(string option) => option.Contains(',') ? $"\"{option}\"" : option;
+ string OptionsToString(IEnumerable<string> options) => string.Join(",", options.Select(EscapeOption));
+
+ var runtimeArgs = new List<string>();
+
+ // The '--debug' runtime option is required when using the 'soft-debug' and 'dwarfdebug' AOT options
+ if (aotSoftDebug || aotDwarfDebug)
+ runtimeArgs.Add("--debug");
+
+ if (aotOpts.EnableLLVM)
+ runtimeArgs.Add("--llvm");
+
+ runtimeArgs.Add(aotOptions.Count > 0 ? $"--aot={OptionsToString(aotOptions)}" : "--aot");
+
+ if (optimizerOptions.Count > 0)
+ runtimeArgs.Add($"-O={OptionsToString(optimizerOptions)}");
+
+ runtimeArgs.Add(assemblyPath);
+
+ return runtimeArgs;
+ }
+
+ private static void ExecuteCompiler(string compiler, IEnumerable<string> compilerArgs, string bclDir)
+ {
+ // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead
+ string CmdLineArgsToString(IEnumerable<string> args)
+ {
+ // Not perfect, but as long as we are careful...
+ return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
+ }
+
+ using (var process = new Process())
+ {
+ process.StartInfo = new ProcessStartInfo(compiler, CmdLineArgsToString(compilerArgs))
+ {
+ UseShellExecute = false
+ };
+
+ process.StartInfo.EnvironmentVariables.Remove("MONO_ENV_OPTIONS");
+ process.StartInfo.EnvironmentVariables.Remove("MONO_THREADS_SUSPEND");
+ process.StartInfo.EnvironmentVariables.Add("MONO_PATH", bclDir);
+
+ Console.WriteLine($"Running: \"{process.StartInfo.FileName}\" {process.StartInfo.Arguments}");
+
+ if (!process.Start())
+ throw new Exception("Failed to start process for Mono AOT compiler");
+
+ process.WaitForExit();
+
+ if (process.ExitCode != 0)
+ throw new Exception($"Mono AOT compiler exited with code: {process.ExitCode}");
+ }
+ }
+
+ private static IEnumerable<string> GetEnablediOSArchs(string[] features)
+ {
+ var iosArchs = new[]
+ {
+ "armv7",
+ "arm64"
+ };
+
+ return iosArchs.Where(features.Contains);
+ }
+
+ private static IEnumerable<string> GetEnabledAndroidAbis(string[] features)
+ {
+ var androidAbis = new[]
+ {
+ "armeabi-v7a",
+ "arm64-v8a",
+ "x86",
+ "x86_64"
+ };
+
+ return androidAbis.Where(features.Contains);
+ }
+
+ private static string GetAndroidTriple(string abi)
+ {
+ var abiArchs = new Dictionary<string, string>
+ {
+ ["armeabi-v7a"] = "armv7",
+ ["arm64-v8a"] = "aarch64-v8a",
+ ["x86"] = "i686",
+ ["x86_64"] = "x86_64"
+ };
+
+ string arch = abiArchs[abi];
+
+ return $"{arch}-linux-android";
+ }
+
+ private static string GetMonoCrossDesktopDirName(string platform, string bits)
+ {
+ switch (platform)
+ {
+ case OS.Platforms.Windows:
+ case OS.Platforms.UWP:
+ {
+ string arch = bits == "64" ? "x86_64" : "i686";
+ return $"windows-{arch}";
+ }
+ case OS.Platforms.MacOS:
+ {
+ Debug.Assert(bits == null || bits == "64");
+ string arch = "x86_64";
+ return $"{platform}-{arch}";
+ }
+ case OS.Platforms.LinuxBSD:
+ case OS.Platforms.Server:
+ {
+ string arch = bits == "64" ? "x86_64" : "i686";
+ return $"linux-{arch}";
+ }
+ case OS.Platforms.Haiku:
+ {
+ string arch = bits == "64" ? "x86_64" : "i686";
+ return $"{platform}-{arch}";
+ }
+ default:
+ throw new NotSupportedException($"Platform not supported: {platform}");
+ }
+ }
+
+ // TODO: Replace this for a specific path for each platform
+ private static string FindCrossCompiler(string monoCrossBin)
+ {
+ string exeExt = OS.IsWindows ? ".exe" : string.Empty;
+
+ var files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono-sgen{exeExt}", SearchOption.TopDirectoryOnly);
+ if (files.Length > 0)
+ return Path.Combine(monoCrossBin, files[0].Name);
+
+ throw new FileNotFoundException($"Cannot find the mono runtime executable in {monoCrossBin}");
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 3e2a8c22a9..270be8b6bf 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -5,8 +5,10 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
+using GodotTools.Build;
using GodotTools.Core;
using GodotTools.Internals;
+using JetBrains.Annotations;
using static GodotTools.Internals.Globals;
using Directory = GodotTools.Utils.Directory;
using File = GodotTools.Utils.File;
@@ -18,7 +20,7 @@ namespace GodotTools.Export
public class ExportPlugin : EditorExportPlugin
{
[Flags]
- enum I18NCodesets
+ enum I18NCodesets : long
{
None = 0,
CJK = 1,
@@ -29,15 +31,13 @@ namespace GodotTools.Export
All = CJK | MidEast | Other | Rare | West
}
- private void AddI18NAssemblies(Godot.Collections.Dictionary<string, string> assemblies, string platform)
+ private void AddI18NAssemblies(Godot.Collections.Dictionary<string, string> assemblies, string bclDir)
{
- var codesets = (I18NCodesets) ProjectSettings.GetSetting("mono/export/i18n_codesets");
+ var codesets = (I18NCodesets)ProjectSettings.GetSetting("mono/export/i18n_codesets");
if (codesets == I18NCodesets.None)
return;
- string bclDir = DeterminePlatformBclDir(platform) ?? typeof(object).Assembly.Location.GetBaseDir();
-
void AddI18NAssembly(string name) => assemblies.Add(name, Path.Combine(bclDir, $"{name}.dll"));
AddI18NAssembly("I18N");
@@ -73,6 +73,7 @@ namespace GodotTools.Export
GlobalDef("mono/export/aot/enabled", false);
GlobalDef("mono/export/aot/full_aot", false);
+ GlobalDef("mono/export/aot/use_interpreter", true);
// --aot or --aot=opt1,opt2 (use 'mono --aot=help AuxAssembly.dll' to list AOT options)
GlobalDef("mono/export/aot/extra_aot_options", new string[] { });
@@ -86,9 +87,11 @@ namespace GodotTools.Export
private void AddFile(string srcPath, string dstPath, bool remap = false)
{
+ // Add file to the PCK
AddFile(dstPath.Replace("\\", "/"), File.ReadAllBytes(srcPath), remap);
}
+ // With this method we can override how a file is exported in the PCK
public override void _ExportFile(string path, string type, string[] features)
{
base._ExportFile(path, type, features);
@@ -96,7 +99,7 @@ namespace GodotTools.Export
if (type != Internal.CSharpLanguageType)
return;
- if (Path.GetExtension(path) != $".{Internal.CSharpLanguageExtension}")
+ if (Path.GetExtension(path) != Internal.CSharpLanguageExtension)
throw new ArgumentException($"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path));
// TODO What if the source file is not part of the game's C# project
@@ -110,6 +113,8 @@ namespace GodotTools.Export
// Sadly, Godot prints errors when adding an empty file (nothing goes wrong, it's just noise).
// Because of this, we add a file which contains a line break.
AddFile(path, System.Text.Encoding.UTF8.GetBytes("\n"), remap: false);
+
+ // Tell the Godot exporter that we already took care of the file
Skip();
}
}
@@ -139,44 +144,33 @@ namespace GodotTools.Export
private void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags)
{
+ _ = flags; // Unused
+
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return;
- string platform = DeterminePlatformFromFeatures(features);
-
- if (platform == null)
+ if (!DeterminePlatformFromFeatures(features, out string platform))
throw new NotSupportedException("Target platform not supported");
string outputDir = new FileInfo(path).Directory?.FullName ??
throw new FileNotFoundException("Base directory not found");
- string buildConfig = isDebug ? "Debug" : "Release";
-
- string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}");
- CsProjOperations.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);
-
- AddFile(scriptsMetadataPath, scriptsMetadataPath);
-
- // Turn export features into defines
- var godotDefines = features;
+ string buildConfig = isDebug ? "ExportDebug" : "ExportRelease";
- if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines))
+ if (!BuildManager.BuildProjectBlocking(buildConfig, platform: platform))
throw new Exception("Failed to build project");
// Add dependency assemblies
- var dependencies = new Godot.Collections.Dictionary<string, string>();
-
- var projectDllName = (string)ProjectSettings.GetSetting("application/config/name");
- if (projectDllName.Empty())
- {
- projectDllName = "UnnamedProject";
- }
+ var assemblies = new Godot.Collections.Dictionary<string, string>();
+ string projectDllName = GodotSharpEditor.ProjectAssemblyName;
string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig);
string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll");
- dependencies[projectDllName] = projectDllSrcPath;
+ assemblies[projectDllName] = projectDllSrcPath;
+
+ string bclDir = DeterminePlatformBclDir(platform);
if (platform == OS.Platforms.Android)
{
@@ -186,13 +180,56 @@ namespace GodotTools.Export
if (!File.Exists(monoAndroidAssemblyPath))
throw new FileNotFoundException("Assembly not found: 'Mono.Android'", monoAndroidAssemblyPath);
- dependencies["Mono.Android"] = monoAndroidAssemblyPath;
+ assemblies["Mono.Android"] = monoAndroidAssemblyPath;
+ }
+ else if (platform == OS.Platforms.HTML5)
+ {
+ // Ideally these would be added automatically since they're referenced by the wasm BCL assemblies.
+ // However, at least in the case of 'WebAssembly.Net.Http' for some reason the BCL assemblies
+ // reference a different version even though the assembly is the same, for some weird reason.
+
+ var wasmFrameworkAssemblies = new[] {"WebAssembly.Bindings", "WebAssembly.Net.WebSockets"};
+
+ foreach (string thisWasmFrameworkAssemblyName in wasmFrameworkAssemblies)
+ {
+ string thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName + ".dll");
+ if (!File.Exists(thisWasmFrameworkAssemblyPath))
+ throw new FileNotFoundException($"Assembly not found: '{thisWasmFrameworkAssemblyName}'", thisWasmFrameworkAssemblyPath);
+ assemblies[thisWasmFrameworkAssemblyName] = thisWasmFrameworkAssemblyPath;
+ }
+
+ // Assemblies that can have a different name in a newer version. Newer version must come first and it has priority.
+ (string newName, string oldName)[] wasmFrameworkAssembliesOneOf = new[]
+ {
+ ("System.Net.Http.WebAssemblyHttpHandler", "WebAssembly.Net.Http")
+ };
+
+ foreach (var thisWasmFrameworkAssemblyName in wasmFrameworkAssembliesOneOf)
+ {
+ string thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName.newName + ".dll");
+ if (File.Exists(thisWasmFrameworkAssemblyPath))
+ {
+ assemblies[thisWasmFrameworkAssemblyName.newName] = thisWasmFrameworkAssemblyPath;
+ }
+ else
+ {
+ thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName.oldName + ".dll");
+ if (!File.Exists(thisWasmFrameworkAssemblyPath))
+ {
+ throw new FileNotFoundException("Expected one of the following assemblies but none were found: " +
+ $"'{thisWasmFrameworkAssemblyName.newName}' / '{thisWasmFrameworkAssemblyName.oldName}'",
+ thisWasmFrameworkAssemblyPath);
+ }
+
+ assemblies[thisWasmFrameworkAssemblyName.oldName] = thisWasmFrameworkAssemblyPath;
+ }
+ }
}
- var initialDependencies = dependencies.Duplicate();
- internal_GetExportedAssemblyDependencies(initialDependencies, buildConfig, DeterminePlatformBclDir(platform), dependencies);
+ var initialAssemblies = assemblies.Duplicate();
+ internal_GetExportedAssemblyDependencies(initialAssemblies, buildConfig, bclDir, assemblies);
- AddI18NAssemblies(dependencies, platform);
+ AddI18NAssemblies(assemblies, bclDir);
string outputDataDir = null;
@@ -211,27 +248,62 @@ namespace GodotTools.Export
Directory.CreateDirectory(outputDataGameAssembliesDir);
}
- foreach (var dependency in dependencies)
+ foreach (var assembly in assemblies)
{
- string dependSrcPath = dependency.Value;
-
- if (assembliesInsidePck)
- {
- string dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile());
- AddFile(dependSrcPath, dependDstPath);
- }
- else
+ void AddToAssembliesDir(string fileSrcPath)
{
- string dependDstPath = Path.Combine(outputDataDir, "Assemblies", dependSrcPath.GetFile());
- File.Copy(dependSrcPath, dependDstPath);
+ if (assembliesInsidePck)
+ {
+ string fileDstPath = Path.Combine(resAssembliesDir, fileSrcPath.GetFile());
+ AddFile(fileSrcPath, fileDstPath);
+ }
+ else
+ {
+ Debug.Assert(outputDataDir != null);
+ string fileDstPath = Path.Combine(outputDataDir, "Assemblies", fileSrcPath.GetFile());
+ File.Copy(fileSrcPath, fileDstPath);
+ }
}
+
+ string assemblySrcPath = assembly.Value;
+
+ string assemblyPathWithoutExtension = Path.ChangeExtension(assemblySrcPath, null);
+ string pdbSrcPath = assemblyPathWithoutExtension + ".pdb";
+
+ AddToAssembliesDir(assemblySrcPath);
+
+ if (File.Exists(pdbSrcPath))
+ AddToAssembliesDir(pdbSrcPath);
}
- // AOT
+ // AOT compilation
+ bool aotEnabled = platform == OS.Platforms.iOS || (bool)ProjectSettings.GetSetting("mono/export/aot/enabled");
- if ((bool)ProjectSettings.GetSetting("mono/export/aot/enabled"))
+ if (aotEnabled)
{
- AotCompileDependencies(features, platform, isDebug, outputDir, outputDataDir, dependencies);
+ string aotToolchainPath = null;
+
+ if (platform == OS.Platforms.Android)
+ aotToolchainPath = (string)ProjectSettings.GetSetting("mono/export/aot/android_toolchain_path");
+
+ if (aotToolchainPath == string.Empty)
+ aotToolchainPath = null; // Don't risk it being used as current working dir
+
+ // TODO: LLVM settings are hard-coded and disabled for now
+ var aotOpts = new AotOptions
+ {
+ EnableLLVM = false,
+ LLVMOnly = false,
+ LLVMPath = "",
+ LLVMOutputPath = "",
+ FullAot = platform == OS.Platforms.iOS || (bool)(ProjectSettings.GetSetting("mono/export/aot/full_aot") ?? false),
+ UseInterpreter = (bool)ProjectSettings.GetSetting("mono/export/aot/use_interpreter"),
+ ExtraAotOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_aot_options") ?? new string[] { },
+ ExtraOptimizerOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_optimizer_options") ?? new string[] { },
+ ToolchainPath = aotToolchainPath
+ };
+
+ AotBuilder.CompileAssemblies(this, aotOpts, features, platform, isDebug, bclDir, outputDir, outputDataDir, assemblies);
}
}
@@ -254,11 +326,13 @@ namespace GodotTools.Export
}
}
+ [NotNull]
private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir)
{
string target = isDebug ? "release_debug" : "release";
- // NOTE: Bits is ok for now as all platforms with a data directory have it, but that may change in the future.
+ // NOTE: Bits is ok for now as all platforms with a data directory only have one or two architectures.
+ // However, this may change in the future if we add arm linux or windows desktop templates.
string bits = features.Contains("64") ? "64" : "32";
string TemplateDirName() => $"data.mono.{platform}.{bits}.{target}";
@@ -284,7 +358,7 @@ namespace GodotTools.Export
if (!validTemplatePathFound)
throw new FileNotFoundException("Data template directory not found", templateDirPath);
- string outputDataDir = Path.Combine(outputDir, DataDirName);
+ string outputDataDir = Path.Combine(outputDir, DetermineDataDirNameForProject());
if (Directory.Exists(outputDataDir))
Directory.Delete(outputDataDir, recursive: true); // Clean first
@@ -304,344 +378,22 @@ namespace GodotTools.Export
return outputDataDir;
}
- private void AotCompileDependencies(string[] features, string platform, bool isDebug, string outputDir, string outputDataDir, IDictionary<string, string> dependencies)
- {
- // TODO: WASM
-
- string bclDir = DeterminePlatformBclDir(platform) ?? typeof(object).Assembly.Location.GetBaseDir();
-
- string aotTempDir = Path.Combine(Path.GetTempPath(), $"godot-aot-{Process.GetCurrentProcess().Id}");
-
- if (!Directory.Exists(aotTempDir))
- Directory.CreateDirectory(aotTempDir);
-
- var assemblies = new Dictionary<string, string>();
-
- foreach (var dependency in dependencies)
- {
- string assemblyName = dependency.Key;
- string assemblyPath = dependency.Value;
-
- string assemblyPathInBcl = Path.Combine(bclDir, assemblyName + ".dll");
-
- if (File.Exists(assemblyPathInBcl))
- {
- // Don't create teporaries for assemblies from the BCL
- assemblies.Add(assemblyName, assemblyPathInBcl);
- }
- else
- {
- string tempAssemblyPath = Path.Combine(aotTempDir, assemblyName + ".dll");
- File.Copy(assemblyPath, tempAssemblyPath);
- assemblies.Add(assemblyName, tempAssemblyPath);
- }
- }
-
- foreach (var assembly in assemblies)
- {
- string assemblyName = assembly.Key;
- string assemblyPath = assembly.Value;
-
- string sharedLibExtension = platform == OS.Platforms.Windows ? ".dll" :
- platform == OS.Platforms.OSX ? ".dylib" :
- platform == OS.Platforms.HTML5 ? ".wasm" :
- ".so";
-
- string outputFileName = assemblyName + ".dll" + sharedLibExtension;
-
- if (platform == OS.Platforms.Android)
- {
- // Not sure if the 'lib' prefix is an Android thing or just Godot being picky,
- // but we use '-aot-' as well just in case to avoid conflicts with other libs.
- outputFileName = "lib-aot-" + outputFileName;
- }
-
- string outputFilePath = null;
- string tempOutputFilePath;
-
- switch (platform)
- {
- case OS.Platforms.OSX:
- tempOutputFilePath = Path.Combine(aotTempDir, outputFileName);
- break;
- case OS.Platforms.Android:
- tempOutputFilePath = Path.Combine(aotTempDir, "%%ANDROID_ABI%%", outputFileName);
- break;
- case OS.Platforms.HTML5:
- tempOutputFilePath = Path.Combine(aotTempDir, outputFileName);
- outputFilePath = Path.Combine(outputDir, outputFileName);
- break;
- default:
- tempOutputFilePath = Path.Combine(aotTempDir, outputFileName);
- outputFilePath = Path.Combine(outputDataDir, "Mono", platform == OS.Platforms.Windows ? "bin" : "lib", outputFileName);
- break;
- }
-
- var data = new Dictionary<string, string>();
- var enabledAndroidAbis = platform == OS.Platforms.Android ? GetEnabledAndroidAbis(features).ToArray() : null;
-
- if (platform == OS.Platforms.Android)
- {
- Debug.Assert(enabledAndroidAbis != null);
-
- foreach (var abi in enabledAndroidAbis)
- {
- data["abi"] = abi;
- var outputFilePathForThisAbi = tempOutputFilePath.Replace("%%ANDROID_ABI%%", abi);
-
- AotCompileAssembly(platform, isDebug, data, assemblyPath, outputFilePathForThisAbi);
-
- AddSharedObject(outputFilePathForThisAbi, tags: new[] { abi });
- }
- }
- else
- {
- string bits = features.Contains("64") ? "64" : features.Contains("64") ? "32" : null;
-
- if (bits != null)
- data["bits"] = bits;
-
- AotCompileAssembly(platform, isDebug, data, assemblyPath, tempOutputFilePath);
-
- if (platform == OS.Platforms.OSX)
- {
- AddSharedObject(tempOutputFilePath, tags: null);
- }
- else
- {
- Debug.Assert(outputFilePath != null);
- File.Copy(tempOutputFilePath, outputFilePath);
- }
- }
- }
- }
-
- private static void AotCompileAssembly(string platform, bool isDebug, Dictionary<string, string> data, string assemblyPath, string outputFilePath)
- {
- // Make sure the output directory exists
- Directory.CreateDirectory(outputFilePath.GetBaseDir());
-
- string exeExt = OS.IsWindows ? ".exe" : string.Empty;
-
- string monoCrossDirName = DetermineMonoCrossDirName(platform, data);
- string monoCrossRoot = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", monoCrossDirName);
- string monoCrossBin = Path.Combine(monoCrossRoot, "bin");
-
- string toolPrefix = DetermineToolPrefix(monoCrossBin);
- string monoExeName = System.IO.File.Exists(Path.Combine(monoCrossBin, $"{toolPrefix}mono{exeExt}")) ? "mono" : "mono-sgen";
-
- string compilerCommand = Path.Combine(monoCrossBin, $"{toolPrefix}{monoExeName}{exeExt}");
-
- bool fullAot = (bool)ProjectSettings.GetSetting("mono/export/aot/full_aot");
-
- string EscapeOption(string option) => option.Contains(',') ? $"\"{option}\"" : option;
- string OptionsToString(IEnumerable<string> options) => string.Join(",", options.Select(EscapeOption));
-
- var aotOptions = new List<string>();
- var optimizerOptions = new List<string>();
-
- if (fullAot)
- aotOptions.Add("full");
-
- aotOptions.Add(isDebug ? "soft-debug" : "nodebug");
-
- if (platform == OS.Platforms.Android)
- {
- string abi = data["abi"];
-
- string androidToolchain = (string)ProjectSettings.GetSetting("mono/export/aot/android_toolchain_path");
-
- if (string.IsNullOrEmpty(androidToolchain))
- {
- androidToolchain = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "android-toolchains", $"{abi}"); // TODO: $"{abi}-{apiLevel}{(clang?"clang":"")}"
-
- if (!Directory.Exists(androidToolchain))
- throw new FileNotFoundException("Missing android toolchain. Specify one in the AOT export settings.");
- }
- else if (!Directory.Exists(androidToolchain))
- {
- throw new FileNotFoundException("Android toolchain not found: " + androidToolchain);
- }
-
- var androidToolPrefixes = new Dictionary<string, string>
- {
- ["armeabi-v7a"] = "arm-linux-androideabi-",
- ["arm64-v8a"] = "aarch64-linux-android-",
- ["x86"] = "i686-linux-android-",
- ["x86_64"] = "x86_64-linux-android-"
- };
-
- aotOptions.Add("tool-prefix=" + Path.Combine(androidToolchain, "bin", androidToolPrefixes[abi]));
-
- string triple = GetAndroidTriple(abi);
- aotOptions.Add($"mtriple={triple}");
- }
-
- aotOptions.Add($"outfile={outputFilePath}");
-
- var extraAotOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_aot_options");
- var extraOptimizerOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_optimizer_options");
-
- if (extraAotOptions.Length > 0)
- aotOptions.AddRange(extraAotOptions);
-
- if (extraOptimizerOptions.Length > 0)
- optimizerOptions.AddRange(extraOptimizerOptions);
-
- var compilerArgs = new List<string>();
-
- if (isDebug)
- compilerArgs.Add("--debug"); // Required for --aot=soft-debug
-
- compilerArgs.Add(aotOptions.Count > 0 ? $"--aot={OptionsToString(aotOptions)}" : "--aot");
-
- if (optimizerOptions.Count > 0)
- compilerArgs.Add($"-O={OptionsToString(optimizerOptions)}");
-
- compilerArgs.Add(ProjectSettings.GlobalizePath(assemblyPath));
-
- // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead
- string CmdLineArgsToString(IEnumerable<string> args)
- {
- // Not perfect, but as long as we are careful...
- return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
- }
-
- using (var process = new Process())
- {
- process.StartInfo = new ProcessStartInfo(compilerCommand, CmdLineArgsToString(compilerArgs))
- {
- UseShellExecute = false
- };
-
- string platformBclDir = DeterminePlatformBclDir(platform);
- process.StartInfo.EnvironmentVariables.Add("MONO_PATH", string.IsNullOrEmpty(platformBclDir) ?
- typeof(object).Assembly.Location.GetBaseDir() :
- platformBclDir);
-
- Console.WriteLine($"Running: \"{process.StartInfo.FileName}\" {process.StartInfo.Arguments}");
-
- if (!process.Start())
- throw new Exception("Failed to start process for Mono AOT compiler");
-
- process.WaitForExit();
-
- if (process.ExitCode != 0)
- throw new Exception($"Mono AOT compiler exited with error code: {process.ExitCode}");
-
- if (!System.IO.File.Exists(outputFilePath))
- throw new Exception("Mono AOT compiler finished successfully but the output file is missing");
- }
- }
-
- private static string DetermineMonoCrossDirName(string platform, IReadOnlyDictionary<string, string> data)
- {
- switch (platform)
- {
- case OS.Platforms.Windows:
- case OS.Platforms.UWP:
- {
- string arch = data["bits"] == "64" ? "x86_64" : "i686";
- return $"windows-{arch}";
- }
- case OS.Platforms.OSX:
- {
- string arch = "x86_64";
- return $"{platform}-{arch}";
- }
- case OS.Platforms.X11:
- case OS.Platforms.Server:
- {
- string arch = data["bits"] == "64" ? "x86_64" : "i686";
- return $"linux-{arch}";
- }
- case OS.Platforms.Haiku:
- {
- string arch = data["bits"] == "64" ? "x86_64" : "i686";
- return $"{platform}-{arch}";
- }
- case OS.Platforms.Android:
- {
- string abi = data["abi"];
- return $"{platform}-{abi}";
- }
- case OS.Platforms.HTML5:
- return "wasm-wasm32";
- default:
- throw new NotSupportedException($"Platform not supported: {platform}");
- }
- }
-
- private static string DetermineToolPrefix(string monoCrossBin)
- {
- string exeExt = OS.IsWindows ? ".exe" : string.Empty;
-
- if (System.IO.File.Exists(Path.Combine(monoCrossBin, $"mono{exeExt}")))
- return string.Empty;
-
- if (System.IO.File.Exists(Path.Combine(monoCrossBin, $"mono-sgen{exeExt}" + exeExt)))
- return string.Empty;
-
- var files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono{exeExt}" + exeExt, SearchOption.TopDirectoryOnly);
- if (files.Length > 0)
- {
- string fileName = files[0].Name;
- return fileName.Substring(0, fileName.Length - $"mono{exeExt}".Length);
- }
-
- files = new DirectoryInfo(monoCrossBin).GetFiles($"*mono-sgen{exeExt}" + exeExt, SearchOption.TopDirectoryOnly);
- if (files.Length > 0)
- {
- string fileName = files[0].Name;
- return fileName.Substring(0, fileName.Length - $"mono-sgen{exeExt}".Length);
- }
-
- throw new FileNotFoundException($"Cannot find the mono runtime executable in {monoCrossBin}");
- }
-
- private static IEnumerable<string> GetEnabledAndroidAbis(string[] features)
- {
- var androidAbis = new[]
- {
- "armeabi-v7a",
- "arm64-v8a",
- "x86",
- "x86_64"
- };
-
- return androidAbis.Where(features.Contains);
- }
-
- private static string GetAndroidTriple(string abi)
- {
- var abiArchs = new Dictionary<string, string>
- {
- ["armeabi-v7a"] = "armv7",
- ["arm64-v8a"] = "aarch64-v8a",
- ["x86"] = "i686",
- ["x86_64"] = "x86_64"
- };
-
- string arch = abiArchs[abi];
-
- return $"{arch}-linux-android";
- }
-
private static bool PlatformHasTemplateDir(string platform)
{
// OSX export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest.
- return !new[] { OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.HTML5 }.Contains(platform);
+ return !new[] {OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform);
}
- private static string DeterminePlatformFromFeatures(IEnumerable<string> features)
+ private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, out string platform)
{
foreach (var feature in features)
{
- if (OS.PlatformNameMap.TryGetValue(feature, out string platform))
- return platform;
+ if (OS.PlatformNameMap.TryGetValue(feature, out platform))
+ return true;
}
- return null;
+ platform = null;
+ return false;
}
private static string GetBclProfileDir(string profile)
@@ -665,7 +417,7 @@ namespace GodotTools.Export
if (PlatformRequiresCustomBcl(platform))
throw new FileNotFoundException($"Missing BCL (Base Class Library) for platform: {platform}");
- platformBclDir = null; // Use the one we're running on
+ platformBclDir = typeof(object).Assembly.Location.GetBaseDir(); // Use the one we're running on
}
}
@@ -678,7 +430,7 @@ namespace GodotTools.Export
/// </summary>
private static bool PlatformRequiresCustomBcl(string platform)
{
- if (new[] { OS.Platforms.Android, OS.Platforms.HTML5 }.Contains(platform))
+ if (new[] {OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform))
return true;
// The 'net_4_x' BCL is not compatible between Windows and the other platforms.
@@ -700,13 +452,15 @@ namespace GodotTools.Export
case OS.Platforms.Windows:
case OS.Platforms.UWP:
return "net_4_x_win";
- case OS.Platforms.OSX:
- case OS.Platforms.X11:
+ case OS.Platforms.MacOS:
+ case OS.Platforms.LinuxBSD:
case OS.Platforms.Server:
case OS.Platforms.Haiku:
return "net_4_x";
case OS.Platforms.Android:
return "monodroid";
+ case OS.Platforms.iOS:
+ return "monotouch";
case OS.Platforms.HTML5:
return "wasm";
default:
@@ -714,18 +468,15 @@ namespace GodotTools.Export
}
}
- private static string DataDirName
+ private static string DetermineDataDirNameForProject()
{
- get
- {
- var appName = (string)ProjectSettings.GetSetting("application/config/name");
- string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false);
- return $"data_{appNameSafe}";
- }
+ var appName = (string)ProjectSettings.GetSetting("application/config/name");
+ string appNameSafe = appName.ToSafeDirName();
+ return $"data_{appNameSafe}";
}
[MethodImpl(MethodImplOptions.InternalCall)]
- private static extern void internal_GetExportedAssemblyDependencies(Godot.Collections.Dictionary<string, string> initialDependencies,
- string buildConfig, string customBclDir, Godot.Collections.Dictionary<string, string> dependencies);
+ private static extern void internal_GetExportedAssemblyDependencies(Godot.Collections.Dictionary<string, string> initialAssemblies,
+ string buildConfig, string customBclDir, Godot.Collections.Dictionary<string, string> dependencyAssemblies);
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs
new file mode 100644
index 0000000000..93ef837a83
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs
@@ -0,0 +1,93 @@
+using System;
+using System.IO;
+
+namespace GodotTools.Export
+{
+ public static class XcodeHelper
+ {
+ private static string _XcodePath = null;
+
+ public static string XcodePath
+ {
+ get
+ {
+ if (_XcodePath == null)
+ {
+ _XcodePath = FindXcode();
+
+ if (_XcodePath == null)
+ throw new Exception("Could not find Xcode");
+ }
+
+ return _XcodePath;
+ }
+ }
+
+ private static string FindSelectedXcode()
+ {
+ var outputWrapper = new Godot.Collections.Array();
+
+ int exitCode = Godot.OS.Execute("xcode-select", new string[] { "--print-path" }, output: outputWrapper);
+
+ if (exitCode == 0)
+ {
+ string output = (string)outputWrapper[0];
+ return output.Trim();
+ }
+
+ Console.Error.WriteLine($"'xcode-select --print-path' exited with code: {exitCode}");
+
+ return null;
+ }
+
+ public static string FindXcode()
+ {
+ string selectedXcode = FindSelectedXcode();
+ if (selectedXcode != null)
+ {
+ if (Directory.Exists(Path.Combine(selectedXcode, "Contents", "Developer")))
+ return selectedXcode;
+
+ // The path already pointed to Contents/Developer
+ var dirInfo = new DirectoryInfo(selectedXcode);
+ if (dirInfo.Name != "Developer" || dirInfo.Parent.Name != "Contents")
+ {
+ Console.WriteLine(Path.GetDirectoryName(selectedXcode));
+ Console.WriteLine(System.IO.Directory.GetParent(selectedXcode).Name);
+ Console.Error.WriteLine("Unrecognized path for selected Xcode");
+ }
+ else
+ {
+ return System.IO.Path.GetFullPath($"{selectedXcode}/../..");
+ }
+ }
+ else
+ {
+ Console.Error.WriteLine("Could not find the selected Xcode; trying with a hint path");
+ }
+
+ const string XcodeHintPath = "/Applications/Xcode.app";
+
+ if (Directory.Exists(XcodeHintPath))
+ {
+ if (Directory.Exists(Path.Combine(XcodeHintPath, "Contents", "Developer")))
+ return XcodeHintPath;
+
+ Console.Error.WriteLine($"Found Xcode at '{XcodeHintPath}' but it's missing the 'Contents/Developer' sub-directory");
+ }
+
+ return null;
+ }
+
+ public static string FindXcodeTool(string toolName)
+ {
+ string XcodeDefaultToolchain = Path.Combine(XcodePath, "Contents", "Developer", "Toolchains", "XcodeDefault.xctoolchain");
+
+ string path = Path.Combine(XcodeDefaultToolchain, "usr", "bin", toolName);
+ if (File.Exists(path))
+ return path;
+
+ throw new FileNotFoundException($"Cannot find Xcode tool: {toolName}");
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs
index bb218c2f19..90d6eb960e 100644
--- a/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs
@@ -1,6 +1,6 @@
namespace GodotTools
{
- public enum ExternalEditorId
+ public enum ExternalEditorId : long
{
None,
VisualStudio, // TODO (Windows-only)
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 147bc95bb8..c71c44d79d 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -1,10 +1,12 @@
using Godot;
+using GodotTools.Core;
using GodotTools.Export;
using GodotTools.Utils;
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Linq;
+using GodotTools.Build;
using GodotTools.Ides;
using GodotTools.Ides.Rider;
using GodotTools.Internals;
@@ -13,10 +15,10 @@ using JetBrains.Annotations;
using static GodotTools.Internals.Globals;
using File = GodotTools.Utils.File;
using OS = GodotTools.Utils.OS;
+using Path = System.IO.Path;
namespace GodotTools
{
- [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
public class GodotSharpEditor : EditorPlugin, ISerializationListener
{
private EditorSettings editorSettings;
@@ -27,13 +29,28 @@ namespace GodotTools
private AcceptDialog aboutDialog;
private CheckBox aboutDialogCheckBox;
- private ToolButton bottomPanelBtn;
+ private Button bottomPanelBtn;
+ private Button toolBarBuildButton;
public GodotIdeManager GodotIdeManager { get; private set; }
private WeakRef exportPluginWeak; // TODO Use WeakReference once we have proper serialization
- public BottomPanel BottomPanel { get; private set; }
+ public MSBuildPanel MSBuildPanel { get; private set; }
+
+ public bool SkipBuildBeforePlaying { get; set; } = false;
+
+ public static string ProjectAssemblyName
+ {
+ get
+ {
+ var projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name");
+ projectAssemblyName = projectAssemblyName.ToSafeDirName();
+ if (string.IsNullOrEmpty(projectAssemblyName))
+ projectAssemblyName = "UnnamedProject";
+ return projectAssemblyName;
+ }
+ }
private bool CreateProjectSolution()
{
@@ -44,9 +61,7 @@ namespace GodotTools
string resourceDir = ProjectSettings.GlobalizePath("res://");
string path = resourceDir;
- string name = (string)ProjectSettings.GetSetting("application/config/name");
- if (name.Empty())
- name = "UnnamedProject";
+ string name = ProjectAssemblyName;
string guid = CsProjOperations.GenerateGameProject(path, name);
@@ -61,7 +76,7 @@ namespace GodotTools
{
Guid = guid,
PathRelativeToSolution = name + ".csproj",
- Configs = new List<string> { "Debug", "Release", "Tools" }
+ Configs = new List<string> {"Debug", "ExportDebug", "ExportRelease"}
};
solution.AddNewProject(name, projectInfo);
@@ -112,25 +127,19 @@ namespace GodotTools
{
menuPopup.RemoveItem(menuPopup.GetItemIndex((int)MenuOptions.CreateSln));
bottomPanelBtn.Show();
+ toolBarBuildButton.Show();
}
private void _ShowAboutDialog()
{
bool showOnStart = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start");
aboutDialogCheckBox.Pressed = showOnStart;
- aboutDialog.PopupCenteredMinsize();
+ aboutDialog.PopupCentered();
}
- private void _ToggleAboutDialogOnStart(bool enabled)
+ private void _MenuOptionPressed(int id)
{
- bool showOnStart = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start");
- if (showOnStart != enabled)
- editorSettings.SetSetting("mono/editor/show_info_on_start", enabled);
- }
-
- private void _MenuOptionPressed(MenuOptions id)
- {
- switch (id)
+ switch ((MenuOptions)id)
{
case MenuOptions.CreateSln:
CreateProjectSolution();
@@ -138,12 +147,27 @@ namespace GodotTools
case MenuOptions.AboutCSharp:
_ShowAboutDialog();
break;
+ case MenuOptions.SetupGodotNugetFallbackFolder:
+ {
+ try
+ {
+ string fallbackFolder = NuGetUtils.GodotFallbackFolderPath;
+ NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, fallbackFolder);
+ NuGetUtils.AddBundledPackagesToFallbackFolder(fallbackFolder);
+ }
+ catch (Exception e)
+ {
+ ShowErrorDialog("Failed to setup Godot NuGet Offline Packages: " + e.Message);
+ }
+
+ break;
+ }
default:
throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid menu option");
}
}
- private void _BuildSolutionPressed()
+ private void BuildSolutionPressed()
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
{
@@ -151,23 +175,22 @@ namespace GodotTools
return; // Failed to create solution
}
- Instance.BottomPanel.BuildProjectPressed();
+ Instance.MSBuildPanel.BuildSolution();
}
- public override void _Notification(int what)
+ public override void _Ready()
{
- base._Notification(what);
+ base._Ready();
- if (what == NotificationReady)
+ MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged;
+
+ bool showInfoDialog = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start");
+ if (showInfoDialog)
{
- bool showInfoDialog = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start");
- if (showInfoDialog)
- {
- aboutDialog.PopupExclusive = true;
- _ShowAboutDialog();
- // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on.
- aboutDialog.PopupExclusive = false;
- }
+ aboutDialog.Exclusive = true;
+ _ShowAboutDialog();
+ // Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on.
+ aboutDialog.Exclusive = false;
}
}
@@ -175,13 +198,14 @@ namespace GodotTools
{
CreateSln,
AboutCSharp,
+ SetupGodotNugetFallbackFolder,
}
public void ShowErrorDialog(string message, string title = "Error")
{
- errorDialog.WindowTitle = title;
+ errorDialog.Title = title;
errorDialog.DialogText = message;
- errorDialog.PopupCenteredMinsize();
+ errorDialog.PopupCentered();
}
private static string _vsCodePath = string.Empty;
@@ -194,15 +218,39 @@ namespace GodotTools
[UsedImplicitly]
public Error OpenInExternalEditor(Script script, int line, int col)
{
- var editor = (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor");
+ var editorId = (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor");
- switch (editor)
+ switch (editorId)
{
case ExternalEditorId.None:
- // Tells the caller to fallback to the global external editor settings or the built-in editor
+ // Not an error. Tells the caller to fallback to the global external editor settings or the built-in editor.
return Error.Unavailable;
case ExternalEditorId.VisualStudio:
- throw new NotSupportedException();
+ {
+ string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
+
+ var args = new List<string>
+ {
+ GodotSharpDirs.ProjectSlnPath,
+ line >= 0 ? $"{scriptPath};{line + 1};{col + 1}" : scriptPath
+ };
+
+ string command = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "GodotTools.OpenVisualStudio.exe");
+
+ try
+ {
+ if (Godot.OS.IsStdoutVerbose())
+ Console.WriteLine($"Running: \"{command}\" {string.Join(" ", args.Select(a => $"\"{a}\""))}");
+
+ OS.RunProcess(command, args);
+ }
+ catch (Exception e)
+ {
+ GD.PushError($"Error when trying to run code editor: VisualStudio. Exception message: '{e.Message}'");
+ }
+
+ break;
+ }
case ExternalEditorId.VisualStudioForMac:
goto case ExternalEditorId.MonoDevelop;
case ExternalEditorId.Rider:
@@ -210,22 +258,25 @@ namespace GodotTools
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
RiderPathManager.OpenFile(GodotSharpDirs.ProjectSlnPath, scriptPath, line);
return Error.Ok;
- }
+ }
case ExternalEditorId.MonoDevelop:
{
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
- if (line >= 0)
- GodotIdeManager.SendOpenFile(scriptPath, line + 1, col);
- else
- GodotIdeManager.SendOpenFile(scriptPath);
+ GodotIdeManager.LaunchIdeAsync().ContinueWith(launchTask =>
+ {
+ var editorPick = launchTask.Result;
+ if (line >= 0)
+ editorPick?.SendOpenFile(scriptPath, line + 1, col);
+ else
+ editorPick?.SendOpenFile(scriptPath);
+ });
break;
}
-
case ExternalEditorId.VsCode:
{
- if (_vsCodePath.Empty() || !File.Exists(_vsCodePath))
+ if (string.IsNullOrEmpty(_vsCodePath) || !File.Exists(_vsCodePath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_vsCodePath = VsCodeNames.SelectFirstNotNull(OS.PathWhich, orElse: string.Empty);
@@ -235,7 +286,7 @@ namespace GodotTools
bool osxAppBundleInstalled = false;
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
// The package path is '/Applications/Visual Studio Code.app'
const string vscodeBundleId = "com.microsoft.VSCode";
@@ -266,7 +317,7 @@ namespace GodotTools
if (line >= 0)
{
args.Add("-g");
- args.Add($"{scriptPath}:{line + 1}:{col}");
+ args.Add($"{scriptPath}:{line}:{col}");
}
else
{
@@ -275,9 +326,9 @@ namespace GodotTools
string command;
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
- if (!osxAppBundleInstalled && _vsCodePath.Empty())
+ if (!osxAppBundleInstalled && string.IsNullOrEmpty(_vsCodePath))
{
GD.PushError("Cannot find code editor: VSCode");
return Error.FileNotFound;
@@ -287,7 +338,7 @@ namespace GodotTools
}
else
{
- if (_vsCodePath.Empty())
+ if (string.IsNullOrEmpty(_vsCodePath))
{
GD.PushError("Cannot find code editor: VSCode");
return Error.FileNotFound;
@@ -307,7 +358,6 @@ namespace GodotTools
break;
}
-
default:
throw new ArgumentOutOfRangeException();
}
@@ -321,14 +371,51 @@ namespace GodotTools
return (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None;
}
- public override bool Build()
+ public override bool _Build()
{
return BuildManager.EditorBuildCallback();
}
- public override void EnablePlugin()
+ private void ApplyNecessaryChangesToSolution()
+ {
+ try
+ {
+ // Migrate solution from old configuration names to: Debug, ExportDebug and ExportRelease
+ DotNetSolution.MigrateFromOldConfigNames(GodotSharpDirs.ProjectSlnPath);
+
+ var msbuildProject = ProjectUtils.Open(GodotSharpDirs.ProjectCsProjPath)
+ ?? throw new Exception("Cannot open C# project");
+
+ // NOTE: The order in which changes are made to the project is important
+
+ // Migrate to MSBuild project Sdks style if using the old style
+ ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, ProjectAssemblyName);
+
+ ProjectUtils.EnsureGodotSdkIsUpToDate(msbuildProject);
+
+ if (msbuildProject.HasUnsavedChanges)
+ {
+ // Save a copy of the project before replacing it
+ FileUtils.SaveBackupCopy(GodotSharpDirs.ProjectCsProjPath);
+
+ msbuildProject.Save();
+ }
+ }
+ catch (Exception e)
+ {
+ GD.PushError(e.ToString());
+ }
+ }
+
+ private void BuildStateChanged()
+ {
+ if (bottomPanelBtn != null)
+ bottomPanelBtn.Icon = MSBuildPanel.BuildOutputView.BuildStateIcon;
+ }
+
+ public override void _EnablePlugin()
{
- base.EnablePlugin();
+ base._EnablePlugin();
if (Instance != null)
throw new InvalidOperationException();
@@ -342,24 +429,23 @@ namespace GodotTools
errorDialog = new AcceptDialog();
editorBaseControl.AddChild(errorDialog);
- BottomPanel = new BottomPanel();
+ MSBuildPanel = new MSBuildPanel();
+ bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR());
- bottomPanelBtn = AddControlToBottomPanel(BottomPanel, "Mono".TTR());
-
- AddChild(new HotReloadAssemblyWatcher { Name = "HotReloadAssemblyWatcher" });
+ AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"});
menuPopup = new PopupMenu();
menuPopup.Hide();
- menuPopup.SetAsToplevel(true);
- AddToolSubmenuItem("Mono", menuPopup);
+ AddToolSubmenuItem("C#", menuPopup);
// TODO: Remove or edit this info dialog once Mono support is no longer in alpha
{
menuPopup.AddItem("About C# support".TTR(), (int)MenuOptions.AboutCSharp);
+ menuPopup.AddItem("Setup Godot NuGet Offline Packages".TTR(), (int)MenuOptions.SetupGodotNugetFallbackFolder);
aboutDialog = new AcceptDialog();
editorBaseControl.AddChild(aboutDialog);
- aboutDialog.WindowTitle = "Important: C# support is not feature-complete";
+ aboutDialog.Title = "Important: C# support is not feature-complete";
// We don't use DialogText as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox
// we'll add. Instead we add containers and a new autowrapped Label inside.
@@ -373,7 +459,7 @@ namespace GodotTools
aboutVBox.AddChild(aboutHBox);
var aboutIcon = new TextureRect();
- aboutIcon.Texture = aboutIcon.GetIcon("NodeWarning", "EditorIcons");
+ aboutIcon.Texture = aboutIcon.GetThemeIcon("NodeWarning", "EditorIcons");
aboutHBox.AddChild(aboutIcon);
var aboutLabel = new Label();
@@ -384,7 +470,7 @@ namespace GodotTools
aboutLabel.Text =
"C# support in Godot Engine is in late alpha stage and, while already usable, " +
"it is not meant for use in production.\n\n" +
- "Projects can be exported to Linux, macOS, Windows and Android, but not yet to iOS, HTML5 or UWP. " +
+ "Projects can be exported to Linux, macOS, Windows, Android, iOS and HTML5, but not yet to UWP. " +
"Bugs and usability issues will be addressed gradually over future releases, " +
"potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" +
"If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" +
@@ -394,32 +480,37 @@ namespace GodotTools
EditorDef("mono/editor/show_info_on_start", true);
// CheckBox in main container
- aboutDialogCheckBox = new CheckBox { Text = "Show this warning when starting the editor" };
- aboutDialogCheckBox.Connect("toggled", this, nameof(_ToggleAboutDialogOnStart));
+ aboutDialogCheckBox = new CheckBox {Text = "Show this warning when starting the editor"};
+ aboutDialogCheckBox.Toggled += enabled =>
+ {
+ bool showOnStart = (bool)editorSettings.GetSetting("mono/editor/show_info_on_start");
+ if (showOnStart != enabled)
+ editorSettings.SetSetting("mono/editor/show_info_on_start", enabled);
+ };
aboutVBox.AddChild(aboutDialogCheckBox);
}
+ toolBarBuildButton = new Button
+ {
+ Text = "Build",
+ HintTooltip = "Build solution",
+ FocusMode = Control.FocusModeEnum.None
+ };
+ toolBarBuildButton.PressedSignal += BuildSolutionPressed;
+ AddControlToContainer(CustomControlContainer.Toolbar, toolBarBuildButton);
+
if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
{
- // Make sure the existing project has Api assembly references configured correctly
- CsProjOperations.FixApiHintPath(GodotSharpDirs.ProjectCsProjPath);
+ ApplyNecessaryChangesToSolution();
}
else
{
bottomPanelBtn.Hide();
+ toolBarBuildButton.Hide();
menuPopup.AddItem("Create C# solution".TTR(), (int)MenuOptions.CreateSln);
}
- menuPopup.Connect("id_pressed", this, nameof(_MenuOptionPressed));
-
- var buildButton = new ToolButton
- {
- Text = "Build",
- HintTooltip = "Build solution",
- FocusMode = Control.FocusModeEnum.None
- };
- buildButton.Connect("pressed", this, nameof(_BuildSolutionPressed));
- AddControlToContainer(CustomControlContainer.Toolbar, buildButton);
+ menuPopup.IdPressed += _MenuOptionPressed;
// External editor settings
EditorDef("mono/editor/external_editor", ExternalEditorId.None);
@@ -428,18 +519,19 @@ namespace GodotTools
if (OS.IsWindows)
{
- settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
+ settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudio}" +
+ $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
$",JetBrains Rider:{(int)ExternalEditorId.Rider}";
}
- else if (OS.IsOSX)
+ else if (OS.IsMacOS)
{
settingsHintStr += $",Visual Studio:{(int)ExternalEditorId.VisualStudioForMac}" +
$",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
$",JetBrains Rider:{(int)ExternalEditorId.Rider}";
}
- else if (OS.IsUnixLike())
+ else if (OS.IsUnixLike)
{
settingsHintStr += $",MonoDevelop:{(int)ExternalEditorId.MonoDevelop}" +
$",Visual Studio Code:{(int)ExternalEditorId.VsCode}" +
@@ -460,6 +552,16 @@ namespace GodotTools
exportPlugin.RegisterExportSettings();
exportPluginWeak = WeakRef(exportPlugin);
+ try
+ {
+ // At startup we make sure NuGet.Config files have our Godot NuGet fallback folder included
+ NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, NuGetUtils.GodotFallbackFolderPath);
+ }
+ catch (Exception e)
+ {
+ GD.PushError("Failed to add Godot NuGet Offline Packages to NuGet.Config: " + e.Message);
+ }
+
BuildManager.Initialize();
RiderPathManager.Initialize();
@@ -498,6 +600,7 @@ namespace GodotTools
public static GodotSharpEditor Instance { get; private set; }
+ [UsedImplicitly]
private GodotSharpEditor()
{
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
index 379dfd9f7d..3f14629b11 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj
@@ -1,121 +1,39 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid>
- <OutputType>Library</OutputType>
- <RootNamespace>GodotTools</RootNamespace>
- <AssemblyName>GodotTools</AssemblyName>
- <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
+ <TargetFramework>net472</TargetFramework>
+ <LangVersion>7.2</LangVersion>
+ <GodotApiConfiguration>Debug</GodotApiConfiguration> <!-- The Godot editor uses the Debug Godot API assemblies -->
<GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
- <DataDirToolsOutputPath>$(GodotSourceRootPath)/bin/GodotSharp/Tools</DataDirToolsOutputPath>
- <GodotApiConfiguration>Debug</GodotApiConfiguration>
- <LangVersion>7</LangVersion>
+ <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir>
+ <GodotApiAssembliesDir>$(GodotOutputDataDir)/Api/$(GodotApiConfiguration)</GodotApiAssembliesDir>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
- <Optimize>false</Optimize>
- <OutputPath>bin\Debug</OutputPath>
- <DefineConstants>DEBUG;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <Optimize>true</Optimize>
- <OutputPath>bin\Release</OutputPath>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
+ <PropertyGroup Condition=" Exists('$(GodotApiAssembliesDir)/GodotSharp.dll') ">
+ <!-- The project is part of the Godot source tree -->
+ <!-- Use the Godot source tree output folder instead of '$(ProjectDir)/bin' -->
+ <OutputPath>$(GodotOutputDataDir)/Tools</OutputPath>
+ <!-- Must not append '$(TargetFramework)' to the output path in this case -->
+ <AppendTargetFrameworkToOutputPath>False</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<ItemGroup>
- <Reference Include="JetBrains.Annotations, Version=2019.1.3.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325">
- <HintPath>..\packages\JetBrains.Annotations.2019.1.3\lib\net20\JetBrains.Annotations.dll</HintPath>
- <Private>True</Private>
- </Reference>
- <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
- <HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
- <Private>True</Private>
- </Reference>
- <Reference Include="System" />
+ <PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" />
+ <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
+ <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<Reference Include="GodotSharp">
- <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharp.dll</HintPath>
+ <HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="GodotSharpEditor">
- <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharpEditor.dll</HintPath>
+ <HintPath>$(GodotApiAssembliesDir)/GodotSharpEditor.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
- <Compile Include="Build\MsBuildFinder.cs" />
- <Compile Include="Export\ExportPlugin.cs" />
- <Compile Include="ExternalEditorId.cs" />
- <Compile Include="Ides\GodotIdeManager.cs" />
- <Compile Include="Ides\GodotIdeServer.cs" />
- <Compile Include="Ides\MonoDevelop\EditorId.cs" />
- <Compile Include="Ides\MonoDevelop\Instance.cs" />
- <Compile Include="Ides\Rider\RiderPathLocator.cs" />
- <Compile Include="Ides\Rider\RiderPathManager.cs" />
- <Compile Include="Internals\EditorProgress.cs" />
- <Compile Include="Internals\GodotSharpDirs.cs" />
- <Compile Include="Internals\Internal.cs" />
- <Compile Include="Internals\ScriptClassParser.cs" />
- <Compile Include="Internals\Globals.cs" />
- <Compile Include="Properties\AssemblyInfo.cs" />
- <Compile Include="Build\BuildSystem.cs" />
- <Compile Include="Utils\Directory.cs" />
- <Compile Include="Utils\File.cs" />
- <Compile Include="Utils\NotifyAwaiter.cs" />
- <Compile Include="Utils\OS.cs" />
- <Compile Include="GodotSharpEditor.cs" />
- <Compile Include="BuildManager.cs" />
- <Compile Include="HotReloadAssemblyWatcher.cs" />
- <Compile Include="BuildInfo.cs" />
- <Compile Include="BuildTab.cs" />
- <Compile Include="BottomPanel.cs" />
- <Compile Include="CsProjOperations.cs" />
- <Compile Include="Utils\CollectionExtensions.cs" />
- <Compile Include="Utils\User32Dll.cs" />
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj">
- <Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project>
- <Name>GodotTools.BuildLogger</Name>
- </ProjectReference>
- <ProjectReference Include="..\GodotTools.IdeConnection\GodotTools.IdeConnection.csproj">
- <Project>{92600954-25f0-4291-8e11-1fee9fc4be20}</Project>
- <Name>GodotTools.IdeConnection</Name>
- </ProjectReference>
- <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj">
- <Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project>
- <Name>GodotTools.ProjectEditor</Name>
- </ProjectReference>
- <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
- <Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project>
- <Name>GodotTools.Core</Name>
- </ProjectReference>
- </ItemGroup>
- <ItemGroup>
- <None Include="packages.config" />
+ <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj" />
+ <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" />
+ <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj" />
+ <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" />
+ <!-- Include it if this is an SCons build targeting Windows, or if it's not an SCons build but we're on Windows -->
+ <ProjectReference Include="..\GodotTools.OpenVisualStudio\GodotTools.OpenVisualStudio.csproj" Condition=" '$(GodotPlatform)' == 'windows' Or ( '$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT' ) " />
</ItemGroup>
- <Target Name="CopyToDataDir" AfterTargets="Build">
- <ItemGroup>
- <GodotToolsCopy Include="$(OutputPath)\GodotTools*.dll" />
- <GodotToolsCopy Include="$(OutputPath)\Newtonsoft.Json.dll" />
- <GodotToolsCopy Include="$(OutputPath)\DotNet.Glob.dll" />
- </ItemGroup>
- <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <GodotToolsCopy Include="$(OutputPath)\GodotTools*.pdb" />
- </ItemGroup>
- <Copy SourceFiles="@(GodotToolsCopy)" DestinationFolder="$(DataDirToolsOutputPath)" ContinueOnError="false" />
- </Target>
- <Target Name="BuildAlwaysCopyToDataDir">
- <!-- Custom target run by SCons to make sure the CopyToDataDir target is always executed, without having to use DisableFastUpToDateCheck -->
- <CallTarget Targets="Build" />
- <CallTarget Targets="CopyToDataDir" />
- </Target>
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
index 0ed567afd1..b30c857c64 100644
--- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs
@@ -10,7 +10,7 @@ namespace GodotTools
public override void _Notification(int what)
{
- if (what == MainLoop.NotificationWmFocusIn)
+ if (what == Node.NotificationWmWindowFocusIn)
{
RestartTimer();
@@ -40,7 +40,7 @@ namespace GodotTools
OneShot = false,
WaitTime = (float)EditorDef("mono/assembly_watch_interval_sec", 0.5)
};
- watchTimer.Connect("timeout", this, nameof(TimerTimeout));
+ watchTimer.Timeout += TimerTimeout;
AddChild(watchTimer);
watchTimer.Start();
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
index 54f0ffab96..451ce39f5c 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs
@@ -1,73 +1,104 @@
using System;
using System.IO;
+using System.Threading.Tasks;
using Godot;
-using GodotTools.IdeConnection;
+using GodotTools.IdeMessaging;
+using GodotTools.IdeMessaging.Requests;
using GodotTools.Internals;
namespace GodotTools.Ides
{
- public class GodotIdeManager : Node, ISerializationListener
+ public sealed class GodotIdeManager : Node, ISerializationListener
{
- public GodotIdeServer GodotIdeServer { get; private set; }
+ private MessagingServer MessagingServer { get; set; }
private MonoDevelop.Instance monoDevelInstance;
private MonoDevelop.Instance vsForMacInstance;
- private GodotIdeServer GetRunningServer()
+ private MessagingServer GetRunningOrNewServer()
{
- if (GodotIdeServer != null && !GodotIdeServer.IsDisposed)
- return GodotIdeServer;
- StartServer();
- return GodotIdeServer;
+ if (MessagingServer != null && !MessagingServer.IsDisposed)
+ return MessagingServer;
+
+ MessagingServer?.Dispose();
+ MessagingServer = new MessagingServer(OS.GetExecutablePath(), ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir), new GodotLogger());
+
+ _ = MessagingServer.Listen();
+
+ return MessagingServer;
}
public override void _Ready()
{
- StartServer();
+ _ = GetRunningOrNewServer();
}
public void OnBeforeSerialize()
{
- GodotIdeServer?.Dispose();
}
public void OnAfterDeserialize()
{
- StartServer();
+ _ = GetRunningOrNewServer();
}
- private ILogger logger;
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
- protected ILogger Logger
+ if (disposing)
+ {
+ MessagingServer?.Dispose();
+ }
+ }
+
+ private string GetExternalEditorIdentity(ExternalEditorId editorId)
{
- get => logger ?? (logger = new GodotLogger());
+ // Manually convert to string to avoid breaking compatibility in case we rename the enum fields.
+ switch (editorId)
+ {
+ case ExternalEditorId.None:
+ return null;
+ case ExternalEditorId.VisualStudio:
+ return "VisualStudio";
+ case ExternalEditorId.VsCode:
+ return "VisualStudioCode";
+ case ExternalEditorId.Rider:
+ return "Rider";
+ case ExternalEditorId.VisualStudioForMac:
+ return "VisualStudioForMac";
+ case ExternalEditorId.MonoDevelop:
+ return "MonoDevelop";
+ default:
+ throw new NotImplementedException();
+ }
}
- private void StartServer()
+ public async Task<EditorPick?> LaunchIdeAsync(int millisecondsTimeout = 10000)
{
- GodotIdeServer?.Dispose();
- GodotIdeServer = new GodotIdeServer(LaunchIde,
- OS.GetExecutablePath(),
- ProjectSettings.GlobalizePath(GodotSharpDirs.ResMetadataDir));
+ var editorId = (ExternalEditorId)GodotSharpEditor.Instance.GetEditorInterface()
+ .GetEditorSettings().GetSetting("mono/editor/external_editor");
+ string editorIdentity = GetExternalEditorIdentity(editorId);
- GodotIdeServer.Logger = Logger;
+ var runningServer = GetRunningOrNewServer();
- GodotIdeServer.StartServer();
- }
+ if (runningServer.IsAnyConnected(editorIdentity))
+ return new EditorPick(editorIdentity);
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
+ LaunchIde(editorId, editorIdentity);
+
+ var timeoutTask = Task.Delay(millisecondsTimeout);
+ var completedTask = await Task.WhenAny(timeoutTask, runningServer.AwaitClientConnected(editorIdentity));
+
+ if (completedTask != timeoutTask)
+ return new EditorPick(editorIdentity);
- GodotIdeServer?.Dispose();
+ return null;
}
- private void LaunchIde()
+ private void LaunchIde(ExternalEditorId editorId, string editorIdentity)
{
- var editor = (ExternalEditorId)GodotSharpEditor.Instance.GetEditorInterface()
- .GetEditorSettings().GetSetting("mono/editor/external_editor");
-
- switch (editor)
+ switch (editorId)
{
case ExternalEditorId.None:
case ExternalEditorId.VisualStudio:
@@ -80,14 +111,14 @@ namespace GodotTools.Ides
{
MonoDevelop.Instance GetMonoDevelopInstance(string solutionPath)
{
- if (Utils.OS.IsOSX && editor == ExternalEditorId.VisualStudioForMac)
+ if (Utils.OS.IsMacOS && editorId == ExternalEditorId.VisualStudioForMac)
{
- vsForMacInstance = vsForMacInstance ??
+ vsForMacInstance = (vsForMacInstance?.IsDisposed ?? true ? null : vsForMacInstance) ??
new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.VisualStudioForMac);
return vsForMacInstance;
}
- monoDevelInstance = monoDevelInstance ??
+ monoDevelInstance = (monoDevelInstance?.IsDisposed ?? true ? null : monoDevelInstance) ??
new MonoDevelop.Instance(solutionPath, MonoDevelop.EditorId.MonoDevelop);
return monoDevelInstance;
}
@@ -96,12 +127,25 @@ namespace GodotTools.Ides
{
var instance = GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath);
- if (!instance.IsRunning)
+ if (instance.IsRunning && !GetRunningOrNewServer().IsAnyConnected(editorIdentity))
+ {
+ // After launch we wait up to 30 seconds for the IDE to connect to our messaging server.
+ var waitAfterLaunch = TimeSpan.FromSeconds(30);
+ var timeSinceLaunch = DateTime.Now - instance.LaunchTime;
+ if (timeSinceLaunch > waitAfterLaunch)
+ {
+ instance.Dispose();
+ instance.Execute();
+ }
+ }
+ else if (!instance.IsRunning)
+ {
instance.Execute();
+ }
}
catch (FileNotFoundException)
{
- string editorName = editor == ExternalEditorId.VisualStudioForMac ? "Visual Studio" : "MonoDevelop";
+ string editorName = editorId == ExternalEditorId.VisualStudioForMac ? "Visual Studio" : "MonoDevelop";
GD.PushError($"Cannot find code editor: {editorName}");
}
@@ -113,26 +157,45 @@ namespace GodotTools.Ides
}
}
- private void WriteMessage(string id, params string[] arguments)
+ public readonly struct EditorPick
{
- GetRunningServer().WriteMessage(new Message(id, arguments));
- }
+ private readonly string identity;
- public void SendOpenFile(string file)
- {
- WriteMessage("OpenFile", file);
- }
+ public EditorPick(string identity)
+ {
+ this.identity = identity;
+ }
- public void SendOpenFile(string file, int line)
- {
- WriteMessage("OpenFile", file, line.ToString());
- }
+ public bool IsAnyConnected() =>
+ GodotSharpEditor.Instance.GodotIdeManager.GetRunningOrNewServer().IsAnyConnected(identity);
- public void SendOpenFile(string file, int line, int column)
- {
- WriteMessage("OpenFile", file, line.ToString(), column.ToString());
+ private void SendRequest<TResponse>(Request request)
+ where TResponse : Response, new()
+ {
+ // Logs an error if no client is connected with the specified identity
+ GodotSharpEditor.Instance.GodotIdeManager
+ .GetRunningOrNewServer()
+ .BroadcastRequest<TResponse>(identity, request);
+ }
+
+ public void SendOpenFile(string file)
+ {
+ SendRequest<OpenFileResponse>(new OpenFileRequest {File = file});
+ }
+
+ public void SendOpenFile(string file, int line)
+ {
+ SendRequest<OpenFileResponse>(new OpenFileRequest {File = file, Line = line});
+ }
+
+ public void SendOpenFile(string file, int line, int column)
+ {
+ SendRequest<OpenFileResponse>(new OpenFileRequest {File = file, Line = line, Column = column});
+ }
}
+ public EditorPick PickEditor(ExternalEditorId editorId) => new EditorPick(GetExternalEditorIdentity(editorId));
+
private class GodotLogger : ILogger
{
public void LogDebug(string message)
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs
deleted file mode 100644
index 72676a8b24..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeServer.cs
+++ /dev/null
@@ -1,212 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using GodotTools.IdeConnection;
-using GodotTools.Internals;
-using GodotTools.Utils;
-using Directory = System.IO.Directory;
-using File = System.IO.File;
-using Thread = System.Threading.Thread;
-
-namespace GodotTools.Ides
-{
- public class GodotIdeServer : GodotIdeBase
- {
- private readonly TcpListener listener;
- private readonly FileStream metaFile;
- private readonly Action launchIdeAction;
- private readonly NotifyAwaiter<bool> clientConnectedAwaiter = new NotifyAwaiter<bool>();
-
- private async Task<bool> AwaitClientConnected()
- {
- return await clientConnectedAwaiter.Reset();
- }
-
- public GodotIdeServer(Action launchIdeAction, string editorExecutablePath, string projectMetadataDir)
- : base(projectMetadataDir)
- {
- messageHandlers = InitializeMessageHandlers();
-
- this.launchIdeAction = launchIdeAction;
-
- // Make sure the directory exists
- Directory.CreateDirectory(projectMetadataDir);
-
- // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing...
- const FileShare metaFileShare = FileShare.ReadWrite;
-
- metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare);
-
- listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0));
- listener.Start();
-
- int port = ((IPEndPoint)listener.Server.LocalEndPoint).Port;
- using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8))
- {
- metaFileWriter.WriteLine(port);
- metaFileWriter.WriteLine(editorExecutablePath);
- }
-
- StartServer();
- }
-
- public void StartServer()
- {
- var serverThread = new Thread(RunServerThread) { Name = "Godot Ide Connection Server" };
- serverThread.Start();
- }
-
- private void RunServerThread()
- {
- SynchronizationContext.SetSynchronizationContext(Godot.Dispatcher.SynchronizationContext);
-
- try
- {
- while (!IsDisposed)
- {
- TcpClient tcpClient = listener.AcceptTcpClient();
-
- Logger.LogInfo("Connection open with Ide Client");
-
- lock (ConnectionLock)
- {
- Connection = new GodotIdeConnectionServer(tcpClient, HandleMessage);
- Connection.Logger = Logger;
- }
-
- Connected += () => clientConnectedAwaiter.SetResult(true);
-
- Connection.Start();
- }
- }
- catch (Exception e)
- {
- if (!IsDisposed && !(e is SocketException se && se.SocketErrorCode == SocketError.Interrupted))
- throw;
- }
- }
-
- public async void WriteMessage(Message message)
- {
- async Task LaunchIde()
- {
- if (IsConnected)
- return;
-
- launchIdeAction();
- await Task.WhenAny(Task.Delay(10000), AwaitClientConnected());
- }
-
- await LaunchIde();
-
- if (!IsConnected)
- {
- Logger.LogError("Cannot write message: Godot Ide Server not connected");
- return;
- }
-
- Connection.WriteMessage(message);
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (disposing)
- {
- listener?.Stop();
-
- metaFile?.Dispose();
-
- File.Delete(MetaFilePath);
- }
- }
-
- protected virtual bool HandleMessage(Message message)
- {
- if (messageHandlers.TryGetValue(message.Id, out var action))
- {
- action(message.Arguments);
- return true;
- }
-
- return false;
- }
-
- private readonly Dictionary<string, Action<string[]>> messageHandlers;
-
- private Dictionary<string, Action<string[]>> InitializeMessageHandlers()
- {
- return new Dictionary<string, Action<string[]>>
- {
- ["Play"] = args =>
- {
- switch (args.Length)
- {
- case 0:
- Play();
- return;
- case 2:
- Play(debuggerHost: args[0], debuggerPort: int.Parse(args[1]));
- return;
- default:
- throw new ArgumentException();
- }
- },
- ["ReloadScripts"] = args => ReloadScripts()
- };
- }
-
- private void DispatchToMainThread(Action action)
- {
- var d = new SendOrPostCallback(state => action());
- Godot.Dispatcher.SynchronizationContext.Post(d, null);
- }
-
- private void Play()
- {
- DispatchToMainThread(() =>
- {
- CurrentPlayRequest = new PlayRequest();
- Internal.EditorRunPlay();
- CurrentPlayRequest = null;
- });
- }
-
- private void Play(string debuggerHost, int debuggerPort)
- {
- DispatchToMainThread(() =>
- {
- CurrentPlayRequest = new PlayRequest(debuggerHost, debuggerPort);
- Internal.EditorRunPlay();
- CurrentPlayRequest = null;
- });
- }
-
- private void ReloadScripts()
- {
- DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts);
- }
-
- public PlayRequest? CurrentPlayRequest { get; private set; }
-
- public struct PlayRequest
- {
- public bool HasDebugger { get; }
- public string DebuggerHost { get; }
- public int DebuggerPort { get; }
-
- public PlayRequest(string debuggerHost, int debuggerPort)
- {
- HasDebugger = true;
- DebuggerHost = debuggerHost;
- DebuggerPort = debuggerPort;
- }
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs
new file mode 100644
index 0000000000..eb34a2d0f7
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MessagingServer.cs
@@ -0,0 +1,390 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using GodotTools.IdeMessaging;
+using GodotTools.IdeMessaging.Requests;
+using GodotTools.IdeMessaging.Utils;
+using GodotTools.Internals;
+using GodotTools.Utils;
+using Newtonsoft.Json;
+using Directory = System.IO.Directory;
+using File = System.IO.File;
+
+namespace GodotTools.Ides
+{
+ public sealed class MessagingServer : IDisposable
+ {
+ private readonly ILogger logger;
+
+ private readonly FileStream metaFile;
+ private string MetaFilePath { get; }
+
+ private readonly SemaphoreSlim peersSem = new SemaphoreSlim(1);
+
+ private readonly TcpListener listener;
+
+ private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> clientConnectedAwaiters = new Dictionary<string, Queue<NotifyAwaiter<bool>>>();
+ private readonly Dictionary<string, Queue<NotifyAwaiter<bool>>> clientDisconnectedAwaiters = new Dictionary<string, Queue<NotifyAwaiter<bool>>>();
+
+ public async Task<bool> AwaitClientConnected(string identity)
+ {
+ if (!clientConnectedAwaiters.TryGetValue(identity, out var queue))
+ {
+ queue = new Queue<NotifyAwaiter<bool>>();
+ clientConnectedAwaiters.Add(identity, queue);
+ }
+
+ var awaiter = new NotifyAwaiter<bool>();
+ queue.Enqueue(awaiter);
+ return await awaiter;
+ }
+
+ public async Task<bool> AwaitClientDisconnected(string identity)
+ {
+ if (!clientDisconnectedAwaiters.TryGetValue(identity, out var queue))
+ {
+ queue = new Queue<NotifyAwaiter<bool>>();
+ clientDisconnectedAwaiters.Add(identity, queue);
+ }
+
+ var awaiter = new NotifyAwaiter<bool>();
+ queue.Enqueue(awaiter);
+ return await awaiter;
+ }
+
+ public bool IsDisposed { get; private set; }
+
+ public bool IsAnyConnected(string identity) => string.IsNullOrEmpty(identity) ?
+ Peers.Count > 0 :
+ Peers.Any(c => c.RemoteIdentity == identity);
+
+ private List<Peer> Peers { get; } = new List<Peer>();
+
+ ~MessagingServer()
+ {
+ Dispose(disposing: false);
+ }
+
+ public async void Dispose()
+ {
+ if (IsDisposed)
+ return;
+
+ using (await peersSem.UseAsync())
+ {
+ if (IsDisposed) // lock may not be fair
+ return;
+ IsDisposed = true;
+ }
+
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ foreach (var connection in Peers)
+ connection.Dispose();
+ Peers.Clear();
+ listener?.Stop();
+
+ metaFile?.Dispose();
+
+ File.Delete(MetaFilePath);
+ }
+ }
+
+ public MessagingServer(string editorExecutablePath, string projectMetadataDir, ILogger logger)
+ {
+ this.logger = logger;
+
+ MetaFilePath = Path.Combine(projectMetadataDir, GodotIdeMetadata.DefaultFileName);
+
+ // Make sure the directory exists
+ Directory.CreateDirectory(projectMetadataDir);
+
+ // The Godot editor's file system thread can keep the file open for writing, so we are forced to allow write sharing...
+ const FileShare metaFileShare = FileShare.ReadWrite;
+
+ metaFile = File.Open(MetaFilePath, FileMode.Create, FileAccess.Write, metaFileShare);
+
+ listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, port: 0));
+ listener.Start();
+
+ int port = ((IPEndPoint)listener.Server.LocalEndPoint).Port;
+ using (var metaFileWriter = new StreamWriter(metaFile, Encoding.UTF8))
+ {
+ metaFileWriter.WriteLine(port);
+ metaFileWriter.WriteLine(editorExecutablePath);
+ }
+ }
+
+ private async Task AcceptClient(TcpClient tcpClient)
+ {
+ logger.LogDebug("Accept client...");
+
+ using (var peer = new Peer(tcpClient, new ServerHandshake(), new ServerMessageHandler(), logger))
+ {
+ // ReSharper disable AccessToDisposedClosure
+ peer.Connected += () =>
+ {
+ logger.LogInfo("Connection open with Ide Client");
+
+ if (clientConnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue))
+ {
+ while (queue.Count > 0)
+ queue.Dequeue().SetResult(true);
+ clientConnectedAwaiters.Remove(peer.RemoteIdentity);
+ }
+ };
+
+ peer.Disconnected += () =>
+ {
+ if (clientDisconnectedAwaiters.TryGetValue(peer.RemoteIdentity, out var queue))
+ {
+ while (queue.Count > 0)
+ queue.Dequeue().SetResult(true);
+ clientDisconnectedAwaiters.Remove(peer.RemoteIdentity);
+ }
+ };
+ // ReSharper restore AccessToDisposedClosure
+
+ try
+ {
+ if (!await peer.DoHandshake("server"))
+ {
+ logger.LogError("Handshake failed");
+ return;
+ }
+ }
+ catch (Exception e)
+ {
+ logger.LogError("Handshake failed with unhandled exception: ", e);
+ return;
+ }
+
+ using (await peersSem.UseAsync())
+ Peers.Add(peer);
+
+ try
+ {
+ await peer.Process();
+ }
+ finally
+ {
+ using (await peersSem.UseAsync())
+ Peers.Remove(peer);
+ }
+ }
+ }
+
+ public async Task Listen()
+ {
+ try
+ {
+ while (!IsDisposed)
+ _ = AcceptClient(await listener.AcceptTcpClientAsync());
+ }
+ catch (Exception e)
+ {
+ if (!IsDisposed && !(e is SocketException se && se.SocketErrorCode == SocketError.Interrupted))
+ throw;
+ }
+ }
+
+ public async void BroadcastRequest<TResponse>(string identity, Request request)
+ where TResponse : Response, new()
+ {
+ using (await peersSem.UseAsync())
+ {
+ if (!IsAnyConnected(identity))
+ {
+ logger.LogError("Cannot write request. No client connected to the Godot Ide Server.");
+ return;
+ }
+
+ var selectedConnections = string.IsNullOrEmpty(identity) ?
+ Peers :
+ Peers.Where(c => c.RemoteIdentity == identity);
+
+ string body = JsonConvert.SerializeObject(request);
+
+ foreach (var connection in selectedConnections)
+ _ = connection.SendRequest<TResponse>(request.Id, body);
+ }
+ }
+
+ private class ServerHandshake : IHandshake
+ {
+ private static readonly string ServerHandshakeBase = $"{Peer.ServerHandshakeName},Version={Peer.ProtocolVersionMajor}.{Peer.ProtocolVersionMinor}.{Peer.ProtocolVersionRevision}";
+ private static readonly string ClientHandshakePattern = $@"{Regex.Escape(Peer.ClientHandshakeName)},Version=([0-9]+)\.([0-9]+)\.([0-9]+),([_a-zA-Z][_a-zA-Z0-9]{{0,63}})";
+
+ public string GetHandshakeLine(string identity) => $"{ServerHandshakeBase},{identity}";
+
+ public bool IsValidPeerHandshake(string handshake, out string identity, ILogger logger)
+ {
+ identity = null;
+
+ var match = Regex.Match(handshake, ClientHandshakePattern);
+
+ if (!match.Success)
+ return false;
+
+ if (!uint.TryParse(match.Groups[1].Value, out uint clientMajor) || Peer.ProtocolVersionMajor != clientMajor)
+ {
+ logger.LogDebug("Incompatible major version: " + match.Groups[1].Value);
+ return false;
+ }
+
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+ if (!uint.TryParse(match.Groups[2].Value, out uint clientMinor) || Peer.ProtocolVersionMinor > clientMinor)
+ {
+ logger.LogDebug("Incompatible minor version: " + match.Groups[2].Value);
+ return false;
+ }
+
+ if (!uint.TryParse(match.Groups[3].Value, out uint _)) // Revision
+ {
+ logger.LogDebug("Incompatible revision build: " + match.Groups[3].Value);
+ return false;
+ }
+
+ identity = match.Groups[4].Value;
+
+ return true;
+ }
+ }
+
+ private class ServerMessageHandler : IMessageHandler
+ {
+ private static void DispatchToMainThread(Action action)
+ {
+ var d = new SendOrPostCallback(state => action());
+ Godot.Dispatcher.SynchronizationContext.Post(d, null);
+ }
+
+ private readonly Dictionary<string, Peer.RequestHandler> requestHandlers = InitializeRequestHandlers();
+
+ public async Task<MessageContent> HandleRequest(Peer peer, string id, MessageContent content, ILogger logger)
+ {
+ if (!requestHandlers.TryGetValue(id, out var handler))
+ {
+ logger.LogError($"Received unknown request: {id}");
+ return new MessageContent(MessageStatus.RequestNotSupported, "null");
+ }
+
+ try
+ {
+ var response = await handler(peer, content);
+ return new MessageContent(response.Status, JsonConvert.SerializeObject(response));
+ }
+ catch (JsonException)
+ {
+ logger.LogError($"Received request with invalid body: {id}");
+ return new MessageContent(MessageStatus.InvalidRequestBody, "null");
+ }
+ }
+
+ private static Dictionary<string, Peer.RequestHandler> InitializeRequestHandlers()
+ {
+ return new Dictionary<string, Peer.RequestHandler>
+ {
+ [PlayRequest.Id] = async (peer, content) =>
+ {
+ _ = JsonConvert.DeserializeObject<PlayRequest>(content.Body);
+ return await HandlePlay();
+ },
+ [DebugPlayRequest.Id] = async (peer, content) =>
+ {
+ var request = JsonConvert.DeserializeObject<DebugPlayRequest>(content.Body);
+ return await HandleDebugPlay(request);
+ },
+ [StopPlayRequest.Id] = async (peer, content) =>
+ {
+ var request = JsonConvert.DeserializeObject<StopPlayRequest>(content.Body);
+ return await HandleStopPlay(request);
+ },
+ [ReloadScriptsRequest.Id] = async (peer, content) =>
+ {
+ _ = JsonConvert.DeserializeObject<ReloadScriptsRequest>(content.Body);
+ return await HandleReloadScripts();
+ },
+ [CodeCompletionRequest.Id] = async (peer, content) =>
+ {
+ var request = JsonConvert.DeserializeObject<CodeCompletionRequest>(content.Body);
+ return await HandleCodeCompletionRequest(request);
+ }
+ };
+ }
+
+ private static Task<Response> HandlePlay()
+ {
+ DispatchToMainThread(() =>
+ {
+ // TODO: Add BuildBeforePlaying flag to PlayRequest
+
+ // Run the game
+ Internal.EditorRunPlay();
+ });
+ return Task.FromResult<Response>(new PlayResponse());
+ }
+
+ private static Task<Response> HandleDebugPlay(DebugPlayRequest request)
+ {
+ DispatchToMainThread(() =>
+ {
+ // Tell the build callback whether the editor already built the solution or not
+ GodotSharpEditor.Instance.SkipBuildBeforePlaying = !(request.BuildBeforePlaying ?? true);
+
+ // Pass the debugger agent settings to the player via an environment variables
+ // TODO: It would be better if this was an argument in EditorRunPlay instead
+ Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT",
+ "--debugger-agent=transport=dt_socket" +
+ $",address={request.DebuggerHost}:{request.DebuggerPort}" +
+ ",server=n");
+
+ // Run the game
+ Internal.EditorRunPlay();
+
+ // Restore normal settings
+ Environment.SetEnvironmentVariable("GODOT_MONO_DEBUGGER_AGENT", "");
+ GodotSharpEditor.Instance.SkipBuildBeforePlaying = false;
+ });
+ return Task.FromResult<Response>(new DebugPlayResponse());
+ }
+
+ private static Task<Response> HandleStopPlay(StopPlayRequest request)
+ {
+ DispatchToMainThread(Internal.EditorRunStop);
+ return Task.FromResult<Response>(new StopPlayResponse());
+ }
+
+ private static Task<Response> HandleReloadScripts()
+ {
+ DispatchToMainThread(Internal.ScriptEditorDebugger_ReloadScripts);
+ return Task.FromResult<Response>(new ReloadScriptsResponse());
+ }
+
+ private static async Task<Response> HandleCodeCompletionRequest(CodeCompletionRequest request)
+ {
+ // This is needed if the "resource path" part of the path is case insensitive.
+ // However, it doesn't fix resource loading if the rest of the path is also case insensitive.
+ string scriptFileLocalized = FsPathUtils.LocalizePathWithCaseChecked(request.ScriptFile);
+
+ var response = new CodeCompletionResponse {Kind = request.Kind, ScriptFile = request.ScriptFile};
+ response.Suggestions = await Task.Run(() =>
+ Internal.CodeCompletionRequest(response.Kind, scriptFileLocalized ?? request.ScriptFile));
+ return response;
+ }
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
index 6026c109ad..fd7bbd5578 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/MonoDevelop/Instance.cs
@@ -7,14 +7,16 @@ using GodotTools.Utils;
namespace GodotTools.Ides.MonoDevelop
{
- public class Instance
+ public class Instance : IDisposable
{
+ public DateTime LaunchTime { get; private set; }
private readonly string solutionFile;
private readonly EditorId editorId;
private Process process;
public bool IsRunning => process != null && !process.HasExited;
+ public bool IsDisposed { get; private set; }
public void Execute()
{
@@ -24,7 +26,7 @@ namespace GodotTools.Ides.MonoDevelop
string command;
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
string bundleId = BundleIds[editorId];
@@ -59,6 +61,8 @@ namespace GodotTools.Ides.MonoDevelop
if (command == null)
throw new FileNotFoundException();
+ LaunchTime = DateTime.Now;
+
if (newWindow)
{
process = Process.Start(new ProcessStartInfo
@@ -81,19 +85,25 @@ namespace GodotTools.Ides.MonoDevelop
public Instance(string solutionFile, EditorId editorId)
{
- if (editorId == EditorId.VisualStudioForMac && !OS.IsOSX)
+ if (editorId == EditorId.VisualStudioForMac && !OS.IsMacOS)
throw new InvalidOperationException($"{nameof(EditorId.VisualStudioForMac)} not supported on this platform");
this.solutionFile = solutionFile;
this.editorId = editorId;
}
+ public void Dispose()
+ {
+ IsDisposed = true;
+ process?.Dispose();
+ }
+
private static readonly IReadOnlyDictionary<EditorId, string> ExecutableNames;
private static readonly IReadOnlyDictionary<EditorId, string> BundleIds;
static Instance()
{
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
ExecutableNames = new Dictionary<EditorId, string>
{
@@ -118,7 +128,7 @@ namespace GodotTools.Ides.MonoDevelop
{EditorId.MonoDevelop, "MonoDevelop.exe"}
};
}
- else if (OS.IsUnixLike())
+ else if (OS.IsUnixLike)
{
ExecutableNames = new Dictionary<EditorId, string>
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs
index 9038333d38..94fc5da425 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs
@@ -11,6 +11,10 @@ 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
{
@@ -28,11 +32,11 @@ namespace GodotTools.Ides.Rider
{
return CollectRiderInfosWindows();
}
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
return CollectRiderInfosMac();
}
- if (OS.IsUnixLike())
+ if (OS.IsUnixLike)
{
return CollectAllRiderPathsLinux();
}
@@ -131,28 +135,45 @@ namespace GodotTools.Ides.Rider
if (OS.IsWindows)
{
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
- return Path.Combine(localAppData, @"JetBrains\Toolbox\apps\Rider");
+ return GetToolboxRiderRootPath(localAppData);
}
- if (OS.IsOSX)
+ if (OS.IsMacOS)
{
var home = Environment.GetEnvironmentVariable("HOME");
- if (!string.IsNullOrEmpty(home))
- {
- return Path.Combine(home, @"Library/Application Support/JetBrains/Toolbox/apps/Rider");
- }
+ if (string.IsNullOrEmpty(home))
+ return string.Empty;
+ var localAppData = Path.Combine(home, @"Library/Application Support");
+ return GetToolboxRiderRootPath(localAppData);
}
- if (OS.IsUnixLike())
+ if (OS.IsUnixLike)
{
var home = Environment.GetEnvironmentVariable("HOME");
- if (!string.IsNullOrEmpty(home))
- {
- return Path.Combine(home, @".local/share/JetBrains/Toolbox/apps/Rider");
- }
+ if (string.IsNullOrEmpty(home))
+ return string.Empty;
+ var localAppData = Path.Combine(home, @".local/share");
+ return GetToolboxRiderRootPath(localAppData);
}
- throw new Exception("Unexpected OS.");
+ 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)
@@ -188,29 +209,38 @@ namespace GodotTools.Ides.Rider
private static string GetRelativePathToBuildTxt()
{
- if (OS.IsWindows || OS.IsUnixLike())
+ if (OS.IsWindows || OS.IsUnixLike)
return "../../build.txt";
- if (OS.IsOSX)
+ if (OS.IsMacOS)
return "Contents/Resources/build.txt";
throw new Exception("Unknown OS.");
}
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))
{
- if (key == null) return;
- foreach (var subkeyName in key.GetSubKeyNames().Where(a => a.Contains("Rider")))
+ CollectPathsFromRegistry(installPaths, key);
+ }
+ }
+
+ 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))
{
- 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);
- }
+ 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);
}
}
}
@@ -226,8 +256,8 @@ namespace GodotTools.Ides.Rider
{
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");
+ // 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);
@@ -255,14 +285,14 @@ namespace GodotTools.Ides.Rider
}
}
- // 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));
+ // 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);
+ // do not write to Debug.Log, just log it.
+ Logger.Warn($"Failed to get RiderPath from {channelDir}", e);
}
return new string[0];
@@ -289,6 +319,27 @@ namespace GodotTools.Ides.Rider
#pragma warning disable 0649
[Serializable]
+ class SettingsJson
+ {
+ public string install_location;
+
+ [CanBeNull]
+ 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;
@@ -372,7 +423,6 @@ namespace GodotTools.Ides.Rider
[Serializable]
class ActiveApplication
{
- // ReSharper disable once InconsistentNaming
public List<string> builds;
}
@@ -380,6 +430,7 @@ namespace GodotTools.Ides.Rider
public struct RiderInfo
{
+ // ReSharper disable once NotAccessedField.Global
public bool IsToolbox;
public string Presentation;
public Version BuildNumber;
diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
index 558a242bf9..ed25cdaa63 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs
@@ -9,13 +9,13 @@ namespace GodotTools.Ides.Rider
{
public static class RiderPathManager
{
- private static readonly string editorPathSettingName = "mono/editor/editor_path_optional";
+ public static readonly string EditorPathSettingName = "mono/editor/editor_path_optional";
private static string GetRiderPathFromSettings()
{
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- if (editorSettings.HasSetting(editorPathSettingName))
- return (string)editorSettings.GetSetting(editorPathSettingName);
+ if (editorSettings.HasSetting(EditorPathSettingName))
+ return (string)editorSettings.GetSetting(EditorPathSettingName);
return null;
}
@@ -25,22 +25,22 @@ namespace GodotTools.Ides.Rider
var editor = (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor");
if (editor == ExternalEditorId.Rider)
{
- if (!editorSettings.HasSetting(editorPathSettingName))
+ if (!editorSettings.HasSetting(EditorPathSettingName))
{
- Globals.EditorDef(editorPathSettingName, "Optional");
+ Globals.EditorDef(EditorPathSettingName, "Optional");
editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
{
["type"] = Variant.Type.String,
- ["name"] = editorPathSettingName,
+ ["name"] = EditorPathSettingName,
["hint"] = PropertyHint.File,
["hint_string"] = ""
});
}
- var riderPath = (string)editorSettings.GetSetting(editorPathSettingName);
+ var riderPath = (string)editorSettings.GetSetting(EditorPathSettingName);
if (IsRiderAndExists(riderPath))
{
- Globals.EditorDef(editorPathSettingName, riderPath);
+ Globals.EditorDef(EditorPathSettingName, riderPath);
return;
}
@@ -50,17 +50,20 @@ namespace GodotTools.Ides.Rider
return;
var newPath = paths.Last().Path;
- Globals.EditorDef(editorPathSettingName, newPath);
- editorSettings.SetSetting(editorPathSettingName, newPath);
+ Globals.EditorDef(EditorPathSettingName, newPath);
+ editorSettings.SetSetting(EditorPathSettingName, newPath);
}
}
- private static bool IsRider(string path)
+ public static bool IsExternalEditorSetToRider(EditorSettings editorSettings)
+ {
+ return editorSettings.HasSetting(EditorPathSettingName) && IsRider((string) editorSettings.GetSetting(EditorPathSettingName));
+ }
+
+ public static bool IsRider(string path)
{
if (string.IsNullOrEmpty(path))
- {
return false;
- }
var fileInfo = new FileInfo(path);
var filename = fileInfo.Name.ToLowerInvariant();
@@ -81,8 +84,8 @@ namespace GodotTools.Ides.Rider
return null;
var newPath = paths.Last().Path;
- editorSettings.SetSetting(editorPathSettingName, newPath);
- Globals.EditorDef(editorPathSettingName, newPath);
+ editorSettings.SetSetting(EditorPathSettingName, newPath);
+ Globals.EditorDef(EditorPathSettingName, newPath);
return newPath;
}
@@ -101,7 +104,7 @@ namespace GodotTools.Ides.Rider
if (line >= 0)
{
args.Add("--line");
- args.Add(line.ToString());
+ args.Add((line + 1).ToString()); // https://github.com/JetBrains/godot-support/issues/61
}
args.Add(scriptPath);
try
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
index de361ba844..77370090ec 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs
@@ -1,14 +1,13 @@
-using System;
using System.Runtime.CompilerServices;
using Godot;
-using Godot.Collections;
+using GodotTools.IdeMessaging.Requests;
namespace GodotTools.Internals
{
public static class Internal
{
public const string CSharpLanguageType = "CSharpScript";
- public const string CSharpLanguageExtension = "cs";
+ public const string CSharpLanguageExtension = ".cs";
public static string UpdateApiAssembliesFromPrebuilt(string config) =>
internal_UpdateApiAssembliesFromPrebuilt(config);
@@ -34,16 +33,13 @@ namespace GodotTools.Internals
public static void ReloadAssemblies(bool softReload) => internal_ReloadAssemblies(softReload);
- public static void ScriptEditorDebuggerReloadScripts() => internal_ScriptEditorDebuggerReloadScripts();
+ public static void EditorDebuggerNodeReloadScripts() => internal_EditorDebuggerNodeReloadScripts();
public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) =>
internal_ScriptEditorEdit(resource, line, col, grabFocus);
public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen();
- public static Dictionary<string, object> GetScriptsMetadataOrNothing() =>
- internal_GetScriptsMetadataOrNothing(typeof(Dictionary<string, object>));
-
public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot();
public static void EditorRunPlay() => internal_EditorRunPlay();
@@ -52,6 +48,9 @@ namespace GodotTools.Internals
public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts();
+ public static string[] CodeCompletionRequest(CodeCompletionRequest.CompletionKind kind, string scriptFile) =>
+ internal_CodeCompletionRequest((int)kind, scriptFile);
+
#region Internal
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -88,7 +87,7 @@ namespace GodotTools.Internals
private static extern void internal_ReloadAssemblies(bool softReload);
[MethodImpl(MethodImplOptions.InternalCall)]
- private static extern void internal_ScriptEditorDebuggerReloadScripts();
+ private static extern void internal_EditorDebuggerNodeReloadScripts();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus);
@@ -97,9 +96,6 @@ namespace GodotTools.Internals
private static extern void internal_EditorNodeShowScriptScreen();
[MethodImpl(MethodImplOptions.InternalCall)]
- private static extern Dictionary<string, object> internal_GetScriptsMetadataOrNothing(Type dictType);
-
- [MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoWindowsInstallRoot();
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -111,6 +107,9 @@ namespace GodotTools.Internals
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_ScriptEditorDebugger_ReloadScripts();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string[] internal_CodeCompletionRequest(int kind, string scriptFile);
+
#endregion
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
deleted file mode 100644
index 7fb087467f..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using Godot;
-using Godot.Collections;
-
-namespace GodotTools.Internals
-{
- public static class ScriptClassParser
- {
- public class ClassDecl
- {
- public string Name { get; }
- public string Namespace { get; }
- public bool Nested { get; }
- public int BaseCount { get; }
-
- public ClassDecl(string name, string @namespace, bool nested, int baseCount)
- {
- Name = name;
- Namespace = @namespace;
- Nested = nested;
- BaseCount = baseCount;
- }
- }
-
- [MethodImpl(MethodImplOptions.InternalCall)]
- private static extern Error internal_ParseFile(string filePath, Array<Dictionary> classes, out string errorStr);
-
- public static Error ParseFile(string filePath, out IEnumerable<ClassDecl> classes, out string errorStr)
- {
- var classesArray = new Array<Dictionary>();
- var error = internal_ParseFile(filePath, classesArray, out errorStr);
- if (error != Error.Ok)
- {
- classes = null;
- return error;
- }
-
- var classesList = new List<ClassDecl>();
-
- foreach (var classDeclDict in classesArray)
- {
- classesList.Add(new ClassDecl(
- (string)classDeclDict["name"],
- (string)classDeclDict["namespace"],
- (bool)classDeclDict["nested"],
- (int)classDeclDict["base_count"]
- ));
- }
-
- classes = classesList;
-
- return Error.Ok;
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs b/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs
new file mode 100644
index 0000000000..820d0c0b83
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/PlaySettings.cs
@@ -0,0 +1,19 @@
+namespace GodotTools
+{
+ public struct PlaySettings
+ {
+ public bool HasDebugger { get; }
+ public string DebuggerHost { get; }
+ public int DebuggerPort { get; }
+
+ public bool BuildBeforePlaying { get; }
+
+ public PlaySettings(string debuggerHost, int debuggerPort, bool buildBeforePlaying)
+ {
+ HasDebugger = true;
+ DebuggerHost = debuggerHost;
+ DebuggerPort = debuggerPort;
+ BuildBeforePlaying = buildBeforePlaying;
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs
deleted file mode 100644
index f5fe85c722..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle("GodotTools")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("Godot Engine contributors")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-
-[assembly: AssemblyVersion("1.0.*")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs
new file mode 100644
index 0000000000..c6724ccaf7
--- /dev/null
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs
@@ -0,0 +1,48 @@
+using System;
+using System.IO;
+using Godot;
+using GodotTools.Core;
+using JetBrains.Annotations;
+
+namespace GodotTools.Utils
+{
+ public static class FsPathUtils
+ {
+ private static readonly string ResourcePath = ProjectSettings.GlobalizePath("res://");
+
+ private static bool PathStartsWithAlreadyNorm(this string childPath, string parentPath)
+ {
+ // This won't work for Linux/macOS case insensitive file systems, but it's enough for our current problems
+ bool caseSensitive = !OS.IsWindows;
+
+ string parentPathNorm = parentPath.NormalizePath() + Path.DirectorySeparatorChar;
+ string childPathNorm = childPath.NormalizePath() + Path.DirectorySeparatorChar;
+
+ return childPathNorm.StartsWith(parentPathNorm,
+ caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
+ }
+
+ public static bool PathStartsWith(this string childPath, string parentPath)
+ {
+ string childPathNorm = childPath.NormalizePath() + Path.DirectorySeparatorChar;
+ string parentPathNorm = parentPath.NormalizePath() + Path.DirectorySeparatorChar;
+
+ return childPathNorm.PathStartsWithAlreadyNorm(parentPathNorm);
+ }
+
+ [CanBeNull]
+ public static string LocalizePathWithCaseChecked(string path)
+ {
+ string pathNorm = path.NormalizePath() + Path.DirectorySeparatorChar;
+ string resourcePathNorm = ResourcePath.NormalizePath() + Path.DirectorySeparatorChar;
+
+ if (!pathNorm.PathStartsWithAlreadyNorm(resourcePathNorm))
+ return null;
+
+ string result = "res://" + pathNorm.Substring(resourcePathNorm.Length);
+
+ // Remove the last separator we added
+ return result.Substring(0, result.Length - 1);
+ }
+ }
+}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
index 279e67b3eb..e745966435 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs
@@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
+using JetBrains.Annotations;
namespace GodotTools.Utils
{
@@ -20,36 +21,45 @@ namespace GodotTools.Utils
public static class Names
{
public const string Windows = "Windows";
- public const string OSX = "OSX";
- public const string X11 = "X11";
+ public const string MacOS = "macOS";
+ public const string Linux = "Linux";
+ public const string FreeBSD = "FreeBSD";
+ public const string NetBSD = "NetBSD";
+ public const string BSD = "BSD";
public const string Server = "Server";
public const string UWP = "UWP";
public const string Haiku = "Haiku";
public const string Android = "Android";
+ public const string iOS = "iOS";
public const string HTML5 = "HTML5";
}
public static class Platforms
{
public const string Windows = "windows";
- public const string OSX = "osx";
- public const string X11 = "x11";
+ public const string MacOS = "osx";
+ public const string LinuxBSD = "linuxbsd";
public const string Server = "server";
public const string UWP = "uwp";
public const string Haiku = "haiku";
public const string Android = "android";
+ public const string iOS = "iphone";
public const string HTML5 = "javascript";
}
public static readonly Dictionary<string, string> PlatformNameMap = new Dictionary<string, string>
{
[Names.Windows] = Platforms.Windows,
- [Names.OSX] = Platforms.OSX,
- [Names.X11] = Platforms.X11,
+ [Names.MacOS] = Platforms.MacOS,
+ [Names.Linux] = Platforms.LinuxBSD,
+ [Names.FreeBSD] = Platforms.LinuxBSD,
+ [Names.NetBSD] = Platforms.LinuxBSD,
+ [Names.BSD] = Platforms.LinuxBSD,
[Names.Server] = Platforms.Server,
[Names.UWP] = Platforms.UWP,
[Names.Haiku] = Platforms.Haiku,
[Names.Android] = Platforms.Android,
+ [Names.iOS] = Platforms.iOS,
[Names.HTML5] = Platforms.HTML5
};
@@ -58,45 +68,48 @@ namespace GodotTools.Utils
return name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
+ private static bool IsAnyOS(IEnumerable<string> names)
+ {
+ return names.Any(p => p.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase));
+ }
+
+ private static readonly IEnumerable<string> LinuxBSDPlatforms =
+ new[] {Names.Linux, Names.FreeBSD, Names.NetBSD, Names.BSD};
+
+ private static readonly IEnumerable<string> UnixLikePlatforms =
+ new[] {Names.MacOS, Names.Server, Names.Haiku, Names.Android, Names.iOS}
+ .Concat(LinuxBSDPlatforms).ToArray();
+
private static readonly Lazy<bool> _isWindows = new Lazy<bool>(() => IsOS(Names.Windows));
- private static readonly Lazy<bool> _isOSX = new Lazy<bool>(() => IsOS(Names.OSX));
- private static readonly Lazy<bool> _isX11 = new Lazy<bool>(() => IsOS(Names.X11));
+ private static readonly Lazy<bool> _isMacOS = new Lazy<bool>(() => IsOS(Names.MacOS));
+ private static readonly Lazy<bool> _isLinuxBSD = new Lazy<bool>(() => IsAnyOS(LinuxBSDPlatforms));
private static readonly Lazy<bool> _isServer = new Lazy<bool>(() => IsOS(Names.Server));
private static readonly Lazy<bool> _isUWP = new Lazy<bool>(() => IsOS(Names.UWP));
private static readonly Lazy<bool> _isHaiku = new Lazy<bool>(() => IsOS(Names.Haiku));
private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android));
+ private static readonly Lazy<bool> _isiOS = new Lazy<bool>(() => IsOS(Names.iOS));
private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5));
+ private static readonly Lazy<bool> _isUnixLike = new Lazy<bool>(() => IsAnyOS(UnixLikePlatforms));
public static bool IsWindows => _isWindows.Value || IsUWP;
- public static bool IsOSX => _isOSX.Value;
- public static bool IsX11 => _isX11.Value;
+ public static bool IsMacOS => _isMacOS.Value;
+ public static bool IsLinuxBSD => _isLinuxBSD.Value;
public static bool IsServer => _isServer.Value;
public static bool IsUWP => _isUWP.Value;
public static bool IsHaiku => _isHaiku.Value;
public static bool IsAndroid => _isAndroid.Value;
+ public static bool IsiOS => _isiOS.Value;
public static bool IsHTML5 => _isHTML5.Value;
-
- private static bool? _isUnixCache;
- private static readonly string[] UnixLikePlatforms = { Names.OSX, Names.X11, Names.Server, Names.Haiku, Names.Android };
-
- public static bool IsUnixLike()
- {
- if (_isUnixCache.HasValue)
- return _isUnixCache.Value;
-
- string osName = GetPlatformName();
- _isUnixCache = UnixLikePlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
- return _isUnixCache.Value;
- }
+ public static bool IsUnixLike => _isUnixLike.Value;
public static char PathSep => IsWindows ? ';' : ':';
- public static string PathWhich(string name)
+ public static string PathWhich([NotNull] string name)
{
return IsWindows ? PathWhichWindows(name) : PathWhichUnix(name);
}
- private static string PathWhichWindows(string name)
+ private static string PathWhichWindows([NotNull] string name)
{
string[] windowsExts = Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? new string[] { };
string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
@@ -115,13 +128,13 @@ namespace GodotTools.Utils
return searchDirs.Select(dir => Path.Combine(dir, name)).FirstOrDefault(File.Exists);
return (from dir in searchDirs
- select Path.Combine(dir, name)
+ select Path.Combine(dir, name)
into path
- from ext in windowsExts
- select path + ext).FirstOrDefault(File.Exists);
+ from ext in windowsExts
+ select path + ext).FirstOrDefault(File.Exists);
}
- private static string PathWhichUnix(string name)
+ private static string PathWhichUnix([NotNull] string name)
{
string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
@@ -163,5 +176,33 @@ namespace GodotTools.Utils
User32Dll.AllowSetForegroundWindow(process.Id); // allows application to focus itself
}
}
+
+ public static int ExecuteCommand(string command, IEnumerable<string> arguments)
+ {
+ // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead
+ string CmdLineArgsToString(IEnumerable<string> args)
+ {
+ // Not perfect, but as long as we are careful...
+ return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
+ }
+
+ var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments));
+
+ Console.WriteLine($"Executing: \"{startInfo.FileName}\" {startInfo.Arguments}");
+
+ // Print the output
+ startInfo.RedirectStandardOutput = false;
+ startInfo.RedirectStandardError = false;
+
+ startInfo.UseShellExecute = false;
+
+ using (var process = new Process {StartInfo = startInfo})
+ {
+ process.Start();
+ process.WaitForExit();
+
+ return process.ExitCode;
+ }
+ }
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/packages.config b/modules/mono/editor/GodotTools/GodotTools/packages.config
deleted file mode 100644
index dd3de2865a..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools/packages.config
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
- <package id="JetBrains.Annotations" version="2019.1.3" targetFramework="net45" />
- <package id="Newtonsoft.Json" version="12.0.3" targetFramework="net45" />
-</packages>
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 9beadb1778..620b031ddb 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -32,20 +32,20 @@
#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
-#include "core/engine.h"
-#include "core/global_constants.h"
+#include "core/config/engine.h"
+#include "core/core_constants.h"
#include "core/io/compression.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
#include "core/os/os.h"
-#include "core/ucaps.h"
+#include "core/string/ucaps.h"
+#include "main/main.h"
#include "../glue/cs_glue_version.gen.h"
#include "../godotsharp_defs.h"
#include "../mono_gd/gd_mono_marshal.h"
#include "../utils/path_utils.h"
#include "../utils/string_utils.h"
-#include "csharp_project.h"
#define CS_INDENT " " // 4 whitespaces
@@ -62,10 +62,8 @@
#define OPEN_BLOCK_L2 INDENT2 OPEN_BLOCK INDENT3
#define OPEN_BLOCK_L3 INDENT3 OPEN_BLOCK INDENT4
-#define OPEN_BLOCK_L4 INDENT4 OPEN_BLOCK INDENT5
#define CLOSE_BLOCK_L2 INDENT2 CLOSE_BLOCK
#define CLOSE_BLOCK_L3 INDENT3 CLOSE_BLOCK
-#define CLOSE_BLOCK_L4 INDENT4 CLOSE_BLOCK
#define CS_FIELD_MEMORYOWN "memoryOwn"
#define CS_PARAM_METHODBIND "method"
@@ -76,7 +74,7 @@
#define GLUE_HEADER_FILE "glue_header.h"
#define ICALL_PREFIX "godot_icall_"
#define SINGLETON_ICALL_SUFFIX "_get_singleton"
-#define ICALL_GET_METHODBIND ICALL_PREFIX "Object_ClassDB_get_method"
+#define ICALL_GET_METHODBIND "__ClassDB_get_method"
#define C_LOCAL_RET "ret"
#define C_LOCAL_VARARG_RET "vararg_ret"
@@ -95,13 +93,16 @@
#define C_METHOD_MONOSTR_FROM_GODOT C_NS_MONOMARSHAL "::mono_string_from_godot"
#define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type
#define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array"
+#define C_METHOD_MANAGED_TO_CALLABLE C_NS_MONOMARSHAL "::managed_to_callable"
+#define C_METHOD_MANAGED_FROM_CALLABLE C_NS_MONOMARSHAL "::callable_to_managed"
+#define C_METHOD_MANAGED_TO_SIGNAL C_NS_MONOMARSHAL "::signal_info_to_callable"
+#define C_METHOD_MANAGED_FROM_SIGNAL C_NS_MONOMARSHAL "::callable_to_signal_info"
-#define BINDINGS_GENERATOR_VERSION UINT32_C(11)
+#define BINDINGS_GENERATOR_VERSION UINT32_C(13)
const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN("\t%0 %1_in = %1;\n");
static String fix_doc_description(const String &p_bbcode) {
-
// This seems to be the correct way to do this. It's the same EditorHelp does.
return p_bbcode.dedent()
@@ -111,7 +112,6 @@ static String fix_doc_description(const String &p_bbcode) {
}
static String snake_to_pascal_case(const String &p_identifier, bool p_input_is_upper = false) {
-
String ret;
Vector<String> parts = p_identifier.split("_", true);
@@ -121,8 +121,9 @@ static String snake_to_pascal_case(const String &p_identifier, bool p_input_is_u
if (part.length()) {
part[0] = _find_upper(part[0]);
if (p_input_is_upper) {
- for (int j = 1; j < part.length(); j++)
+ for (int j = 1; j < part.length(); j++) {
part[j] = _find_lower(part[j]);
+ }
}
ret += part;
} else {
@@ -144,7 +145,6 @@ static String snake_to_pascal_case(const String &p_identifier, bool p_input_is_u
}
static String snake_to_camel_case(const String &p_identifier, bool p_input_is_upper = false) {
-
String ret;
Vector<String> parts = p_identifier.split("_", true);
@@ -156,8 +156,9 @@ static String snake_to_camel_case(const String &p_identifier, bool p_input_is_up
part[0] = _find_upper(part[0]);
}
if (p_input_is_upper) {
- for (int j = i != 0 ? 1 : 0; j < part.length(); j++)
+ for (int j = i != 0 ? 1 : 0; j < part.length(); j++) {
part[j] = _find_lower(part[j]);
+ }
}
ret += part;
} else {
@@ -179,13 +180,13 @@ static String snake_to_camel_case(const String &p_identifier, bool p_input_is_up
}
String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype) {
-
// Based on the version in EditorHelp
- if (p_bbcode.empty())
+ if (p_bbcode.is_empty()) {
return String();
+ }
- DocData *doc = EditorHelp::get_doc_data();
+ DocTools *doc = EditorHelp::get_doc_data();
String bbcode = p_bbcode;
@@ -200,8 +201,9 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
while (pos < bbcode.length()) {
int brk_pos = bbcode.find("[", pos);
- if (brk_pos < 0)
+ if (brk_pos < 0) {
brk_pos = bbcode.length();
+ }
if (brk_pos > pos) {
String text = bbcode.substr(pos, brk_pos - pos);
@@ -210,19 +212,22 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
} else {
Vector<String> lines = text.split("\n");
for (int i = 0; i < lines.size(); i++) {
- if (i != 0)
+ if (i != 0) {
xml_output.append("<para>");
+ }
xml_output.append(lines[i].xml_escape());
- if (i != lines.size() - 1)
+ if (i != lines.size() - 1) {
xml_output.append("</para>\n");
+ }
}
}
}
- if (brk_pos == bbcode.length())
+ if (brk_pos == bbcode.length()) {
break; // nothing else to add
+ }
int brk_end = bbcode.find("]", brk_pos + 1);
@@ -233,13 +238,15 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
} else {
Vector<String> lines = text.split("\n");
for (int i = 0; i < lines.size(); i++) {
- if (i != 0)
+ if (i != 0) {
xml_output.append("<para>");
+ }
xml_output.append(lines[i].xml_escape());
- if (i != lines.size() - 1)
+ if (i != lines.size() - 1) {
xml_output.append("</para>\n");
+ }
}
}
@@ -278,7 +285,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
Vector<String> link_target_parts = link_target.split(".");
if (link_target_parts.size() <= 0 || link_target_parts.size() > 2) {
- ERR_PRINTS("Invalid reference format: '" + tag + "'.");
+ ERR_PRINT("Invalid reference format: '" + tag + "'.");
xml_output.append("<c>");
xml_output.append(tag);
@@ -359,7 +366,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
xml_output.append("</c>");
} else if (link_tag == "enum") {
StringName search_cname = !target_itype ? target_cname :
- StringName(target_itype->name + "." + (String)target_cname);
+ StringName(target_itype->name + "." + (String)target_cname);
const Map<StringName, TypeInterface>::Element *enum_match = enum_types.find(search_cname);
@@ -374,7 +381,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
xml_output.append(target_enum_itype.proxy_name); // Includes nesting class if any
xml_output.append("\"/>");
} else {
- ERR_PRINTS("Cannot resolve enum reference in documentation: '" + link_target + "'.");
+ ERR_PRINT("Cannot resolve enum reference in documentation: '" + link_target + "'.");
xml_output.append("<c>");
xml_output.append(link_target);
@@ -407,13 +414,14 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
xml_output.append("\"/>");
} else {
// Try to find as global enum constant
- const EnumInterface *target_ienum = NULL;
+ const EnumInterface *target_ienum = nullptr;
for (const List<EnumInterface>::Element *E = global_enums.front(); E; E = E->next()) {
target_ienum = &E->get();
target_iconst = find_constant_by_name(target_name, target_ienum->constants);
- if (target_iconst)
+ if (target_iconst) {
break;
+ }
}
if (target_iconst) {
@@ -423,7 +431,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
xml_output.append(target_iconst->proxy_name);
xml_output.append("\"/>");
} else {
- ERR_PRINTS("Cannot resolve global constant reference in documentation: '" + link_target + "'.");
+ ERR_PRINT("Cannot resolve global constant reference in documentation: '" + link_target + "'.");
xml_output.append("<c>");
xml_output.append(link_target);
@@ -445,13 +453,14 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
xml_output.append("\"/>");
} else {
// Try to find as enum constant in the current class
- const EnumInterface *target_ienum = NULL;
+ const EnumInterface *target_ienum = nullptr;
for (const List<EnumInterface>::Element *E = target_itype->enums.front(); E; E = E->next()) {
target_ienum = &E->get();
target_iconst = find_constant_by_name(target_name, target_ienum->constants);
- if (target_iconst)
+ if (target_iconst) {
break;
+ }
}
if (target_iconst) {
@@ -463,7 +472,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
xml_output.append(target_iconst->proxy_name);
xml_output.append("\"/>");
} else {
- ERR_PRINTS("Cannot resolve constant reference in documentation: '" + link_target + "'.");
+ ERR_PRINT("Cannot resolve constant reference in documentation: '" + link_target + "'.");
xml_output.append("<c>");
xml_output.append(link_target);
@@ -503,24 +512,24 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
xml_output.append("<c>");
xml_output.append(tag);
xml_output.append("</c>");
- } else if (tag == "PoolByteArray") {
- xml_output.append("<see cref=\"byte\"/>");
- } else if (tag == "PoolIntArray") {
- xml_output.append("<see cref=\"int\"/>");
- } else if (tag == "PoolRealArray") {
-#ifdef REAL_T_IS_DOUBLE
- xml_output.append("<see cref=\"double\"/>");
-#else
- xml_output.append("<see cref=\"float\"/>");
-#endif
- } else if (tag == "PoolStringArray") {
- xml_output.append("<see cref=\"string\"/>");
- } else if (tag == "PoolVector2Array") {
- xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector2\"/>");
- } else if (tag == "PoolVector3Array") {
- xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector3\"/>");
- } else if (tag == "PoolColorArray") {
- xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Color\"/>");
+ } else if (tag == "PackedByteArray") {
+ xml_output.append("<see cref=\"T:byte[]\"/>");
+ } else if (tag == "PackedInt32Array") {
+ xml_output.append("<see cref=\"T:int[]\"/>");
+ } else if (tag == "PackedInt64Array") {
+ xml_output.append("<see cref=\"T:long[]\"/>");
+ } else if (tag == "PackedFloat32Array") {
+ xml_output.append("<see cref=\"T:float[]\"/>");
+ } else if (tag == "PackedFloat64Array") {
+ xml_output.append("<see cref=\"T:double[]\"/>");
+ } else if (tag == "PackedStringArray") {
+ xml_output.append("<see cref=\"T:string[]\"/>");
+ } else if (tag == "PackedVector2Array") {
+ xml_output.append("<see cref=\"T:" BINDINGS_NAMESPACE ".Vector2[]\"/>");
+ } else if (tag == "PackedVector3Array") {
+ xml_output.append("<see cref=\"T:" BINDINGS_NAMESPACE ".Vector3[]\"/>");
+ } else if (tag == "PackedColorArray") {
+ xml_output.append("<see cref=\"T:" BINDINGS_NAMESPACE ".Color[]\"/>");
} else {
const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag));
@@ -533,7 +542,7 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
xml_output.append(target_itype->proxy_name);
xml_output.append("\"/>");
} else {
- ERR_PRINTS("Cannot resolve type reference in documentation: '" + tag + "'.");
+ ERR_PRINT("Cannot resolve type reference in documentation: '" + tag + "'.");
xml_output.append("<c>");
xml_output.append(tag);
@@ -562,8 +571,12 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
code_tag = true;
pos = brk_end + 1;
tag_stack.push_front(tag);
+ } else if (tag == "kbd") {
+ // keyboard combinations are not supported in xml comments
+ pos = brk_end + 1;
+ tag_stack.push_front(tag);
} else if (tag == "center") {
- // center is alignment not supported in xml comments
+ // center alignment is not supported in xml comments
pos = brk_end + 1;
tag_stack.push_front(tag);
} else if (tag == "br") {
@@ -579,8 +592,9 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
tag_stack.push_front(tag);
} else if (tag == "url") {
int end = bbcode.find("[", brk_end);
- if (end == -1)
+ if (end == -1) {
end = bbcode.length();
+ }
String url = bbcode.substr(brk_end + 1, end - brk_end - 1);
xml_output.append("<a href=\"");
xml_output.append(url);
@@ -599,8 +613,9 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
tag_stack.push_front("url");
} else if (tag == "img") {
int end = bbcode.find("[", brk_end);
- if (end == -1)
+ if (end == -1) {
end = bbcode.length();
+ }
String image = bbcode.substr(brk_end + 1, end - brk_end - 1);
// Not supported. Just append the bbcode.
@@ -630,15 +645,15 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf
}
int BindingsGenerator::_determine_enum_prefix(const EnumInterface &p_ienum) {
-
- CRASH_COND(p_ienum.constants.empty());
+ CRASH_COND(p_ienum.constants.is_empty());
const ConstantInterface &front_iconstant = p_ienum.constants.front()->get();
Vector<String> front_parts = front_iconstant.name.split("_", /* p_allow_empty: */ true);
int candidate_len = front_parts.size() - 1;
- if (candidate_len == 0)
+ if (candidate_len == 0) {
return 0;
+ }
for (const List<ConstantInterface>::Element *E = p_ienum.constants.front()->next(); E; E = E->next()) {
const ConstantInterface &iconstant = E->get();
@@ -650,21 +665,22 @@ int BindingsGenerator::_determine_enum_prefix(const EnumInterface &p_ienum) {
if (front_parts[i] != parts[i]) {
// HARDCODED: Some Flag enums have the prefix 'FLAG_' for everything except 'FLAGS_DEFAULT' (same for 'METHOD_FLAG_' and'METHOD_FLAGS_DEFAULT').
bool hardcoded_exc = (i == candidate_len - 1 && ((front_parts[i] == "FLAGS" && parts[i] == "FLAG") || (front_parts[i] == "FLAG" && parts[i] == "FLAGS")));
- if (!hardcoded_exc)
+ if (!hardcoded_exc) {
break;
+ }
}
}
candidate_len = i;
- if (candidate_len == 0)
+ if (candidate_len == 0) {
return 0;
+ }
}
return candidate_len;
}
void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumInterface &p_ienum, int p_prefix_length) {
-
if (p_prefix_length > 0) {
for (List<ConstantInterface>::Element *E = p_ienum.constants.front(); E; E = E->next()) {
int curr_prefix_length = p_prefix_length;
@@ -675,22 +691,25 @@ void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumI
Vector<String> parts = constant_name.split("_", /* p_allow_empty: */ true);
- if (parts.size() <= curr_prefix_length)
+ if (parts.size() <= curr_prefix_length) {
continue;
+ }
if (parts[curr_prefix_length][0] >= '0' && parts[curr_prefix_length][0] <= '9') {
// The name of enum constants may begin with a numeric digit when strip from the enum prefix,
// so we make the prefix for this constant one word shorter in those cases.
for (curr_prefix_length = curr_prefix_length - 1; curr_prefix_length > 0; curr_prefix_length--) {
- if (parts[curr_prefix_length][0] < '0' || parts[curr_prefix_length][0] > '9')
+ if (parts[curr_prefix_length][0] < '0' || parts[curr_prefix_length][0] > '9') {
break;
+ }
}
}
constant_name = "";
for (int i = curr_prefix_length; i < parts.size(); i++) {
- if (i > curr_prefix_length)
+ if (i > curr_prefix_length) {
constant_name += "_";
+ }
constant_name += parts[i];
}
@@ -700,12 +719,12 @@ void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumI
}
void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) {
-
for (const List<MethodInterface>::Element *E = p_itype.methods.front(); E; E = E->next()) {
const MethodInterface &imethod = E->get();
- if (imethod.is_virtual)
+ if (imethod.is_virtual) {
continue;
+ }
const TypeInterface *return_type = _get_type_or_placeholder(imethod.return_type);
@@ -754,8 +773,9 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) {
List<InternalCall>::Element *match = method_icalls.find(im_icall);
if (match) {
- if (p_itype.api_type != ClassDB::API_EDITOR)
+ if (p_itype.api_type != ClassDB::API_EDITOR) {
match->get().editor_only = false;
+ }
method_icalls_map.insert(&E->get(), &match->get());
} else {
List<InternalCall>::Element *added = method_icalls.push_back(im_icall);
@@ -764,8 +784,73 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) {
}
}
-void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
+void BindingsGenerator::_generate_array_extensions(StringBuilder &p_output) {
+ p_output.append("using System;\n\n");
+ p_output.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
+ // The class where we put the extensions doesn't matter, so just use "GD".
+ p_output.append(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{");
+
+#define ARRAY_IS_EMPTY(m_type) \
+ p_output.append("\n" INDENT2 "/// <summary>\n"); \
+ p_output.append(INDENT2 "/// Returns true if this " #m_type " array is empty or doesn't exist.\n"); \
+ p_output.append(INDENT2 "/// </summary>\n"); \
+ p_output.append(INDENT2 "/// <param name=\"instance\">The " #m_type " array check.</param>\n"); \
+ p_output.append(INDENT2 "/// <returns>Whether or not the array is empty.</returns>\n"); \
+ p_output.append(INDENT2 "public static bool IsEmpty(this " #m_type "[] instance)\n"); \
+ p_output.append(INDENT2 OPEN_BLOCK); \
+ p_output.append(INDENT3 "return instance == null || instance.Length == 0;\n"); \
+ p_output.append(INDENT2 CLOSE_BLOCK);
+
+#define ARRAY_JOIN(m_type) \
+ p_output.append("\n" INDENT2 "/// <summary>\n"); \
+ p_output.append(INDENT2 "/// Converts this " #m_type " array to a string delimited by the given string.\n"); \
+ p_output.append(INDENT2 "/// </summary>\n"); \
+ p_output.append(INDENT2 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \
+ p_output.append(INDENT2 "/// <param name=\"delimiter\">The delimiter to use between items.</param>\n"); \
+ p_output.append(INDENT2 "/// <returns>A single string with all items.</returns>\n"); \
+ p_output.append(INDENT2 "public static string Join(this " #m_type "[] instance, string delimiter = \", \")\n"); \
+ p_output.append(INDENT2 OPEN_BLOCK); \
+ p_output.append(INDENT3 "return String.Join(delimiter, instance);\n"); \
+ p_output.append(INDENT2 CLOSE_BLOCK);
+
+#define ARRAY_STRINGIFY(m_type) \
+ p_output.append("\n" INDENT2 "/// <summary>\n"); \
+ p_output.append(INDENT2 "/// Converts this " #m_type " array to a string with brackets.\n"); \
+ p_output.append(INDENT2 "/// </summary>\n"); \
+ p_output.append(INDENT2 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \
+ p_output.append(INDENT2 "/// <returns>A single string with all items.</returns>\n"); \
+ p_output.append(INDENT2 "public static string Stringify(this " #m_type "[] instance)\n"); \
+ p_output.append(INDENT2 OPEN_BLOCK); \
+ p_output.append(INDENT3 "return \"[\" + instance.Join() + \"]\";\n"); \
+ p_output.append(INDENT2 CLOSE_BLOCK);
+
+#define ARRAY_ALL(m_type) \
+ ARRAY_IS_EMPTY(m_type) \
+ ARRAY_JOIN(m_type) \
+ ARRAY_STRINGIFY(m_type)
+
+ ARRAY_ALL(byte);
+ ARRAY_ALL(int);
+ ARRAY_ALL(long);
+ ARRAY_ALL(float);
+ ARRAY_ALL(double);
+ ARRAY_ALL(string);
+ ARRAY_ALL(Color);
+ ARRAY_ALL(Vector2);
+ ARRAY_ALL(Vector2i);
+ ARRAY_ALL(Vector3);
+ ARRAY_ALL(Vector3i);
+
+#undef ARRAY_ALL
+#undef ARRAY_IS_EMPTY
+#undef ARRAY_JOIN
+#undef ARRAY_STRINGIFY
+
+ p_output.append(INDENT1 CLOSE_BLOCK); // End of GD class.
+ p_output.append(CLOSE_BLOCK); // End of namespace.
+}
+void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
// Constants (in partial GD class)
p_output.append("\n#pragma warning disable CS1591 // Disable warning: "
@@ -778,7 +863,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
const ConstantInterface &iconstant = E->get();
if (iconstant.const_doc && iconstant.const_doc->description.size()) {
- String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL);
+ String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), nullptr);
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
if (summary_lines.size()) {
@@ -801,8 +886,9 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
p_output.append(";");
}
- if (!global_constants.empty())
+ if (!global_constants.is_empty()) {
p_output.append("\n");
+ }
p_output.append(INDENT1 CLOSE_BLOCK); // end of GD class
@@ -811,7 +897,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
for (List<EnumInterface>::Element *E = global_enums.front(); E; E = E->next()) {
const EnumInterface &ienum = E->get();
- CRASH_COND(ienum.constants.empty());
+ CRASH_COND(ienum.constants.is_empty());
String enum_proxy_name = ienum.cname.operator String();
@@ -839,7 +925,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
const ConstantInterface &iconstant = F->get();
if (iconstant.const_doc && iconstant.const_doc->description.size()) {
- String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), NULL);
+ String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), nullptr);
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
if (summary_lines.size()) {
@@ -864,8 +950,9 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
p_output.append(INDENT1 CLOSE_BLOCK);
- if (enum_in_static_class)
+ if (enum_in_static_class) {
p_output.append(INDENT1 CLOSE_BLOCK);
+ }
}
p_output.append(CLOSE_BLOCK); // end of namespace
@@ -874,7 +961,6 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
}
Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {
-
ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@@ -900,8 +986,22 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {
_generate_global_constants(constants_source);
String output_file = path::join(base_gen_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_constants.cs");
Error save_err = _save_file(output_file, constants_source);
- if (save_err != OK)
+ if (save_err != OK) {
return save_err;
+ }
+
+ compile_items.push_back(output_file);
+ }
+
+ // Generate source file for array extensions
+ {
+ StringBuilder extensions_source;
+ _generate_array_extensions(extensions_source);
+ String output_file = path::join(base_gen_dir, BINDINGS_GLOBAL_SCOPE_CLASS "_extensions.cs");
+ Error save_err = _save_file(output_file, extensions_source);
+ if (save_err != OK) {
+ return save_err;
+ }
compile_items.push_back(output_file);
}
@@ -909,17 +1009,20 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {
for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) {
const TypeInterface &itype = E.get();
- if (itype.api_type == ClassDB::API_EDITOR)
+ if (itype.api_type == ClassDB::API_EDITOR) {
continue;
+ }
String output_file = path::join(godot_objects_gen_dir, itype.proxy_name + ".cs");
Error err = _generate_cs_type(itype, output_file);
- if (err == ERR_SKIP)
+ if (err == ERR_SKIP) {
continue;
+ }
- if (err != OK)
+ if (err != OK) {
return err;
+ }
compile_items.push_back(output_file);
}
@@ -932,7 +1035,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {
"using System.Runtime.CompilerServices;\n"
"\n");
cs_icalls_content.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK);
- cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK);
+ cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 "{");
cs_icalls_content.append(MEMBER_BEGIN "internal static ulong godot_api_hash = ");
cs_icalls_content.append(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n");
@@ -944,16 +1047,18 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {
#define ADD_INTERNAL_CALL(m_icall) \
if (!m_icall.editor_only) { \
cs_icalls_content.append(MEMBER_BEGIN "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \
- cs_icalls_content.append(INDENT2 "internal extern static "); \
+ cs_icalls_content.append(INDENT2 "internal static extern "); \
cs_icalls_content.append(m_icall.im_type_out + " "); \
cs_icalls_content.append(m_icall.name + "("); \
cs_icalls_content.append(m_icall.im_sig + ");\n"); \
}
- for (const List<InternalCall>::Element *E = core_custom_icalls.front(); E; E = E->next())
+ for (const List<InternalCall>::Element *E = core_custom_icalls.front(); E; E = E->next()) {
ADD_INTERNAL_CALL(E->get());
- for (const List<InternalCall>::Element *E = method_icalls.front(); E; E = E->next())
+ }
+ for (const List<InternalCall>::Element *E = method_icalls.front(); E; E = E->next()) {
ADD_INTERNAL_CALL(E->get());
+ }
#undef ADD_INTERNAL_CALL
@@ -962,8 +1067,9 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {
String internal_methods_file = path::join(base_gen_dir, BINDINGS_CLASS_NATIVECALLS ".cs");
Error err = _save_file(internal_methods_file, cs_icalls_content);
- if (err != OK)
+ if (err != OK) {
return err;
+ }
compile_items.push_back(internal_methods_file);
@@ -982,14 +1088,14 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) {
String includes_props_file = path::join(base_gen_dir, "GeneratedIncludes.props");
err = _save_file(includes_props_file, includes_props_content);
- if (err != OK)
+ if (err != OK) {
return err;
+ }
return OK;
}
Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) {
-
ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@@ -1012,17 +1118,20 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) {
for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) {
const TypeInterface &itype = E.get();
- if (itype.api_type != ClassDB::API_EDITOR)
+ if (itype.api_type != ClassDB::API_EDITOR) {
continue;
+ }
String output_file = path::join(godot_objects_gen_dir, itype.proxy_name + ".cs");
Error err = _generate_cs_type(itype, output_file);
- if (err == ERR_SKIP)
+ if (err == ERR_SKIP) {
continue;
+ }
- if (err != OK)
+ if (err != OK) {
return err;
+ }
compile_items.push_back(output_file);
}
@@ -1046,16 +1155,18 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) {
#define ADD_INTERNAL_CALL(m_icall) \
if (m_icall.editor_only) { \
cs_icalls_content.append(INDENT2 "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \
- cs_icalls_content.append(INDENT2 "internal extern static "); \
+ cs_icalls_content.append(INDENT2 "internal static extern "); \
cs_icalls_content.append(m_icall.im_type_out + " "); \
cs_icalls_content.append(m_icall.name + "("); \
cs_icalls_content.append(m_icall.im_sig + ");\n"); \
}
- for (const List<InternalCall>::Element *E = editor_custom_icalls.front(); E; E = E->next())
+ for (const List<InternalCall>::Element *E = editor_custom_icalls.front(); E; E = E->next()) {
ADD_INTERNAL_CALL(E->get());
- for (const List<InternalCall>::Element *E = method_icalls.front(); E; E = E->next())
+ }
+ for (const List<InternalCall>::Element *E = method_icalls.front(); E; E = E->next()) {
ADD_INTERNAL_CALL(E->get());
+ }
#undef ADD_INTERNAL_CALL
@@ -1064,8 +1175,9 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) {
String internal_methods_file = path::join(base_gen_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs");
Error err = _save_file(internal_methods_file, cs_icalls_content);
- if (err != OK)
+ if (err != OK) {
return err;
+ }
compile_items.push_back(internal_methods_file);
@@ -1084,14 +1196,14 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) {
String includes_props_file = path::join(base_gen_dir, "GeneratedIncludes.props");
err = _save_file(includes_props_file, includes_props_content);
- if (err != OK)
+ if (err != OK) {
return err;
+ }
return OK;
}
Error BindingsGenerator::generate_cs_api(const String &p_output_dir) {
-
ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
String output_dir = path::abspath(path::realpath(p_output_dir));
@@ -1139,7 +1251,6 @@ Error BindingsGenerator::generate_cs_api(const String &p_output_dir) {
// - Csc warning e.g.:
// ObjectType/LineEdit.cs(140,38): warning CS0108: 'LineEdit.FocusMode' hides inherited member 'Control.FocusMode'. Use the new keyword if hiding was intended.
Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const String &p_output_file) {
-
CRASH_COND(!itype.is_object_type);
bool is_derived_type = itype.base_name != StringName();
@@ -1149,7 +1260,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
CRASH_COND(itype.cname != name_cache.type_Object);
CRASH_COND(!itype.is_instantiable);
CRASH_COND(itype.api_type != ClassDB::API_CORE);
- CRASH_COND(itype.is_reference);
+ CRASH_COND(itype.is_ref_counted);
CRASH_COND(itype.is_singleton);
}
@@ -1207,100 +1318,96 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
output.append(obj_types[itype.base_name].proxy_name);
output.append("\n");
} else {
- ERR_PRINTS("Base type '" + itype.base_name.operator String() + "' does not exist, for class '" + itype.name + "'.");
+ ERR_PRINT("Base type '" + itype.base_name.operator String() + "' does not exist, for class '" + itype.name + "'.");
return ERR_INVALID_DATA;
}
}
output.append(INDENT1 "{");
- if (class_doc) {
-
- // Add constants
-
- for (const List<ConstantInterface>::Element *E = itype.constants.front(); E; E = E->next()) {
- const ConstantInterface &iconstant = E->get();
+ // Add constants
- if (iconstant.const_doc && iconstant.const_doc->description.size()) {
- String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);
- Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
+ for (const List<ConstantInterface>::Element *E = itype.constants.front(); E; E = E->next()) {
+ const ConstantInterface &iconstant = E->get();
- if (summary_lines.size()) {
- output.append(MEMBER_BEGIN "/// <summary>\n");
+ if (iconstant.const_doc && iconstant.const_doc->description.size()) {
+ String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
- for (int i = 0; i < summary_lines.size(); i++) {
- output.append(INDENT2 "/// ");
- output.append(summary_lines[i]);
- output.append("\n");
- }
+ if (summary_lines.size()) {
+ output.append(MEMBER_BEGIN "/// <summary>\n");
- output.append(INDENT2 "/// </summary>");
+ for (int i = 0; i < summary_lines.size(); i++) {
+ output.append(INDENT2 "/// ");
+ output.append(summary_lines[i]);
+ output.append("\n");
}
- }
- output.append(MEMBER_BEGIN "public const int ");
- output.append(iconstant.proxy_name);
- output.append(" = ");
- output.append(itos(iconstant.value));
- output.append(";");
+ output.append(INDENT2 "/// </summary>");
+ }
}
- if (itype.constants.size())
- output.append("\n");
+ output.append(MEMBER_BEGIN "public const int ");
+ output.append(iconstant.proxy_name);
+ output.append(" = ");
+ output.append(itos(iconstant.value));
+ output.append(";");
+ }
- // Add enums
+ if (itype.constants.size()) {
+ output.append("\n");
+ }
- for (const List<EnumInterface>::Element *E = itype.enums.front(); E; E = E->next()) {
- const EnumInterface &ienum = E->get();
+ // Add enums
- ERR_FAIL_COND_V(ienum.constants.empty(), ERR_BUG);
+ for (const List<EnumInterface>::Element *E = itype.enums.front(); E; E = E->next()) {
+ const EnumInterface &ienum = E->get();
- output.append(MEMBER_BEGIN "public enum ");
- output.append(ienum.cname.operator String());
- output.append(MEMBER_BEGIN OPEN_BLOCK);
+ ERR_FAIL_COND_V(ienum.constants.is_empty(), ERR_BUG);
- for (const List<ConstantInterface>::Element *F = ienum.constants.front(); F; F = F->next()) {
- const ConstantInterface &iconstant = F->get();
+ output.append(MEMBER_BEGIN "public enum ");
+ output.append(ienum.cname.operator String());
+ output.append(MEMBER_BEGIN OPEN_BLOCK);
- if (iconstant.const_doc && iconstant.const_doc->description.size()) {
- String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);
- Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
+ for (const List<ConstantInterface>::Element *F = ienum.constants.front(); F; F = F->next()) {
+ const ConstantInterface &iconstant = F->get();
- if (summary_lines.size()) {
- output.append(INDENT3 "/// <summary>\n");
+ if (iconstant.const_doc && iconstant.const_doc->description.size()) {
+ String xml_summary = bbcode_to_xml(fix_doc_description(iconstant.const_doc->description), &itype);
+ Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
- for (int i = 0; i < summary_lines.size(); i++) {
- output.append(INDENT3 "/// ");
- output.append(summary_lines[i]);
- output.append("\n");
- }
+ if (summary_lines.size()) {
+ output.append(INDENT3 "/// <summary>\n");
- output.append(INDENT3 "/// </summary>\n");
+ for (int i = 0; i < summary_lines.size(); i++) {
+ output.append(INDENT3 "/// ");
+ output.append(summary_lines[i]);
+ output.append("\n");
}
- }
- output.append(INDENT3);
- output.append(iconstant.proxy_name);
- output.append(" = ");
- output.append(itos(iconstant.value));
- output.append(F != ienum.constants.back() ? ",\n" : "\n");
+ output.append(INDENT3 "/// </summary>\n");
+ }
}
- output.append(INDENT2 CLOSE_BLOCK);
+ output.append(INDENT3);
+ output.append(iconstant.proxy_name);
+ output.append(" = ");
+ output.append(itos(iconstant.value));
+ output.append(F != ienum.constants.back() ? ",\n" : "\n");
}
- // Add properties
-
- for (const List<PropertyInterface>::Element *E = itype.properties.front(); E; E = E->next()) {
- const PropertyInterface &iprop = E->get();
- Error prop_err = _generate_cs_property(itype, iprop, output);
- ERR_FAIL_COND_V_MSG(prop_err != OK, prop_err,
- "Failed to generate property '" + iprop.cname.operator String() +
- "' for class '" + itype.name + "'.");
- }
+ output.append(INDENT2 CLOSE_BLOCK);
}
- // TODO: BINDINGS_NATIVE_NAME_FIELD should be StringName, once we support it in C#
+ // Add properties
+
+ for (const List<PropertyInterface>::Element *E = itype.properties.front(); E; E = E->next()) {
+ const PropertyInterface &iprop = E->get();
+ Error prop_err = _generate_cs_property(itype, iprop, output);
+ ERR_FAIL_COND_V_MSG(prop_err != OK, prop_err,
+ "Failed to generate property '" + iprop.cname.operator String() +
+ "' for class '" + itype.name + "'.");
+ }
if (itype.is_singleton) {
// Add the type name and the singleton pointer as static fields
@@ -1312,7 +1419,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
output.append(itype.proxy_name);
output.append(").Name);\n" INDENT4 "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n");
- output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \"");
+ output.append(MEMBER_BEGIN "private static StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");
output.append(itype.name);
output.append("\";\n");
@@ -1324,7 +1431,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
} else if (is_derived_type) {
// Add member fields
- output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \"");
+ output.append(MEMBER_BEGIN "private static StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");
output.append(itype.name);
output.append("\";\n");
@@ -1341,7 +1448,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
output.append(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = ");
output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS);
output.append("." + ctor_method);
- output.append("(this);\n" CLOSE_BLOCK_L2);
+ output.append("(this);\n" INDENT3 "_InitializeGodotScriptInstanceInternals();\n" CLOSE_BLOCK_L2);
} else {
// Hide the constructor
output.append(MEMBER_BEGIN "internal ");
@@ -1363,18 +1470,27 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
"Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'.");
}
+ for (const List<SignalInterface>::Element *E = itype.signals_.front(); E; E = E->next()) {
+ const SignalInterface &isignal = E->get();
+ Error method_err = _generate_cs_signal(itype, isignal, output);
+ ERR_FAIL_COND_V_MSG(method_err != OK, method_err,
+ "Failed to generate signal '" + isignal.name + "' for class '" + itype.name + "'.");
+ }
+
if (itype.is_singleton) {
InternalCall singleton_icall = InternalCall(itype.api_type, ICALL_PREFIX + itype.name + SINGLETON_ICALL_SUFFIX, "IntPtr");
- if (!find_icall_by_name(singleton_icall.name, custom_icalls))
+ if (!find_icall_by_name(singleton_icall.name, custom_icalls)) {
custom_icalls.push_back(singleton_icall);
+ }
}
if (is_derived_type && itype.is_instantiable) {
InternalCall ctor_icall = InternalCall(itype.api_type, ctor_method, "IntPtr", itype.proxy_name + " obj");
- if (!find_icall_by_name(ctor_icall.name, custom_icalls))
+ if (!find_icall_by_name(ctor_icall.name, custom_icalls)) {
custom_icalls.push_back(ctor_icall);
+ }
}
output.append(INDENT1 CLOSE_BLOCK /* class */
@@ -1388,14 +1504,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
}
Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output) {
-
const MethodInterface *setter = p_itype.find_method_by_name(p_iprop.setter);
// Search it in base types too
const TypeInterface *current_type = &p_itype;
while (!setter && current_type->base_name != StringName()) {
OrderedHashMap<StringName, TypeInterface>::Element base_match = obj_types.find(current_type->base_name);
- ERR_FAIL_COND_V(!base_match, ERR_BUG);
+ ERR_FAIL_COND_V_MSG(!base_match, ERR_BUG, "Type not found '" + current_type->base_name + "'. Inherited by '" + current_type->name + "'.");
current_type = &base_match.get();
setter = current_type->find_method_by_name(p_iprop.setter);
}
@@ -1406,7 +1521,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
current_type = &p_itype;
while (!getter && current_type->base_name != StringName()) {
OrderedHashMap<StringName, TypeInterface>::Element base_match = obj_types.find(current_type->base_name);
- ERR_FAIL_COND_V(!base_match, ERR_BUG);
+ ERR_FAIL_COND_V_MSG(!base_match, ERR_BUG, "Type not found '" + current_type->base_name + "'. Inherited by '" + current_type->name + "'.");
current_type = &base_match.get();
getter = current_type->find_method_by_name(p_iprop.getter);
}
@@ -1424,7 +1539,16 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
}
if (getter && setter) {
- ERR_FAIL_COND_V(getter->return_type.cname != setter->arguments.back()->get().type.cname, ERR_BUG);
+ const ArgumentInterface &setter_first_arg = setter->arguments.back()->get();
+ if (getter->return_type.cname != setter_first_arg.type.cname) {
+ // Special case for Node::set_name
+ bool whitelisted = getter->return_type.cname == name_cache.type_StringName &&
+ setter_first_arg.type.cname == name_cache.type_String;
+
+ ERR_FAIL_COND_V_MSG(!whitelisted, ERR_BUG,
+ "Return type from getter doesn't match first argument of setter for property: '" +
+ p_itype.name + "." + String(p_iprop.cname) + "'.");
+ }
}
const TypeReference &proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type;
@@ -1432,6 +1556,15 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
const TypeInterface *prop_itype = _get_type_or_null(proptype_name);
ERR_FAIL_NULL_V(prop_itype, ERR_BUG); // Property type not found
+ ERR_FAIL_COND_V_MSG(prop_itype->is_singleton, ERR_BUG,
+ "Property type is a singleton: '" + p_itype.name + "." + String(p_iprop.cname) + "'.");
+
+ if (p_itype.api_type == ClassDB::API_CORE) {
+ ERR_FAIL_COND_V_MSG(prop_itype->api_type == ClassDB::API_EDITOR, ERR_BUG,
+ "Property '" + p_itype.name + "." + String(p_iprop.cname) + "' has type '" + prop_itype->name +
+ "' from the editor API. Core API cannot have dependencies on the editor API.");
+ }
+
if (p_iprop.prop_doc && p_iprop.prop_doc->description.size()) {
String xml_summary = bbcode_to_xml(fix_doc_description(p_iprop.prop_doc->description), &p_itype);
Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>();
@@ -1451,8 +1584,9 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
p_output.append(MEMBER_BEGIN "public ");
- if (p_itype.is_singleton)
+ if (p_itype.is_singleton) {
p_output.append("static ");
+ }
p_output.append(prop_itype->cs_type);
p_output.append(" ");
@@ -1474,7 +1608,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
if (idx_arg.type.cname != name_cache.type_int) {
// Assume the index parameter is an enum
const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type);
- CRASH_COND(idx_arg_type == NULL);
+ CRASH_COND(idx_arg_type == nullptr);
p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index));
} else {
p_output.append(itos(p_iprop.index));
@@ -1502,7 +1636,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
if (idx_arg.type.cname != name_cache.type_int) {
// Assume the index parameter is an enum
const TypeInterface *idx_arg_type = _get_type_or_null(idx_arg.type);
- CRASH_COND(idx_arg_type == NULL);
+ CRASH_COND(idx_arg_type == nullptr);
p_output.append("(" + idx_arg_type->proxy_name + ")" + itos(p_iprop.index) + ", ");
} else {
p_output.append(itos(p_iprop.index) + ", ");
@@ -1522,10 +1656,18 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte
}
Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) {
-
const TypeInterface *return_type = _get_type_or_placeholder(p_imethod.return_type);
- String method_bind_field = "method_bind_" + itos(p_method_bind_count);
+ ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG,
+ "Method return type is a singleton: '" + p_itype.name + "." + p_imethod.name + "'.");
+
+ if (p_itype.api_type == ClassDB::API_CORE) {
+ ERR_FAIL_COND_V_MSG(return_type->api_type == ClassDB::API_EDITOR, ERR_BUG,
+ "Method '" + p_itype.name + "." + p_imethod.name + "' has return type '" + return_type->name +
+ "' from the editor API. Core API cannot have dependencies on the editor API.");
+ }
+
+ String method_bind_field = "__method_bind_" + itos(p_method_bind_count);
String arguments_sig;
String cs_in_statements;
@@ -1540,29 +1682,47 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
const ArgumentInterface &iarg = F->get();
const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type);
+ ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG,
+ "Argument type is a singleton: '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'.");
+
+ if (p_itype.api_type == ClassDB::API_CORE) {
+ ERR_FAIL_COND_V_MSG(arg_type->api_type == ClassDB::API_EDITOR, ERR_BUG,
+ "Argument '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "' has type '" +
+ arg_type->name + "' from the editor API. Core API cannot have dependencies on the editor API.");
+ }
+
+ if (iarg.default_argument.size()) {
+ CRASH_COND_MSG(!_arg_default_value_is_assignable_to_type(iarg.def_param_value, *arg_type),
+ "Invalid default value for parameter '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'.");
+ }
+
// Add the current arguments to the signature
// If the argument has a default value which is not a constant, we will make it Nullable
{
- if (F != p_imethod.arguments.front())
+ if (F != p_imethod.arguments.front()) {
arguments_sig += ", ";
+ }
- if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL)
+ if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
arguments_sig += "Nullable<";
+ }
arguments_sig += arg_type->cs_type;
- if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL)
+ if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
arguments_sig += "> ";
- else
+ } else {
arguments_sig += " ";
+ }
arguments_sig += iarg.name;
if (iarg.default_argument.size()) {
- if (iarg.def_param_mode != ArgumentInterface::CONSTANT)
+ if (iarg.def_param_mode != ArgumentInterface::CONSTANT) {
arguments_sig += " = null";
- else
+ } else {
arguments_sig += " = " + sformat(iarg.default_argument, arg_type->cs_type);
+ }
}
}
@@ -1580,39 +1740,42 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
cs_in_statements += " = ";
cs_in_statements += iarg.name;
- if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL)
+ if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
cs_in_statements += ".HasValue ? ";
- else
+ } else {
cs_in_statements += " != null ? ";
+ }
cs_in_statements += iarg.name;
- if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL)
+ if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) {
cs_in_statements += ".Value : ";
- else
+ } else {
cs_in_statements += " : ";
+ }
String def_arg = sformat(iarg.default_argument, arg_type->cs_type);
cs_in_statements += def_arg;
cs_in_statements += ";\n" INDENT3;
- icall_params += arg_type->cs_in.empty() ? arg_in : sformat(arg_type->cs_in, arg_in);
+ icall_params += arg_type->cs_in.is_empty() ? arg_in : sformat(arg_type->cs_in, arg_in);
// Apparently the name attribute must not include the @
String param_tag_name = iarg.name.begins_with("@") ? iarg.name.substr(1, iarg.name.length()) : iarg.name;
default_args_doc.append(MEMBER_BEGIN "/// <param name=\"" + param_tag_name + "\">If the parameter is null, then the default value is " + def_arg + "</param>");
} else {
- icall_params += arg_type->cs_in.empty() ? iarg.name : sformat(arg_type->cs_in, iarg.name);
+ icall_params += arg_type->cs_in.is_empty() ? iarg.name : sformat(arg_type->cs_in, iarg.name);
}
}
// Generate method
{
if (!p_imethod.is_virtual && !p_imethod.requires_object_call) {
- p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static IntPtr ");
- p_output.append(method_bind_field + " = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \"");
+ p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static readonly IntPtr ");
+ p_output.append(method_bind_field);
+ p_output.append(" = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \"");
p_output.append(p_imethod.name);
p_output.append("\");\n");
}
@@ -1639,14 +1802,19 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
}
if (!p_imethod.is_internal) {
+ // TODO: This alone adds ~0.2 MB of bloat to the core API assembly. It would be
+ // better to generate a table in the C++ glue instead. That way the strings wouldn't
+ // add that much extra bloat as they're already used in engine code. Also, it would
+ // probably be much faster than looking up the attributes when fetching methods.
p_output.append(MEMBER_BEGIN "[GodotMethod(\"");
p_output.append(p_imethod.name);
p_output.append("\")]");
}
if (p_imethod.is_deprecated) {
- if (p_imethod.deprecation_message.empty())
- WARN_PRINTS("An empty deprecation message is discouraged. Method: '" + p_imethod.proxy_name + "'.");
+ if (p_imethod.deprecation_message.is_empty()) {
+ WARN_PRINT("An empty deprecation message is discouraged. Method: '" + p_imethod.proxy_name + "'.");
+ }
p_output.append(MEMBER_BEGIN "[Obsolete(\"");
p_output.append(p_imethod.deprecation_message);
@@ -1706,12 +1874,13 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
im_call += ".";
im_call += im_icall->name;
- if (p_imethod.arguments.size())
+ if (p_imethod.arguments.size()) {
p_output.append(cs_in_statements);
+ }
if (return_type->cname == name_cache.type_void) {
p_output.append(im_call + "(" + icall_params + ");\n");
- } else if (return_type->cs_out.empty()) {
+ } else if (return_type->cs_out.is_empty()) {
p_output.append("return " + im_call + "(" + icall_params + ");\n");
} else {
p_output.append(sformat(return_type->cs_out, im_call, icall_params, return_type->cs_type, return_type->im_type_out));
@@ -1726,8 +1895,121 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf
return OK;
}
-Error BindingsGenerator::generate_glue(const String &p_output_dir) {
+Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output) {
+ String arguments_sig;
+
+ // Retrieve information from the arguments
+ for (const List<ArgumentInterface>::Element *F = p_isignal.arguments.front(); F; F = F->next()) {
+ const ArgumentInterface &iarg = F->get();
+ const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type);
+
+ ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG,
+ "Argument type is a singleton: '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "'.");
+
+ if (p_itype.api_type == ClassDB::API_CORE) {
+ ERR_FAIL_COND_V_MSG(arg_type->api_type == ClassDB::API_EDITOR, ERR_BUG,
+ "Argument '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "' has type '" +
+ arg_type->name + "' from the editor API. Core API cannot have dependencies on the editor API.");
+ }
+
+ // Add the current arguments to the signature
+
+ if (F != p_isignal.arguments.front()) {
+ arguments_sig += ", ";
+ }
+ arguments_sig += arg_type->cs_type;
+ arguments_sig += " ";
+ arguments_sig += iarg.name;
+ }
+
+ // Generate signal
+ {
+ 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>();
+
+ if (summary_lines.size()) {
+ p_output.append(MEMBER_BEGIN "/// <summary>\n");
+
+ for (int i = 0; i < summary_lines.size(); i++) {
+ p_output.append(INDENT2 "/// ");
+ p_output.append(summary_lines[i]);
+ p_output.append("\n");
+ }
+
+ p_output.append(INDENT2 "/// </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 += "Handler"; // Delegate name is [SignalName]Handler
+
+ // 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.
+
+ // Cached signal name (StringName)
+ p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static StringName __signal_name_");
+ p_output.append(p_isignal.name);
+ p_output.append(" = \"");
+ p_output.append(p_isignal.name);
+ p_output.append("\";\n");
+
+ // Generate event
+ p_output.append(MEMBER_BEGIN "[Signal]" MEMBER_BEGIN "public ");
+
+ if (p_itype.is_singleton) {
+ p_output.append("static ");
+ }
+
+ p_output.append("event ");
+ p_output.append(delegate_name);
+ p_output.append(" ");
+ p_output.append(p_isignal.proxy_name);
+ p_output.append("\n" OPEN_BLOCK_L2);
+
+ if (p_itype.is_singleton) {
+ p_output.append("add => Singleton.Connect(__signal_name_");
+ } else {
+ p_output.append("add => Connect(__signal_name_");
+ }
+
+ p_output.append(p_isignal.name);
+ p_output.append(", new Callable(value));\n");
+
+ if (p_itype.is_singleton) {
+ p_output.append(INDENT3 "remove => Singleton.Disconnect(__signal_name_");
+ } else {
+ p_output.append(INDENT3 "remove => Disconnect(__signal_name_");
+ }
+
+ p_output.append(p_isignal.name);
+ p_output.append(", new Callable(value));\n");
+ p_output.append(CLOSE_BLOCK_L2);
+ }
+
+ return OK;
+}
+
+Error BindingsGenerator::generate_glue(const String &p_output_dir) {
ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED);
bool dir_exists = DirAccess::exists(p_output_dir);
@@ -1751,7 +2033,6 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
CRASH_COND(itype.cname != name_cache.type_Object);
CRASH_COND(!itype.is_instantiable);
CRASH_COND(itype.api_type != ClassDB::API_CORE);
- CRASH_COND(itype.is_reference);
CRASH_COND(itype.is_singleton);
}
@@ -1772,8 +2053,9 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
String singleton_icall_name = ICALL_PREFIX + itype.name + SINGLETON_ICALL_SUFFIX;
InternalCall singleton_icall = InternalCall(itype.api_type, singleton_icall_name, "IntPtr");
- if (!find_icall_by_name(singleton_icall.name, custom_icalls))
+ if (!find_icall_by_name(singleton_icall.name, custom_icalls)) {
custom_icalls.push_back(singleton_icall);
+ }
output.append("Object* ");
output.append(singleton_icall_name);
@@ -1785,8 +2067,9 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
if (is_derived_type && itype.is_instantiable) {
InternalCall ctor_icall = InternalCall(itype.api_type, ctor_method, "IntPtr", itype.proxy_name + " obj");
- if (!find_icall_by_name(ctor_icall.name, custom_icalls))
+ if (!find_icall_by_name(ctor_icall.name, custom_icalls)) {
custom_icalls.push_back(ctor_icall);
+ }
output.append("Object* ");
output.append(ctor_method);
@@ -1820,19 +2103,18 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \
{ \
- output.append("\tmono_add_internal_call("); \
+ output.append("\tGDMonoUtils::add_internal_call("); \
output.append("\"" BINDINGS_NAMESPACE "."); \
output.append(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \
output.append("::"); \
output.append(m_icall.name); \
- output.append("\", (void*)"); \
+ output.append("\", "); \
output.append(m_icall.name); \
output.append(");\n"); \
}
bool tools_sequence = false;
for (const List<InternalCall>::Element *E = core_custom_icalls.front(); E; E = E->next()) {
-
if (tools_sequence) {
if (!E->get().editor_only) {
tools_sequence = false;
@@ -1886,8 +2168,9 @@ Error BindingsGenerator::generate_glue(const String &p_output_dir) {
output.append("\n#endif // MONO_GLUE_ENABLED\n");
Error save_err = _save_file(path::join(p_output_dir, "mono_glue.gen.cpp"), output);
- if (save_err != OK)
+ if (save_err != OK) {
return save_err;
+ }
OS::get_singleton()->print("Mono glue generated successfully\n");
@@ -1899,7 +2182,6 @@ uint32_t BindingsGenerator::get_version() {
}
Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p_content) {
-
FileAccessRef file = FileAccess::open(p_path, FileAccess::WRITE);
ERR_FAIL_COND_V_MSG(!file, ERR_FILE_CANT_WRITE, "Cannot open file: '" + p_path + "'.");
@@ -1911,9 +2193,9 @@ Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p
}
Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, StringBuilder &p_output) {
-
- if (p_imethod.is_virtual)
+ if (p_imethod.is_virtual) {
return OK; // Ignore
+ }
bool ret_void = p_imethod.return_type.cname == name_cache.type_void;
@@ -1941,10 +2223,12 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
c_in_statements += sformat(", &%s_in);\n", c_param_name);
}
} else {
- if (i > 0)
+ if (i > 0) {
c_args_var_content += ", ";
- if (arg_type->c_in.size())
+ }
+ if (arg_type->c_in.size()) {
c_in_statements += sformat(arg_type->c_in, arg_type->c_type, c_param_name);
+ }
c_args_var_content += sformat(arg_type->c_arg_in, c_param_name);
}
@@ -1974,8 +2258,9 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
if (!generated_icall_funcs.find(im_icall)) {
generated_icall_funcs.push_back(im_icall);
- if (im_icall->editor_only)
+ if (im_icall->editor_only) {
p_output.append("#ifdef TOOLS_ENABLED\n");
+ }
// Generate icall function
@@ -1999,8 +2284,8 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
}
if (return_type->is_object_type) {
- ptrcall_return_type = return_type->is_reference ? "Ref<Reference>" : return_type->c_type;
- initialization = return_type->is_reference ? "" : " = NULL";
+ ptrcall_return_type = return_type->is_ref_counted ? "Ref<RefCounted>" : return_type->c_type;
+ initialization = return_type->is_ref_counted ? "" : " = nullptr";
} else {
ptrcall_return_type = return_type->c_type;
}
@@ -2009,12 +2294,12 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
p_output.append(" " C_LOCAL_RET);
p_output.append(initialization + ";\n");
- String fail_ret = return_type->c_type_out.ends_with("*") && !return_type->ret_as_byref_arg ? "NULL" : return_type->c_type_out + "()";
+ String fail_ret = return_type->c_type_out.ends_with("*") && !return_type->ret_as_byref_arg ? "nullptr" : return_type->c_type_out + "()";
if (return_type->ret_as_byref_arg) {
- p_output.append("\tif (" CS_PARAM_INSTANCE " == NULL) { *arg_ret = ");
+ p_output.append("\tif (" CS_PARAM_INSTANCE " == nullptr) { *arg_ret = ");
p_output.append(fail_ret);
- p_output.append("; ERR_FAIL_MSG(\"Parameter ' arg_ret ' is null.\"); }\n");
+ p_output.append("; ERR_FAIL_MSG(\"Parameter ' " CS_PARAM_INSTANCE " ' is null.\"); }\n");
} else {
p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE ", ");
p_output.append(fail_ret);
@@ -2054,7 +2339,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
}
if (p_imethod.is_vararg) {
- p_output.append("\tVariant::CallError vcall_error;\n\t");
+ p_output.append("\tCallable::CallError vcall_error;\n\t");
if (!ret_void) {
// See the comment on the C_LOCAL_VARARG_RET declaration
@@ -2066,7 +2351,7 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
}
p_output.append(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", ");
- p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL");
+ p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "nullptr");
p_output.append(", total_length, vcall_error);\n");
if (!ret_void) {
@@ -2077,12 +2362,12 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
}
} else {
p_output.append("\t" CS_PARAM_METHODBIND "->ptrcall(" CS_PARAM_INSTANCE ", ");
- p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "NULL, ");
- p_output.append(!ret_void ? "&" C_LOCAL_RET ");\n" : "NULL);\n");
+ p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "nullptr, ");
+ p_output.append(!ret_void ? "&" C_LOCAL_RET ");\n" : "nullptr);\n");
}
if (!ret_void) {
- if (return_type->c_out.empty()) {
+ if (return_type->c_out.is_empty()) {
p_output.append("\treturn " C_LOCAL_RET ";\n");
} else if (return_type->ret_as_byref_arg) {
p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name, "arg_ret"));
@@ -2093,53 +2378,57 @@ Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInte
p_output.append(CLOSE_BLOCK "\n");
- if (im_icall->editor_only)
+ if (im_icall->editor_only) {
p_output.append("#endif // TOOLS_ENABLED\n");
+ }
}
return OK;
}
const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(const TypeReference &p_typeref) {
-
const Map<StringName, TypeInterface>::Element *builtin_type_match = builtin_types.find(p_typeref.cname);
- if (builtin_type_match)
+ if (builtin_type_match) {
return &builtin_type_match->get();
+ }
const OrderedHashMap<StringName, TypeInterface>::Element obj_type_match = obj_types.find(p_typeref.cname);
- if (obj_type_match)
+ if (obj_type_match) {
return &obj_type_match.get();
+ }
if (p_typeref.is_enum) {
const Map<StringName, TypeInterface>::Element *enum_match = enum_types.find(p_typeref.cname);
- if (enum_match)
+ if (enum_match) {
return &enum_match->get();
+ }
// Enum not found. Most likely because none of its constants were bound, so it's empty. That's fine. Use int instead.
const Map<StringName, TypeInterface>::Element *int_match = builtin_types.find(name_cache.type_int);
- ERR_FAIL_NULL_V(int_match, NULL);
+ ERR_FAIL_NULL_V(int_match, nullptr);
return &int_match->get();
}
- return NULL;
+ return nullptr;
}
const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placeholder(const TypeReference &p_typeref) {
-
const TypeInterface *found = _get_type_or_null(p_typeref);
- if (found)
+ if (found) {
return found;
+ }
- ERR_PRINTS(String() + "Type not found. Creating placeholder: '" + p_typeref.cname.operator String() + "'.");
+ ERR_PRINT(String() + "Type not found. Creating placeholder: '" + p_typeref.cname.operator String() + "'.");
const Map<StringName, TypeInterface>::Element *match = placeholder_types.find(p_typeref.cname);
- if (match)
+ if (match) {
return &match->get();
+ }
TypeInterface placeholder;
TypeInterface::create_placeholder_type(placeholder, p_typeref.cname);
@@ -2148,7 +2437,6 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placehol
}
StringName BindingsGenerator::_get_int_type_name_from_meta(GodotTypeInfo::Metadata p_meta) {
-
switch (p_meta) {
case GodotTypeInfo::METADATA_INT_IS_INT8:
return "sbyte";
@@ -2181,7 +2469,6 @@ StringName BindingsGenerator::_get_int_type_name_from_meta(GodotTypeInfo::Metada
}
StringName BindingsGenerator::_get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta) {
-
switch (p_meta) {
case GodotTypeInfo::METADATA_REAL_IS_FLOAT:
return "float";
@@ -2199,8 +2486,85 @@ StringName BindingsGenerator::_get_float_type_name_from_meta(GodotTypeInfo::Meta
}
}
-bool BindingsGenerator::_populate_object_type_interfaces() {
+bool BindingsGenerator::_arg_default_value_is_assignable_to_type(const Variant &p_val, const TypeInterface &p_arg_type) {
+ if (p_arg_type.name == name_cache.type_Variant) {
+ // Variant can take anything
+ return true;
+ }
+
+ switch (p_val.get_type()) {
+ case Variant::NIL:
+ return p_arg_type.is_object_type ||
+ name_cache.is_nullable_type(p_arg_type.name);
+ case Variant::BOOL:
+ return p_arg_type.name == name_cache.type_bool;
+ case Variant::INT:
+ return p_arg_type.name == name_cache.type_sbyte ||
+ p_arg_type.name == name_cache.type_short ||
+ p_arg_type.name == name_cache.type_int ||
+ p_arg_type.name == name_cache.type_byte ||
+ p_arg_type.name == name_cache.type_ushort ||
+ p_arg_type.name == name_cache.type_uint ||
+ p_arg_type.name == name_cache.type_long ||
+ p_arg_type.name == name_cache.type_ulong ||
+ p_arg_type.name == name_cache.type_float ||
+ p_arg_type.name == name_cache.type_double ||
+ p_arg_type.is_enum;
+ case Variant::FLOAT:
+ return p_arg_type.name == name_cache.type_float ||
+ p_arg_type.name == name_cache.type_double;
+ case Variant::STRING:
+ case Variant::STRING_NAME:
+ return p_arg_type.name == name_cache.type_String ||
+ p_arg_type.name == name_cache.type_StringName ||
+ p_arg_type.name == name_cache.type_NodePath;
+ case Variant::NODE_PATH:
+ return p_arg_type.name == name_cache.type_NodePath;
+ case Variant::TRANSFORM2D:
+ case Variant::TRANSFORM3D:
+ case Variant::BASIS:
+ case Variant::QUATERNION:
+ case Variant::PLANE:
+ case Variant::AABB:
+ case Variant::COLOR:
+ case Variant::VECTOR2:
+ case Variant::RECT2:
+ case Variant::VECTOR3:
+ case Variant::RID:
+ case Variant::ARRAY:
+ case Variant::DICTIONARY:
+ case Variant::PACKED_BYTE_ARRAY:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY:
+ case Variant::PACKED_FLOAT32_ARRAY:
+ case Variant::PACKED_FLOAT64_ARRAY:
+ case Variant::PACKED_STRING_ARRAY:
+ case Variant::PACKED_VECTOR2_ARRAY:
+ case Variant::PACKED_VECTOR3_ARRAY:
+ case Variant::PACKED_COLOR_ARRAY:
+ case Variant::CALLABLE:
+ case Variant::SIGNAL:
+ return p_arg_type.name == Variant::get_type_name(p_val.get_type());
+ case Variant::OBJECT:
+ return p_arg_type.is_object_type;
+ case Variant::VECTOR2I:
+ return p_arg_type.name == name_cache.type_Vector2 ||
+ p_arg_type.name == Variant::get_type_name(p_val.get_type());
+ case Variant::RECT2I:
+ return p_arg_type.name == name_cache.type_Rect2 ||
+ p_arg_type.name == Variant::get_type_name(p_val.get_type());
+ case Variant::VECTOR3I:
+ return p_arg_type.name == name_cache.type_Vector3 ||
+ p_arg_type.name == Variant::get_type_name(p_val.get_type());
+ default:
+ CRASH_NOW_MSG("Unexpected Variant type: " + itos(p_val.get_type()));
+ break;
+ }
+ return false;
+}
+
+bool BindingsGenerator::_populate_object_type_interfaces() {
obj_types.clear();
List<StringName> class_list;
@@ -2236,12 +2600,12 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
itype.base_name = ClassDB::get_parent_class(type_cname);
itype.is_singleton = Engine::get_singleton()->has_singleton(itype.proxy_name);
itype.is_instantiable = class_info->creation_func && !itype.is_singleton;
- itype.is_reference = ClassDB::is_parent_class(type_cname, name_cache.type_Reference);
- itype.memory_own = itype.is_reference;
+ itype.is_ref_counted = ClassDB::is_parent_class(type_cname, name_cache.type_RefCounted);
+ itype.memory_own = itype.is_ref_counted;
itype.c_out = "\treturn ";
itype.c_out += C_METHOD_UNMANAGED_GET_MANAGED;
- itype.c_out += itype.is_reference ? "(%1.ptr());\n" : "(%1);\n";
+ itype.c_out += itype.is_ref_counted ? "(%1.ptr());\n" : "(%1);\n";
itype.cs_in = itype.is_singleton ? BINDINGS_PTR_FIELD : "Object." CS_SMETHOD_GETINSTANCE "(%0)";
@@ -2262,22 +2626,30 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
for (const List<PropertyInfo>::Element *E = property_list.front(); E; E = E->next()) {
const PropertyInfo &property = E->get();
- if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_CATEGORY)
+ if (property.usage & PROPERTY_USAGE_GROUP || property.usage & PROPERTY_USAGE_SUBGROUP || property.usage & PROPERTY_USAGE_CATEGORY) {
continue;
+ }
+
+ if (property.name.find("/") >= 0) {
+ // Ignore properties with '/' (slash) in the name. These are only meant for use in the inspector.
+ continue;
+ }
PropertyInterface iprop;
iprop.cname = property.name;
iprop.setter = ClassDB::get_property_setter(type_cname, iprop.cname);
iprop.getter = ClassDB::get_property_getter(type_cname, iprop.cname);
- if (iprop.setter != StringName())
+ if (iprop.setter != StringName()) {
accessor_methods[iprop.setter] = iprop.cname;
- if (iprop.getter != StringName())
+ }
+ if (iprop.getter != StringName()) {
accessor_methods[iprop.getter] = iprop.cname;
+ }
bool valid = false;
iprop.index = ClassDB::get_property_index(type_cname, iprop.cname, &valid);
- ERR_FAIL_COND_V(!valid, false);
+ ERR_FAIL_COND_V_MSG(!valid, false, "Invalid property: '" + itype.name + "." + String(iprop.cname) + "'.");
iprop.proxy_name = escape_csharp_keyword(snake_to_pascal_case(iprop.cname));
@@ -2289,9 +2661,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
iprop.proxy_name += "_";
}
- iprop.proxy_name = iprop.proxy_name.replace("/", "__"); // Some members have a slash...
-
- iprop.prop_doc = NULL;
+ iprop.prop_doc = nullptr;
for (int i = 0; i < itype.class_doc->properties.size(); i++) {
const DocData::PropertyDoc &prop_doc = itype.class_doc->properties[i];
@@ -2319,24 +2689,27 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
int argc = method_info.arguments.size();
- if (method_info.name.empty())
+ if (method_info.name.is_empty()) {
continue;
+ }
String cname = method_info.name;
- if (blacklisted_methods.find(itype.cname) && blacklisted_methods[itype.cname].find(cname))
+ if (blacklisted_methods.find(itype.cname) && blacklisted_methods[itype.cname].find(cname)) {
continue;
+ }
MethodInterface imethod;
imethod.name = method_info.name;
imethod.cname = cname;
- if (method_info.flags & METHOD_FLAG_VIRTUAL)
+ if (method_info.flags & METHOD_FLAG_VIRTUAL) {
imethod.is_virtual = true;
+ }
PropertyInfo return_info = method_info.return_val;
- MethodBind *m = imethod.is_virtual ? NULL : ClassDB::get_method(type_cname, method_info.name);
+ MethodBind *m = imethod.is_virtual ? nullptr : ClassDB::get_method(type_cname, method_info.name);
imethod.is_vararg = m && m->is_vararg();
@@ -2354,26 +2727,24 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
// We assume the return type is void.
imethod.return_type.cname = name_cache.type_void;
- // Actually, more methods like this may be added in the future,
- // which could actually will return something different.
- // Let's put this to notify us if that ever happens.
+ // Actually, more methods like this may be added in the future, which could return
+ // something different. Let's put this check to notify us if that ever happens.
if (itype.cname != name_cache.type_Object || imethod.name != "free") {
- WARN_PRINTS("Notification: New unexpected virtual non-overridable method found."
- " We only expected Object.free, but found '" +
- itype.name + "." + imethod.name + "'.");
+ WARN_PRINT("Notification: New unexpected virtual non-overridable method found."
+ " We only expected Object.free, but found '" +
+ itype.name + "." + imethod.name + "'.");
}
} else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
imethod.return_type.cname = return_info.class_name;
imethod.return_type.is_enum = true;
} else if (return_info.class_name != StringName()) {
imethod.return_type.cname = return_info.class_name;
- if (!imethod.is_virtual && ClassDB::is_parent_class(return_info.class_name, name_cache.type_Reference) && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE) {
- /* clang-format off */
- ERR_PRINTS("Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'."
- " Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'.");
- /* clang-format on */
- ERR_FAIL_V(false);
- }
+
+ bool bad_reference_hint = !imethod.is_virtual && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE &&
+ ClassDB::is_parent_class(return_info.class_name, name_cache.type_RefCounted);
+ ERR_FAIL_COND_V_MSG(bad_reference_hint, false,
+ String() + "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." +
+ " Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'.");
} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
imethod.return_type.cname = return_info.hint_string;
} else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
@@ -2383,7 +2754,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
} else {
if (return_info.type == Variant::INT) {
imethod.return_type.cname = _get_int_type_name_from_meta(m ? m->get_argument_meta(-1) : GodotTypeInfo::METADATA_NONE);
- } else if (return_info.type == Variant::REAL) {
+ } else if (return_info.type == Variant::FLOAT) {
imethod.return_type.cname = _get_float_type_name_from_meta(m ? m->get_argument_meta(-1) : GodotTypeInfo::METADATA_NONE);
} else {
imethod.return_type.cname = Variant::get_type_name(return_info.type);
@@ -2410,7 +2781,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
} else {
if (arginfo.type == Variant::INT) {
iarg.type.cname = _get_int_type_name_from_meta(m ? m->get_argument_meta(i) : GodotTypeInfo::METADATA_NONE);
- } else if (arginfo.type == Variant::REAL) {
+ } else if (arginfo.type == Variant::FLOAT) {
iarg.type.cname = _get_float_type_name_from_meta(m ? m->get_argument_meta(i) : GodotTypeInfo::METADATA_NONE);
} else {
iarg.type.cname = Variant::get_type_name(arginfo.type);
@@ -2464,6 +2835,10 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
}
}
+ ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false,
+ "Method name conflicts with property: '" + itype.name + "." + imethod.name + "'.");
+
+ // Classes starting with an underscore are ignored unless they're used as a property setter or getter
if (!imethod.is_virtual && imethod.name[0] == '_') {
for (const List<PropertyInterface>::Element *F = itype.properties.front(); F; F = F->next()) {
const PropertyInterface &iprop = F->get();
@@ -2479,13 +2854,92 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
}
}
+ // Populate signals
+
+ const HashMap<StringName, MethodInfo> &signal_map = class_info->signal_map;
+ const StringName *k = nullptr;
+
+ while ((k = signal_map.next(k))) {
+ SignalInterface isignal;
+
+ const MethodInfo &method_info = signal_map.get(*k);
+
+ isignal.name = method_info.name;
+ isignal.cname = method_info.name;
+
+ int argc = method_info.arguments.size();
+
+ for (int i = 0; i < argc; i++) {
+ PropertyInfo arginfo = method_info.arguments[i];
+
+ String orig_arg_name = arginfo.name;
+
+ ArgumentInterface iarg;
+ iarg.name = orig_arg_name;
+
+ if (arginfo.type == Variant::INT && arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
+ iarg.type.cname = arginfo.class_name;
+ iarg.type.is_enum = true;
+ } else if (arginfo.class_name != StringName()) {
+ iarg.type.cname = arginfo.class_name;
+ } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) {
+ iarg.type.cname = arginfo.hint_string;
+ } else if (arginfo.type == Variant::NIL) {
+ iarg.type.cname = name_cache.type_Variant;
+ } else {
+ if (arginfo.type == Variant::INT) {
+ iarg.type.cname = _get_int_type_name_from_meta(GodotTypeInfo::METADATA_NONE);
+ } else if (arginfo.type == Variant::FLOAT) {
+ iarg.type.cname = _get_float_type_name_from_meta(GodotTypeInfo::METADATA_NONE);
+ } else {
+ iarg.type.cname = Variant::get_type_name(arginfo.type);
+ }
+ }
+
+ iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name));
+
+ isignal.add_argument(iarg);
+ }
+
+ isignal.proxy_name = escape_csharp_keyword(snake_to_pascal_case(isignal.name));
+
+ // Prevent the signal and its enclosing type from sharing the same name
+ if (isignal.proxy_name == itype.proxy_name) {
+ _log("Name of signal '%s' is ambiguous with the name of its enclosing class '%s'. Renaming signal to '%s_'\n",
+ isignal.proxy_name.utf8().get_data(), itype.proxy_name.utf8().get_data(), isignal.proxy_name.utf8().get_data());
+
+ isignal.proxy_name += "_";
+ }
+
+ if (itype.find_property_by_proxy_name(isignal.proxy_name) || itype.find_method_by_proxy_name(isignal.proxy_name)) {
+ // ClassDB allows signal names that conflict with method or property names.
+ // While registering a signal with a conflicting name is considered wrong,
+ // it may still happen and it may take some time until someone fixes the name.
+ // We can't allow the bindings to be in a broken state while we wait for a fix;
+ // that's why we must handle this possibility by renaming the signal.
+ isignal.proxy_name += "Signal";
+ }
+
+ if (itype.class_doc) {
+ for (int i = 0; i < itype.class_doc->signals.size(); i++) {
+ const DocData::MethodDoc &signal_doc = itype.class_doc->signals[i];
+ if (signal_doc.name == isignal.name) {
+ isignal.method_doc = &signal_doc;
+ break;
+ }
+ }
+ }
+
+ itype.signals_.push_back(isignal);
+ }
+
// Populate enums and constants
List<String> constants;
ClassDB::get_integer_constant_list(type_cname, &constants, true);
- const HashMap<StringName, List<StringName> > &enum_map = class_info->enum_map;
- const StringName *k = NULL;
+ const HashMap<StringName, List<StringName>> &enum_map = class_info->enum_map;
+ k = nullptr;
while ((k = enum_map.next(k))) {
StringName enum_proxy_cname = *k;
@@ -2507,7 +2961,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);
- iconstant.const_doc = NULL;
+ iconstant.const_doc = nullptr;
for (int i = 0; i < itype.class_doc->constants.size(); i++) {
const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i];
@@ -2542,7 +2996,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), *value);
- iconstant.const_doc = NULL;
+ iconstant.const_doc = nullptr;
for (int i = 0; i < itype.class_doc->constants.size(); i++) {
const DocData::ConstantDoc &const_doc = itype.class_doc->constants[i];
@@ -2564,8 +3018,8 @@ bool BindingsGenerator::_populate_object_type_interfaces() {
}
bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) {
-
- r_iarg.default_argument = p_val;
+ r_iarg.def_param_value = p_val;
+ r_iarg.default_argument = p_val.operator String();
switch (p_val.get_type()) {
case Variant::NIL:
@@ -2581,30 +3035,47 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
r_iarg.default_argument = "(%s)" + r_iarg.default_argument;
}
break;
- case Variant::REAL:
-#ifndef REAL_T_IS_DOUBLE
- r_iarg.default_argument += "f";
-#endif
+ case Variant::FLOAT:
+ if (r_iarg.type.cname == name_cache.type_float) {
+ r_iarg.default_argument += "f";
+ }
break;
case Variant::STRING:
+ case Variant::STRING_NAME:
case Variant::NODE_PATH:
- r_iarg.default_argument = "\"" + r_iarg.default_argument + "\"";
+ if (r_iarg.type.cname == name_cache.type_StringName || r_iarg.type.cname == name_cache.type_NodePath) {
+ r_iarg.default_argument = "(%s)\"" + r_iarg.default_argument + "\"";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
+ } else {
+ CRASH_COND(r_iarg.type.cname != name_cache.type_String);
+ r_iarg.default_argument = "\"" + r_iarg.default_argument + "\"";
+ }
break;
- case Variant::TRANSFORM:
- if (p_val.operator Transform() == Transform())
- r_iarg.default_argument.clear();
- r_iarg.default_argument = "new %s(" + r_iarg.default_argument + ")";
+ case Variant::PLANE: {
+ Plane plane = p_val.operator Plane();
+ r_iarg.default_argument = "new Plane(new Vector3" + plane.normal.operator String() + ", " + rtos(plane.d) + ")";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
- break;
- case Variant::PLANE:
- case Variant::AABB:
- case Variant::COLOR:
- r_iarg.default_argument = "new Color(1, 1, 1, 1)";
+ } break;
+ case Variant::AABB: {
+ AABB aabb = p_val.operator ::AABB();
+ r_iarg.default_argument = "new AABB(new Vector3" + aabb.position.operator String() + ", new Vector3" + aabb.size.operator String() + ")";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
- break;
+ } break;
+ case Variant::RECT2: {
+ Rect2 rect = p_val.operator Rect2();
+ r_iarg.default_argument = "new Rect2(new Vector2" + rect.position.operator String() + ", new Vector2" + rect.size.operator String() + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ } break;
+ case Variant::RECT2I: {
+ Rect2i rect = p_val.operator Rect2i();
+ r_iarg.default_argument = "new Rect2i(new Vector2i" + rect.position.operator String() + ", new Vector2i" + rect.size.operator String() + ")";
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ } break;
+ case Variant::COLOR:
case Variant::VECTOR2:
- case Variant::RECT2:
+ case Variant::VECTOR2I:
case Variant::VECTOR3:
+ case Variant::VECTOR3I:
r_iarg.default_argument = "new %s" + r_iarg.default_argument;
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
break;
@@ -2618,7 +3089,7 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
r_iarg.default_argument = "new %s()";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
break;
- case Variant::_RID:
+ case Variant::RID:
ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_RID, false,
"Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value of type '" + String(name_cache.type_RID) + "'.");
@@ -2628,34 +3099,72 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar
r_iarg.default_argument = "null";
break;
case Variant::ARRAY:
- case Variant::POOL_BYTE_ARRAY:
- case Variant::POOL_INT_ARRAY:
- case Variant::POOL_REAL_ARRAY:
- case Variant::POOL_STRING_ARRAY:
- case Variant::POOL_VECTOR2_ARRAY:
- case Variant::POOL_VECTOR3_ARRAY:
- case Variant::POOL_COLOR_ARRAY:
+ case Variant::PACKED_BYTE_ARRAY:
+ case Variant::PACKED_INT32_ARRAY:
+ case Variant::PACKED_INT64_ARRAY:
+ case Variant::PACKED_FLOAT32_ARRAY:
+ case Variant::PACKED_FLOAT64_ARRAY:
+ case Variant::PACKED_STRING_ARRAY:
+ case Variant::PACKED_VECTOR2_ARRAY:
+ case Variant::PACKED_VECTOR3_ARRAY:
+ case Variant::PACKED_COLOR_ARRAY:
r_iarg.default_argument = "new %s {}";
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
break;
- case Variant::TRANSFORM2D:
- case Variant::BASIS:
- case Variant::QUAT:
- r_iarg.default_argument = Variant::get_type_name(p_val.get_type()) + ".Identity";
+ case Variant::TRANSFORM2D: {
+ Transform2D transform = p_val.operator Transform2D();
+ if (transform == Transform2D()) {
+ r_iarg.default_argument = "Transform2D.Identity";
+ } else {
+ r_iarg.default_argument = "new Transform2D(new Vector2" + transform.elements[0].operator String() + ", new Vector2" + transform.elements[1].operator String() + ", new Vector2" + transform.elements[2].operator String() + ")";
+ }
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ } break;
+ case Variant::TRANSFORM3D: {
+ Transform3D transform = p_val.operator Transform3D();
+ if (transform == Transform3D()) {
+ r_iarg.default_argument = "Transform3D.Identity";
+ } else {
+ Basis basis = transform.basis;
+ r_iarg.default_argument = "new Transform3D(new Vector3" + basis.get_column(0).operator String() + ", new Vector3" + basis.get_column(1).operator String() + ", new Vector3" + basis.get_column(2).operator String() + ", new Vector3" + transform.origin.operator String() + ")";
+ }
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ } break;
+ case Variant::BASIS: {
+ Basis basis = p_val.operator Basis();
+ if (basis == Basis()) {
+ r_iarg.default_argument = "Basis.Identity";
+ } else {
+ r_iarg.default_argument = "new Basis(new Vector3" + basis.get_column(0).operator String() + ", new Vector3" + basis.get_column(1).operator String() + ", new Vector3" + basis.get_column(2).operator String() + ")";
+ }
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ } break;
+ case Variant::QUATERNION: {
+ Quaternion quaternion = p_val.operator Quaternion();
+ if (quaternion == Quaternion()) {
+ r_iarg.default_argument = "Quaternion.Identity";
+ } else {
+ r_iarg.default_argument = "new Quaternion" + quaternion.operator String();
+ }
+ r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL;
+ } break;
+ case Variant::CALLABLE:
+ case Variant::SIGNAL:
+ CRASH_NOW_MSG("Parameter of type '" + String(r_iarg.type.cname) + "' cannot have a default value.");
+ break;
+ default:
+ CRASH_NOW_MSG("Unexpected Variant type: " + itos(p_val.get_type()));
break;
- default: {
- }
}
- if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "null")
+ if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "null") {
r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF;
+ }
return true;
}
void BindingsGenerator::_populate_builtin_type_interfaces() {
-
builtin_types.clear();
TypeInterface itype;
@@ -2670,19 +3179,22 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.c_type_out = "GDMonoMarshal::M_" #m_type; \
itype.cs_in = "ref %s"; \
/* in cs_out, im_type_out (%3) includes the 'out ' part */ \
- itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;"; \
+ itype.cs_out = "%0(%1, %3 argRet); return argRet;"; \
itype.im_type_out = "out " + itype.cs_type; \
itype.ret_as_byref_arg = true; \
builtin_types.insert(itype.cname, itype); \
}
INSERT_STRUCT_TYPE(Vector2)
+ INSERT_STRUCT_TYPE(Vector2i)
INSERT_STRUCT_TYPE(Rect2)
+ INSERT_STRUCT_TYPE(Rect2i)
INSERT_STRUCT_TYPE(Transform2D)
INSERT_STRUCT_TYPE(Vector3)
+ INSERT_STRUCT_TYPE(Vector3i)
INSERT_STRUCT_TYPE(Basis)
- INSERT_STRUCT_TYPE(Quat)
- INSERT_STRUCT_TYPE(Transform)
+ INSERT_STRUCT_TYPE(Quaternion)
+ INSERT_STRUCT_TYPE(Transform3D)
INSERT_STRUCT_TYPE(AABB)
INSERT_STRUCT_TYPE(Color)
INSERT_STRUCT_TYPE(Plane)
@@ -2729,44 +3241,11 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
INSERT_INT_TYPE("sbyte", int8_t, int64_t);
INSERT_INT_TYPE("short", int16_t, int64_t);
INSERT_INT_TYPE("int", int32_t, int64_t);
+ INSERT_INT_TYPE("long", int64_t, int64_t);
INSERT_INT_TYPE("byte", uint8_t, int64_t);
INSERT_INT_TYPE("ushort", uint16_t, int64_t);
INSERT_INT_TYPE("uint", uint32_t, int64_t);
-
- itype = TypeInterface::create_value_type(String("long"));
- {
- itype.c_out = "\treturn (%0)%1;\n";
- itype.c_in = "\t%0 %1_in = (%0)*%1;\n";
- itype.c_out = "\t*%3 = (%0)%1;\n";
- itype.c_type = "int64_t";
- itype.c_arg_in = "&%s_in";
- }
- itype.c_type_in = "int64_t*";
- itype.c_type_out = "int64_t";
- itype.im_type_in = "ref " + itype.name;
- itype.im_type_out = "out " + itype.name;
- itype.cs_in = "ref %0";
- /* in cs_out, im_type_out (%3) includes the 'out ' part */
- itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;";
- itype.ret_as_byref_arg = true;
- builtin_types.insert(itype.cname, itype);
-
- itype = TypeInterface::create_value_type(String("ulong"));
- {
- itype.c_in = "\t%0 %1_in = (%0)*%1;\n";
- itype.c_out = "\t*%3 = (%0)%1;\n";
- itype.c_type = "int64_t";
- itype.c_arg_in = "&%s_in";
- }
- itype.c_type_in = "uint64_t*";
- itype.c_type_out = "uint64_t";
- itype.im_type_in = "ref " + itype.name;
- itype.im_type_out = "out " + itype.name;
- itype.cs_in = "ref %0";
- /* in cs_out, im_type_out (%3) includes the 'out ' part */
- itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;";
- itype.ret_as_byref_arg = true;
- builtin_types.insert(itype.cname, itype);
+ INSERT_INT_TYPE("ulong", uint64_t, int64_t);
}
// Floating point types
@@ -2778,20 +3257,16 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.proxy_name = "float";
{
// The expected type for 'float' in ptrcall is 'double'
- itype.c_in = "\t%0 %1_in = (%0)*%1;\n";
- itype.c_out = "\t*%3 = (%0)%1;\n";
+ itype.c_in = "\t%0 %1_in = (%0)%1;\n";
+ itype.c_out = "\treturn (%0)%1;\n";
itype.c_type = "double";
- itype.c_type_in = "float*";
+ itype.c_type_in = "float";
itype.c_type_out = "float";
itype.c_arg_in = "&%s_in";
}
itype.cs_type = itype.proxy_name;
- itype.im_type_in = "ref " + itype.proxy_name;
- itype.im_type_out = "out " + itype.proxy_name;
- itype.cs_in = "ref %0";
- /* in cs_out, im_type_out (%3) includes the 'out ' part */
- itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;";
- itype.ret_as_byref_arg = true;
+ itype.im_type_in = itype.proxy_name;
+ itype.im_type_out = itype.proxy_name;
builtin_types.insert(itype.cname, itype);
// double
@@ -2800,20 +3275,14 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.cname = itype.name;
itype.proxy_name = "double";
{
- itype.c_in = "\t%0 %1_in = (%0)*%1;\n";
- itype.c_out = "\t*%3 = (%0)%1;\n";
itype.c_type = "double";
- itype.c_type_in = "double*";
+ itype.c_type_in = "double";
itype.c_type_out = "double";
- itype.c_arg_in = "&%s_in";
+ itype.c_arg_in = "&%s";
}
itype.cs_type = itype.proxy_name;
- itype.im_type_in = "ref " + itype.proxy_name;
- itype.im_type_out = "out " + itype.proxy_name;
- itype.cs_in = "ref %0";
- /* in cs_out, im_type_out (%3) includes the 'out ' part */
- itype.cs_out = "%0(%1, %3 argRet); return (%2)argRet;";
- itype.ret_as_byref_arg = true;
+ itype.im_type_in = itype.proxy_name;
+ itype.im_type_out = itype.proxy_name;
builtin_types.insert(itype.cname, itype);
}
@@ -2833,6 +3302,24 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.im_type_out = itype.proxy_name;
builtin_types.insert(itype.cname, itype);
+ // StringName
+ itype = TypeInterface();
+ itype.name = "StringName";
+ itype.cname = itype.name;
+ itype.proxy_name = "StringName";
+ itype.c_in = "\t%0 %1_in = %1 ? *%1 : StringName();\n";
+ itype.c_out = "\treturn memnew(StringName(%1));\n";
+ itype.c_arg_in = "&%s_in";
+ itype.c_type = itype.name;
+ itype.c_type_in = itype.c_type + "*";
+ itype.c_type_out = itype.c_type + "*";
+ itype.cs_type = itype.proxy_name;
+ itype.cs_in = "StringName." CS_SMETHOD_GETINSTANCE "(%0)";
+ itype.cs_out = "return new %2(%0(%1));";
+ itype.im_type_in = "IntPtr";
+ itype.im_type_out = "IntPtr";
+ builtin_types.insert(itype.cname, itype);
+
// NodePath
itype = TypeInterface();
itype.name = "NodePath";
@@ -2881,6 +3368,40 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
itype.im_type_out = itype.proxy_name;
builtin_types.insert(itype.cname, itype);
+ // Callable
+ itype = TypeInterface::create_value_type(String("Callable"));
+ itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_CALLABLE "(*%1);\n";
+ itype.c_out = "\t*%3 = " C_METHOD_MANAGED_FROM_CALLABLE "(%1);\n";
+ itype.c_arg_in = "&%s_in";
+ itype.c_type_in = "GDMonoMarshal::M_Callable*";
+ itype.c_type_out = "GDMonoMarshal::M_Callable";
+ itype.cs_in = "ref %s";
+ /* in cs_out, im_type_out (%3) includes the 'out ' part */
+ itype.cs_out = "%0(%1, %3 argRet); return argRet;";
+ itype.im_type_out = "out " + itype.cs_type;
+ itype.ret_as_byref_arg = true;
+ builtin_types.insert(itype.cname, itype);
+
+ // Signal
+ itype = TypeInterface();
+ itype.name = "Signal";
+ itype.cname = itype.name;
+ itype.proxy_name = "SignalInfo";
+ itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_SIGNAL "(*%1);\n";
+ itype.c_out = "\t*%3 = " C_METHOD_MANAGED_FROM_SIGNAL "(%1);\n";
+ itype.c_arg_in = "&%s_in";
+ itype.c_type = itype.name;
+ itype.c_type_in = "GDMonoMarshal::M_SignalInfo*";
+ itype.c_type_out = "GDMonoMarshal::M_SignalInfo";
+ itype.cs_in = "ref %s";
+ /* in cs_out, im_type_out (%3) includes the 'out ' part */
+ itype.cs_out = "%0(%1, %3 argRet); return argRet;";
+ itype.cs_type = itype.proxy_name;
+ itype.im_type_in = "ref " + itype.cs_type;
+ itype.im_type_out = "out " + itype.cs_type;
+ itype.ret_as_byref_arg = true;
+ builtin_types.insert(itype.cname, itype);
+
// VarArg (fictitious type to represent variable arguments)
itype = TypeInterface();
itype.name = "VarArg";
@@ -2914,20 +3435,18 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
#define INSERT_ARRAY(m_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_proxy_t)
- INSERT_ARRAY(PoolIntArray, int);
- INSERT_ARRAY_FULL(PoolByteArray, PoolByteArray, byte);
+ INSERT_ARRAY(PackedInt32Array, int);
+ INSERT_ARRAY(PackedInt64Array, long);
+ INSERT_ARRAY_FULL(PackedByteArray, PackedByteArray, byte);
-#ifdef REAL_T_IS_DOUBLE
- INSERT_ARRAY(PoolRealArray, double);
-#else
- INSERT_ARRAY(PoolRealArray, float);
-#endif
+ INSERT_ARRAY(PackedFloat32Array, float);
+ INSERT_ARRAY(PackedFloat64Array, double);
- INSERT_ARRAY(PoolStringArray, string);
+ INSERT_ARRAY(PackedStringArray, string);
- INSERT_ARRAY(PoolColorArray, Color);
- INSERT_ARRAY(PoolVector2Array, Vector2);
- INSERT_ARRAY(PoolVector3Array, Vector3);
+ INSERT_ARRAY(PackedColorArray, Color);
+ INSERT_ARRAY(PackedVector2Array, Vector2);
+ INSERT_ARRAY(PackedVector3Array, Vector3);
#undef INSERT_ARRAY
@@ -2978,8 +3497,7 @@ void BindingsGenerator::_populate_builtin_type_interfaces() {
}
void BindingsGenerator::_populate_global_constants() {
-
- int global_constants_count = GlobalConstants::get_global_constant_count();
+ int global_constants_count = CoreConstants::get_global_constant_count();
if (global_constants_count > 0) {
Map<String, DocData::ClassDoc>::Element *match = EditorHelp::get_doc_data()->class_list.find("@GlobalScope");
@@ -2989,10 +3507,9 @@ void BindingsGenerator::_populate_global_constants() {
const DocData::ClassDoc &global_scope_doc = match->value();
for (int i = 0; i < global_constants_count; i++) {
+ String constant_name = CoreConstants::get_global_constant_name(i);
- String constant_name = GlobalConstants::get_global_constant_name(i);
-
- const DocData::ConstantDoc *const_doc = NULL;
+ const DocData::ConstantDoc *const_doc = nullptr;
for (int j = 0; j < global_scope_doc.constants.size(); j++) {
const DocData::ConstantDoc &curr_const_doc = global_scope_doc.constants[j];
@@ -3002,8 +3519,8 @@ void BindingsGenerator::_populate_global_constants() {
}
}
- int constant_value = GlobalConstants::get_global_constant_value(i);
- StringName enum_name = GlobalConstants::get_global_constant_enum(i);
+ int constant_value = CoreConstants::get_global_constant_value(i);
+ StringName enum_name = CoreConstants::get_global_constant_enum(i);
ConstantInterface iconstant(constant_name, snake_to_pascal_case(constant_name, true), constant_value);
iconstant.const_doc = const_doc;
@@ -3038,7 +3555,7 @@ void BindingsGenerator::_populate_global_constants() {
// HARDCODED: The Error enum have the prefix 'ERR_' for everything except 'OK' and 'FAILED'.
if (ienum.cname == name_cache.enum_Error) {
if (prefix_length > 0) { // Just in case it ever changes
- ERR_PRINTS("Prefix for enum '" _STR(Error) "' is not empty.");
+ ERR_PRINT("Prefix for enum '" _STR(Error) "' is not empty.");
}
prefix_length = 1; // 'ERR_'
@@ -3050,7 +3567,10 @@ void BindingsGenerator::_populate_global_constants() {
// HARDCODED
List<StringName> hardcoded_enums;
+ hardcoded_enums.push_back("Vector2.Axis");
+ hardcoded_enums.push_back("Vector2i.Axis");
hardcoded_enums.push_back("Vector3.Axis");
+ hardcoded_enums.push_back("Vector3i.Axis");
for (List<StringName>::Element *E = hardcoded_enums.front(); E; E = E->next()) {
// These enums are not generated and must be written manually (e.g.: Vector3.Axis)
// Here, we assume core types do not begin with underscore
@@ -3065,14 +3585,12 @@ void BindingsGenerator::_populate_global_constants() {
}
void BindingsGenerator::_initialize_blacklisted_methods() {
-
blacklisted_methods["Object"].push_back("to_string"); // there is already ToString
blacklisted_methods["Object"].push_back("_to_string"); // override ToString instead
blacklisted_methods["Object"].push_back("_init"); // never called in C# (TODO: implement it)
}
void BindingsGenerator::_log(const char *p_format, ...) {
-
if (log_print_enabled) {
va_list list;
@@ -3083,7 +3601,6 @@ void BindingsGenerator::_log(const char *p_format, ...) {
}
void BindingsGenerator::_initialize() {
-
initialized = false;
EditorHelp::generate_doc();
@@ -3104,18 +3621,51 @@ void BindingsGenerator::_initialize() {
core_custom_icalls.clear();
editor_custom_icalls.clear();
- for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next())
+ for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) {
_generate_method_icalls(E.get());
+ }
initialized = true;
}
-void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) {
+static String generate_all_glue_option = "--generate-mono-glue";
+static String generate_cs_glue_option = "--generate-mono-cs-glue";
+static String generate_cpp_glue_option = "--generate-mono-cpp-glue";
+
+static void handle_cmdline_options(String glue_dir_path, String cs_dir_path, String cpp_dir_path) {
+ BindingsGenerator bindings_generator;
+ bindings_generator.set_log_print_enabled(true);
+ if (!bindings_generator.is_initialized()) {
+ ERR_PRINT("Failed to initialize the bindings generator");
+ return;
+ }
+
+ if (glue_dir_path.length()) {
+ if (bindings_generator.generate_glue(glue_dir_path) != OK) {
+ ERR_PRINT(generate_all_glue_option + ": Failed to generate the C++ glue.");
+ }
+
+ if (bindings_generator.generate_cs_api(glue_dir_path.plus_file(API_SOLUTION_NAME)) != OK) {
+ ERR_PRINT(generate_all_glue_option + ": Failed to generate the C# API.");
+ }
+ }
+
+ if (cs_dir_path.length()) {
+ if (bindings_generator.generate_cs_api(cs_dir_path) != OK) {
+ ERR_PRINT(generate_cs_glue_option + ": Failed to generate the C# API.");
+ }
+ }
+
+ if (cpp_dir_path.length()) {
+ if (bindings_generator.generate_glue(cpp_dir_path) != OK) {
+ ERR_PRINT(generate_cpp_glue_option + ": Failed to generate the C++ glue.");
+ }
+ }
+}
+
+void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) {
const int NUM_OPTIONS = 2;
- String generate_all_glue_option = "--generate-mono-glue";
- String generate_cs_glue_option = "--generate-mono-cs-glue";
- String generate_cpp_glue_option = "--generate-mono-cpp-glue";
String glue_dir_path;
String cs_dir_path;
@@ -3123,6 +3673,8 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
int options_left = NUM_OPTIONS;
+ bool exit_godot = false;
+
const List<String>::Element *elem = p_cmdline_args.front();
while (elem && options_left) {
@@ -3133,7 +3685,8 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
glue_dir_path = path_elem->get();
elem = elem->next();
} else {
- ERR_PRINTS(generate_all_glue_option + ": No output directory specified (expected path to '{GODOT_ROOT}/modules/mono/glue').");
+ ERR_PRINT(generate_all_glue_option + ": No output directory specified (expected path to '{GODOT_ROOT}/modules/mono/glue').");
+ exit_godot = true;
}
--options_left;
@@ -3144,7 +3697,8 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
cs_dir_path = path_elem->get();
elem = elem->next();
} else {
- ERR_PRINTS(generate_cs_glue_option + ": No output directory specified.");
+ ERR_PRINT(generate_cs_glue_option + ": No output directory specified.");
+ exit_godot = true;
}
--options_left;
@@ -3155,7 +3709,8 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
cpp_dir_path = path_elem->get();
elem = elem->next();
} else {
- ERR_PRINTS(generate_cpp_glue_option + ": No output directory specified.");
+ ERR_PRINT(generate_cpp_glue_option + ": No output directory specified.");
+ exit_godot = true;
}
--options_left;
@@ -3165,33 +3720,13 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
}
if (glue_dir_path.length() || cs_dir_path.length() || cpp_dir_path.length()) {
- BindingsGenerator bindings_generator;
- bindings_generator.set_log_print_enabled(true);
-
- if (!bindings_generator.initialized) {
- ERR_PRINTS("Failed to initialize the bindings generator");
- ::exit(0);
- }
-
- if (glue_dir_path.length()) {
- if (bindings_generator.generate_glue(glue_dir_path) != OK)
- ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue.");
-
- if (bindings_generator.generate_cs_api(glue_dir_path.plus_file(API_SOLUTION_NAME)) != OK)
- ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C# API.");
- }
-
- if (cs_dir_path.length()) {
- if (bindings_generator.generate_cs_api(cs_dir_path) != OK)
- ERR_PRINTS(generate_cs_glue_option + ": Failed to generate the C# API.");
- }
-
- if (cpp_dir_path.length()) {
- if (bindings_generator.generate_glue(cpp_dir_path) != OK)
- ERR_PRINTS(generate_cpp_glue_option + ": Failed to generate the C++ glue.");
- }
+ handle_cmdline_options(glue_dir_path, cs_dir_path, cpp_dir_path);
+ exit_godot = true;
+ }
+ if (exit_godot) {
// Exit once done
+ Main::cleanup(true);
::exit(0);
}
}
diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h
index d3a8937313..48c0e02723 100644
--- a/modules/mono/editor/bindings_generator.h
+++ b/modules/mono/editor/bindings_generator.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,21 +31,21 @@
#ifndef BINDINGS_GENERATOR_H
#define BINDINGS_GENERATOR_H
-#include "core/class_db.h"
-#include "core/string_builder.h"
-#include "editor/doc/doc_data.h"
+#include "core/doc_data.h"
+#include "core/object/class_db.h"
+#include "core/string/string_builder.h"
+#include "editor/doc_tools.h"
#include "editor/editor_help.h"
#if defined(DEBUG_METHODS_ENABLED) && defined(TOOLS_ENABLED)
-#include "core/ustring.h"
+#include "core/string/ustring.h"
class BindingsGenerator {
-
struct ConstantInterface {
String name;
String proxy_name;
- int value;
+ int value = 0;
const DocData::ConstantDoc *const_doc;
ConstantInterface() {}
@@ -75,7 +75,7 @@ class BindingsGenerator {
struct PropertyInterface {
StringName cname;
String proxy_name;
- int index;
+ int index = 0;
StringName setter;
StringName getter;
@@ -85,16 +85,12 @@ class BindingsGenerator {
struct TypeReference {
StringName cname;
- bool is_enum;
+ bool is_enum = false;
- TypeReference() :
- is_enum(false) {
- }
+ TypeReference() {}
TypeReference(const StringName &p_cname) :
- cname(p_cname),
- is_enum(false) {
- }
+ cname(p_cname) {}
};
struct ArgumentInterface {
@@ -107,12 +103,18 @@ class BindingsGenerator {
TypeReference type;
String name;
+
+ Variant def_param_value;
+ DefaultParamMode def_param_mode = CONSTANT;
+
+ /**
+ * Determines the expression for the parameter default value.
+ * Formatting elements:
+ * %0 or %s: [cs_type] of the argument type
+ */
String default_argument;
- DefaultParamMode def_param_mode;
- ArgumentInterface() {
- def_param_mode = CONSTANT;
- }
+ ArgumentInterface() {}
};
struct MethodInterface {
@@ -132,19 +134,19 @@ class BindingsGenerator {
/**
* Determines if the method has a variable number of arguments (VarArg)
*/
- bool is_vararg;
+ bool is_vararg = false;
/**
* Virtual methods ("virtual" as defined by the Godot API) are methods that by default do nothing,
* but can be overridden by the user to add custom functionality.
* e.g.: _ready, _process, etc.
*/
- bool is_virtual;
+ bool is_virtual = false;
/**
* Determines if the call should fallback to Godot's object.Call(string, params) in C#.
*/
- bool requires_object_call;
+ bool requires_object_call = false;
/**
* Determines if the method visibility is 'internal' (visible only to files in the same assembly).
@@ -152,27 +154,43 @@ class BindingsGenerator {
* but are required by properties as getters or setters.
* Methods that are not meant to be exposed are those that begin with underscore and are not virtual.
*/
- bool is_internal;
+ bool is_internal = false;
List<ArgumentInterface> arguments;
- const DocData::MethodDoc *method_doc;
+ const DocData::MethodDoc *method_doc = nullptr;
- bool is_deprecated;
+ bool is_deprecated = false;
String deprecation_message;
void add_argument(const ArgumentInterface &argument) {
arguments.push_back(argument);
}
- MethodInterface() {
- is_vararg = false;
- is_virtual = false;
- requires_object_call = false;
- is_internal = false;
- method_doc = NULL;
- is_deprecated = false;
+ MethodInterface() {}
+ };
+
+ struct SignalInterface {
+ String name;
+ StringName cname;
+
+ /**
+ * Name of the C# method
+ */
+ String proxy_name;
+
+ List<ArgumentInterface> arguments;
+
+ const DocData::MethodDoc *method_doc = nullptr;
+
+ bool is_deprecated = false;
+ String deprecation_message;
+
+ void add_argument(const ArgumentInterface &argument) {
+ arguments.push_back(argument);
}
+
+ SignalInterface() {}
};
struct TypeInterface {
@@ -193,26 +211,26 @@ class BindingsGenerator {
*/
String proxy_name;
- ClassDB::APIType api_type;
+ ClassDB::APIType api_type = ClassDB::API_NONE;
- bool is_enum;
- bool is_object_type;
- bool is_singleton;
- bool is_reference;
+ bool is_enum = false;
+ bool is_object_type = false;
+ bool is_singleton = false;
+ bool is_ref_counted = false;
/**
* Used only by Object-derived types.
* Determines if this type is not abstract (incomplete).
* e.g.: CanvasItem cannot be instantiated.
*/
- bool is_instantiable;
+ bool is_instantiable = false;
/**
* Used only by Object-derived types.
* Determines if the C# class owns the native handle and must free it somehow when disposed.
- * e.g.: Reference types must notify when the C# instance is disposed, for proper refcounting.
+ * e.g.: RefCounted types must notify when the C# instance is disposed, for proper refcounting.
*/
- bool memory_own;
+ bool memory_own = false;
/**
* This must be set to true for any struct bigger than 32-bits. Those cannot be passed/returned by value
@@ -220,7 +238,7 @@ class BindingsGenerator {
* In this case, [c_out] and [cs_out] must have a different format, explained below.
* The Mono IL interpreter icall trampolines don't support passing structs bigger than 32-bits by value (at least not on WASM).
*/
- bool ret_as_byref_arg;
+ bool ret_as_byref_arg = false;
// !! The comments of the following fields make reference to other fields via square brackets, e.g.: [field_name]
// !! When renaming those fields, make sure to rename their references in the comments
@@ -247,7 +265,7 @@ class BindingsGenerator {
* Formatting elements:
* %0 or %s: name of the parameter
*/
- String c_arg_in;
+ String c_arg_in = "%s";
/**
* One or more statements that determine how a variable of this type is returned from a function.
@@ -277,7 +295,7 @@ class BindingsGenerator {
* VarArg (fictitious type to represent variable arguments): Array
* float: double (because ptrcall only supports double)
* int: int64_t (because ptrcall only supports int64_t and uint64_t)
- * Reference types override this for the type of the return variable: Ref<Reference>
+ * RefCounted types override this for the type of the return variable: Ref<RefCounted>
*/
String c_type;
@@ -330,38 +348,52 @@ class BindingsGenerator {
*/
String im_type_out;
- const DocData::ClassDoc *class_doc;
+ const DocData::ClassDoc *class_doc = nullptr;
List<ConstantInterface> constants;
List<EnumInterface> enums;
List<PropertyInterface> properties;
List<MethodInterface> methods;
+ List<SignalInterface> signals_;
const MethodInterface *find_method_by_name(const StringName &p_cname) const {
for (const List<MethodInterface>::Element *E = methods.front(); E; E = E->next()) {
- if (E->get().cname == p_cname)
+ if (E->get().cname == p_cname) {
return &E->get();
+ }
}
- return NULL;
+ return nullptr;
}
const PropertyInterface *find_property_by_name(const StringName &p_cname) const {
for (const List<PropertyInterface>::Element *E = properties.front(); E; E = E->next()) {
- if (E->get().cname == p_cname)
+ if (E->get().cname == p_cname) {
return &E->get();
+ }
}
- return NULL;
+ return nullptr;
}
const PropertyInterface *find_property_by_proxy_name(const String &p_proxy_name) const {
for (const List<PropertyInterface>::Element *E = properties.front(); E; E = E->next()) {
- if (E->get().proxy_name == p_proxy_name)
+ if (E->get().proxy_name == p_proxy_name) {
+ return &E->get();
+ }
+ }
+
+ return nullptr;
+ }
+
+ const MethodInterface *find_method_by_proxy_name(const String &p_proxy_name) const {
+ for (const List<MethodInterface>::Element *E = methods.front(); E; E = E->next()) {
+ if (E->get().proxy_name == p_proxy_name) {
return &E->get();
+ }
}
- return NULL;
+ return nullptr;
}
private:
@@ -440,24 +472,7 @@ class BindingsGenerator {
r_enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[r_enum_itype.proxy_name];
}
- TypeInterface() {
-
- api_type = ClassDB::API_NONE;
-
- is_enum = false;
- is_object_type = false;
- is_singleton = false;
- is_reference = false;
- is_instantiable = false;
-
- memory_own = false;
-
- ret_as_byref_arg = false;
-
- c_arg_in = "%s";
-
- class_doc = NULL;
- }
+ TypeInterface() {}
};
struct InternalCall {
@@ -465,7 +480,7 @@ class BindingsGenerator {
String im_type_out; // Return type for the C# method declaration. Also used as companion of [unique_siq]
String im_sig; // Signature for the C# method declaration
String unique_sig; // Unique signature to avoid duplicates in containers
- bool editor_only;
+ bool editor_only = false;
InternalCall() {}
@@ -490,8 +505,8 @@ class BindingsGenerator {
}
};
- bool log_print_enabled;
- bool initialized;
+ bool log_print_enabled = true;
+ bool initialized = false;
OrderedHashMap<StringName, TypeInterface> obj_types;
@@ -510,59 +525,75 @@ class BindingsGenerator {
List<InternalCall> core_custom_icalls;
List<InternalCall> editor_custom_icalls;
- Map<StringName, List<StringName> > blacklisted_methods;
+ Map<StringName, List<StringName>> blacklisted_methods;
void _initialize_blacklisted_methods();
struct NameCache {
- StringName type_void;
- StringName type_Array;
- StringName type_Dictionary;
- StringName type_Variant;
- StringName type_VarArg;
- StringName type_Object;
- StringName type_Reference;
- StringName type_RID;
- StringName type_String;
- StringName type_at_GlobalScope;
- StringName enum_Error;
-
- StringName type_sbyte;
- StringName type_short;
- StringName type_int;
- StringName type_long;
- StringName type_byte;
- StringName type_ushort;
- StringName type_uint;
- StringName type_ulong;
- StringName type_float;
- StringName type_double;
-
- NameCache() {
- type_void = StaticCString::create("void");
- type_Array = StaticCString::create("Array");
- type_Dictionary = StaticCString::create("Dictionary");
- type_Variant = StaticCString::create("Variant");
- type_VarArg = StaticCString::create("VarArg");
- type_Object = StaticCString::create("Object");
- type_Reference = StaticCString::create("Reference");
- type_RID = StaticCString::create("RID");
- type_String = StaticCString::create("String");
- type_at_GlobalScope = StaticCString::create("@GlobalScope");
- enum_Error = StaticCString::create("Error");
-
- type_sbyte = StaticCString::create("sbyte");
- type_short = StaticCString::create("short");
- type_int = StaticCString::create("int");
- type_long = StaticCString::create("long");
- type_byte = StaticCString::create("byte");
- type_ushort = StaticCString::create("ushort");
- type_uint = StaticCString::create("uint");
- type_ulong = StaticCString::create("ulong");
- type_float = StaticCString::create("float");
- type_double = StaticCString::create("double");
+ StringName type_void = StaticCString::create("void");
+ StringName type_Variant = StaticCString::create("Variant");
+ StringName type_VarArg = StaticCString::create("VarArg");
+ StringName type_Object = StaticCString::create("Object");
+ StringName type_RefCounted = StaticCString::create("RefCounted");
+ StringName type_RID = StaticCString::create("RID");
+ StringName type_String = StaticCString::create("String");
+ StringName type_StringName = StaticCString::create("StringName");
+ StringName type_NodePath = StaticCString::create("NodePath");
+ StringName type_at_GlobalScope = StaticCString::create("@GlobalScope");
+ StringName enum_Error = StaticCString::create("Error");
+
+ StringName type_sbyte = StaticCString::create("sbyte");
+ StringName type_short = StaticCString::create("short");
+ StringName type_int = StaticCString::create("int");
+ StringName type_byte = StaticCString::create("byte");
+ StringName type_ushort = StaticCString::create("ushort");
+ StringName type_uint = StaticCString::create("uint");
+ StringName type_long = StaticCString::create("long");
+ StringName type_ulong = StaticCString::create("ulong");
+
+ StringName type_bool = StaticCString::create("bool");
+ StringName type_float = StaticCString::create("float");
+ StringName type_double = StaticCString::create("double");
+
+ StringName type_Vector2 = StaticCString::create("Vector2");
+ StringName type_Rect2 = StaticCString::create("Rect2");
+ StringName type_Vector3 = StaticCString::create("Vector3");
+
+ // Object not included as it must be checked for all derived classes
+ static constexpr int nullable_types_count = 17;
+ StringName nullable_types[nullable_types_count] = {
+ type_String,
+ type_StringName,
+ type_NodePath,
+
+ StaticCString::create(_STR(Array)),
+ StaticCString::create(_STR(Dictionary)),
+ StaticCString::create(_STR(Callable)),
+ StaticCString::create(_STR(Signal)),
+
+ StaticCString::create(_STR(PackedByteArray)),
+ StaticCString::create(_STR(PackedInt32Array)),
+ StaticCString::create(_STR(PackedInt64rray)),
+ StaticCString::create(_STR(PackedFloat32Array)),
+ StaticCString::create(_STR(PackedFloat64Array)),
+ StaticCString::create(_STR(PackedStringArray)),
+ StaticCString::create(_STR(PackedVector2Array)),
+ StaticCString::create(_STR(PackedVector3Array)),
+ StaticCString::create(_STR(PackedColorArray)),
+ };
+
+ bool is_nullable_type(const StringName &p_type) const {
+ for (int i = 0; i < nullable_types_count; i++) {
+ if (p_type == nullable_types[i]) {
+ return true;
+ }
+ }
+
+ return false;
}
+ NameCache() {}
+
private:
NameCache(const NameCache &);
NameCache &operator=(const NameCache &);
@@ -573,28 +604,32 @@ class BindingsGenerator {
const List<InternalCall>::Element *find_icall_by_name(const String &p_name, const List<InternalCall> &p_list) {
const List<InternalCall>::Element *it = p_list.front();
while (it) {
- if (it->get().name == p_name) return it;
+ if (it->get().name == p_name) {
+ return it;
+ }
it = it->next();
}
- return NULL;
+ return nullptr;
}
const ConstantInterface *find_constant_by_name(const String &p_name, const List<ConstantInterface> &p_constants) const {
for (const List<ConstantInterface>::Element *E = p_constants.front(); E; E = E->next()) {
- if (E->get().name == p_name)
+ if (E->get().name == p_name) {
return &E->get();
+ }
}
- return NULL;
+ return nullptr;
}
inline String get_unique_sig(const TypeInterface &p_type) {
- if (p_type.is_reference)
+ if (p_type.is_ref_counted) {
return "Ref";
- else if (p_type.is_object_type)
+ } else if (p_type.is_object_type) {
return "Obj";
- else if (p_type.is_enum)
+ } else if (p_type.is_enum) {
return "int";
+ }
return p_type.name;
}
@@ -613,6 +648,7 @@ class BindingsGenerator {
StringName _get_float_type_name_from_meta(GodotTypeInfo::Metadata p_meta);
bool _arg_default_value_from_variant(const Variant &p_val, ArgumentInterface &r_iarg);
+ bool _arg_default_value_is_assignable_to_type(const Variant &p_val, const TypeInterface &p_arg_type);
bool _populate_object_type_interfaces();
void _populate_builtin_type_interfaces();
@@ -623,7 +659,9 @@ class BindingsGenerator {
Error _generate_cs_property(const TypeInterface &p_itype, const PropertyInterface &p_iprop, StringBuilder &p_output);
Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output);
+ Error _generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output);
+ void _generate_array_extensions(StringBuilder &p_output);
void _generate_global_constants(StringBuilder &p_output);
Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, StringBuilder &p_output);
@@ -649,9 +687,7 @@ public:
static void handle_cmdline_args(const List<String> &p_cmdline_args);
- BindingsGenerator() :
- log_print_enabled(true),
- initialized(false) {
+ BindingsGenerator() {
_initialize();
}
};
diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp
new file mode 100644
index 0000000000..bbfba83e6f
--- /dev/null
+++ b/modules/mono/editor/code_completion.cpp
@@ -0,0 +1,261 @@
+/*************************************************************************/
+/* code_completion.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "code_completion.h"
+
+#include "core/config/project_settings.h"
+#include "editor/editor_file_system.h"
+#include "editor/editor_settings.h"
+#include "scene/gui/control.h"
+#include "scene/main/node.h"
+
+namespace gdmono {
+
+// Almost everything here is taken from functions used by GDScript for code completion, adapted for C#.
+
+_FORCE_INLINE_ String quoted(const String &p_str) {
+ return "\"" + p_str + "\"";
+}
+
+void _add_nodes_suggestions(const Node *p_base, const Node *p_node, PackedStringArray &r_suggestions) {
+ if (p_node != p_base && !p_node->get_owner()) {
+ return;
+ }
+
+ String path_relative_to_orig = p_base->get_path_to(p_node);
+
+ r_suggestions.push_back(quoted(path_relative_to_orig));
+
+ for (int i = 0; i < p_node->get_child_count(); i++) {
+ _add_nodes_suggestions(p_base, p_node->get_child(i), r_suggestions);
+ }
+}
+
+Node *_find_node_for_script(Node *p_base, Node *p_current, const Ref<Script> &p_script) {
+ if (p_current->get_owner() != p_base && p_base != p_current) {
+ return nullptr;
+ }
+
+ Ref<Script> c = p_current->get_script();
+
+ if (c == p_script) {
+ return p_current;
+ }
+
+ for (int i = 0; i < p_current->get_child_count(); i++) {
+ Node *found = _find_node_for_script(p_base, p_current->get_child(i), p_script);
+ if (found) {
+ return found;
+ }
+ }
+
+ return nullptr;
+}
+
+void _get_directory_contents(EditorFileSystemDirectory *p_dir, PackedStringArray &r_suggestions) {
+ for (int i = 0; i < p_dir->get_file_count(); i++) {
+ r_suggestions.push_back(quoted(p_dir->get_file_path(i)));
+ }
+
+ for (int i = 0; i < p_dir->get_subdir_count(); i++) {
+ _get_directory_contents(p_dir->get_subdir(i), r_suggestions);
+ }
+}
+
+Node *_try_find_owner_node_in_tree(const Ref<Script> p_script) {
+ SceneTree *tree = SceneTree::get_singleton();
+ if (!tree) {
+ return nullptr;
+ }
+ Node *base = tree->get_edited_scene_root();
+ if (base) {
+ base = _find_node_for_script(base, base, p_script);
+ }
+ return base;
+}
+
+PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_script_file) {
+ PackedStringArray suggestions;
+
+ switch (p_kind) {
+ case CompletionKind::INPUT_ACTIONS: {
+ List<PropertyInfo> project_props;
+ ProjectSettings::get_singleton()->get_property_list(&project_props);
+
+ for (List<PropertyInfo>::Element *E = project_props.front(); E; E = E->next()) {
+ const PropertyInfo &prop = E->get();
+
+ if (!prop.name.begins_with("input/")) {
+ continue;
+ }
+
+ String name = prop.name.substr(prop.name.find("/") + 1, prop.name.length());
+ suggestions.push_back(quoted(name));
+ }
+ } break;
+ case CompletionKind::NODE_PATHS: {
+ {
+ // AutoLoads
+ Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
+
+ for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) {
+ const ProjectSettings::AutoloadInfo &info = E->value();
+ suggestions.push_back(quoted("/root/" + String(info.name)));
+ }
+ }
+
+ {
+ // Current edited scene tree
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+ Node *base = _try_find_owner_node_in_tree(script);
+ if (base) {
+ _add_nodes_suggestions(base, base, suggestions);
+ }
+ }
+ } break;
+ case CompletionKind::RESOURCE_PATHS: {
+ if (bool(EditorSettings::get_singleton()->get("text_editor/completion/complete_file_paths"))) {
+ _get_directory_contents(EditorFileSystem::get_singleton()->get_filesystem(), suggestions);
+ }
+ } break;
+ case CompletionKind::SCENE_PATHS: {
+ DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ List<String> directories;
+ directories.push_back(dir_access->get_current_dir());
+
+ while (!directories.is_empty()) {
+ dir_access->change_dir(directories.back()->get());
+ directories.pop_back();
+
+ dir_access->list_dir_begin();
+ String filename = dir_access->get_next();
+
+ while (filename != "") {
+ if (filename == "." || filename == "..") {
+ filename = dir_access->get_next();
+ continue;
+ }
+
+ if (dir_access->dir_exists(filename)) {
+ directories.push_back(dir_access->get_current_dir().plus_file(filename));
+ } else if (filename.ends_with(".tscn") || filename.ends_with(".scn")) {
+ suggestions.push_back(quoted(dir_access->get_current_dir().plus_file(filename)));
+ }
+
+ filename = dir_access->get_next();
+ }
+ }
+ } break;
+ case CompletionKind::SHADER_PARAMS: {
+ print_verbose("Shared params completion for C# not implemented.");
+ } break;
+ case CompletionKind::SIGNALS: {
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+
+ List<MethodInfo> signals;
+ script->get_script_signal_list(&signals);
+
+ StringName native = script->get_instance_base_type();
+ if (native != StringName()) {
+ ClassDB::get_signal_list(native, &signals, /* p_no_inheritance: */ false);
+ }
+
+ for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) {
+ const String &signal = E->get().name;
+ suggestions.push_back(quoted(signal));
+ }
+ } break;
+ case CompletionKind::THEME_COLORS: {
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+ Node *base = _try_find_owner_node_in_tree(script);
+ if (base && Object::cast_to<Control>(base)) {
+ List<StringName> sn;
+ Theme::get_default()->get_color_list(base->get_class(), &sn);
+
+ for (List<StringName>::Element *E = sn.front(); E; E = E->next()) {
+ suggestions.push_back(quoted(E->get()));
+ }
+ }
+ } break;
+ case CompletionKind::THEME_CONSTANTS: {
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+ Node *base = _try_find_owner_node_in_tree(script);
+ if (base && Object::cast_to<Control>(base)) {
+ List<StringName> sn;
+ Theme::get_default()->get_constant_list(base->get_class(), &sn);
+
+ for (List<StringName>::Element *E = sn.front(); E; E = E->next()) {
+ suggestions.push_back(quoted(E->get()));
+ }
+ }
+ } break;
+ case CompletionKind::THEME_FONTS: {
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+ Node *base = _try_find_owner_node_in_tree(script);
+ if (base && Object::cast_to<Control>(base)) {
+ List<StringName> sn;
+ Theme::get_default()->get_font_list(base->get_class(), &sn);
+
+ for (List<StringName>::Element *E = sn.front(); E; E = E->next()) {
+ suggestions.push_back(quoted(E->get()));
+ }
+ }
+ } break;
+ case CompletionKind::THEME_FONT_SIZES: {
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+ Node *base = _try_find_owner_node_in_tree(script);
+ if (base && Object::cast_to<Control>(base)) {
+ List<StringName> sn;
+ Theme::get_default()->get_font_size_list(base->get_class(), &sn);
+
+ for (List<StringName>::Element *E = sn.front(); E; E = E->next()) {
+ suggestions.push_back(quoted(E->get()));
+ }
+ }
+ } break;
+ case CompletionKind::THEME_STYLES: {
+ Ref<Script> script = ResourceLoader::load(p_script_file.simplify_path());
+ Node *base = _try_find_owner_node_in_tree(script);
+ if (base && Object::cast_to<Control>(base)) {
+ List<StringName> sn;
+ Theme::get_default()->get_stylebox_list(base->get_class(), &sn);
+
+ for (List<StringName>::Element *E = sn.front(); E; E = E->next()) {
+ suggestions.push_back(quoted(E->get()));
+ }
+ }
+ } break;
+ default:
+ ERR_FAIL_V_MSG(suggestions, "Invalid completion kind.");
+ }
+
+ return suggestions;
+}
+} // namespace gdmono
diff --git a/modules/mono/glue/rid_glue.h b/modules/mono/editor/code_completion.h
index 506d715451..7f7521672b 100644
--- a/modules/mono/glue/rid_glue.h
+++ b/modules/mono/editor/code_completion.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* rid_glue.h */
+/* code_completion.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,26 +28,29 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef RID_GLUE_H
-#define RID_GLUE_H
+#ifndef CODE_COMPLETION_H
+#define CODE_COMPLETION_H
-#ifdef MONO_GLUE_ENABLED
+#include "core/string/ustring.h"
+#include "core/variant/variant.h"
-#include "core/object.h"
-#include "core/rid.h"
+namespace gdmono {
-#include "../mono_gd/gd_mono_marshal.h"
+enum class CompletionKind {
+ INPUT_ACTIONS = 0,
+ NODE_PATHS,
+ RESOURCE_PATHS,
+ SCENE_PATHS,
+ SHADER_PARAMS,
+ SIGNALS,
+ THEME_COLORS,
+ THEME_CONSTANTS,
+ THEME_FONTS,
+ THEME_FONT_SIZES,
+ THEME_STYLES
+};
-RID *godot_icall_RID_Ctor(Object *p_from);
+PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_script_file);
+} // namespace gdmono
-void godot_icall_RID_Dtor(RID *p_ptr);
-
-uint32_t godot_icall_RID_get_id(RID *p_ptr);
-
-// Register internal calls
-
-void godot_register_rid_icalls();
-
-#endif // MONO_GLUE_ENABLED
-
-#endif // RID_GLUE_H
+#endif // CODE_COMPLETION_H
diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp
deleted file mode 100644
index 872f45ba91..0000000000
--- a/modules/mono/editor/csharp_project.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/*************************************************************************/
-/* csharp_project.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "csharp_project.h"
-
-#include "core/io/json.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/os/os.h"
-#include "core/project_settings.h"
-
-#include "../csharp_script.h"
-#include "../mono_gd/gd_mono_class.h"
-#include "../mono_gd/gd_mono_marshal.h"
-#include "../utils/string_utils.h"
-#include "script_class_parser.h"
-
-namespace CSharpProject {
-
-void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) {
-
- if (!GLOBAL_DEF("mono/project/auto_update_project", true))
- return;
-
- GDMonoAssembly *tools_project_editor_assembly = GDMono::get_singleton()->get_tools_project_editor_assembly();
-
- GDMonoClass *klass = tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ProjectUtils");
-
- Variant project_path = p_project_path;
- Variant item_type = p_item_type;
- Variant include = p_include;
- const Variant *args[3] = { &project_path, &item_type, &include };
- MonoException *exc = NULL;
- klass->get_method("AddItemToProjectChecked", 3)->invoke(NULL, args, &exc);
-
- if (exc) {
- GDMonoUtils::debug_print_unhandled_exception(exc);
- ERR_FAIL();
- }
-}
-
-} // namespace CSharpProject
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index c8d20e80be..21efd58938 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -36,10 +36,10 @@
#include "core/os/os.h"
#include "core/version.h"
+#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
#include "editor/plugins/script_editor_plugin.h"
-#include "editor/script_editor_debugger.h"
#include "main/main.h"
#include "../csharp_script.h"
@@ -47,9 +47,8 @@
#include "../godotsharp_dirs.h"
#include "../mono_gd/gd_mono_marshal.h"
#include "../utils/osx_utils.h"
-#include "bindings_generator.h"
+#include "code_completion.h"
#include "godotsharp_export.h"
-#include "script_class_parser.h"
MonoString *godot_icall_GodotSharpDirs_ResDataDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir());
@@ -95,7 +94,7 @@ MonoString *godot_icall_GodotSharpDirs_MonoSolutionsDir() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_solutions_dir());
#else
- return NULL;
+ return nullptr;
#endif
}
@@ -103,7 +102,7 @@ MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_build_logs_dir());
#else
- return NULL;
+ return nullptr;
#endif
}
@@ -111,7 +110,7 @@ MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path());
#else
- return NULL;
+ return nullptr;
#endif
}
@@ -119,7 +118,7 @@ MonoString *godot_icall_GodotSharpDirs_ProjectCsProjPath() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_csproj_path());
#else
- return NULL;
+ return nullptr;
#endif
}
@@ -127,7 +126,7 @@ MonoString *godot_icall_GodotSharpDirs_DataEditorToolsDir() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_tools_dir());
#else
- return NULL;
+ return nullptr;
#endif
}
@@ -135,7 +134,7 @@ MonoString *godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_prebuilt_api_dir());
#else
- return NULL;
+ return nullptr;
#endif
}
@@ -151,7 +150,7 @@ MonoString *godot_icall_GodotSharpDirs_DataMonoBinDir() {
#ifdef WINDOWS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_bin_dir());
#else
- return NULL;
+ return nullptr;
#endif
}
@@ -172,73 +171,14 @@ MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_st
return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh);
}
-BindingsGenerator *godot_icall_BindingsGenerator_Ctor() {
- return memnew(BindingsGenerator);
-}
-
-void godot_icall_BindingsGenerator_Dtor(BindingsGenerator *p_handle) {
- memdelete(p_handle);
-}
-
-MonoBoolean godot_icall_BindingsGenerator_LogPrintEnabled(BindingsGenerator *p_handle) {
- return p_handle->is_log_print_enabled();
-}
-
-void godot_icall_BindingsGenerator_SetLogPrintEnabled(BindingsGenerator p_handle, MonoBoolean p_enabled) {
- p_handle.set_log_print_enabled(p_enabled);
-}
-
-int32_t godot_icall_BindingsGenerator_GenerateCsApi(BindingsGenerator *p_handle, MonoString *p_output_dir) {
- String output_dir = GDMonoMarshal::mono_string_to_godot(p_output_dir);
- return p_handle->generate_cs_api(output_dir);
-}
-
-uint32_t godot_icall_BindingsGenerator_Version() {
- return BindingsGenerator::get_version();
-}
-
-uint32_t godot_icall_BindingsGenerator_CsGlueVersion() {
- return CS_GLUE_VERSION;
-}
-
-int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes, MonoString **r_error_str) {
- *r_error_str = NULL;
-
- String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath);
-
- ScriptClassParser scp;
- Error err = scp.parse_file(filepath);
- if (err == OK) {
- Array classes = GDMonoMarshal::mono_object_to_variant(p_classes);
- const Vector<ScriptClassParser::ClassDecl> &class_decls = scp.get_classes();
-
- for (int i = 0; i < class_decls.size(); i++) {
- const ScriptClassParser::ClassDecl &classDecl = class_decls[i];
-
- Dictionary classDeclDict;
- classDeclDict["name"] = classDecl.name;
- classDeclDict["namespace"] = classDecl.namespace_;
- classDeclDict["nested"] = classDecl.nested;
- classDeclDict["base_count"] = classDecl.base.size();
- classes.push_back(classDeclDict);
- }
- } else {
- String error_str = scp.get_error();
- if (!error_str.empty()) {
- *r_error_str = GDMonoMarshal::mono_string_from_godot(error_str);
- }
- }
- return err;
-}
-
-uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_dependencies,
- MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_dependencies) {
- Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_dependencies);
+uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_assemblies,
+ MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_assembly_dependencies) {
+ Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_assemblies);
String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config);
String custom_bcl_dir = GDMonoMarshal::mono_string_to_godot(p_custom_bcl_dir);
- Dictionary dependencies = GDMonoMarshal::mono_object_to_variant(r_dependencies);
+ Dictionary assembly_dependencies = GDMonoMarshal::mono_object_to_variant(r_assembly_dependencies);
- return GodotSharpExport::get_exported_assembly_dependencies(initial_dependencies, build_config, custom_bcl_dir, dependencies);
+ return GodotSharpExport::get_exported_assembly_dependencies(initial_dependencies, build_config, custom_bcl_dir, assembly_dependencies);
}
MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) {
@@ -305,8 +245,8 @@ void godot_icall_Internal_ReloadAssemblies(MonoBoolean p_soft_reload) {
#endif
}
-void godot_icall_Internal_ScriptEditorDebuggerReloadScripts() {
- ScriptEditor::get_singleton()->get_debugger()->reload_scripts();
+void godot_icall_Internal_EditorDebuggerNodeReloadScripts() {
+ EditorDebuggerNode::get_singleton()->reload_scripts();
}
MonoBoolean godot_icall_Internal_ScriptEditorEdit(MonoObject *p_resource, int32_t p_line, int32_t p_col, MonoBoolean p_grab_focus) {
@@ -318,24 +258,12 @@ void godot_icall_Internal_EditorNodeShowScriptScreen() {
EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT);
}
-MonoObject *godot_icall_Internal_GetScriptsMetadataOrNothing(MonoReflectionType *p_dict_reftype) {
- Dictionary maybe_metadata = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing();
-
- MonoType *dict_type = mono_reflection_type_get_type(p_dict_reftype);
-
- uint32_t type_encoding = mono_type_get_type(dict_type);
- MonoClass *type_class_raw = mono_class_from_mono_type(dict_type);
- GDMonoClass *type_class = GDMono::get_singleton()->get_class(type_class_raw);
-
- return GDMonoMarshal::variant_to_mono_object(maybe_metadata, ManagedType(type_encoding, type_class));
-}
-
MonoString *godot_icall_Internal_MonoWindowsInstallRoot() {
#ifdef WINDOWS_ENABLED
String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir;
return GDMonoMarshal::mono_string_from_godot(install_root_dir);
#else
- return NULL;
+ return nullptr;
#endif
}
@@ -348,12 +276,18 @@ void godot_icall_Internal_EditorRunStop() {
}
void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() {
- ScriptEditorDebugger *sed = ScriptEditor::get_singleton()->get_debugger();
- if (sed) {
- sed->reload_scripts();
+ EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton();
+ if (ed) {
+ ed->reload_scripts();
}
}
+MonoArray *godot_icall_Internal_CodeCompletionRequest(int32_t p_kind, MonoString *p_script_file) {
+ String script_file = GDMonoMarshal::mono_string_to_godot(p_script_file);
+ PackedStringArray suggestions = gdmono::get_code_completion((gdmono::CompletionKind)p_kind, script_file);
+ return GDMonoMarshal::PackedStringArray_to_mono_array(suggestions);
+}
+
float godot_icall_Globals_EditorScale() {
return EDSCALE;
}
@@ -392,76 +326,63 @@ MonoBoolean godot_icall_Utils_OS_UnixFileHasExecutableAccess(MonoString *p_file_
}
void register_editor_internal_calls() {
-
// GodotSharpDirs
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", (void *)godot_icall_GodotSharpDirs_ResDataDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", (void *)godot_icall_GodotSharpDirs_ResMetadataDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesBaseDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", (void *)godot_icall_GodotSharpDirs_ResConfigDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", (void *)godot_icall_GodotSharpDirs_ResTempDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", (void *)godot_icall_GodotSharpDirs_MonoUserDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", (void *)godot_icall_GodotSharpDirs_MonoLogsDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", (void *)godot_icall_GodotSharpDirs_MonoSolutionsDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", (void *)godot_icall_GodotSharpDirs_BuildLogsDirs);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", (void *)godot_icall_GodotSharpDirs_ProjectSlnPath);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", (void *)godot_icall_GodotSharpDirs_ProjectCsProjPath);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", (void *)godot_icall_GodotSharpDirs_DataEditorToolsDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", (void *)godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", (void *)godot_icall_GodotSharpDirs_DataMonoEtcDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", (void *)godot_icall_GodotSharpDirs_DataMonoLibDir);
- mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", (void *)godot_icall_GodotSharpDirs_DataMonoBinDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", godot_icall_GodotSharpDirs_ResDataDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", godot_icall_GodotSharpDirs_ResMetadataDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", godot_icall_GodotSharpDirs_ResAssembliesBaseDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", godot_icall_GodotSharpDirs_ResAssembliesDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", godot_icall_GodotSharpDirs_ResConfigDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", godot_icall_GodotSharpDirs_ResTempDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", godot_icall_GodotSharpDirs_ResTempAssembliesDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", godot_icall_GodotSharpDirs_MonoUserDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", godot_icall_GodotSharpDirs_MonoLogsDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", godot_icall_GodotSharpDirs_MonoSolutionsDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", godot_icall_GodotSharpDirs_BuildLogsDirs);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", godot_icall_GodotSharpDirs_ProjectSlnPath);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", godot_icall_GodotSharpDirs_ProjectCsProjPath);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", godot_icall_GodotSharpDirs_DataEditorToolsDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", godot_icall_GodotSharpDirs_DataMonoEtcDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", godot_icall_GodotSharpDirs_DataMonoLibDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", godot_icall_GodotSharpDirs_DataMonoBinDir);
// EditorProgress
- mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", (void *)godot_icall_EditorProgress_Create);
- mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", (void *)godot_icall_EditorProgress_Dispose);
- mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", (void *)godot_icall_EditorProgress_Step);
-
- // BiningsGenerator
- mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Ctor", (void *)godot_icall_BindingsGenerator_Ctor);
- mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Dtor", (void *)godot_icall_BindingsGenerator_Dtor);
- mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_LogPrintEnabled", (void *)godot_icall_BindingsGenerator_LogPrintEnabled);
- mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_SetLogPrintEnabled", (void *)godot_icall_BindingsGenerator_SetLogPrintEnabled);
- mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_GenerateCsApi", (void *)godot_icall_BindingsGenerator_GenerateCsApi);
- mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Version", (void *)godot_icall_BindingsGenerator_Version);
- mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_CsGlueVersion", (void *)godot_icall_BindingsGenerator_CsGlueVersion);
-
- // ScriptClassParser
- mono_add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", (void *)godot_icall_ScriptClassParser_ParseFile);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", godot_icall_EditorProgress_Create);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", godot_icall_EditorProgress_Dispose);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", godot_icall_EditorProgress_Step);
// ExportPlugin
- mono_add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", (void *)godot_icall_ExportPlugin_GetExportedAssemblyDependencies);
+ GDMonoUtils::add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", godot_icall_ExportPlugin_GetExportedAssemblyDependencies);
// Internals
- mono_add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", (void *)godot_icall_Internal_UpdateApiAssembliesFromPrebuilt);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", (void *)godot_icall_Internal_FullTemplatesDir);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", (void *)godot_icall_Internal_SimplifyGodotPath);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", (void *)godot_icall_Internal_IsOsxAppBundleInstalled);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", (void *)godot_icall_Internal_GodotIs32Bits);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", (void *)godot_icall_Internal_GodotIsRealTDouble);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", (void *)godot_icall_Internal_GodotMainIteration);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", (void *)godot_icall_Internal_GetCoreApiHash);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", (void *)godot_icall_Internal_GetEditorApiHash);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", (void *)godot_icall_Internal_IsAssembliesReloadingNeeded);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", (void *)godot_icall_Internal_ReloadAssemblies);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebuggerReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebuggerReloadScripts);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", (void *)godot_icall_Internal_EditorRunPlay);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", (void *)godot_icall_Internal_EditorRunStop);
- mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", godot_icall_Internal_UpdateApiAssembliesFromPrebuilt);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", godot_icall_Internal_FullTemplatesDir);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", godot_icall_Internal_SimplifyGodotPath);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", godot_icall_Internal_IsOsxAppBundleInstalled);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", godot_icall_Internal_GodotIs32Bits);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", godot_icall_Internal_GodotIsRealTDouble);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", godot_icall_Internal_GodotMainIteration);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", godot_icall_Internal_GetCoreApiHash);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", godot_icall_Internal_GetEditorApiHash);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", godot_icall_Internal_IsAssembliesReloadingNeeded);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", godot_icall_Internal_ReloadAssemblies);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorDebuggerNodeReloadScripts", godot_icall_Internal_EditorDebuggerNodeReloadScripts);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", godot_icall_Internal_ScriptEditorEdit);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", godot_icall_Internal_EditorNodeShowScriptScreen);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", godot_icall_Internal_MonoWindowsInstallRoot);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", godot_icall_Internal_EditorRunPlay);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", godot_icall_Internal_EditorRunStop);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", godot_icall_Internal_ScriptEditorDebugger_ReloadScripts);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_CodeCompletionRequest", godot_icall_Internal_CodeCompletionRequest);
// Globals
- mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", (void *)godot_icall_Globals_EditorScale);
- mono_add_internal_call("GodotTools.Internals.Globals::internal_GlobalDef", (void *)godot_icall_Globals_GlobalDef);
- mono_add_internal_call("GodotTools.Internals.Globals::internal_EditorDef", (void *)godot_icall_Globals_EditorDef);
- mono_add_internal_call("GodotTools.Internals.Globals::internal_TTR", (void *)godot_icall_Globals_TTR);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", godot_icall_Globals_EditorScale);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_GlobalDef", godot_icall_Globals_GlobalDef);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorDef", godot_icall_Globals_EditorDef);
+ GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_TTR", godot_icall_Globals_TTR);
// Utils.OS
- mono_add_internal_call("GodotTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName);
- mono_add_internal_call("GodotTools.Utils.OS::UnixFileHasExecutableAccess", (void *)godot_icall_Utils_OS_UnixFileHasExecutableAccess);
+ GDMonoUtils::add_internal_call("GodotTools.Utils.OS::GetPlatformName", godot_icall_Utils_OS_GetPlatformName);
+ GDMonoUtils::add_internal_call("GodotTools.Utils.OS::UnixFileHasExecutableAccess", godot_icall_Utils_OS_UnixFileHasExecutableAccess);
}
diff --git a/modules/mono/editor/editor_internal_calls.h b/modules/mono/editor/editor_internal_calls.h
index ef4e639161..24080cd867 100644
--- a/modules/mono/editor/editor_internal_calls.h
+++ b/modules/mono/editor/editor_internal_calls.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp
index ce0b6ad0e6..54dbaebf38 100644
--- a/modules/mono/editor/godotsharp_export.cpp
+++ b/modules/mono/editor/godotsharp_export.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -32,77 +32,81 @@
#include <mono/metadata/image.h>
+#include "core/config/project_settings.h"
+#include "core/io/file_access_pack.h"
#include "core/os/os.h"
#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_assembly.h"
#include "../mono_gd/gd_mono_cache.h"
+#include "../utils/macros.h"
namespace GodotSharpExport {
-String get_assemblyref_name(MonoImage *p_image, int index) {
+MonoAssemblyName *new_mono_assembly_name() {
+ // Mono has no public API to create an empty MonoAssemblyName and the struct is private.
+ // As such the only way to create it is with a stub name and then clear it.
+
+ MonoAssemblyName *aname = mono_assembly_name_new("stub");
+ CRASH_COND(aname == nullptr);
+ mono_assembly_name_free(aname); // Frees the string fields, not the struct
+ return aname;
+}
+
+struct AssemblyRefInfo {
+ String name;
+ uint16_t major = 0;
+ uint16_t minor = 0;
+ uint16_t build = 0;
+ uint16_t revision = 0;
+};
+
+AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) {
const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);
uint32_t cols[MONO_ASSEMBLYREF_SIZE];
mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE);
- return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
+ return {
+ String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])),
+ (uint16_t)cols[MONO_ASSEMBLYREF_MAJOR_VERSION],
+ (uint16_t)cols[MONO_ASSEMBLYREF_MINOR_VERSION],
+ (uint16_t)cols[MONO_ASSEMBLYREF_BUILD_NUMBER],
+ (uint16_t)cols[MONO_ASSEMBLYREF_REV_NUMBER]
+ };
}
-Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) {
+Error get_assembly_dependencies(GDMonoAssembly *p_assembly, MonoAssemblyName *reusable_aname, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) {
MonoImage *image = p_assembly->get_image();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
- String ref_name = get_assemblyref_name(image, i);
+ AssemblyRefInfo ref_info = get_assemblyref_name(image, i);
- if (r_dependencies.has(ref_name))
- continue;
+ const String &ref_name = ref_info.name;
- GDMonoAssembly *ref_assembly = NULL;
- String path;
- bool has_extension = ref_name.ends_with(".dll") || ref_name.ends_with(".exe");
-
- for (int j = 0; j < p_search_dirs.size(); j++) {
- const String &search_dir = p_search_dirs[j];
-
- if (has_extension) {
- path = search_dir.plus_file(ref_name);
- if (FileAccess::exists(path)) {
- GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), path, &ref_assembly, true);
- if (ref_assembly != NULL)
- break;
- }
- } else {
- path = search_dir.plus_file(ref_name + ".dll");
- if (FileAccess::exists(path)) {
- GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
- if (ref_assembly != NULL)
- break;
- }
-
- path = search_dir.plus_file(ref_name + ".exe");
- if (FileAccess::exists(path)) {
- GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
- if (ref_assembly != NULL)
- break;
- }
- }
+ if (r_assembly_dependencies.has(ref_name)) {
+ continue;
}
- ERR_FAIL_COND_V_MSG(!ref_assembly, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
+ mono_assembly_get_assemblyref(image, i, reusable_aname);
- r_dependencies[ref_name] = ref_assembly->get_path();
+ GDMonoAssembly *ref_assembly = nullptr;
+ if (!GDMono::get_singleton()->load_assembly(ref_name, reusable_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) {
+ ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'.");
+ }
- Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies);
+ r_assembly_dependencies[ref_name] = ref_assembly->get_path();
+
+ Error err = get_assembly_dependencies(ref_assembly, reusable_aname, p_search_dirs, r_assembly_dependencies);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'.");
}
return OK;
}
-Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencies,
- const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_dependencies) {
+Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
+ const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_assembly_dependencies) {
MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport");
ERR_FAIL_NULL_V(export_domain, FAILED);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
@@ -112,21 +116,29 @@ Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencie
Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir);
- for (const Variant *key = p_initial_dependencies.next(); key; key = p_initial_dependencies.next(key)) {
+ if (p_custom_bcl_dir.length()) {
+ // Only one mscorlib can be loaded. We need this workaround to make sure we get it from the right BCL directory.
+ r_assembly_dependencies["mscorlib"] = p_custom_bcl_dir.plus_file("mscorlib.dll").simplify_path();
+ }
+
+ for (const Variant *key = p_initial_assemblies.next(); key; key = p_initial_assemblies.next(key)) {
String assembly_name = *key;
- String assembly_path = p_initial_dependencies[*key];
+ String assembly_path = p_initial_assemblies[*key];
- GDMonoAssembly *assembly = NULL;
+ GDMonoAssembly *assembly = nullptr;
bool load_success = GDMono::get_singleton()->load_assembly_from(assembly_name, assembly_path, &assembly, /* refonly: */ true);
ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + assembly_name + "'.");
- Error err = get_assembly_dependencies(assembly, search_dirs, r_dependencies);
- if (err != OK)
+ MonoAssemblyName *reusable_aname = new_mono_assembly_name();
+ SCOPE_EXIT { mono_free(reusable_aname); };
+
+ Error err = get_assembly_dependencies(assembly, reusable_aname, search_dirs, r_assembly_dependencies);
+ if (err != OK) {
return err;
+ }
}
return OK;
}
-
} // namespace GodotSharpExport
diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h
index 36138f81b7..0e9d689618 100644
--- a/modules/mono/editor/godotsharp_export.h
+++ b/modules/mono/editor/godotsharp_export.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,9 +31,9 @@
#ifndef GODOTSHARP_EXPORT_H
#define GODOTSHARP_EXPORT_H
-#include "core/dictionary.h"
-#include "core/error_list.h"
-#include "core/ustring.h"
+#include "core/error/error_list.h"
+#include "core/string/ustring.h"
+#include "core/variant/dictionary.h"
#include "../mono_gd/gd_mono_header.h"
@@ -41,9 +41,8 @@ namespace GodotSharpExport {
Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies);
-Error get_exported_assembly_dependencies(const Dictionary &p_initial_dependencies,
- const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies);
-
+Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies,
+ const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_assembly_dependencies);
} // namespace GodotSharpExport
#endif // GODOTSHARP_EXPORT_H
diff --git a/modules/mono/editor/script_class_parser.cpp b/modules/mono/editor/script_class_parser.cpp
deleted file mode 100644
index bece23c9a6..0000000000
--- a/modules/mono/editor/script_class_parser.cpp
+++ /dev/null
@@ -1,735 +0,0 @@
-/*************************************************************************/
-/* script_class_parser.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "script_class_parser.h"
-
-#include "core/map.h"
-#include "core/os/os.h"
-
-#include "../utils/string_utils.h"
-
-const char *ScriptClassParser::token_names[ScriptClassParser::TK_MAX] = {
- "[",
- "]",
- "{",
- "}",
- ".",
- ":",
- ",",
- "Symbol",
- "Identifier",
- "String",
- "Number",
- "<",
- ">",
- "EOF",
- "Error"
-};
-
-String ScriptClassParser::get_token_name(ScriptClassParser::Token p_token) {
-
- ERR_FAIL_INDEX_V(p_token, TK_MAX, "<error>");
- return token_names[p_token];
-}
-
-ScriptClassParser::Token ScriptClassParser::get_token() {
-
- while (true) {
- switch (code[idx]) {
- case '\n': {
- line++;
- idx++;
- break;
- };
- case 0: {
- return TK_EOF;
- } break;
- case '{': {
- idx++;
- return TK_CURLY_BRACKET_OPEN;
- };
- case '}': {
- idx++;
- return TK_CURLY_BRACKET_CLOSE;
- };
- case '[': {
- idx++;
- return TK_BRACKET_OPEN;
- };
- case ']': {
- idx++;
- return TK_BRACKET_CLOSE;
- };
- case '<': {
- idx++;
- return TK_OP_LESS;
- };
- case '>': {
- idx++;
- return TK_OP_GREATER;
- };
- case ':': {
- idx++;
- return TK_COLON;
- };
- case ',': {
- idx++;
- return TK_COMMA;
- };
- case '.': {
- idx++;
- return TK_PERIOD;
- };
- case '#': {
- //compiler directive
- while (code[idx] != '\n' && code[idx] != 0) {
- idx++;
- }
- continue;
- } break;
- case '/': {
- switch (code[idx + 1]) {
- case '*': { // block comment
- idx += 2;
- while (true) {
- if (code[idx] == 0) {
- error_str = "Unterminated comment";
- error = true;
- return TK_ERROR;
- } else if (code[idx] == '*' && code[idx + 1] == '/') {
- idx += 2;
- break;
- } else if (code[idx] == '\n') {
- line++;
- }
-
- idx++;
- }
-
- } break;
- case '/': { // line comment skip
- while (code[idx] != '\n' && code[idx] != 0) {
- idx++;
- }
-
- } break;
- default: {
- value = "/";
- idx++;
- return TK_SYMBOL;
- }
- }
-
- continue; // a comment
- } break;
- case '\'':
- case '"': {
- bool verbatim = idx != 0 && code[idx - 1] == '@';
-
- CharType begin_str = code[idx];
- idx++;
- String tk_string = String();
- while (true) {
- if (code[idx] == 0) {
- error_str = "Unterminated String";
- error = true;
- return TK_ERROR;
- } else if (code[idx] == begin_str) {
- if (verbatim && code[idx + 1] == '"') { // '""' is verbatim string's '\"'
- idx += 2; // skip next '"' as well
- continue;
- }
-
- idx += 1;
- break;
- } else if (code[idx] == '\\' && !verbatim) {
- //escaped characters...
- idx++;
- CharType next = code[idx];
- if (next == 0) {
- error_str = "Unterminated String";
- error = true;
- return TK_ERROR;
- }
- CharType res = 0;
-
- switch (next) {
- case 'b': res = 8; break;
- case 't': res = 9; break;
- case 'n': res = 10; break;
- case 'f': res = 12; break;
- case 'r':
- res = 13;
- break;
- case '\"': res = '\"'; break;
- case '\\':
- res = '\\';
- break;
- default: {
- res = next;
- } break;
- }
-
- tk_string += res;
-
- } else {
- if (code[idx] == '\n')
- line++;
- tk_string += code[idx];
- }
- idx++;
- }
-
- value = tk_string;
-
- return TK_STRING;
- } break;
- default: {
- if (code[idx] <= 32) {
- idx++;
- break;
- }
-
- if ((code[idx] >= 33 && code[idx] <= 47) || (code[idx] >= 58 && code[idx] <= 63) || (code[idx] >= 91 && code[idx] <= 94) || code[idx] == 96 || (code[idx] >= 123 && code[idx] <= 127)) {
- value = String::chr(code[idx]);
- idx++;
- return TK_SYMBOL;
- }
-
- if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) {
- //a number
- const CharType *rptr;
- double number = String::to_double(&code[idx], &rptr);
- idx += (rptr - &code[idx]);
- value = number;
- return TK_NUMBER;
-
- } else if ((code[idx] == '@' && code[idx + 1] != '"') || code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) {
- String id;
-
- id += code[idx];
- idx++;
-
- while (code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || (code[idx] >= '0' && code[idx] <= '9') || code[idx] > 127) {
- id += code[idx];
- idx++;
- }
-
- value = id;
- return TK_IDENTIFIER;
- } else if (code[idx] == '@' && code[idx + 1] == '"') {
- // begin of verbatim string
- idx++;
- } else {
- error_str = "Unexpected character.";
- error = true;
- return TK_ERROR;
- }
- }
- }
- }
-}
-
-Error ScriptClassParser::_skip_generic_type_params() {
-
- Token tk;
-
- while (true) {
- tk = get_token();
-
- if (tk == TK_IDENTIFIER) {
- tk = get_token();
- // Type specifications can end with "?" to denote nullable types, such as IList<int?>
- if (tk == TK_SYMBOL) {
- tk = get_token();
- if (value.operator String() != "?") {
- error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found unexpected symbol '" + value + "'";
- error = true;
- return ERR_PARSE_ERROR;
- }
- if (tk != TK_OP_GREATER && tk != TK_COMMA) {
- error_str = "Nullable type symbol '?' is only allowed after an identifier, but found " + get_token_name(tk) + " next.";
- error = true;
- return ERR_PARSE_ERROR;
- }
- }
-
- if (tk == TK_PERIOD) {
- while (true) {
- tk = get_token();
-
- if (tk != TK_IDENTIFIER) {
- error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
-
- tk = get_token();
-
- if (tk != TK_PERIOD)
- break;
- }
- }
-
- if (tk == TK_OP_LESS) {
- Error err = _skip_generic_type_params();
- if (err)
- return err;
- tk = get_token();
- }
-
- if (tk == TK_OP_GREATER) {
- return OK;
- } else if (tk != TK_COMMA) {
- error_str = "Unexpected token: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
- } else if (tk == TK_OP_LESS) {
- error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found " + get_token_name(TK_OP_LESS);
- error = true;
- return ERR_PARSE_ERROR;
- } else if (tk == TK_OP_GREATER) {
- return OK;
- } else {
- error_str = "Unexpected token: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
- }
-}
-
-Error ScriptClassParser::_parse_type_full_name(String &r_full_name) {
-
- Token tk = get_token();
-
- if (tk != TK_IDENTIFIER) {
- error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
-
- r_full_name += String(value);
-
- if (code[idx] == '<') {
- idx++;
-
- // We don't mind if the base is generic, but we skip it any ways since this information is not needed
- Error err = _skip_generic_type_params();
- if (err)
- return err;
- }
-
- if (code[idx] != '.') // We only want to take the next token if it's a period
- return OK;
-
- tk = get_token();
-
- CRASH_COND(tk != TK_PERIOD); // Assertion
-
- r_full_name += ".";
-
- return _parse_type_full_name(r_full_name);
-}
-
-Error ScriptClassParser::_parse_class_base(Vector<String> &r_base) {
-
- String name;
-
- Error err = _parse_type_full_name(name);
- if (err)
- return err;
-
- Token tk = get_token();
-
- if (tk == TK_COMMA) {
- err = _parse_class_base(r_base);
- if (err)
- return err;
- } else if (tk == TK_IDENTIFIER && String(value) == "where") {
- err = _parse_type_constraints();
- if (err) {
- return err;
- }
-
- // An open curly bracket was parsed by _parse_type_constraints, so we can exit
- } else if (tk == TK_CURLY_BRACKET_OPEN) {
- // we are finished when we hit the open curly bracket
- } else {
- error_str = "Unexpected token: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
-
- r_base.push_back(name);
-
- return OK;
-}
-
-Error ScriptClassParser::_parse_type_constraints() {
- Token tk = get_token();
- if (tk != TK_IDENTIFIER) {
- error_str = "Unexpected token: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
-
- tk = get_token();
- if (tk != TK_COLON) {
- error_str = "Unexpected token: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
-
- while (true) {
- tk = get_token();
- if (tk == TK_IDENTIFIER) {
- if (String(value) == "where") {
- return _parse_type_constraints();
- }
-
- tk = get_token();
- if (tk == TK_PERIOD) {
- while (true) {
- tk = get_token();
-
- if (tk != TK_IDENTIFIER) {
- error_str = "Expected " + get_token_name(TK_IDENTIFIER) + ", found: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
-
- tk = get_token();
-
- if (tk != TK_PERIOD)
- break;
- }
- }
- }
-
- if (tk == TK_COMMA) {
- continue;
- } else if (tk == TK_IDENTIFIER && String(value) == "where") {
- return _parse_type_constraints();
- } else if (tk == TK_SYMBOL && String(value) == "(") {
- tk = get_token();
- if (tk != TK_SYMBOL || String(value) != ")") {
- error_str = "Unexpected token: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
- } else if (tk == TK_OP_LESS) {
- Error err = _skip_generic_type_params();
- if (err)
- return err;
- } else if (tk == TK_CURLY_BRACKET_OPEN) {
- return OK;
- } else {
- error_str = "Unexpected token: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
- }
-}
-
-Error ScriptClassParser::_parse_namespace_name(String &r_name, int &r_curly_stack) {
-
- Token tk = get_token();
-
- if (tk == TK_IDENTIFIER) {
- r_name += String(value);
- } else {
- error_str = "Unexpected token: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
-
- tk = get_token();
-
- if (tk == TK_PERIOD) {
- r_name += ".";
- return _parse_namespace_name(r_name, r_curly_stack);
- } else if (tk == TK_CURLY_BRACKET_OPEN) {
- r_curly_stack++;
- return OK;
- } else {
- error_str = "Unexpected token: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
-}
-
-Error ScriptClassParser::parse(const String &p_code) {
-
- code = p_code;
- idx = 0;
- line = 0;
- error_str = String();
- error = false;
- value = Variant();
- classes.clear();
-
- Token tk = get_token();
-
- Map<int, NameDecl> name_stack;
- int curly_stack = 0;
- int type_curly_stack = 0;
-
- while (!error && tk != TK_EOF) {
- String identifier = value;
- if (tk == TK_IDENTIFIER && (identifier == "class" || identifier == "struct")) {
- bool is_class = identifier == "class";
-
- tk = get_token();
-
- if (tk == TK_IDENTIFIER) {
- String name = value;
- int at_level = curly_stack;
-
- ClassDecl class_decl;
-
- for (Map<int, NameDecl>::Element *E = name_stack.front(); E; E = E->next()) {
- const NameDecl &name_decl = E->value();
-
- if (name_decl.type == NameDecl::NAMESPACE_DECL) {
- if (E != name_stack.front())
- class_decl.namespace_ += ".";
- class_decl.namespace_ += name_decl.name;
- } else {
- class_decl.name += name_decl.name + ".";
- }
- }
-
- class_decl.name += name;
- class_decl.nested = type_curly_stack > 0;
-
- bool generic = false;
-
- while (true) {
- tk = get_token();
-
- if (tk == TK_COLON) {
- Error err = _parse_class_base(class_decl.base);
- if (err)
- return err;
-
- curly_stack++;
- type_curly_stack++;
-
- break;
- } else if (tk == TK_CURLY_BRACKET_OPEN) {
- curly_stack++;
- type_curly_stack++;
- break;
- } else if (tk == TK_OP_LESS && !generic) {
- generic = true;
-
- Error err = _skip_generic_type_params();
- if (err)
- return err;
- } else if (tk == TK_IDENTIFIER && String(value) == "where") {
- Error err = _parse_type_constraints();
- if (err) {
- return err;
- }
-
- // An open curly bracket was parsed by _parse_type_constraints, so we can exit
- curly_stack++;
- type_curly_stack++;
- break;
- } else {
- error_str = "Unexpected token: " + get_token_name(tk);
- error = true;
- return ERR_PARSE_ERROR;
- }
- }
-
- NameDecl name_decl;
- name_decl.name = name;
- name_decl.type = is_class ? NameDecl::CLASS_DECL : NameDecl::STRUCT_DECL;
- name_stack[at_level] = name_decl;
-
- if (is_class) {
- if (!generic) { // no generics, thanks
- classes.push_back(class_decl);
- } else if (OS::get_singleton()->is_stdout_verbose()) {
- String full_name = class_decl.namespace_;
- if (full_name.length())
- full_name += ".";
- full_name += class_decl.name;
- OS::get_singleton()->print("Ignoring generic class declaration: %s\n", full_name.utf8().get_data());
- }
- }
- }
- } else if (tk == TK_IDENTIFIER && identifier == "namespace") {
- if (type_curly_stack > 0) {
- error_str = "Found namespace nested inside type.";
- error = true;
- return ERR_PARSE_ERROR;
- }
-
- String name;
- int at_level = curly_stack;
-
- Error err = _parse_namespace_name(name, curly_stack);
- if (err)
- return err;
-
- NameDecl name_decl;
- name_decl.name = name;
- name_decl.type = NameDecl::NAMESPACE_DECL;
- name_stack[at_level] = name_decl;
- } else if (tk == TK_CURLY_BRACKET_OPEN) {
- curly_stack++;
- } else if (tk == TK_CURLY_BRACKET_CLOSE) {
- curly_stack--;
- if (name_stack.has(curly_stack)) {
- if (name_stack[curly_stack].type != NameDecl::NAMESPACE_DECL)
- type_curly_stack--;
- name_stack.erase(curly_stack);
- }
- }
-
- tk = get_token();
- }
-
- if (!error && tk == TK_EOF && curly_stack > 0) {
- error_str = "Reached EOF with missing close curly brackets.";
- error = true;
- }
-
- if (error)
- return ERR_PARSE_ERROR;
-
- return OK;
-}
-
-static String get_preprocessor_directive(const String &p_line, int p_from) {
- CRASH_COND(p_line[p_from] != '#');
- p_from++;
- int i = p_from;
- while (i < p_line.length() && (p_line[i] == '_' || (p_line[i] >= 'A' && p_line[i] <= 'Z') ||
- (p_line[i] >= 'a' && p_line[i] <= 'z') || p_line[i] > 127)) {
- i++;
- }
- return p_line.substr(p_from, i - p_from);
-}
-
-static void run_dummy_preprocessor(String &r_source, const String &p_filepath) {
-
- Vector<String> lines = r_source.split("\n", /* p_allow_empty: */ true);
-
- bool *include_lines = memnew_arr(bool, lines.size());
-
- int if_level = -1;
- Vector<bool> is_branch_being_compiled;
-
- for (int i = 0; i < lines.size(); i++) {
- const String &line = lines[i];
-
- const int line_len = line.length();
-
- int j;
- for (j = 0; j < line_len; j++) {
- if (line[j] != ' ' && line[j] != '\t') {
- if (line[j] == '#') {
- // First non-whitespace char of the line is '#'
- include_lines[i] = false;
-
- String directive = get_preprocessor_directive(line, j);
-
- if (directive == "if") {
- if_level++;
- is_branch_being_compiled.push_back(if_level == 0 || is_branch_being_compiled[if_level - 1]);
- } else if (directive == "elif") {
- ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#elif' directive. File: '" + p_filepath + "'.");
- is_branch_being_compiled.write[if_level] = false;
- } else if (directive == "else") {
- ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#else' directive. File: '" + p_filepath + "'.");
- is_branch_being_compiled.write[if_level] = false;
- } else if (directive == "endif") {
- ERR_CONTINUE_MSG(if_level == -1, "Found unexpected '#endif' directive. File: '" + p_filepath + "'.");
- is_branch_being_compiled.remove(if_level);
- if_level--;
- }
-
- break;
- } else {
- // First non-whitespace char of the line is not '#'
- include_lines[i] = if_level == -1 || is_branch_being_compiled[if_level];
- break;
- }
- }
- }
-
- if (j == line_len) {
- // Loop ended without finding a non-whitespace character.
- // Either the line was empty or it only contained whitespaces.
- include_lines[i] = if_level == -1 || is_branch_being_compiled[if_level];
- }
- }
-
- r_source.clear();
-
- // Custom join ignoring lines removed by the preprocessor
- for (int i = 0; i < lines.size(); i++) {
- if (i > 0 && include_lines[i - 1])
- r_source += '\n';
-
- if (include_lines[i]) {
- r_source += lines[i];
- }
- }
-}
-
-Error ScriptClassParser::parse_file(const String &p_filepath) {
-
- String source;
-
- Error ferr = read_all_file_utf8(p_filepath, source);
-
- ERR_FAIL_COND_V_MSG(ferr != OK, ferr,
- ferr == ERR_INVALID_DATA ?
- "File '" + p_filepath + "' contains invalid unicode (UTF-8), so it was not loaded."
- " Please ensure that scripts are saved in valid UTF-8 unicode." :
- "Failed to read file: '" + p_filepath + "'.");
-
- run_dummy_preprocessor(source, p_filepath);
-
- return parse(source);
-}
-
-String ScriptClassParser::get_error() {
- return error_str;
-}
-
-Vector<ScriptClassParser::ClassDecl> ScriptClassParser::get_classes() {
- return classes;
-}
diff --git a/modules/mono/editor/script_class_parser.h b/modules/mono/editor/script_class_parser.h
deleted file mode 100644
index a76a3a50a9..0000000000
--- a/modules/mono/editor/script_class_parser.h
+++ /dev/null
@@ -1,110 +0,0 @@
-/*************************************************************************/
-/* script_class_parser.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef SCRIPT_CLASS_PARSER_H
-#define SCRIPT_CLASS_PARSER_H
-
-#include "core/ustring.h"
-#include "core/variant.h"
-#include "core/vector.h"
-
-class ScriptClassParser {
-
-public:
- struct NameDecl {
- enum Type {
- NAMESPACE_DECL,
- CLASS_DECL,
- STRUCT_DECL
- };
-
- String name;
- Type type;
- };
-
- struct ClassDecl {
- String name;
- String namespace_;
- Vector<String> base;
- bool nested;
- bool has_script_attr;
- };
-
-private:
- String code;
- int idx;
- int line;
- String error_str;
- bool error;
- Variant value;
-
- Vector<ClassDecl> classes;
-
- enum Token {
- TK_BRACKET_OPEN,
- TK_BRACKET_CLOSE,
- TK_CURLY_BRACKET_OPEN,
- TK_CURLY_BRACKET_CLOSE,
- TK_PERIOD,
- TK_COLON,
- TK_COMMA,
- TK_SYMBOL,
- TK_IDENTIFIER,
- TK_STRING,
- TK_NUMBER,
- TK_OP_LESS,
- TK_OP_GREATER,
- TK_EOF,
- TK_ERROR,
- TK_MAX
- };
-
- static const char *token_names[TK_MAX];
- static String get_token_name(Token p_token);
-
- Token get_token();
-
- Error _skip_generic_type_params();
-
- Error _parse_type_full_name(String &r_full_name);
- Error _parse_class_base(Vector<String> &r_base);
- Error _parse_type_constraints();
- Error _parse_namespace_name(String &r_name, int &r_curly_stack);
-
-public:
- Error parse(const String &p_code);
- Error parse_file(const String &p_filepath);
-
- String get_error();
-
- Vector<ClassDecl> get_classes();
-};
-
-#endif // SCRIPT_CLASS_PARSER_H
diff --git a/modules/mono/glue/GodotSharp/GodotSharp.sln b/modules/mono/glue/GodotSharp/GodotSharp.sln
index a496e36da3..4896d0a07d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp.sln
+++ b/modules/mono/glue/GodotSharp/GodotSharp.sln
@@ -8,8 +8,6 @@ Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AEBF0036-DA76-4341-B651-A3F2856AB2FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs
index 6a4f785551..2b641a8937 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs
@@ -14,6 +14,10 @@ using real_t = System.Single;
namespace Godot
{
+ /// <summary>
+ /// Axis-Aligned Bounding Box. AABB consists of a position, a size, and
+ /// several utility functions. It is typically used for fast overlap tests.
+ /// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct AABB : IEquatable<AABB>
@@ -21,24 +25,55 @@ namespace Godot
private Vector3 _position;
private Vector3 _size;
+ /// <summary>
+ /// Beginning corner. Typically has values lower than End.
+ /// </summary>
+ /// <value>Directly uses a private field.</value>
public Vector3 Position
{
get { return _position; }
set { _position = value; }
}
+ /// <summary>
+ /// Size from Position to End. Typically all components are positive.
+ /// If the size is negative, you can use <see cref="Abs"/> to fix it.
+ /// </summary>
+ /// <value>Directly uses a private field.</value>
public Vector3 Size
{
get { return _size; }
set { _size = value; }
}
+ /// <summary>
+ /// Ending corner. This is calculated as <see cref="Position"/> plus
+ /// <see cref="Size"/>. Setting this value will change the size.
+ /// </summary>
+ /// <value>Getting is equivalent to `value = Position + Size`, setting is equivalent to `Size = value - Position`.</value>
public Vector3 End
{
get { return _position + _size; }
set { _size = value - _position; }
}
+ /// <summary>
+ /// Returns an AABB with equivalent position and size, modified so that
+ /// the most-negative corner is the origin and the size is positive.
+ /// </summary>
+ /// <returns>The modified AABB.</returns>
+ public AABB Abs()
+ {
+ Vector3 end = End;
+ Vector3 topLeft = new Vector3(Mathf.Min(_position.x, end.x), Mathf.Min(_position.y, end.y), Mathf.Min(_position.z, end.z));
+ return new AABB(topLeft, _size.Abs());
+ }
+
+ /// <summary>
+ /// Returns true if this AABB completely encloses another one.
+ /// </summary>
+ /// <param name="with">The other AABB that may be enclosed.</param>
+ /// <returns>A bool for whether or not this AABB encloses `b`.</returns>
public bool Encloses(AABB with)
{
Vector3 src_min = _position;
@@ -54,33 +89,59 @@ namespace Godot
src_max.z > dst_max.z;
}
+ /// <summary>
+ /// Returns this AABB expanded to include a given point.
+ /// </summary>
+ /// <param name="point">The point to include.</param>
+ /// <returns>The expanded AABB.</returns>
public AABB Expand(Vector3 point)
{
Vector3 begin = _position;
Vector3 end = _position + _size;
if (point.x < begin.x)
+ {
begin.x = point.x;
+ }
if (point.y < begin.y)
+ {
begin.y = point.y;
+ }
if (point.z < begin.z)
+ {
begin.z = point.z;
+ }
if (point.x > end.x)
+ {
end.x = point.x;
+ }
if (point.y > end.y)
+ {
end.y = point.y;
+ }
if (point.z > end.z)
+ {
end.z = point.z;
+ }
return new AABB(begin, end - begin);
}
+ /// <summary>
+ /// Returns the area of the AABB.
+ /// </summary>
+ /// <returns>The area.</returns>
public real_t GetArea()
{
return _size.x * _size.y * _size.z;
}
+ /// <summary>
+ /// Gets the position of one of the 8 endpoints of the AABB.
+ /// </summary>
+ /// <param name="idx">Which endpoint to get.</param>
+ /// <returns>An endpoint of the AABB.</returns>
public Vector3 GetEndpoint(int idx)
{
switch (idx)
@@ -106,6 +167,10 @@ namespace Godot
}
}
+ /// <summary>
+ /// Returns the normalized longest axis of the AABB.
+ /// </summary>
+ /// <returns>A vector representing the normalized longest axis of the AABB.</returns>
public Vector3 GetLongestAxis()
{
var axis = new Vector3(1f, 0f, 0f);
@@ -125,6 +190,10 @@ namespace Godot
return axis;
}
+ /// <summary>
+ /// Returns the <see cref="Vector3.Axis"/> index of the longest axis of the AABB.
+ /// </summary>
+ /// <returns>A <see cref="Vector3.Axis"/> index for which axis is longest.</returns>
public Vector3.Axis GetLongestAxisIndex()
{
var axis = Vector3.Axis.X;
@@ -144,6 +213,10 @@ namespace Godot
return axis;
}
+ /// <summary>
+ /// Returns the scalar length of the longest axis of the AABB.
+ /// </summary>
+ /// <returns>The scalar length of the longest axis of the AABB.</returns>
public real_t GetLongestAxisSize()
{
real_t max_size = _size.x;
@@ -157,6 +230,10 @@ namespace Godot
return max_size;
}
+ /// <summary>
+ /// Returns the normalized shortest axis of the AABB.
+ /// </summary>
+ /// <returns>A vector representing the normalized shortest axis of the AABB.</returns>
public Vector3 GetShortestAxis()
{
var axis = new Vector3(1f, 0f, 0f);
@@ -176,6 +253,10 @@ namespace Godot
return axis;
}
+ /// <summary>
+ /// Returns the <see cref="Vector3.Axis"/> index of the shortest axis of the AABB.
+ /// </summary>
+ /// <returns>A <see cref="Vector3.Axis"/> index for which axis is shortest.</returns>
public Vector3.Axis GetShortestAxisIndex()
{
var axis = Vector3.Axis.X;
@@ -195,6 +276,10 @@ namespace Godot
return axis;
}
+ /// <summary>
+ /// Returns the scalar length of the shortest axis of the AABB.
+ /// </summary>
+ /// <returns>The scalar length of the shortest axis of the AABB.</returns>
public real_t GetShortestAxisSize()
{
real_t max_size = _size.x;
@@ -208,6 +293,12 @@ namespace Godot
return max_size;
}
+ /// <summary>
+ /// Returns the support point in a given direction.
+ /// This is useful for collision detection algorithms.
+ /// </summary>
+ /// <param name="dir">The direction to find support for.</param>
+ /// <returns>A vector representing the support.</returns>
public Vector3 GetSupport(Vector3 dir)
{
Vector3 half_extents = _size * 0.5f;
@@ -219,6 +310,11 @@ namespace Godot
dir.z > 0f ? -half_extents.z : half_extents.z);
}
+ /// <summary>
+ /// Returns a copy of the AABB grown a given amount of units towards all the sides.
+ /// </summary>
+ /// <param name="by">The amount to grow by.</param>
+ /// <returns>The grown AABB.</returns>
public AABB Grow(real_t by)
{
var res = this;
@@ -233,16 +329,29 @@ namespace Godot
return res;
}
+ /// <summary>
+ /// Returns true if the AABB is flat or empty, or false otherwise.
+ /// </summary>
+ /// <returns>A bool for whether or not the AABB has area.</returns>
public bool HasNoArea()
{
return _size.x <= 0f || _size.y <= 0f || _size.z <= 0f;
}
+ /// <summary>
+ /// Returns true if the AABB has no surface (no size), or false otherwise.
+ /// </summary>
+ /// <returns>A bool for whether or not the AABB has area.</returns>
public bool HasNoSurface()
{
return _size.x <= 0f && _size.y <= 0f && _size.z <= 0f;
}
+ /// <summary>
+ /// Returns true if the AABB contains a point, or false otherwise.
+ /// </summary>
+ /// <param name="point">The point to check.</param>
+ /// <returns>A bool for whether or not the AABB contains `point`.</returns>
public bool HasPoint(Vector3 point)
{
if (point.x < _position.x)
@@ -261,6 +370,11 @@ namespace Godot
return true;
}
+ /// <summary>
+ /// Returns the intersection of this AABB and `b`.
+ /// </summary>
+ /// <param name="with">The other AABB.</param>
+ /// <returns>The clipped AABB.</returns>
public AABB Intersection(AABB with)
{
Vector3 src_min = _position;
@@ -297,24 +411,57 @@ namespace Godot
return new AABB(min, max - min);
}
- public bool Intersects(AABB with)
+ /// <summary>
+ /// Returns true if the AABB overlaps with `b`
+ /// (i.e. they have at least one point in common).
+ ///
+ /// If `includeBorders` is true, they will also be considered overlapping
+ /// if their borders touch, even without intersection.
+ /// </summary>
+ /// <param name="with">The other AABB to check for intersections with.</param>
+ /// <param name="includeBorders">Whether or not to consider borders.</param>
+ /// <returns>A bool for whether or not they are intersecting.</returns>
+ public bool Intersects(AABB with, bool includeBorders = false)
{
- if (_position.x >= with._position.x + with._size.x)
- return false;
- if (_position.x + _size.x <= with._position.x)
- return false;
- if (_position.y >= with._position.y + with._size.y)
- return false;
- if (_position.y + _size.y <= with._position.y)
- return false;
- if (_position.z >= with._position.z + with._size.z)
- return false;
- if (_position.z + _size.z <= with._position.z)
- return false;
+ if (includeBorders)
+ {
+ if (_position.x > with._position.x + with._size.x)
+ return false;
+ if (_position.x + _size.x < with._position.x)
+ return false;
+ if (_position.y > with._position.y + with._size.y)
+ return false;
+ if (_position.y + _size.y < with._position.y)
+ return false;
+ if (_position.z > with._position.z + with._size.z)
+ return false;
+ if (_position.z + _size.z < with._position.z)
+ return false;
+ }
+ else
+ {
+ if (_position.x >= with._position.x + with._size.x)
+ return false;
+ if (_position.x + _size.x <= with._position.x)
+ return false;
+ if (_position.y >= with._position.y + with._size.y)
+ return false;
+ if (_position.y + _size.y <= with._position.y)
+ return false;
+ if (_position.z >= with._position.z + with._size.z)
+ return false;
+ if (_position.z + _size.z <= with._position.z)
+ return false;
+ }
return true;
}
+ /// <summary>
+ /// Returns true if the AABB is on both sides of `plane`.
+ /// </summary>
+ /// <param name="plane">The plane to check for intersection.</param>
+ /// <returns>A bool for whether or not the AABB intersects the plane.</returns>
public bool IntersectsPlane(Plane plane)
{
Vector3[] points =
@@ -335,14 +482,24 @@ namespace Godot
for (int i = 0; i < 8; i++)
{
if (plane.DistanceTo(points[i]) > 0)
+ {
over = true;
+ }
else
+ {
under = true;
+ }
}
return under && over;
}
+ /// <summary>
+ /// Returns true if the AABB intersects the line segment between `from` and `to`.
+ /// </summary>
+ /// <param name="from">The start of the line segment.</param>
+ /// <param name="to">The end of the line segment.</param>
+ /// <returns>A bool for whether or not the AABB intersects the line segment.</returns>
public bool IntersectsSegment(Vector3 from, Vector3 to)
{
real_t min = 0f;
@@ -359,7 +516,9 @@ namespace Godot
if (segFrom < segTo)
{
if (segFrom > boxEnd || segTo < boxBegin)
+ {
return false;
+ }
real_t length = segTo - segFrom;
cmin = segFrom < boxBegin ? (boxBegin - segFrom) / length : 0f;
@@ -368,7 +527,9 @@ namespace Godot
else
{
if (segTo > boxEnd || segFrom < boxBegin)
+ {
return false;
+ }
real_t length = segTo - segFrom;
cmin = segFrom > boxEnd ? (boxEnd - segFrom) / length : 0f;
@@ -381,14 +542,23 @@ namespace Godot
}
if (cmax < max)
+ {
max = cmax;
+ }
if (max < min)
+ {
return false;
+ }
}
return true;
}
+ /// <summary>
+ /// Returns a larger AABB that contains this AABB and `b`.
+ /// </summary>
+ /// <param name="with">The other AABB.</param>
+ /// <returns>The merged AABB.</returns>
public AABB Merge(AABB with)
{
Vector3 beg1 = _position;
@@ -411,22 +581,52 @@ namespace Godot
return new AABB(min, max - min);
}
- // Constructors
+ /// <summary>
+ /// Constructs an AABB from a position and size.
+ /// </summary>
+ /// <param name="position">The position.</param>
+ /// <param name="size">The size, typically positive.</param>
public AABB(Vector3 position, Vector3 size)
{
_position = position;
_size = size;
}
+
+ /// <summary>
+ /// Constructs an AABB from a position, width, height, and depth.
+ /// </summary>
+ /// <param name="position">The position.</param>
+ /// <param name="width">The width, typically positive.</param>
+ /// <param name="height">The height, typically positive.</param>
+ /// <param name="depth">The depth, typically positive.</param>
public AABB(Vector3 position, real_t width, real_t height, real_t depth)
{
_position = position;
_size = new Vector3(width, height, depth);
}
+
+ /// <summary>
+ /// Constructs an AABB from x, y, z, and size.
+ /// </summary>
+ /// <param name="x">The position's X coordinate.</param>
+ /// <param name="y">The position's Y coordinate.</param>
+ /// <param name="z">The position's Z coordinate.</param>
+ /// <param name="size">The size, typically positive.</param>
public AABB(real_t x, real_t y, real_t z, Vector3 size)
{
_position = new Vector3(x, y, z);
_size = size;
}
+
+ /// <summary>
+ /// Constructs an AABB from x, y, z, width, height, and depth.
+ /// </summary>
+ /// <param name="x">The position's X coordinate.</param>
+ /// <param name="y">The position's Y coordinate.</param>
+ /// <param name="z">The position's Z coordinate.</param>
+ /// <param name="width">The width, typically positive.</param>
+ /// <param name="height">The height, typically positive.</param>
+ /// <param name="depth">The depth, typically positive.</param>
public AABB(real_t x, real_t y, real_t z, real_t width, real_t height, real_t depth)
{
_position = new Vector3(x, y, z);
@@ -458,6 +658,12 @@ namespace Godot
return _position == other._position && _size == other._size;
}
+ /// <summary>
+ /// Returns true if this AABB and `other` are approximately equal, by running
+ /// <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component.
+ /// </summary>
+ /// <param name="other">The other AABB to compare.</param>
+ /// <returns>Whether or not the AABBs are approximately equal.</returns>
public bool IsEqualApprox(AABB other)
{
return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other._size);
@@ -470,7 +676,7 @@ namespace Godot
public override string ToString()
{
- return String.Format("{0} - {1}", new object[]
+ return String.Format("{0}, {1}", new object[]
{
_position.ToString(),
_size.ToString()
@@ -479,7 +685,7 @@ namespace Godot
public string ToString(string format)
{
- return String.Format("{0} - {1}", new object[]
+ return String.Format("{0}, {1}", new object[]
{
_position.ToString(format),
_size.ToString(format)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
index aba1065498..ce613f7ef9 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs
@@ -15,10 +15,7 @@ namespace Godot.Collections
public override bool IsInvalid
{
- get
- {
- return handle == IntPtr.Zero;
- }
+ get { return handle == IntPtr.Zero; }
}
protected override bool ReleaseHandle()
@@ -43,7 +40,17 @@ namespace Godot.Collections
if (collection == null)
throw new NullReferenceException($"Parameter '{nameof(collection)} cannot be null.'");
- MarshalUtils.EnumerableToArray(collection, GetPtr());
+ foreach (object element in collection)
+ Add(element);
+ }
+
+ public Array(params object[] array) : this()
+ {
+ if (array == null)
+ {
+ throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'");
+ }
+ safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor_MonoArray(array));
}
internal Array(ArraySafeHandle handle)
@@ -74,6 +81,16 @@ namespace Godot.Collections
return godot_icall_Array_Resize(GetPtr(), newSize);
}
+ public void Shuffle()
+ {
+ godot_icall_Array_Shuffle(GetPtr());
+ }
+
+ public static Array operator +(Array left, Array right)
+ {
+ return new Array(godot_icall_Array_Concatenate(left.GetPtr(), right.GetPtr()));
+ }
+
// IDisposable
public void Dispose()
@@ -157,6 +174,9 @@ namespace Godot.Collections
internal extern static IntPtr godot_icall_Array_Ctor();
[MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static IntPtr godot_icall_Array_Ctor_MonoArray(System.Array array);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_Array_Dtor(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -178,6 +198,9 @@ namespace Godot.Collections
internal extern static void godot_icall_Array_Clear(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static IntPtr godot_icall_Array_Concatenate(IntPtr left, IntPtr right);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
internal extern static bool godot_icall_Array_Contains(IntPtr ptr, object item);
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -202,6 +225,9 @@ namespace Godot.Collections
internal extern static Error godot_icall_Array_Resize(IntPtr ptr, int newSize);
[MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static Error godot_icall_Array_Shuffle(IntPtr ptr);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_Array_Generic_GetElementTypeInfo(Type elemType, out int elemTypeEncoding, out IntPtr elemTypeClass);
[MethodImpl(MethodImplOptions.InternalCall)]
@@ -233,6 +259,15 @@ namespace Godot.Collections
objectArray = new Array(collection);
}
+ public Array(params T[] array) : this()
+ {
+ if (array == null)
+ {
+ throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'");
+ }
+ objectArray = new Array(array);
+ }
+
public Array(Array array)
{
objectArray = array;
@@ -268,18 +303,22 @@ namespace Godot.Collections
return objectArray.Resize(newSize);
}
+ public void Shuffle()
+ {
+ objectArray.Shuffle();
+ }
+
+ public static Array<T> operator +(Array<T> left, Array<T> right)
+ {
+ return new Array<T>(left.objectArray + right.objectArray);
+ }
+
// IList<T>
public T this[int index]
{
- get
- {
- return (T)Array.godot_icall_Array_At_Generic(GetPtr(), index, elemTypeEncoding, elemTypeClass);
- }
- set
- {
- objectArray[index] = value;
- }
+ get { return (T)Array.godot_icall_Array_At_Generic(GetPtr(), index, elemTypeEncoding, elemTypeClass); }
+ set { objectArray[index] = value; }
}
public int IndexOf(T item)
@@ -301,18 +340,12 @@ namespace Godot.Collections
public int Count
{
- get
- {
- return objectArray.Count;
- }
+ get { return objectArray.Count; }
}
public bool IsReadOnly
{
- get
- {
- return objectArray.IsReadOnly;
- }
+ get { return objectArray.IsReadOnly; }
}
public void Add(T item)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs
new file mode 100644
index 0000000000..ef135da51a
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Godot
+{
+ [AttributeUsage(AttributeTargets.Assembly)]
+ public class AssemblyHasScriptsAttribute : Attribute
+ {
+ private readonly bool requiresLookup;
+ private readonly System.Type[] scriptTypes;
+
+ public AssemblyHasScriptsAttribute()
+ {
+ requiresLookup = true;
+ }
+
+ public AssemblyHasScriptsAttribute(System.Type[] scriptTypes)
+ {
+ requiresLookup = false;
+ this.scriptTypes = scriptTypes;
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs
new file mode 100644
index 0000000000..ac6cffceb2
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Godot
+{
+ [AttributeUsage(AttributeTargets.Class)]
+ public class DisableGodotGeneratorsAttribute : Attribute
+ {
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs
index 1bf6d5199a..8fc430b6bc 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/RPCAttributes.cs
@@ -6,18 +6,12 @@ namespace Godot
public class RemoteAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
- public class SyncAttribute : Attribute {}
-
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class MasterAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class PuppetAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
- public class SlaveAttribute : Attribute {}
-
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class RemoteSyncAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs
new file mode 100644
index 0000000000..12eb1035c3
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Godot
+{
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+ public class ScriptPathAttribute : Attribute
+ {
+ private string path;
+
+ public ScriptPathAttribute(string path)
+ {
+ this.path = path;
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs
index 3957387be9..39d5782db8 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/SignalAttribute.cs
@@ -2,7 +2,7 @@ using System;
namespace Godot
{
- [AttributeUsage(AttributeTargets.Delegate)]
+ [AttributeUsage(AttributeTargets.Delegate | AttributeTargets.Event)]
public class SignalAttribute : Attribute
{
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
index d38589013e..5dbf5d5657 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
@@ -8,6 +8,20 @@ using real_t = System.Single;
namespace Godot
{
+ /// <summary>
+ /// 3×3 matrix used for 3D rotation and scale.
+ /// Almost always used as an orthogonal basis for a Transform.
+ ///
+ /// Contains 3 vector fields X, Y and Z as its columns, which are typically
+ /// interpreted as the local basis vectors of a 3D transformation. For such use,
+ /// it is composed of a scaling and a rotation matrix, in that order (M = R.S).
+ ///
+ /// Can also be accessed as array of 3D vectors. These vectors are normally
+ /// orthogonal to each other, but are not necessarily normalized (due to scaling).
+ ///
+ /// For more information, read this documentation article:
+ /// https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html
+ /// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Basis : IEquatable<Basis>
@@ -15,9 +29,9 @@ namespace Godot
// NOTE: x, y and z are public-only. Use Column0, Column1 and Column2 internally.
/// <summary>
- /// Returns the basis matrix’s x vector.
- /// This is equivalent to <see cref="Column0"/>.
+ /// The basis matrix's X vector (column 0).
/// </summary>
+ /// <value>Equivalent to <see cref="Column0"/> and array index `[0]`.</value>
public Vector3 x
{
get => Column0;
@@ -25,9 +39,9 @@ namespace Godot
}
/// <summary>
- /// Returns the basis matrix’s y vector.
- /// This is equivalent to <see cref="Column1"/>.
+ /// The basis matrix's Y vector (column 1).
/// </summary>
+ /// <value>Equivalent to <see cref="Column1"/> and array index `[1]`.</value>
public Vector3 y
{
get => Column1;
@@ -35,19 +49,40 @@ namespace Godot
}
/// <summary>
- /// Returns the basis matrix’s z vector.
- /// This is equivalent to <see cref="Column2"/>.
+ /// The basis matrix's Z vector (column 2).
/// </summary>
+ /// <value>Equivalent to <see cref="Column2"/> and array index `[2]`.</value>
public Vector3 z
{
get => Column2;
set => Column2 = value;
}
+ /// <summary>
+ /// Row 0 of the basis matrix. Shows which vectors contribute
+ /// to the X direction. Rows are not very useful for user code,
+ /// but are more efficient for some internal calculations.
+ /// </summary>
public Vector3 Row0;
+
+ /// <summary>
+ /// Row 1 of the basis matrix. Shows which vectors contribute
+ /// to the Y direction. Rows are not very useful for user code,
+ /// but are more efficient for some internal calculations.
+ /// </summary>
public Vector3 Row1;
+
+ /// <summary>
+ /// Row 2 of the basis matrix. Shows which vectors contribute
+ /// to the Z direction. Rows are not very useful for user code,
+ /// but are more efficient for some internal calculations.
+ /// </summary>
public Vector3 Row2;
+ /// <summary>
+ /// Column 0 of the basis matrix (the X vector).
+ /// </summary>
+ /// <value>Equivalent to <see cref="x"/> and array index `[0]`.</value>
public Vector3 Column0
{
get => new Vector3(Row0.x, Row1.x, Row2.x);
@@ -58,6 +93,11 @@ namespace Godot
this.Row2.x = value.z;
}
}
+
+ /// <summary>
+ /// Column 1 of the basis matrix (the Y vector).
+ /// </summary>
+ /// <value>Equivalent to <see cref="y"/> and array index `[1]`.</value>
public Vector3 Column1
{
get => new Vector3(Row0.y, Row1.y, Row2.y);
@@ -68,6 +108,11 @@ namespace Godot
this.Row2.y = value.z;
}
}
+
+ /// <summary>
+ /// Column 2 of the basis matrix (the Z vector).
+ /// </summary>
+ /// <value>Equivalent to <see cref="z"/> and array index `[2]`.</value>
public Vector3 Column2
{
get => new Vector3(Row0.z, Row1.z, Row2.z);
@@ -79,6 +124,10 @@ namespace Godot
}
}
+ /// <summary>
+ /// The scale of this basis.
+ /// </summary>
+ /// <value>Equivalent to the lengths of each column vector, but negative if the determinant is negative.</value>
public Vector3 Scale
{
get
@@ -86,18 +135,29 @@ namespace Godot
real_t detSign = Mathf.Sign(Determinant());
return detSign * new Vector3
(
- new Vector3(this.Row0[0], this.Row1[0], this.Row2[0]).Length(),
- new Vector3(this.Row0[1], this.Row1[1], this.Row2[1]).Length(),
- new Vector3(this.Row0[2], this.Row1[2], this.Row2[2]).Length()
+ Column0.Length(),
+ Column1.Length(),
+ Column2.Length()
);
}
+ set
+ {
+ value /= Scale; // Value becomes what's called "delta_scale" in core.
+ Column0 *= value.x;
+ Column1 *= value.y;
+ Column2 *= value.z;
+ }
}
- public Vector3 this[int columnIndex]
+ /// <summary>
+ /// Access whole columns in the form of Vector3.
+ /// </summary>
+ /// <param name="column">Which column vector.</param>
+ public Vector3 this[int column]
{
get
{
- switch (columnIndex)
+ switch (column)
{
case 0:
return Column0;
@@ -111,7 +171,7 @@ namespace Godot
}
set
{
- switch (columnIndex)
+ switch (column)
{
case 0:
Column0 = value;
@@ -128,75 +188,48 @@ namespace Godot
}
}
- public real_t this[int columnIndex, int rowIndex]
+ /// <summary>
+ /// Access matrix elements in column-major order.
+ /// </summary>
+ /// <param name="column">Which column, the matrix horizontal position.</param>
+ /// <param name="row">Which row, the matrix vertical position.</param>
+ public real_t this[int column, int row]
{
get
{
- switch (columnIndex)
- {
- case 0:
- return Column0[rowIndex];
- case 1:
- return Column1[rowIndex];
- case 2:
- return Column2[rowIndex];
- default:
- throw new IndexOutOfRangeException();
- }
+ return this[column][row];
}
set
{
- switch (columnIndex)
- {
- case 0:
- {
- var column0 = Column0;
- column0[rowIndex] = value;
- Column0 = column0;
- return;
- }
- case 1:
- {
- var column1 = Column1;
- column1[rowIndex] = value;
- Column1 = column1;
- return;
- }
- case 2:
- {
- var column2 = Column2;
- column2[rowIndex] = value;
- Column2 = column2;
- return;
- }
- default:
- throw new IndexOutOfRangeException();
- }
+ Vector3 columnVector = this[column];
+ columnVector[row] = value;
+ this[column] = columnVector;
}
}
- internal Quat RotationQuat()
+ public Quaternion RotationQuaternion()
{
Basis orthonormalizedBasis = Orthonormalized();
real_t det = orthonormalizedBasis.Determinant();
if (det < 0)
{
- // Ensure that the determinant is 1, such that result is a proper rotation matrix which can be represented by Euler angles.
- orthonormalizedBasis = orthonormalizedBasis.Scaled(Vector3.NegOne);
+ // Ensure that the determinant is 1, such that result is a proper
+ // rotation matrix which can be represented by Euler angles.
+ orthonormalizedBasis = orthonormalizedBasis.Scaled(-Vector3.One);
}
- return orthonormalizedBasis.Quat();
+ return orthonormalizedBasis.Quaternion();
}
- internal void SetQuatScale(Quat quat, Vector3 scale)
+ internal void SetQuaternionScale(Quaternion quaternion, Vector3 scale)
{
SetDiagonal(scale);
- Rotate(quat);
+ Rotate(quaternion);
}
- private void Rotate(Quat quat)
+ private void Rotate(Quaternion quaternion)
{
- this *= new Basis(quat);
+ this *= new Basis(quaternion);
}
private void SetDiagonal(Vector3 diagonal)
@@ -206,6 +239,15 @@ namespace Godot
Row2 = new Vector3(0, 0, diagonal.z);
}
+ /// <summary>
+ /// Returns the determinant of the basis matrix. If the basis is
+ /// uniformly scaled, its determinant is the square of the scale.
+ ///
+ /// A negative determinant means the basis has a negative scale.
+ /// A zero determinant means the basis isn't invertible,
+ /// and is usually considered invalid.
+ /// </summary>
+ /// <returns>The determinant of the basis matrix.</returns>
public real_t Determinant()
{
real_t cofac00 = Row1[1] * Row2[2] - Row1[2] * Row2[1];
@@ -215,6 +257,16 @@ namespace Godot
return Row0[0] * cofac00 + Row0[1] * cofac10 + Row0[2] * cofac20;
}
+ /// <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).
+ ///
+ /// Consider using the <see cref="Basis.Quaternion()"/> method instead, which
+ /// returns a <see cref="Godot.Quaternion"/> quaternion instead of Euler angles.
+ /// </summary>
+ /// <returns>A Vector3 representing the basis rotation in Euler angles.</returns>
public Vector3 GetEuler()
{
Basis m = Orthonormalized();
@@ -247,6 +299,12 @@ namespace Godot
return euler;
}
+ /// <summary>
+ /// Get rows by index. Rows are not very useful for user code,
+ /// but are more efficient for some internal calculations.
+ /// </summary>
+ /// <param name="index">Which row.</param>
+ /// <returns>One of `Row0`, `Row1`, or `Row2`.</returns>
public Vector3 GetRow(int index)
{
switch (index)
@@ -262,6 +320,12 @@ namespace Godot
}
}
+ /// <summary>
+ /// Sets rows by index. Rows are not very useful for user code,
+ /// but are more efficient for some internal calculations.
+ /// </summary>
+ /// <param name="index">Which row.</param>
+ /// <param name="value">The vector to set the row to.</param>
public void SetRow(int index, Vector3 value)
{
switch (index)
@@ -280,22 +344,16 @@ namespace Godot
}
}
- public Vector3 GetColumn(int index)
- {
- return this[index];
- }
-
- public void SetColumn(int index, Vector3 value)
- {
- this[index] = value;
- }
-
- [Obsolete("GetAxis is deprecated. Use GetColumn instead.")]
- public Vector3 GetAxis(int axis)
- {
- return new Vector3(this.Row0[axis], this.Row1[axis], this.Row2[axis]);
- }
-
+ /// <summary>
+ /// This function considers a discretization of rotations into
+ /// 24 points on unit sphere, lying along the vectors (x, y, z) with
+ /// each component being either -1, 0, or 1, and returns the index
+ /// of the point best representing the orientation of the object.
+ /// It is mainly used by the <see cref="GridMap"/> editor.
+ ///
+ /// For further details, refer to the Godot source code.
+ /// </summary>
+ /// <returns>The orthogonal index.</returns>
public int GetOrthogonalIndex()
{
var orth = this;
@@ -309,11 +367,17 @@ namespace Godot
real_t v = row[j];
if (v > 0.5f)
+ {
v = 1.0f;
+ }
else if (v < -0.5f)
+ {
v = -1.0f;
+ }
else
+ {
v = 0f;
+ }
row[j] = v;
@@ -324,12 +388,18 @@ namespace Godot
for (int i = 0; i < 24; i++)
{
if (orth == _orthoBases[i])
+ {
return i;
+ }
}
return 0;
}
+ /// <summary>
+ /// Returns the inverse of the matrix.
+ /// </summary>
+ /// <returns>The inverse matrix.</returns>
public Basis Inverse()
{
real_t cofac00 = Row1[1] * Row2[2] - Row1[2] * Row2[1];
@@ -339,7 +409,9 @@ namespace Godot
real_t det = Row0[0] * cofac00 + Row0[1] * cofac10 + Row0[2] * cofac20;
if (det == 0)
+ {
throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted.");
+ }
real_t detInv = 1.0f / det;
@@ -358,11 +430,17 @@ namespace Godot
);
}
+ /// <summary>
+ /// Returns the orthonormalized version of the basis matrix (useful to
+ /// call occasionally to avoid rounding errors for orthogonal matrices).
+ /// This performs a Gram-Schmidt orthonormalization on the basis of the matrix.
+ /// </summary>
+ /// <returns>An orthonormalized basis matrix.</returns>
public Basis Orthonormalized()
{
- Vector3 column0 = GetColumn(0);
- Vector3 column1 = GetColumn(1);
- Vector3 column2 = GetColumn(2);
+ Vector3 column0 = this[0];
+ Vector3 column1 = this[1];
+ Vector3 column2 = this[2];
column0.Normalize();
column1 = column1 - column0 * column0.Dot(column1);
@@ -373,48 +451,86 @@ namespace Godot
return new Basis(column0, column1, column2);
}
+ /// <summary>
+ /// Introduce an additional rotation around the given `axis`
+ /// by `phi` (in radians). The axis must be a normalized vector.
+ /// </summary>
+ /// <param name="axis">The axis to rotate around. Must be normalized.</param>
+ /// <param name="phi">The angle to rotate, in radians.</param>
+ /// <returns>The rotated basis matrix.</returns>
public Basis Rotated(Vector3 axis, real_t phi)
{
return new Basis(axis, phi) * this;
}
+ /// <summary>
+ /// Introduce an additional scaling specified by the given 3D scaling factor.
+ /// </summary>
+ /// <param name="scale">The scale to introduce.</param>
+ /// <returns>The scaled basis matrix.</returns>
public Basis Scaled(Vector3 scale)
{
- var b = this;
+ Basis b = this;
b.Row0 *= scale.x;
b.Row1 *= scale.y;
b.Row2 *= scale.z;
return b;
}
- public Basis Slerp(Basis target, real_t t)
+ /// <summary>
+ /// Assuming that the matrix is a proper rotation matrix, slerp performs
+ /// a spherical-linear interpolation with another rotation matrix.
+ /// </summary>
+ /// <param name="target">The destination basis for interpolation.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting basis matrix of the interpolation.</returns>
+ public Basis Slerp(Basis target, real_t weight)
{
- var from = new Quat(this);
- var to = new Quat(target);
+ Quaternion from = new Quaternion(this);
+ Quaternion to = new Quaternion(target);
- var b = new Basis(from.Slerp(to, t));
- b.Row0 *= Mathf.Lerp(Row0.Length(), target.Row0.Length(), t);
- b.Row1 *= Mathf.Lerp(Row1.Length(), target.Row1.Length(), t);
- b.Row2 *= Mathf.Lerp(Row2.Length(), target.Row2.Length(), t);
+ Basis b = new Basis(from.Slerp(to, weight));
+ b.Row0 *= Mathf.Lerp(Row0.Length(), target.Row0.Length(), weight);
+ b.Row1 *= Mathf.Lerp(Row1.Length(), target.Row1.Length(), weight);
+ b.Row2 *= Mathf.Lerp(Row2.Length(), target.Row2.Length(), weight);
return b;
}
+ /// <summary>
+ /// Transposed dot product with the X axis of the matrix.
+ /// </summary>
+ /// <param name="with">A vector to calculate the dot product with.</param>
+ /// <returns>The resulting dot product.</returns>
public real_t Tdotx(Vector3 with)
{
return this.Row0[0] * with[0] + this.Row1[0] * with[1] + this.Row2[0] * with[2];
}
+ /// <summary>
+ /// Transposed dot product with the Y axis of the matrix.
+ /// </summary>
+ /// <param name="with">A vector to calculate the dot product with.</param>
+ /// <returns>The resulting dot product.</returns>
public real_t Tdoty(Vector3 with)
{
return this.Row0[1] * with[0] + this.Row1[1] * with[1] + this.Row2[1] * with[2];
}
+ /// <summary>
+ /// Transposed dot product with the Z axis of the matrix.
+ /// </summary>
+ /// <param name="with">A vector to calculate the dot product with.</param>
+ /// <returns>The resulting dot product.</returns>
public real_t Tdotz(Vector3 with)
{
return this.Row0[2] * with[0] + this.Row1[2] * with[1] + this.Row2[2] * with[2];
}
+ /// <summary>
+ /// Returns the transposed version of the basis matrix.
+ /// </summary>
+ /// <returns>The transposed basis matrix.</returns>
public Basis Transposed()
{
var tr = this;
@@ -434,6 +550,11 @@ namespace Godot
return tr;
}
+ /// <summary>
+ /// Returns a vector transformed (multiplied) by the basis matrix.
+ /// </summary>
+ /// <param name="v">A vector to transform.</param>
+ /// <returns>The transformed vector.</returns>
public Vector3 Xform(Vector3 v)
{
return new Vector3
@@ -444,6 +565,14 @@ namespace Godot
);
}
+ /// <summary>
+ /// Returns a vector transformed (multiplied) by the transposed basis matrix.
+ ///
+ /// Note: This results in a multiplication by the inverse of the
+ /// basis matrix only if it represents a rotation-reflection.
+ /// </summary>
+ /// <param name="v">A vector to inversely transform.</param>
+ /// <returns>The inversely transformed vector.</returns>
public Vector3 XformInv(Vector3 v)
{
return new Vector3
@@ -454,7 +583,13 @@ namespace Godot
);
}
- public Quat Quat()
+ /// <summary>
+ /// Returns the basis's rotation in the form of a quaternion.
+ /// 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="Godot.Quaternion"/> representing the basis's rotation.</returns>
+ public Quaternion Quaternion()
{
real_t trace = Row0[0] + Row1[1] + Row2[2];
@@ -462,7 +597,7 @@ namespace Godot
{
real_t s = Mathf.Sqrt(trace + 1.0f) * 2f;
real_t inv_s = 1f / s;
- return new Quat(
+ return new Quaternion(
(Row2[1] - Row1[2]) * inv_s,
(Row0[2] - Row2[0]) * inv_s,
(Row1[0] - Row0[1]) * inv_s,
@@ -474,7 +609,7 @@ namespace Godot
{
real_t s = Mathf.Sqrt(Row0[0] - Row1[1] - Row2[2] + 1.0f) * 2f;
real_t inv_s = 1f / s;
- return new Quat(
+ return new Quaternion(
s * 0.25f,
(Row0[1] + Row1[0]) * inv_s,
(Row0[2] + Row2[0]) * inv_s,
@@ -486,7 +621,7 @@ namespace Godot
{
real_t s = Mathf.Sqrt(-Row0[0] + Row1[1] - Row2[2] + 1.0f) * 2f;
real_t inv_s = 1f / s;
- return new Quat(
+ return new Quaternion(
(Row0[1] + Row1[0]) * inv_s,
s * 0.25f,
(Row1[2] + Row2[1]) * inv_s,
@@ -497,7 +632,7 @@ namespace Godot
{
real_t s = Mathf.Sqrt(-Row0[0] - Row1[1] + Row2[2] + 1.0f) * 2f;
real_t inv_s = 1f / s;
- return new Quat(
+ return new Quaternion(
(Row0[2] + Row2[0]) * inv_s,
(Row1[2] + Row2[1]) * inv_s,
s * 0.25f,
@@ -538,53 +673,90 @@ namespace Godot
private static readonly Basis _flipY = new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1);
private static readonly Basis _flipZ = new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1);
+ /// <summary>
+ /// The identity basis, with no rotation or scaling applied.
+ /// This is used as a replacement for `Basis()` in GDScript.
+ /// Do not use `new Basis()` with no arguments in C#, because it sets all values to zero.
+ /// </summary>
+ /// <value>Equivalent to `new Basis(Vector3.Right, Vector3.Up, Vector3.Back)`.</value>
public static Basis Identity { get { return _identity; } }
+ /// <summary>
+ /// The basis that will flip something along the X axis when used in a transformation.
+ /// </summary>
+ /// <value>Equivalent to `new Basis(Vector3.Left, Vector3.Up, Vector3.Back)`.</value>
public static Basis FlipX { get { return _flipX; } }
+ /// <summary>
+ /// The basis that will flip something along the Y axis when used in a transformation.
+ /// </summary>
+ /// <value>Equivalent to `new Basis(Vector3.Right, Vector3.Down, Vector3.Back)`.</value>
public static Basis FlipY { get { return _flipY; } }
+ /// <summary>
+ /// The basis that will flip something along the Z axis when used in a transformation.
+ /// </summary>
+ /// <value>Equivalent to `new Basis(Vector3.Right, Vector3.Up, Vector3.Forward)`.</value>
public static Basis FlipZ { get { return _flipZ; } }
- public Basis(Quat quat)
- {
- real_t s = 2.0f / quat.LengthSquared;
-
- real_t xs = quat.x * s;
- real_t ys = quat.y * s;
- real_t zs = quat.z * s;
- real_t wx = quat.w * xs;
- real_t wy = quat.w * ys;
- real_t wz = quat.w * zs;
- real_t xx = quat.x * xs;
- real_t xy = quat.x * ys;
- real_t xz = quat.x * zs;
- real_t yy = quat.y * ys;
- real_t yz = quat.y * zs;
- real_t zz = quat.z * zs;
+ /// <summary>
+ /// Constructs a pure rotation basis matrix from the given quaternion.
+ /// </summary>
+ /// <param name="quaternion">The quaternion to create the basis from.</param>
+ public Basis(Quaternion quaternion)
+ {
+ real_t s = 2.0f / quaternion.LengthSquared;
+
+ real_t xs = quaternion.x * s;
+ real_t ys = quaternion.y * s;
+ real_t zs = quaternion.z * s;
+ real_t wx = quaternion.w * xs;
+ real_t wy = quaternion.w * ys;
+ real_t wz = quaternion.w * zs;
+ real_t xx = quaternion.x * xs;
+ real_t xy = quaternion.x * ys;
+ real_t xz = quaternion.x * zs;
+ real_t yy = quaternion.y * ys;
+ real_t yz = quaternion.y * zs;
+ real_t zz = quaternion.z * zs;
Row0 = new Vector3(1.0f - (yy + zz), xy - wz, xz + wy);
Row1 = new Vector3(xy + wz, 1.0f - (xx + zz), yz - wx);
Row2 = new Vector3(xz - wy, yz + wx, 1.0f - (xx + yy));
}
- public Basis(Vector3 euler)
+ /// <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="Godot.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(euler.x);
- s = Mathf.Sin(euler.x);
+ 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(euler.y);
- s = Mathf.Sin(euler.y);
+ 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(euler.z);
- s = Mathf.Sin(euler.z);
+ 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 `axis`
+ /// by `phi` (in radians). The axis must be a normalized vector.
+ /// </summary>
+ /// <param name="axis">The axis to rotate around. Must be normalized.</param>
+ /// <param name="phi">The angle to rotate, in radians.</param>
public Basis(Vector3 axis, real_t phi)
{
Vector3 axisSq = new Vector3(axis.x * axis.x, axis.y * axis.y, axis.z * axis.z);
@@ -612,6 +784,12 @@ namespace Godot
Row2.y = xyzt + zyxs;
}
+ /// <summary>
+ /// Constructs a basis matrix from 3 axis vectors (matrix columns).
+ /// </summary>
+ /// <param name="column0">The X vector, or Column0.</param>
+ /// <param name="column1">The Y vector, or Column1.</param>
+ /// <param name="column2">The Z vector, or Column2.</param>
public Basis(Vector3 column0, Vector3 column1, Vector3 column2)
{
Row0 = new Vector3(column0.x, column1.x, column2.x);
@@ -667,6 +845,12 @@ namespace Godot
return Row0.Equals(other.Row0) && Row1.Equals(other.Row1) && Row2.Equals(other.Row2);
}
+ /// <summary>
+ /// Returns true if this basis and `other` are approximately equal, by running
+ /// <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component.
+ /// </summary>
+ /// <param name="other">The other basis to compare.</param>
+ /// <returns>Whether or not the matrices are approximately equal.</returns>
public bool IsEqualApprox(Basis other)
{
return Row0.IsEqualApprox(other.Row0) && Row1.IsEqualApprox(other.Row1) && Row2.IsEqualApprox(other.Row2);
@@ -679,22 +863,16 @@ namespace Godot
public override string ToString()
{
- return String.Format("({0}, {1}, {2})", new object[]
- {
- Row0.ToString(),
- Row1.ToString(),
- Row2.ToString()
- });
+ return "[X: " + x.ToString() +
+ ", Y: " + y.ToString() +
+ ", Z: " + z.ToString() + "]";
}
public string ToString(string format)
{
- return String.Format("({0}, {1}, {2})", new object[]
- {
- Row0.ToString(format),
- Row1.ToString(format),
- Row2.ToString(format)
- });
+ return "[X: " + x.ToString(format) +
+ ", Y: " + y.ToString(format) +
+ ", Z: " + z.ToString(format) + "]";
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
new file mode 100644
index 0000000000..c85cc1884c
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace Godot
+{
+ public struct Callable
+ {
+ private readonly Object _target;
+ private readonly StringName _method;
+ private readonly Delegate _delegate;
+
+ public Object Target => _target;
+ public StringName Method => _method;
+ public Delegate Delegate => _delegate;
+
+ public static implicit operator Callable(Delegate @delegate) => new Callable(@delegate);
+
+ public Callable(Object target, StringName method)
+ {
+ _target = target;
+ _method = method;
+ _delegate = null;
+ }
+
+ public Callable(Delegate @delegate)
+ {
+ _target = null;
+ _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 0462ef1125..155ffcff32 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs
@@ -3,15 +3,44 @@ using System.Runtime.InteropServices;
namespace Godot
{
+ /// <summary>
+ /// A color represented by red, green, blue, and alpha (RGBA) components.
+ /// The alpha component is often used for transparency.
+ /// Values are in floating-point and usually range from 0 to 1.
+ /// Some properties (such as CanvasItem.modulate) may accept values
+ /// greater than 1 (overbright or HDR colors).
+ ///
+ /// If you want to supply values in a range of 0 to 255, you should use
+ /// <see cref="Color8"/> and the `r8`/`g8`/`b8`/`a8` properties.
+ /// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Color : IEquatable<Color>
{
+ /// <summary>
+ /// The color's red component, typically on the range of 0 to 1.
+ /// </summary>
public float r;
+
+ /// <summary>
+ /// The color's green component, typically on the range of 0 to 1.
+ /// </summary>
public float g;
+
+ /// <summary>
+ /// The color's blue component, typically on the range of 0 to 1.
+ /// </summary>
public float b;
+
+ /// <summary>
+ /// The color's alpha (transparency) component, typically on the range of 0 to 1.
+ /// </summary>
public float a;
+ /// <summary>
+ /// Wrapper for <see cref="r"/> that uses the range 0 to 255 instead of 0 to 1.
+ /// </summary>
+ /// <value>Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.</value>
public int r8
{
get
@@ -24,6 +53,10 @@ namespace Godot
}
}
+ /// <summary>
+ /// Wrapper for <see cref="g"/> that uses the range 0 to 255 instead of 0 to 1.
+ /// </summary>
+ /// <value>Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.</value>
public int g8
{
get
@@ -36,6 +69,10 @@ namespace Godot
}
}
+ /// <summary>
+ /// Wrapper for <see cref="b"/> that uses the range 0 to 255 instead of 0 to 1.
+ /// </summary>
+ /// <value>Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.</value>
public int b8
{
get
@@ -48,6 +85,10 @@ namespace Godot
}
}
+ /// <summary>
+ /// Wrapper for <see cref="a"/> that uses the range 0 to 255 instead of 0 to 1.
+ /// </summary>
+ /// <value>Getting is equivalent to multiplying by 255 and rounding. Setting is equivalent to dividing by 255.</value>
public int a8
{
get
@@ -60,6 +101,10 @@ namespace Godot
}
}
+ /// <summary>
+ /// The HSV hue of this color, on the range 0 to 1.
+ /// </summary>
+ /// <value>Getting is a long process, refer to the source code for details. Setting uses <see cref="FromHSV"/>.</value>
public float h
{
get
@@ -70,30 +115,44 @@ namespace Godot
float delta = max - min;
if (delta == 0)
+ {
return 0;
+ }
float h;
if (r == max)
+ {
h = (g - b) / delta; // Between yellow & magenta
+ }
else if (g == max)
+ {
h = 2 + (b - r) / delta; // Between cyan & yellow
+ }
else
+ {
h = 4 + (r - g) / delta; // Between magenta & cyan
+ }
h /= 6.0f;
if (h < 0)
+ {
h += 1.0f;
+ }
return h;
}
set
{
- this = FromHsv(value, s, v, a);
+ this = FromHSV(value, s, v, a);
}
}
+ /// <summary>
+ /// The HSV saturation of this color, on the range 0 to 1.
+ /// </summary>
+ /// <value>Getting is equivalent to the ratio between the min and max RGB value. Setting uses <see cref="FromHSV"/>.</value>
public float s
{
get
@@ -103,14 +162,18 @@ namespace Godot
float delta = max - min;
- return max != 0 ? delta / max : 0;
+ return max == 0 ? 0 : delta / max;
}
set
{
- this = FromHsv(h, value, v, a);
+ this = FromHSV(h, value, v, a);
}
}
+ /// <summary>
+ /// The HSV value (brightness) of this color, on the range 0 to 1.
+ /// </summary>
+ /// <value>Getting is equivalent to using `Max()` on the RGB components. Setting uses <see cref="FromHSV"/>.</value>
public float v
{
get
@@ -119,29 +182,14 @@ namespace Godot
}
set
{
- this = FromHsv(h, s, value, a);
- }
- }
-
- public static Color ColorN(string name, float alpha = 1f)
- {
- name = name.Replace(" ", String.Empty);
- name = name.Replace("-", String.Empty);
- name = name.Replace("_", String.Empty);
- name = name.Replace("'", String.Empty);
- name = name.Replace(".", String.Empty);
- name = name.ToLower();
-
- if (!Colors.namedColors.ContainsKey(name))
- {
- throw new ArgumentOutOfRangeException($"Invalid Color Name: {name}");
+ this = FromHSV(h, s, value, a);
}
-
- Color color = Colors.namedColors[name];
- color.a = alpha;
- return color;
}
+ /// <summary>
+ /// Access color components using their index.
+ /// </summary>
+ /// <value>`[0]` is equivalent to `.r`, `[1]` is equivalent to `.g`, `[2]` is equivalent to `.b`, `[3]` is equivalent to `.a`.</value>
public float this[int index]
{
get
@@ -182,73 +230,13 @@ namespace Godot
}
}
- public void ToHsv(out float hue, out float saturation, out float value)
- {
- float max = (float)Mathf.Max(r, Mathf.Max(g, b));
- float min = (float)Mathf.Min(r, Mathf.Min(g, b));
-
- float delta = max - min;
-
- if (delta == 0)
- {
- hue = 0;
- }
- else
- {
- if (r == max)
- hue = (g - b) / delta; // Between yellow & magenta
- else if (g == max)
- hue = 2 + (b - r) / delta; // Between cyan & yellow
- else
- hue = 4 + (r - g) / delta; // Between magenta & cyan
-
- hue /= 6.0f;
-
- if (hue < 0)
- hue += 1.0f;
- }
-
- saturation = max == 0 ? 0 : 1f - 1f * min / max;
- value = max;
- }
-
- public static Color FromHsv(float hue, float saturation, float value, float alpha = 1.0f)
- {
- if (saturation == 0)
- {
- // acp_hromatic (grey)
- return new Color(value, value, value, alpha);
- }
-
- int i;
- float f, p, q, t;
-
- hue *= 6.0f;
- hue %= 6f;
- i = (int)hue;
-
- f = hue - i;
- p = value * (1 - saturation);
- q = value * (1 - saturation * f);
- t = value * (1 - saturation * (1 - f));
-
- switch (i)
- {
- case 0: // Red is the dominant color
- return new Color(value, t, p, alpha);
- case 1: // Green is the dominant color
- return new Color(q, value, p, alpha);
- case 2:
- return new Color(p, value, t, alpha);
- case 3: // Blue is the dominant color
- return new Color(p, q, value, alpha);
- case 4:
- return new Color(t, p, value, alpha);
- default: // (5) Red is the dominant color
- return new Color(value, p, q, alpha);
- }
- }
-
+ /// <summary>
+ /// Returns a new color resulting from blending this color over another.
+ /// If the color is opaque, the result is also opaque.
+ /// The second color may have a range of alpha values.
+ /// </summary>
+ /// <param name="over">The color to blend over.</param>
+ /// <returns>This color blended over `over`.</returns>
public Color Blend(Color over)
{
Color res;
@@ -268,16 +256,33 @@ namespace Godot
return res;
}
- public Color Contrasted()
- {
- return new Color(
- (r + 0.5f) % 1.0f,
- (g + 0.5f) % 1.0f,
- (b + 0.5f) % 1.0f,
- a
+ /// <summary>
+ /// Returns a new color with all components clamped between the
+ /// components of `min` and `max` using
+ /// <see cref="Mathf.Clamp(float, float, float)"/>.
+ /// </summary>
+ /// <param name="min">The color with minimum allowed values.</param>
+ /// <param name="max">The color with maximum allowed values.</param>
+ /// <returns>The color with all components clamped.</returns>
+ public Color Clamp(Color? min = null, Color? max = null)
+ {
+ Color minimum = min ?? new Color(0, 0, 0, 0);
+ Color maximum = max ?? new Color(1, 1, 1, 1);
+ return new Color
+ (
+ (float)Mathf.Clamp(r, minimum.r, maximum.r),
+ (float)Mathf.Clamp(g, minimum.g, maximum.g),
+ (float)Mathf.Clamp(b, minimum.b, maximum.b),
+ (float)Mathf.Clamp(a, minimum.a, maximum.a)
);
}
+ /// <summary>
+ /// Returns a new color resulting from making this color darker
+ /// by the specified ratio (on the range of 0 to 1).
+ /// </summary>
+ /// <param name="amount">The ratio to darken by.</param>
+ /// <returns>The darkened color.</returns>
public Color Darkened(float amount)
{
Color res = this;
@@ -287,6 +292,10 @@ namespace Godot
return res;
}
+ /// <summary>
+ /// Returns the inverted color: `(1 - r, 1 - g, 1 - b, a)`.
+ /// </summary>
+ /// <returns>The inverted color.</returns>
public Color Inverted()
{
return new Color(
@@ -297,6 +306,12 @@ namespace Godot
);
}
+ /// <summary>
+ /// Returns a new color resulting from making this color lighter
+ /// by the specified ratio (on the range of 0 to 1).
+ /// </summary>
+ /// <param name="amount">The ratio to lighten by.</param>
+ /// <returns>The darkened color.</returns>
public Color Lightened(float amount)
{
Color res = this;
@@ -306,21 +321,51 @@ namespace Godot
return res;
}
- public Color LinearInterpolate(Color c, float t)
- {
- var res = this;
-
- res.r += t * (c.r - r);
- res.g += t * (c.g - g);
- res.b += t * (c.b - b);
- res.a += t * (c.a - a);
+ /// <summary>
+ /// Returns the result of the linear interpolation between
+ /// this color and `to` by amount `weight`.
+ /// </summary>
+ /// <param name="to">The destination color for interpolation.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting color of the interpolation.</returns>
+ public Color Lerp(Color to, float weight)
+ {
+ return new Color
+ (
+ Mathf.Lerp(r, to.r, weight),
+ Mathf.Lerp(g, to.g, weight),
+ Mathf.Lerp(b, to.b, weight),
+ Mathf.Lerp(a, to.a, weight)
+ );
+ }
- return res;
+ /// <summary>
+ /// Returns the result of the linear interpolation between
+ /// this color and `to` by color amount `weight`.
+ /// </summary>
+ /// <param name="to">The destination color for interpolation.</param>
+ /// <param name="weight">A color with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting color of the interpolation.</returns>
+ public Color Lerp(Color to, Color weight)
+ {
+ return new Color
+ (
+ Mathf.Lerp(r, to.r, weight.r),
+ Mathf.Lerp(g, to.g, weight.g),
+ Mathf.Lerp(b, to.b, weight.b),
+ Mathf.Lerp(a, to.a, weight.a)
+ );
}
- public int ToAbgr32()
+ /// <summary>
+ /// Returns the color converted to an unsigned 32-bit integer in ABGR
+ /// format (each byte represents a color channel).
+ /// ABGR is the reversed version of the default format.
+ /// </summary>
+ /// <returns>A uint representing this color in ABGR32 format.</returns>
+ public uint ToAbgr32()
{
- int c = (byte)Math.Round(a * 255);
+ uint c = (byte)Math.Round(a * 255);
c <<= 8;
c |= (byte)Math.Round(b * 255);
c <<= 8;
@@ -331,9 +376,15 @@ namespace Godot
return c;
}
- public long ToAbgr64()
+ /// <summary>
+ /// Returns the color converted to an unsigned 64-bit integer in ABGR
+ /// format (each word represents a color channel).
+ /// ABGR is the reversed version of the default format.
+ /// </summary>
+ /// <returns>A ulong representing this color in ABGR64 format.</returns>
+ public ulong ToAbgr64()
{
- long c = (ushort)Math.Round(a * 65535);
+ ulong c = (ushort)Math.Round(a * 65535);
c <<= 16;
c |= (ushort)Math.Round(b * 65535);
c <<= 16;
@@ -344,9 +395,15 @@ namespace Godot
return c;
}
- public int ToArgb32()
+ /// <summary>
+ /// Returns the color converted to an unsigned 32-bit integer in ARGB
+ /// format (each byte represents a color channel).
+ /// ARGB is more compatible with DirectX, but not used much in Godot.
+ /// </summary>
+ /// <returns>A uint representing this color in ARGB32 format.</returns>
+ public uint ToArgb32()
{
- int c = (byte)Math.Round(a * 255);
+ uint c = (byte)Math.Round(a * 255);
c <<= 8;
c |= (byte)Math.Round(r * 255);
c <<= 8;
@@ -357,9 +414,15 @@ namespace Godot
return c;
}
- public long ToArgb64()
+ /// <summary>
+ /// Returns the color converted to an unsigned 64-bit integer in ARGB
+ /// format (each word represents a color channel).
+ /// ARGB is more compatible with DirectX, but not used much in Godot.
+ /// </summary>
+ /// <returns>A ulong representing this color in ARGB64 format.</returns>
+ public ulong ToArgb64()
{
- long c = (ushort)Math.Round(a * 65535);
+ ulong c = (ushort)Math.Round(a * 65535);
c <<= 16;
c |= (ushort)Math.Round(r * 65535);
c <<= 16;
@@ -370,9 +433,15 @@ namespace Godot
return c;
}
- public int ToRgba32()
+ /// <summary>
+ /// Returns the color converted to an unsigned 32-bit integer in RGBA
+ /// format (each byte represents a color channel).
+ /// RGBA is Godot's default and recommended format.
+ /// </summary>
+ /// <returns>A uint representing this color in RGBA32 format.</returns>
+ public uint ToRgba32()
{
- int c = (byte)Math.Round(r * 255);
+ uint c = (byte)Math.Round(r * 255);
c <<= 8;
c |= (byte)Math.Round(g * 255);
c <<= 8;
@@ -383,9 +452,15 @@ namespace Godot
return c;
}
- public long ToRgba64()
+ /// <summary>
+ /// Returns the color converted to an unsigned 64-bit integer in RGBA
+ /// format (each word represents a color channel).
+ /// RGBA is Godot's default and recommended format.
+ /// </summary>
+ /// <returns>A ulong representing this color in RGBA64 format.</returns>
+ public ulong ToRgba64()
{
- long c = (ushort)Math.Round(r * 65535);
+ ulong c = (ushort)Math.Round(r * 65535);
c <<= 16;
c |= (ushort)Math.Round(g * 65535);
c <<= 16;
@@ -396,7 +471,12 @@ namespace Godot
return c;
}
- public string ToHtml(bool includeAlpha = true)
+ /// <summary>
+ /// Returns the color's HTML hexadecimal color string in RGBA format.
+ /// </summary>
+ /// <param name="includeAlpha">Whether or not to include alpha. If false, the color is RGB instead of RGBA.</param>
+ /// <returns>A string for the HTML hexadecimal representation of this color.</returns>
+ public string ToHTML(bool includeAlpha = true)
{
var txt = string.Empty;
@@ -405,12 +485,20 @@ namespace Godot
txt += ToHex32(b);
if (includeAlpha)
- txt = ToHex32(a) + txt;
+ {
+ txt += ToHex32(a);
+ }
return txt;
}
- // Constructors
+ /// <summary>
+ /// Constructs a color from RGBA values, typically on the range of 0 to 1.
+ /// </summary>
+ /// <param name="r">The color's red component, typically on the range of 0 to 1.</param>
+ /// <param name="g">The color's green component, typically on the range of 0 to 1.</param>
+ /// <param name="b">The color's blue component, typically on the range of 0 to 1.</param>
+ /// <param name="a">The color's alpha (transparency) value, typically on the range of 0 to 1. Default: 1.</param>
public Color(float r, float g, float b, float a = 1.0f)
{
this.r = r;
@@ -419,7 +507,25 @@ namespace Godot
this.a = a;
}
- public Color(int rgba)
+ /// <summary>
+ /// Constructs a color from an existing color and an alpha value.
+ /// </summary>
+ /// <param name="c">The color to construct from. Only its RGB values are used.</param>
+ /// <param name="a">The color's alpha (transparency) value, typically on the range of 0 to 1. Default: 1.</param>
+ public Color(Color c, float a = 1.0f)
+ {
+ r = c.r;
+ g = c.g;
+ b = c.b;
+ this.a = a;
+ }
+
+ /// <summary>
+ /// Constructs a color from an unsigned 32-bit integer in RGBA format
+ /// (each byte represents a color channel).
+ /// </summary>
+ /// <param name="rgba">The uint representing the color.</param>
+ public Color(uint rgba)
{
a = (rgba & 0xFF) / 255.0f;
rgba >>= 8;
@@ -430,7 +536,12 @@ namespace Godot
r = (rgba & 0xFF) / 255.0f;
}
- public Color(long rgba)
+ /// <summary>
+ /// Constructs a color from an unsigned 64-bit integer in RGBA format
+ /// (each word represents a color channel).
+ /// </summary>
+ /// <param name="rgba">The ulong representing the color.</param>
+ public Color(ulong rgba)
{
a = (rgba & 0xFFFF) / 65535.0f;
rgba >>= 16;
@@ -441,168 +552,317 @@ namespace Godot
r = (rgba & 0xFFFF) / 65535.0f;
}
- private static int ParseCol8(string str, int ofs)
+ /// <summary>
+ /// Constructs a color either from an HTML color code or from a
+ /// standardized color name. Supported
+ /// color names are the same as the <see cref="Colors"/> constants.
+ /// </summary>
+ /// <param name="code">The HTML color code or color name to construct from.</param>
+ public Color(string code)
{
- int ig = 0;
-
- for (int i = 0; i < 2; i++)
+ if (HtmlIsValid(code))
{
- int c = str[i + ofs];
- int v;
-
- if (c >= '0' && c <= '9')
- {
- v = c - '0';
- }
- else if (c >= 'a' && c <= 'f')
- {
- v = c - 'a';
- v += 10;
- }
- else if (c >= 'A' && c <= 'F')
- {
- v = c - 'A';
- v += 10;
- }
- else
- {
- return -1;
- }
-
- if (i == 0)
- ig += v * 16;
- else
- ig += v;
+ this = FromHTML(code);
}
-
- return ig;
- }
-
- private String ToHex32(float val)
- {
- int v = Mathf.RoundToInt(Mathf.Clamp(val * 255, 0, 255));
-
- var ret = string.Empty;
-
- for (int i = 0; i < 2; i++)
+ else
{
- char c;
- int lv = v & 0xF;
-
- if (lv < 10)
- c = (char)('0' + lv);
- else
- c = (char)('a' + lv - 10);
-
- v >>= 4;
- ret = c + ret;
+ this = Named(code);
}
+ }
- return ret;
+ /// <summary>
+ /// Constructs a color either from an HTML color code or from a
+ /// standardized color name, with `alpha` on the range of 0 to 1. Supported
+ /// color names are the same as the <see cref="Colors"/> constants.
+ /// </summary>
+ /// <param name="code">The HTML color code or color name to construct from.</param>
+ /// <param name="alpha">The alpha (transparency) value, typically on the range of 0 to 1.</param>
+ public Color(string code, float alpha)
+ {
+ this = new Color(code);
+ a = alpha;
}
- internal static bool HtmlIsValid(string color)
+ /// <summary>
+ /// Constructs a color from the HTML hexadecimal color string in RGBA format.
+ /// </summary>
+ /// <param name="rgba">A string for the HTML hexadecimal representation of this color.</param>
+ private static Color FromHTML(string rgba)
{
- if (color.Length == 0)
- return false;
+ Color c;
+ if (rgba.Length == 0)
+ {
+ c.r = 0f;
+ c.g = 0f;
+ c.b = 0f;
+ c.a = 1.0f;
+ return c;
+ }
- if (color[0] == '#')
- color = color.Substring(1, color.Length - 1);
+ if (rgba[0] == '#')
+ {
+ rgba = rgba.Substring(1);
+ }
+ // If enabled, use 1 hex digit per channel instead of 2.
+ // Other sizes aren't in the HTML/CSS spec but we could add them if desired.
+ bool isShorthand = rgba.Length < 5;
bool alpha;
- switch (color.Length)
+ if (rgba.Length == 8)
{
- case 8:
- alpha = true;
- break;
- case 6:
- alpha = false;
- break;
- default:
- return false;
+ alpha = true;
+ }
+ else if (rgba.Length == 6)
+ {
+ alpha = false;
+ }
+ else if (rgba.Length == 4)
+ {
+ alpha = true;
+ }
+ else if (rgba.Length == 3)
+ {
+ alpha = false;
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException("Invalid color code. Length is " + rgba.Length + " but a length of 6 or 8 is expected: " + rgba);
}
- if (alpha)
+ c.a = 1.0f;
+ if (isShorthand)
{
- if (ParseCol8(color, 0) < 0)
- return false;
+ c.r = ParseCol4(rgba, 0) / 15f;
+ c.g = ParseCol4(rgba, 1) / 15f;
+ c.b = ParseCol4(rgba, 2) / 15f;
+ if (alpha)
+ {
+ c.a = ParseCol4(rgba, 3) / 15f;
+ }
+ }
+ else
+ {
+ c.r = ParseCol8(rgba, 0) / 255f;
+ c.g = ParseCol8(rgba, 2) / 255f;
+ c.b = ParseCol8(rgba, 4) / 255f;
+ if (alpha)
+ {
+ c.a = ParseCol8(rgba, 6) / 255f;
+ }
+ }
+
+ if (c.r < 0)
+ {
+ throw new ArgumentOutOfRangeException("Invalid color code. Red part is not valid hexadecimal: " + rgba);
}
- int from = alpha ? 2 : 0;
+ if (c.g < 0)
+ {
+ throw new ArgumentOutOfRangeException("Invalid color code. Green part is not valid hexadecimal: " + rgba);
+ }
- if (ParseCol8(color, from + 0) < 0)
- return false;
- if (ParseCol8(color, from + 2) < 0)
- return false;
- if (ParseCol8(color, from + 4) < 0)
- return false;
+ if (c.b < 0)
+ {
+ throw new ArgumentOutOfRangeException("Invalid color code. Blue part is not valid hexadecimal: " + rgba);
+ }
- return true;
+ if (c.a < 0)
+ {
+ throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba);
+ }
+ return c;
}
+ /// <summary>
+ /// Returns a color constructed from integer red, green, blue, and alpha channels.
+ /// Each channel should have 8 bits of information ranging from 0 to 255.
+ /// </summary>
+ /// <param name="r8">The red component represented on the range of 0 to 255.</param>
+ /// <param name="g8">The green component represented on the range of 0 to 255.</param>
+ /// <param name="b8">The blue component represented on the range of 0 to 255.</param>
+ /// <param name="a8">The alpha (transparency) component represented on the range of 0 to 255.</param>
+ /// <returns>The constructed color.</returns>
public static Color Color8(byte r8, byte g8, byte b8, byte a8 = 255)
{
return new Color(r8 / 255f, g8 / 255f, b8 / 255f, a8 / 255f);
}
- public Color(string rgba)
+ /// <summary>
+ /// Returns a color according to the standardized name, with the
+ /// specified alpha value. Supported color names are the same as
+ /// the constants defined in <see cref="Colors"/>.
+ /// </summary>
+ /// <param name="name">The name of the color.</param>
+ /// <returns>The constructed color.</returns>
+ private static Color Named(string name)
{
- if (rgba.Length == 0)
+ name = name.Replace(" ", String.Empty);
+ name = name.Replace("-", String.Empty);
+ name = name.Replace("_", String.Empty);
+ name = name.Replace("'", String.Empty);
+ name = name.Replace(".", String.Empty);
+ name = name.ToUpper();
+
+ if (!Colors.namedColors.ContainsKey(name))
{
- r = 0f;
- g = 0f;
- b = 0f;
- a = 1.0f;
- return;
+ throw new ArgumentOutOfRangeException($"Invalid Color Name: {name}");
}
- if (rgba[0] == '#')
- rgba = rgba.Substring(1);
+ return Colors.namedColors[name];
+ }
- bool alpha;
+ /// <summary>
+ /// Constructs a color from an HSV profile, with values on the
+ /// range of 0 to 1. This is equivalent to using each of
+ /// the `h`/`s`/`v` properties, but much more efficient.
+ /// </summary>
+ /// <param name="hue">The HSV hue, typically on the range of 0 to 1.</param>
+ /// <param name="saturation">The HSV saturation, typically on the range of 0 to 1.</param>
+ /// <param name="value">The HSV value (brightness), typically on the range of 0 to 1.</param>
+ /// <param name="alpha">The alpha (transparency) value, typically on the range of 0 to 1.</param>
+ /// <returns>The constructed color.</returns>
+ public static Color FromHSV(float hue, float saturation, float value, float alpha = 1.0f)
+ {
+ if (saturation == 0)
+ {
+ // Achromatic (grey)
+ return new Color(value, value, value, alpha);
+ }
- if (rgba.Length == 8)
+ int i;
+ float f, p, q, t;
+
+ hue *= 6.0f;
+ hue %= 6f;
+ i = (int)hue;
+
+ f = hue - i;
+ p = value * (1 - saturation);
+ q = value * (1 - saturation * f);
+ t = value * (1 - saturation * (1 - f));
+
+ switch (i)
{
- alpha = true;
+ case 0: // Red is the dominant color
+ return new Color(value, t, p, alpha);
+ case 1: // Green is the dominant color
+ return new Color(q, value, p, alpha);
+ case 2:
+ return new Color(p, value, t, alpha);
+ case 3: // Blue is the dominant color
+ return new Color(p, q, value, alpha);
+ case 4:
+ return new Color(t, p, value, alpha);
+ default: // (5) Red is the dominant color
+ return new Color(value, p, q, alpha);
}
- else if (rgba.Length == 6)
+ }
+
+ /// <summary>
+ /// Converts a color to HSV values. This is equivalent to using each of
+ /// the `h`/`s`/`v` properties, but much more efficient.
+ /// </summary>
+ /// <param name="hue">Output parameter for the HSV hue.</param>
+ /// <param name="saturation">Output parameter for the HSV saturation.</param>
+ /// <param name="value">Output parameter for the HSV value.</param>
+ public void ToHSV(out float hue, out float saturation, out float value)
+ {
+ float max = (float)Mathf.Max(r, Mathf.Max(g, b));
+ float min = (float)Mathf.Min(r, Mathf.Min(g, b));
+
+ float delta = max - min;
+
+ if (delta == 0)
{
- alpha = false;
+ hue = 0;
}
else
{
- throw new ArgumentOutOfRangeException("Invalid color code. Length is " + rgba.Length + " but a length of 6 or 8 is expected: " + rgba);
+ if (r == max)
+ {
+ hue = (g - b) / delta; // Between yellow & magenta
+ }
+ else if (g == max)
+ {
+ hue = 2 + (b - r) / delta; // Between cyan & yellow
+ }
+ else
+ {
+ hue = 4 + (r - g) / delta; // Between magenta & cyan
+ }
+
+ hue /= 6.0f;
+
+ if (hue < 0)
+ {
+ hue += 1.0f;
+ }
}
- if (alpha)
- {
- a = ParseCol8(rgba, 0) / 255f;
+ saturation = max == 0 ? 0 : 1f - 1f * min / max;
+ value = max;
+ }
+
+ private static int ParseCol4(string str, int ofs)
+ {
+ char character = str[ofs];
- if (a < 0)
- throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba);
+ if (character >= '0' && character <= '9')
+ {
+ return character - '0';
}
- else
+ else if (character >= 'a' && character <= 'f')
{
- a = 1.0f;
+ return character + (10 - 'a');
}
+ else if (character >= 'A' && character <= 'F')
+ {
+ return character + (10 - 'A');
+ }
+ return -1;
+ }
- int from = alpha ? 2 : 0;
+ private static int ParseCol8(string str, int ofs)
+ {
+ return ParseCol4(str, ofs) * 16 + ParseCol4(str, ofs + 1);
+ }
- r = ParseCol8(rgba, from + 0) / 255f;
+ private string ToHex32(float val)
+ {
+ byte b = (byte)Mathf.RoundToInt(Mathf.Clamp(val * 255, 0, 255));
+ return b.HexEncode();
+ }
- if (r < 0)
- throw new ArgumentOutOfRangeException("Invalid color code. Red part is not valid hexadecimal: " + rgba);
+ internal static bool HtmlIsValid(string color)
+ {
+ if (color.Length == 0)
+ {
+ return false;
+ }
- g = ParseCol8(rgba, from + 2) / 255f;
+ if (color[0] == '#')
+ {
+ color = color.Substring(1);
+ }
- if (g < 0)
- throw new ArgumentOutOfRangeException("Invalid color code. Green part is not valid hexadecimal: " + rgba);
+ // Check if the amount of hex digits is valid.
+ int len = color.Length;
+ if (!(len == 3 || len == 4 || len == 6 || len == 8))
+ {
+ return false;
+ }
- b = ParseCol8(rgba, from + 4) / 255f;
+ // Check if each hex digit is valid.
+ for (int i = 0; i < len; i++)
+ {
+ if (ParseCol4(color, i) == -1)
+ {
+ return false;
+ }
+ }
- if (b < 0)
- throw new ArgumentOutOfRangeException("Invalid color code. Blue part is not valid hexadecimal: " + rgba);
+ return true;
}
public static Color operator +(Color left, Color right)
@@ -690,13 +950,13 @@ namespace Godot
if (Mathf.IsEqualApprox(left.g, right.g))
{
if (Mathf.IsEqualApprox(left.b, right.b))
+ {
return left.a < right.a;
+ }
return left.b < right.b;
}
-
return left.g < right.g;
}
-
return left.r < right.r;
}
@@ -707,13 +967,13 @@ namespace Godot
if (Mathf.IsEqualApprox(left.g, right.g))
{
if (Mathf.IsEqualApprox(left.b, right.b))
+ {
return left.a > right.a;
+ }
return left.b > right.b;
}
-
return left.g > right.g;
}
-
return left.r > right.r;
}
@@ -732,6 +992,12 @@ namespace Godot
return r == other.r && g == other.g && b == other.b && a == other.a;
}
+ /// <summary>
+ /// Returns true if this color and `other` are approximately equal, by running
+ /// <see cref="Godot.Mathf.IsEqualApprox(float, float)"/> on each component.
+ /// </summary>
+ /// <param name="other">The other color to compare.</param>
+ /// <returns>Whether or not the colors are approximately equal.</returns>
public bool IsEqualApprox(Color other)
{
return Mathf.IsEqualApprox(r, other.r) && Mathf.IsEqualApprox(g, other.g) && Mathf.IsEqualApprox(b, other.b) && Mathf.IsEqualApprox(a, other.a);
@@ -744,12 +1010,12 @@ namespace Godot
public override string ToString()
{
- return String.Format("{0},{1},{2},{3}", r.ToString(), g.ToString(), b.ToString(), a.ToString());
+ return String.Format("({0}, {1}, {2}, {3})", r.ToString(), g.ToString(), b.ToString(), a.ToString());
}
public string ToString(string format)
{
- return String.Format("{0},{1},{2},{3}", r.ToString(format), g.ToString(format), b.ToString(format), a.ToString(format));
+ return String.Format("({0}, {1}, {2}, {3})", r.ToString(format), g.ToString(format), b.ToString(format), a.ToString(format));
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs
index f41f5e9fc8..4bb727bd35 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Colors.cs
@@ -3,303 +3,307 @@ using System.Collections.Generic;
namespace Godot
{
+ /// <summary>
+ /// This class contains color constants created from standardized color names.
+ /// The standardized color set is based on the X11 and .NET color names.
+ /// </summary>
public static class Colors
{
- // Color names and values are derived from core/color_names.inc
+ // Color names and values are derived from core/math/color_names.inc
internal static readonly Dictionary<string, Color> namedColors = new Dictionary<string, Color> {
- {"aliceblue", new Color(0.94f, 0.97f, 1.00f)},
- {"antiquewhite", new Color(0.98f, 0.92f, 0.84f)},
- {"aqua", new Color(0.00f, 1.00f, 1.00f)},
- {"aquamarine", new Color(0.50f, 1.00f, 0.83f)},
- {"azure", new Color(0.94f, 1.00f, 1.00f)},
- {"beige", new Color(0.96f, 0.96f, 0.86f)},
- {"bisque", new Color(1.00f, 0.89f, 0.77f)},
- {"black", new Color(0.00f, 0.00f, 0.00f)},
- {"blanchedalmond", new Color(1.00f, 0.92f, 0.80f)},
- {"blue", new Color(0.00f, 0.00f, 1.00f)},
- {"blueviolet", new Color(0.54f, 0.17f, 0.89f)},
- {"brown", new Color(0.65f, 0.16f, 0.16f)},
- {"burlywood", new Color(0.87f, 0.72f, 0.53f)},
- {"cadetblue", new Color(0.37f, 0.62f, 0.63f)},
- {"chartreuse", new Color(0.50f, 1.00f, 0.00f)},
- {"chocolate", new Color(0.82f, 0.41f, 0.12f)},
- {"coral", new Color(1.00f, 0.50f, 0.31f)},
- {"cornflower", new Color(0.39f, 0.58f, 0.93f)},
- {"cornsilk", new Color(1.00f, 0.97f, 0.86f)},
- {"crimson", new Color(0.86f, 0.08f, 0.24f)},
- {"cyan", new Color(0.00f, 1.00f, 1.00f)},
- {"darkblue", new Color(0.00f, 0.00f, 0.55f)},
- {"darkcyan", new Color(0.00f, 0.55f, 0.55f)},
- {"darkgoldenrod", new Color(0.72f, 0.53f, 0.04f)},
- {"darkgray", new Color(0.66f, 0.66f, 0.66f)},
- {"darkgreen", new Color(0.00f, 0.39f, 0.00f)},
- {"darkkhaki", new Color(0.74f, 0.72f, 0.42f)},
- {"darkmagenta", new Color(0.55f, 0.00f, 0.55f)},
- {"darkolivegreen", new Color(0.33f, 0.42f, 0.18f)},
- {"darkorange", new Color(1.00f, 0.55f, 0.00f)},
- {"darkorchid", new Color(0.60f, 0.20f, 0.80f)},
- {"darkred", new Color(0.55f, 0.00f, 0.00f)},
- {"darksalmon", new Color(0.91f, 0.59f, 0.48f)},
- {"darkseagreen", new Color(0.56f, 0.74f, 0.56f)},
- {"darkslateblue", new Color(0.28f, 0.24f, 0.55f)},
- {"darkslategray", new Color(0.18f, 0.31f, 0.31f)},
- {"darkturquoise", new Color(0.00f, 0.81f, 0.82f)},
- {"darkviolet", new Color(0.58f, 0.00f, 0.83f)},
- {"deeppink", new Color(1.00f, 0.08f, 0.58f)},
- {"deepskyblue", new Color(0.00f, 0.75f, 1.00f)},
- {"dimgray", new Color(0.41f, 0.41f, 0.41f)},
- {"dodgerblue", new Color(0.12f, 0.56f, 1.00f)},
- {"firebrick", new Color(0.70f, 0.13f, 0.13f)},
- {"floralwhite", new Color(1.00f, 0.98f, 0.94f)},
- {"forestgreen", new Color(0.13f, 0.55f, 0.13f)},
- {"fuchsia", new Color(1.00f, 0.00f, 1.00f)},
- {"gainsboro", new Color(0.86f, 0.86f, 0.86f)},
- {"ghostwhite", new Color(0.97f, 0.97f, 1.00f)},
- {"gold", new Color(1.00f, 0.84f, 0.00f)},
- {"goldenrod", new Color(0.85f, 0.65f, 0.13f)},
- {"gray", new Color(0.75f, 0.75f, 0.75f)},
- {"green", new Color(0.00f, 1.00f, 0.00f)},
- {"greenyellow", new Color(0.68f, 1.00f, 0.18f)},
- {"honeydew", new Color(0.94f, 1.00f, 0.94f)},
- {"hotpink", new Color(1.00f, 0.41f, 0.71f)},
- {"indianred", new Color(0.80f, 0.36f, 0.36f)},
- {"indigo", new Color(0.29f, 0.00f, 0.51f)},
- {"ivory", new Color(1.00f, 1.00f, 0.94f)},
- {"khaki", new Color(0.94f, 0.90f, 0.55f)},
- {"lavender", new Color(0.90f, 0.90f, 0.98f)},
- {"lavenderblush", new Color(1.00f, 0.94f, 0.96f)},
- {"lawngreen", new Color(0.49f, 0.99f, 0.00f)},
- {"lemonchiffon", new Color(1.00f, 0.98f, 0.80f)},
- {"lightblue", new Color(0.68f, 0.85f, 0.90f)},
- {"lightcoral", new Color(0.94f, 0.50f, 0.50f)},
- {"lightcyan", new Color(0.88f, 1.00f, 1.00f)},
- {"lightgoldenrod", new Color(0.98f, 0.98f, 0.82f)},
- {"lightgray", new Color(0.83f, 0.83f, 0.83f)},
- {"lightgreen", new Color(0.56f, 0.93f, 0.56f)},
- {"lightpink", new Color(1.00f, 0.71f, 0.76f)},
- {"lightsalmon", new Color(1.00f, 0.63f, 0.48f)},
- {"lightseagreen", new Color(0.13f, 0.70f, 0.67f)},
- {"lightskyblue", new Color(0.53f, 0.81f, 0.98f)},
- {"lightslategray", new Color(0.47f, 0.53f, 0.60f)},
- {"lightsteelblue", new Color(0.69f, 0.77f, 0.87f)},
- {"lightyellow", new Color(1.00f, 1.00f, 0.88f)},
- {"lime", new Color(0.00f, 1.00f, 0.00f)},
- {"limegreen", new Color(0.20f, 0.80f, 0.20f)},
- {"linen", new Color(0.98f, 0.94f, 0.90f)},
- {"magenta", new Color(1.00f, 0.00f, 1.00f)},
- {"maroon", new Color(0.69f, 0.19f, 0.38f)},
- {"mediumaquamarine", new Color(0.40f, 0.80f, 0.67f)},
- {"mediumblue", new Color(0.00f, 0.00f, 0.80f)},
- {"mediumorchid", new Color(0.73f, 0.33f, 0.83f)},
- {"mediumpurple", new Color(0.58f, 0.44f, 0.86f)},
- {"mediumseagreen", new Color(0.24f, 0.70f, 0.44f)},
- {"mediumslateblue", new Color(0.48f, 0.41f, 0.93f)},
- {"mediumspringgreen", new Color(0.00f, 0.98f, 0.60f)},
- {"mediumturquoise", new Color(0.28f, 0.82f, 0.80f)},
- {"mediumvioletred", new Color(0.78f, 0.08f, 0.52f)},
- {"midnightblue", new Color(0.10f, 0.10f, 0.44f)},
- {"mintcream", new Color(0.96f, 1.00f, 0.98f)},
- {"mistyrose", new Color(1.00f, 0.89f, 0.88f)},
- {"moccasin", new Color(1.00f, 0.89f, 0.71f)},
- {"navajowhite", new Color(1.00f, 0.87f, 0.68f)},
- {"navyblue", new Color(0.00f, 0.00f, 0.50f)},
- {"oldlace", new Color(0.99f, 0.96f, 0.90f)},
- {"olive", new Color(0.50f, 0.50f, 0.00f)},
- {"olivedrab", new Color(0.42f, 0.56f, 0.14f)},
- {"orange", new Color(1.00f, 0.65f, 0.00f)},
- {"orangered", new Color(1.00f, 0.27f, 0.00f)},
- {"orchid", new Color(0.85f, 0.44f, 0.84f)},
- {"palegoldenrod", new Color(0.93f, 0.91f, 0.67f)},
- {"palegreen", new Color(0.60f, 0.98f, 0.60f)},
- {"paleturquoise", new Color(0.69f, 0.93f, 0.93f)},
- {"palevioletred", new Color(0.86f, 0.44f, 0.58f)},
- {"papayawhip", new Color(1.00f, 0.94f, 0.84f)},
- {"peachpuff", new Color(1.00f, 0.85f, 0.73f)},
- {"peru", new Color(0.80f, 0.52f, 0.25f)},
- {"pink", new Color(1.00f, 0.75f, 0.80f)},
- {"plum", new Color(0.87f, 0.63f, 0.87f)},
- {"powderblue", new Color(0.69f, 0.88f, 0.90f)},
- {"purple", new Color(0.63f, 0.13f, 0.94f)},
- {"rebeccapurple", new Color(0.40f, 0.20f, 0.60f)},
- {"red", new Color(1.00f, 0.00f, 0.00f)},
- {"rosybrown", new Color(0.74f, 0.56f, 0.56f)},
- {"royalblue", new Color(0.25f, 0.41f, 0.88f)},
- {"saddlebrown", new Color(0.55f, 0.27f, 0.07f)},
- {"salmon", new Color(0.98f, 0.50f, 0.45f)},
- {"sandybrown", new Color(0.96f, 0.64f, 0.38f)},
- {"seagreen", new Color(0.18f, 0.55f, 0.34f)},
- {"seashell", new Color(1.00f, 0.96f, 0.93f)},
- {"sienna", new Color(0.63f, 0.32f, 0.18f)},
- {"silver", new Color(0.75f, 0.75f, 0.75f)},
- {"skyblue", new Color(0.53f, 0.81f, 0.92f)},
- {"slateblue", new Color(0.42f, 0.35f, 0.80f)},
- {"slategray", new Color(0.44f, 0.50f, 0.56f)},
- {"snow", new Color(1.00f, 0.98f, 0.98f)},
- {"springgreen", new Color(0.00f, 1.00f, 0.50f)},
- {"steelblue", new Color(0.27f, 0.51f, 0.71f)},
- {"tan", new Color(0.82f, 0.71f, 0.55f)},
- {"teal", new Color(0.00f, 0.50f, 0.50f)},
- {"thistle", new Color(0.85f, 0.75f, 0.85f)},
- {"tomato", new Color(1.00f, 0.39f, 0.28f)},
- {"transparent", new Color(1.00f, 1.00f, 1.00f, 0.00f)},
- {"turquoise", new Color(0.25f, 0.88f, 0.82f)},
- {"violet", new Color(0.93f, 0.51f, 0.93f)},
- {"webgreen", new Color(0.00f, 0.50f, 0.00f)},
- {"webgray", new Color(0.50f, 0.50f, 0.50f)},
- {"webmaroon", new Color(0.50f, 0.00f, 0.00f)},
- {"webpurple", new Color(0.50f, 0.00f, 0.50f)},
- {"wheat", new Color(0.96f, 0.87f, 0.70f)},
- {"white", new Color(1.00f, 1.00f, 1.00f)},
- {"whitesmoke", new Color(0.96f, 0.96f, 0.96f)},
- {"yellow", new Color(1.00f, 1.00f, 0.00f)},
- {"yellowgreen", new Color(0.60f, 0.80f, 0.20f)},
+ {"ALICEBLUE", new Color(0.94f, 0.97f, 1.00f)},
+ {"ANTIQUEWHITE", new Color(0.98f, 0.92f, 0.84f)},
+ {"AQUA", new Color(0.00f, 1.00f, 1.00f)},
+ {"AQUAMARINE", new Color(0.50f, 1.00f, 0.83f)},
+ {"AZURE", new Color(0.94f, 1.00f, 1.00f)},
+ {"BEIGE", new Color(0.96f, 0.96f, 0.86f)},
+ {"BISQUE", new Color(1.00f, 0.89f, 0.77f)},
+ {"BLACK", new Color(0.00f, 0.00f, 0.00f)},
+ {"BLANCHEDALMOND", new Color(1.00f, 0.92f, 0.80f)},
+ {"BLUE", new Color(0.00f, 0.00f, 1.00f)},
+ {"BLUEVIOLET", new Color(0.54f, 0.17f, 0.89f)},
+ {"BROWN", new Color(0.65f, 0.16f, 0.16f)},
+ {"BURLYWOOD", new Color(0.87f, 0.72f, 0.53f)},
+ {"CADETBLUE", new Color(0.37f, 0.62f, 0.63f)},
+ {"CHARTREUSE", new Color(0.50f, 1.00f, 0.00f)},
+ {"CHOCOLATE", new Color(0.82f, 0.41f, 0.12f)},
+ {"CORAL", new Color(1.00f, 0.50f, 0.31f)},
+ {"CORNFLOWERBLUE", new Color(0.39f, 0.58f, 0.93f)},
+ {"CORNSILK", new Color(1.00f, 0.97f, 0.86f)},
+ {"CRIMSON", new Color(0.86f, 0.08f, 0.24f)},
+ {"CYAN", new Color(0.00f, 1.00f, 1.00f)},
+ {"DARKBLUE", new Color(0.00f, 0.00f, 0.55f)},
+ {"DARKCYAN", new Color(0.00f, 0.55f, 0.55f)},
+ {"DARKGOLDENROD", new Color(0.72f, 0.53f, 0.04f)},
+ {"DARKGRAY", new Color(0.66f, 0.66f, 0.66f)},
+ {"DARKGREEN", new Color(0.00f, 0.39f, 0.00f)},
+ {"DARKKHAKI", new Color(0.74f, 0.72f, 0.42f)},
+ {"DARKMAGENTA", new Color(0.55f, 0.00f, 0.55f)},
+ {"DARKOLIVEGREEN", new Color(0.33f, 0.42f, 0.18f)},
+ {"DARKORANGE", new Color(1.00f, 0.55f, 0.00f)},
+ {"DARKORCHID", new Color(0.60f, 0.20f, 0.80f)},
+ {"DARKRED", new Color(0.55f, 0.00f, 0.00f)},
+ {"DARKSALMON", new Color(0.91f, 0.59f, 0.48f)},
+ {"DARKSEAGREEN", new Color(0.56f, 0.74f, 0.56f)},
+ {"DARKSLATEBLUE", new Color(0.28f, 0.24f, 0.55f)},
+ {"DARKSLATEGRAY", new Color(0.18f, 0.31f, 0.31f)},
+ {"DARKTURQUOISE", new Color(0.00f, 0.81f, 0.82f)},
+ {"DARKVIOLET", new Color(0.58f, 0.00f, 0.83f)},
+ {"DEEPPINK", new Color(1.00f, 0.08f, 0.58f)},
+ {"DEEPSKYBLUE", new Color(0.00f, 0.75f, 1.00f)},
+ {"DIMGRAY", new Color(0.41f, 0.41f, 0.41f)},
+ {"DODGERBLUE", new Color(0.12f, 0.56f, 1.00f)},
+ {"FIREBRICK", new Color(0.70f, 0.13f, 0.13f)},
+ {"FLORALWHITE", new Color(1.00f, 0.98f, 0.94f)},
+ {"FORESTGREEN", new Color(0.13f, 0.55f, 0.13f)},
+ {"FUCHSIA", new Color(1.00f, 0.00f, 1.00f)},
+ {"GAINSBORO", new Color(0.86f, 0.86f, 0.86f)},
+ {"GHOSTWHITE", new Color(0.97f, 0.97f, 1.00f)},
+ {"GOLD", new Color(1.00f, 0.84f, 0.00f)},
+ {"GOLDENROD", new Color(0.85f, 0.65f, 0.13f)},
+ {"GRAY", new Color(0.75f, 0.75f, 0.75f)},
+ {"GREEN", new Color(0.00f, 1.00f, 0.00f)},
+ {"GREENYELLOW", new Color(0.68f, 1.00f, 0.18f)},
+ {"HONEYDEW", new Color(0.94f, 1.00f, 0.94f)},
+ {"HOTPINK", new Color(1.00f, 0.41f, 0.71f)},
+ {"INDIANRED", new Color(0.80f, 0.36f, 0.36f)},
+ {"INDIGO", new Color(0.29f, 0.00f, 0.51f)},
+ {"IVORY", new Color(1.00f, 1.00f, 0.94f)},
+ {"KHAKI", new Color(0.94f, 0.90f, 0.55f)},
+ {"LAVENDER", new Color(0.90f, 0.90f, 0.98f)},
+ {"LAVENDERBLUSH", new Color(1.00f, 0.94f, 0.96f)},
+ {"LAWNGREEN", new Color(0.49f, 0.99f, 0.00f)},
+ {"LEMONCHIFFON", new Color(1.00f, 0.98f, 0.80f)},
+ {"LIGHTBLUE", new Color(0.68f, 0.85f, 0.90f)},
+ {"LIGHTCORAL", new Color(0.94f, 0.50f, 0.50f)},
+ {"LIGHTCYAN", new Color(0.88f, 1.00f, 1.00f)},
+ {"LIGHTGOLDENROD", new Color(0.98f, 0.98f, 0.82f)},
+ {"LIGHTGRAY", new Color(0.83f, 0.83f, 0.83f)},
+ {"LIGHTGREEN", new Color(0.56f, 0.93f, 0.56f)},
+ {"LIGHTPINK", new Color(1.00f, 0.71f, 0.76f)},
+ {"LIGHTSALMON", new Color(1.00f, 0.63f, 0.48f)},
+ {"LIGHTSEAGREEN", new Color(0.13f, 0.70f, 0.67f)},
+ {"LIGHTSKYBLUE", new Color(0.53f, 0.81f, 0.98f)},
+ {"LIGHTSLATEGRAY", new Color(0.47f, 0.53f, 0.60f)},
+ {"LIGHTSTEELBLUE", new Color(0.69f, 0.77f, 0.87f)},
+ {"LIGHTYELLOW", new Color(1.00f, 1.00f, 0.88f)},
+ {"LIME", new Color(0.00f, 1.00f, 0.00f)},
+ {"LIMEGREEN", new Color(0.20f, 0.80f, 0.20f)},
+ {"LINEN", new Color(0.98f, 0.94f, 0.90f)},
+ {"MAGENTA", new Color(1.00f, 0.00f, 1.00f)},
+ {"MAROON", new Color(0.69f, 0.19f, 0.38f)},
+ {"MEDIUMAQUAMARINE", new Color(0.40f, 0.80f, 0.67f)},
+ {"MEDIUMBLUE", new Color(0.00f, 0.00f, 0.80f)},
+ {"MEDIUMORCHID", new Color(0.73f, 0.33f, 0.83f)},
+ {"MEDIUMPURPLE", new Color(0.58f, 0.44f, 0.86f)},
+ {"MEDIUMSEAGREEN", new Color(0.24f, 0.70f, 0.44f)},
+ {"MEDIUMSLATEBLUE", new Color(0.48f, 0.41f, 0.93f)},
+ {"MEDIUMSPRINGGREEN", new Color(0.00f, 0.98f, 0.60f)},
+ {"MEDIUMTURQUOISE", new Color(0.28f, 0.82f, 0.80f)},
+ {"MEDIUMVIOLETRED", new Color(0.78f, 0.08f, 0.52f)},
+ {"MIDNIGHTBLUE", new Color(0.10f, 0.10f, 0.44f)},
+ {"MINTCREAM", new Color(0.96f, 1.00f, 0.98f)},
+ {"MISTYROSE", new Color(1.00f, 0.89f, 0.88f)},
+ {"MOCCASIN", new Color(1.00f, 0.89f, 0.71f)},
+ {"NAVAJOWHITE", new Color(1.00f, 0.87f, 0.68f)},
+ {"NAVYBLUE", new Color(0.00f, 0.00f, 0.50f)},
+ {"OLDLACE", new Color(0.99f, 0.96f, 0.90f)},
+ {"OLIVE", new Color(0.50f, 0.50f, 0.00f)},
+ {"OLIVEDRAB", new Color(0.42f, 0.56f, 0.14f)},
+ {"ORANGE", new Color(1.00f, 0.65f, 0.00f)},
+ {"ORANGERED", new Color(1.00f, 0.27f, 0.00f)},
+ {"ORCHID", new Color(0.85f, 0.44f, 0.84f)},
+ {"PALEGOLDENROD", new Color(0.93f, 0.91f, 0.67f)},
+ {"PALEGREEN", new Color(0.60f, 0.98f, 0.60f)},
+ {"PALETURQUOISE", new Color(0.69f, 0.93f, 0.93f)},
+ {"PALEVIOLETRED", new Color(0.86f, 0.44f, 0.58f)},
+ {"PAPAYAWHIP", new Color(1.00f, 0.94f, 0.84f)},
+ {"PEACHPUFF", new Color(1.00f, 0.85f, 0.73f)},
+ {"PERU", new Color(0.80f, 0.52f, 0.25f)},
+ {"PINK", new Color(1.00f, 0.75f, 0.80f)},
+ {"PLUM", new Color(0.87f, 0.63f, 0.87f)},
+ {"POWDERBLUE", new Color(0.69f, 0.88f, 0.90f)},
+ {"PURPLE", new Color(0.63f, 0.13f, 0.94f)},
+ {"REBECCAPURPLE", new Color(0.40f, 0.20f, 0.60f)},
+ {"RED", new Color(1.00f, 0.00f, 0.00f)},
+ {"ROSYBROWN", new Color(0.74f, 0.56f, 0.56f)},
+ {"ROYALBLUE", new Color(0.25f, 0.41f, 0.88f)},
+ {"SADDLEBROWN", new Color(0.55f, 0.27f, 0.07f)},
+ {"SALMON", new Color(0.98f, 0.50f, 0.45f)},
+ {"SANDYBROWN", new Color(0.96f, 0.64f, 0.38f)},
+ {"SEAGREEN", new Color(0.18f, 0.55f, 0.34f)},
+ {"SEASHELL", new Color(1.00f, 0.96f, 0.93f)},
+ {"SIENNA", new Color(0.63f, 0.32f, 0.18f)},
+ {"SILVER", new Color(0.75f, 0.75f, 0.75f)},
+ {"SKYBLUE", new Color(0.53f, 0.81f, 0.92f)},
+ {"SLATEBLUE", new Color(0.42f, 0.35f, 0.80f)},
+ {"SLATEGRAY", new Color(0.44f, 0.50f, 0.56f)},
+ {"SNOW", new Color(1.00f, 0.98f, 0.98f)},
+ {"SPRINGGREEN", new Color(0.00f, 1.00f, 0.50f)},
+ {"STEELBLUE", new Color(0.27f, 0.51f, 0.71f)},
+ {"TAN", new Color(0.82f, 0.71f, 0.55f)},
+ {"TEAL", new Color(0.00f, 0.50f, 0.50f)},
+ {"THISTLE", new Color(0.85f, 0.75f, 0.85f)},
+ {"TOMATO", new Color(1.00f, 0.39f, 0.28f)},
+ {"TRANSPARENT", new Color(1.00f, 1.00f, 1.00f, 0.00f)},
+ {"TURQUOISE", new Color(0.25f, 0.88f, 0.82f)},
+ {"VIOLET", new Color(0.93f, 0.51f, 0.93f)},
+ {"WEBGRAY", new Color(0.50f, 0.50f, 0.50f)},
+ {"WEBGREEN", new Color(0.00f, 0.50f, 0.00f)},
+ {"WEBMAROON", new Color(0.50f, 0.00f, 0.00f)},
+ {"WEBPURPLE", new Color(0.50f, 0.00f, 0.50f)},
+ {"WHEAT", new Color(0.96f, 0.87f, 0.70f)},
+ {"WHITE", new Color(1.00f, 1.00f, 1.00f)},
+ {"WHITESMOKE", new Color(0.96f, 0.96f, 0.96f)},
+ {"YELLOW", new Color(1.00f, 1.00f, 0.00f)},
+ {"YELLOWGREEN", new Color(0.60f, 0.80f, 0.20f)},
};
- public static Color AliceBlue { get { return namedColors["aliceblue"]; } }
- public static Color AntiqueWhite { get { return namedColors["antiquewhite"]; } }
- public static Color Aqua { get { return namedColors["aqua"]; } }
- public static Color Aquamarine { get { return namedColors["aquamarine"]; } }
- public static Color Azure { get { return namedColors["azure"]; } }
- public static Color Beige { get { return namedColors["beige"]; } }
- public static Color Bisque { get { return namedColors["bisque"]; } }
- public static Color Black { get { return namedColors["black"]; } }
- public static Color BlanchedAlmond { get { return namedColors["blanchedalmond"]; } }
- public static Color Blue { get { return namedColors["blue"]; } }
- public static Color BlueViolet { get { return namedColors["blueviolet"]; } }
- public static Color Brown { get { return namedColors["brown"]; } }
- public static Color BurlyWood { get { return namedColors["burlywood"]; } }
- public static Color CadetBlue { get { return namedColors["cadetblue"]; } }
- public static Color Chartreuse { get { return namedColors["chartreuse"]; } }
- public static Color Chocolate { get { return namedColors["chocolate"]; } }
- public static Color Coral { get { return namedColors["coral"]; } }
- public static Color Cornflower { get { return namedColors["cornflower"]; } }
- public static Color Cornsilk { get { return namedColors["cornsilk"]; } }
- public static Color Crimson { get { return namedColors["crimson"]; } }
- public static Color Cyan { get { return namedColors["cyan"]; } }
- public static Color DarkBlue { get { return namedColors["darkblue"]; } }
- public static Color DarkCyan { get { return namedColors["darkcyan"]; } }
- public static Color DarkGoldenrod { get { return namedColors["darkgoldenrod"]; } }
- public static Color DarkGray { get { return namedColors["darkgray"]; } }
- public static Color DarkGreen { get { return namedColors["darkgreen"]; } }
- public static Color DarkKhaki { get { return namedColors["darkkhaki"]; } }
- public static Color DarkMagenta { get { return namedColors["darkmagenta"]; } }
- public static Color DarkOliveGreen { get { return namedColors["darkolivegreen"]; } }
- public static Color DarkOrange { get { return namedColors["darkorange"]; } }
- public static Color DarkOrchid { get { return namedColors["darkorchid"]; } }
- public static Color DarkRed { get { return namedColors["darkred"]; } }
- public static Color DarkSalmon { get { return namedColors["darksalmon"]; } }
- public static Color DarkSeaGreen { get { return namedColors["darkseagreen"]; } }
- public static Color DarkSlateBlue { get { return namedColors["darkslateblue"]; } }
- public static Color DarkSlateGray { get { return namedColors["darkslategray"]; } }
- public static Color DarkTurquoise { get { return namedColors["darkturquoise"]; } }
- public static Color DarkViolet { get { return namedColors["darkviolet"]; } }
- public static Color DeepPink { get { return namedColors["deeppink"]; } }
- public static Color DeepSkyBlue { get { return namedColors["deepskyblue"]; } }
- public static Color DimGray { get { return namedColors["dimgray"]; } }
- public static Color DodgerBlue { get { return namedColors["dodgerblue"]; } }
- public static Color Firebrick { get { return namedColors["firebrick"]; } }
- public static Color FloralWhite { get { return namedColors["floralwhite"]; } }
- public static Color ForestGreen { get { return namedColors["forestgreen"]; } }
- public static Color Fuchsia { get { return namedColors["fuchsia"]; } }
- public static Color Gainsboro { get { return namedColors["gainsboro"]; } }
- public static Color GhostWhite { get { return namedColors["ghostwhite"]; } }
- public static Color Gold { get { return namedColors["gold"]; } }
- public static Color Goldenrod { get { return namedColors["goldenrod"]; } }
- public static Color Gray { get { return namedColors["gray"]; } }
- public static Color Green { get { return namedColors["green"]; } }
- public static Color GreenYellow { get { return namedColors["greenyellow"]; } }
- public static Color Honeydew { get { return namedColors["honeydew"]; } }
- public static Color HotPink { get { return namedColors["hotpink"]; } }
- public static Color IndianRed { get { return namedColors["indianred"]; } }
- public static Color Indigo { get { return namedColors["indigo"]; } }
- public static Color Ivory { get { return namedColors["ivory"]; } }
- public static Color Khaki { get { return namedColors["khaki"]; } }
- public static Color Lavender { get { return namedColors["lavender"]; } }
- public static Color LavenderBlush { get { return namedColors["lavenderblush"]; } }
- public static Color LawnGreen { get { return namedColors["lawngreen"]; } }
- public static Color LemonChiffon { get { return namedColors["lemonchiffon"]; } }
- public static Color LightBlue { get { return namedColors["lightblue"]; } }
- public static Color LightCoral { get { return namedColors["lightcoral"]; } }
- public static Color LightCyan { get { return namedColors["lightcyan"]; } }
- public static Color LightGoldenrod { get { return namedColors["lightgoldenrod"]; } }
- public static Color LightGray { get { return namedColors["lightgray"]; } }
- public static Color LightGreen { get { return namedColors["lightgreen"]; } }
- public static Color LightPink { get { return namedColors["lightpink"]; } }
- public static Color LightSalmon { get { return namedColors["lightsalmon"]; } }
- public static Color LightSeaGreen { get { return namedColors["lightseagreen"]; } }
- public static Color LightSkyBlue { get { return namedColors["lightskyblue"]; } }
- public static Color LightSlateGray { get { return namedColors["lightslategray"]; } }
- public static Color LightSteelBlue { get { return namedColors["lightsteelblue"]; } }
- public static Color LightYellow { get { return namedColors["lightyellow"]; } }
- public static Color Lime { get { return namedColors["lime"]; } }
- public static Color Limegreen { get { return namedColors["limegreen"]; } }
- public static Color Linen { get { return namedColors["linen"]; } }
- public static Color Magenta { get { return namedColors["magenta"]; } }
- public static Color Maroon { get { return namedColors["maroon"]; } }
- public static Color MediumAquamarine { get { return namedColors["mediumaquamarine"]; } }
- public static Color MediumBlue { get { return namedColors["mediumblue"]; } }
- public static Color MediumOrchid { get { return namedColors["mediumorchid"]; } }
- public static Color MediumPurple { get { return namedColors["mediumpurple"]; } }
- public static Color MediumSeaGreen { get { return namedColors["mediumseagreen"]; } }
- public static Color MediumSlateBlue { get { return namedColors["mediumslateblue"]; } }
- public static Color MediumSpringGreen { get { return namedColors["mediumspringgreen"]; } }
- public static Color MediumTurquoise { get { return namedColors["mediumturquoise"]; } }
- public static Color MediumVioletRed { get { return namedColors["mediumvioletred"]; } }
- public static Color MidnightBlue { get { return namedColors["midnightblue"]; } }
- public static Color MintCream { get { return namedColors["mintcream"]; } }
- public static Color MistyRose { get { return namedColors["mistyrose"]; } }
- public static Color Moccasin { get { return namedColors["moccasin"]; } }
- public static Color NavajoWhite { get { return namedColors["navajowhite"]; } }
- public static Color NavyBlue { get { return namedColors["navyblue"]; } }
- public static Color OldLace { get { return namedColors["oldlace"]; } }
- public static Color Olive { get { return namedColors["olive"]; } }
- public static Color OliveDrab { get { return namedColors["olivedrab"]; } }
- public static Color Orange { get { return namedColors["orange"]; } }
- public static Color OrangeRed { get { return namedColors["orangered"]; } }
- public static Color Orchid { get { return namedColors["orchid"]; } }
- public static Color PaleGoldenrod { get { return namedColors["palegoldenrod"]; } }
- public static Color PaleGreen { get { return namedColors["palegreen"]; } }
- public static Color PaleTurquoise { get { return namedColors["paleturquoise"]; } }
- public static Color PaleVioletRed { get { return namedColors["palevioletred"]; } }
- public static Color PapayaWhip { get { return namedColors["papayawhip"]; } }
- public static Color PeachPuff { get { return namedColors["peachpuff"]; } }
- public static Color Peru { get { return namedColors["peru"]; } }
- public static Color Pink { get { return namedColors["pink"]; } }
- public static Color Plum { get { return namedColors["plum"]; } }
- public static Color PowderBlue { get { return namedColors["powderblue"]; } }
- public static Color Purple { get { return namedColors["purple"]; } }
- public static Color RebeccaPurple { get { return namedColors["rebeccapurple"]; } }
- public static Color Red { get { return namedColors["red"]; } }
- public static Color RosyBrown { get { return namedColors["rosybrown"]; } }
- public static Color RoyalBlue { get { return namedColors["royalblue"]; } }
- public static Color SaddleBrown { get { return namedColors["saddlebrown"]; } }
- public static Color Salmon { get { return namedColors["salmon"]; } }
- public static Color SandyBrown { get { return namedColors["sandybrown"]; } }
- public static Color SeaGreen { get { return namedColors["seagreen"]; } }
- public static Color SeaShell { get { return namedColors["seashell"]; } }
- public static Color Sienna { get { return namedColors["sienna"]; } }
- public static Color Silver { get { return namedColors["silver"]; } }
- public static Color SkyBlue { get { return namedColors["skyblue"]; } }
- public static Color SlateBlue { get { return namedColors["slateblue"]; } }
- public static Color SlateGray { get { return namedColors["slategray"]; } }
- public static Color Snow { get { return namedColors["snow"]; } }
- public static Color SpringGreen { get { return namedColors["springgreen"]; } }
- public static Color SteelBlue { get { return namedColors["steelblue"]; } }
- public static Color Tan { get { return namedColors["tan"]; } }
- public static Color Teal { get { return namedColors["teal"]; } }
- public static Color Thistle { get { return namedColors["thistle"]; } }
- public static Color Tomato { get { return namedColors["tomato"]; } }
- public static Color Transparent { get { return namedColors["transparent"]; } }
- public static Color Turquoise { get { return namedColors["turquoise"]; } }
- public static Color Violet { get { return namedColors["violet"]; } }
- public static Color WebGreen { get { return namedColors["webgreen"]; } }
- public static Color WebGray { get { return namedColors["webgray"]; } }
- public static Color WebMaroon { get { return namedColors["webmaroon"]; } }
- public static Color WebPurple { get { return namedColors["webpurple"]; } }
- public static Color Wheat { get { return namedColors["wheat"]; } }
- public static Color White { get { return namedColors["white"]; } }
- public static Color WhiteSmoke { get { return namedColors["whitesmoke"]; } }
- public static Color Yellow { get { return namedColors["yellow"]; } }
- public static Color YellowGreen { get { return namedColors["yellowgreen"]; } }
+ public static Color AliceBlue { get { return namedColors["ALICEBLUE"]; } }
+ public static Color AntiqueWhite { get { return namedColors["ANTIQUEWHITE"]; } }
+ public static Color Aqua { get { return namedColors["AQUA"]; } }
+ public static Color Aquamarine { get { return namedColors["AQUAMARINE"]; } }
+ public static Color Azure { get { return namedColors["AZURE"]; } }
+ public static Color Beige { get { return namedColors["BEIGE"]; } }
+ public static Color Bisque { get { return namedColors["BISQUE"]; } }
+ public static Color Black { get { return namedColors["BLACK"]; } }
+ public static Color BlanchedAlmond { get { return namedColors["BLANCHEDALMOND"]; } }
+ public static Color Blue { get { return namedColors["BLUE"]; } }
+ public static Color BlueViolet { get { return namedColors["BLUEVIOLET"]; } }
+ public static Color Brown { get { return namedColors["BROWN"]; } }
+ public static Color Burlywood { get { return namedColors["BURLYWOOD"]; } }
+ public static Color CadetBlue { get { return namedColors["CADETBLUE"]; } }
+ public static Color Chartreuse { get { return namedColors["CHARTREUSE"]; } }
+ public static Color Chocolate { get { return namedColors["CHOCOLATE"]; } }
+ public static Color Coral { get { return namedColors["CORAL"]; } }
+ public static Color CornflowerBlue { get { return namedColors["CORNFLOWERBLUE"]; } }
+ public static Color Cornsilk { get { return namedColors["CORNSILK"]; } }
+ public static Color Crimson { get { return namedColors["CRIMSON"]; } }
+ public static Color Cyan { get { return namedColors["CYAN"]; } }
+ public static Color DarkBlue { get { return namedColors["DARKBLUE"]; } }
+ public static Color DarkCyan { get { return namedColors["DARKCYAN"]; } }
+ public static Color DarkGoldenrod { get { return namedColors["DARKGOLDENROD"]; } }
+ public static Color DarkGray { get { return namedColors["DARKGRAY"]; } }
+ public static Color DarkGreen { get { return namedColors["DARKGREEN"]; } }
+ public static Color DarkKhaki { get { return namedColors["DARKKHAKI"]; } }
+ public static Color DarkMagenta { get { return namedColors["DARKMAGENTA"]; } }
+ public static Color DarkOliveGreen { get { return namedColors["DARKOLIVEGREEN"]; } }
+ public static Color DarkOrange { get { return namedColors["DARKORANGE"]; } }
+ public static Color DarkOrchid { get { return namedColors["DARKORCHID"]; } }
+ public static Color DarkRed { get { return namedColors["DARKRED"]; } }
+ public static Color DarkSalmon { get { return namedColors["DARKSALMON"]; } }
+ public static Color DarkSeaGreen { get { return namedColors["DARKSEAGREEN"]; } }
+ public static Color DarkSlateBlue { get { return namedColors["DARKSLATEBLUE"]; } }
+ public static Color DarkSlateGray { get { return namedColors["DARKSLATEGRAY"]; } }
+ public static Color DarkTurquoise { get { return namedColors["DARKTURQUOISE"]; } }
+ public static Color DarkViolet { get { return namedColors["DARKVIOLET"]; } }
+ public static Color DeepPink { get { return namedColors["DEEPPINK"]; } }
+ public static Color DeepSkyBlue { get { return namedColors["DEEPSKYBLUE"]; } }
+ public static Color DimGray { get { return namedColors["DIMGRAY"]; } }
+ public static Color DodgerBlue { get { return namedColors["DODGERBLUE"]; } }
+ public static Color Firebrick { get { return namedColors["FIREBRICK"]; } }
+ public static Color FloralWhite { get { return namedColors["FLORALWHITE"]; } }
+ public static Color ForestGreen { get { return namedColors["FORESTGREEN"]; } }
+ public static Color Fuchsia { get { return namedColors["FUCHSIA"]; } }
+ public static Color Gainsboro { get { return namedColors["GAINSBORO"]; } }
+ public static Color GhostWhite { get { return namedColors["GHOSTWHITE"]; } }
+ public static Color Gold { get { return namedColors["GOLD"]; } }
+ public static Color Goldenrod { get { return namedColors["GOLDENROD"]; } }
+ public static Color Gray { get { return namedColors["GRAY"]; } }
+ public static Color Green { get { return namedColors["GREEN"]; } }
+ public static Color GreenYellow { get { return namedColors["GREENYELLOW"]; } }
+ public static Color Honeydew { get { return namedColors["HONEYDEW"]; } }
+ public static Color HotPink { get { return namedColors["HOTPINK"]; } }
+ public static Color IndianRed { get { return namedColors["INDIANRED"]; } }
+ public static Color Indigo { get { return namedColors["INDIGO"]; } }
+ public static Color Ivory { get { return namedColors["IVORY"]; } }
+ public static Color Khaki { get { return namedColors["KHAKI"]; } }
+ public static Color Lavender { get { return namedColors["LAVENDER"]; } }
+ public static Color LavenderBlush { get { return namedColors["LAVENDERBLUSH"]; } }
+ public static Color LawnGreen { get { return namedColors["LAWNGREEN"]; } }
+ public static Color LemonChiffon { get { return namedColors["LEMONCHIFFON"]; } }
+ public static Color LightBlue { get { return namedColors["LIGHTBLUE"]; } }
+ public static Color LightCoral { get { return namedColors["LIGHTCORAL"]; } }
+ public static Color LightCyan { get { return namedColors["LIGHTCYAN"]; } }
+ public static Color LightGoldenrod { get { return namedColors["LIGHTGOLDENROD"]; } }
+ public static Color LightGray { get { return namedColors["LIGHTGRAY"]; } }
+ public static Color LightGreen { get { return namedColors["LIGHTGREEN"]; } }
+ public static Color LightPink { get { return namedColors["LIGHTPINK"]; } }
+ public static Color LightSalmon { get { return namedColors["LIGHTSALMON"]; } }
+ public static Color LightSeaGreen { get { return namedColors["LIGHTSEAGREEN"]; } }
+ public static Color LightSkyBlue { get { return namedColors["LIGHTSKYBLUE"]; } }
+ public static Color LightSlateGray { get { return namedColors["LIGHTSLATEGRAY"]; } }
+ public static Color LightSteelBlue { get { return namedColors["LIGHTSTEELBLUE"]; } }
+ public static Color LightYellow { get { return namedColors["LIGHTYELLOW"]; } }
+ public static Color Lime { get { return namedColors["LIME"]; } }
+ public static Color LimeGreen { get { return namedColors["LIMEGREEN"]; } }
+ public static Color Linen { get { return namedColors["LINEN"]; } }
+ public static Color Magenta { get { return namedColors["MAGENTA"]; } }
+ public static Color Maroon { get { return namedColors["MAROON"]; } }
+ public static Color MediumAquamarine { get { return namedColors["MEDIUMAQUAMARINE"]; } }
+ public static Color MediumBlue { get { return namedColors["MEDIUMBLUE"]; } }
+ public static Color MediumOrchid { get { return namedColors["MEDIUMORCHID"]; } }
+ public static Color MediumPurple { get { return namedColors["MEDIUMPURPLE"]; } }
+ public static Color MediumSeaGreen { get { return namedColors["MEDIUMSEAGREEN"]; } }
+ public static Color MediumSlateBlue { get { return namedColors["MEDIUMSLATEBLUE"]; } }
+ public static Color MediumSpringGreen { get { return namedColors["MEDIUMSPRINGGREEN"]; } }
+ public static Color MediumTurquoise { get { return namedColors["MEDIUMTURQUOISE"]; } }
+ public static Color MediumVioletRed { get { return namedColors["MEDIUMVIOLETRED"]; } }
+ public static Color MidnightBlue { get { return namedColors["MIDNIGHTBLUE"]; } }
+ public static Color MintCream { get { return namedColors["MINTCREAM"]; } }
+ public static Color MistyRose { get { return namedColors["MISTYROSE"]; } }
+ public static Color Moccasin { get { return namedColors["MOCCASIN"]; } }
+ public static Color NavajoWhite { get { return namedColors["NAVAJOWHITE"]; } }
+ public static Color NavyBlue { get { return namedColors["NAVYBLUE"]; } }
+ public static Color OldLace { get { return namedColors["OLDLACE"]; } }
+ public static Color Olive { get { return namedColors["OLIVE"]; } }
+ public static Color OliveDrab { get { return namedColors["OLIVEDRAB"]; } }
+ public static Color Orange { get { return namedColors["ORANGE"]; } }
+ public static Color OrangeRed { get { return namedColors["ORANGERED"]; } }
+ public static Color Orchid { get { return namedColors["ORCHID"]; } }
+ public static Color PaleGoldenrod { get { return namedColors["PALEGOLDENROD"]; } }
+ public static Color PaleGreen { get { return namedColors["PALEGREEN"]; } }
+ public static Color PaleTurquoise { get { return namedColors["PALETURQUOISE"]; } }
+ public static Color PaleVioletRed { get { return namedColors["PALEVIOLETRED"]; } }
+ public static Color PapayaWhip { get { return namedColors["PAPAYAWHIP"]; } }
+ public static Color PeachPuff { get { return namedColors["PEACHPUFF"]; } }
+ public static Color Peru { get { return namedColors["PERU"]; } }
+ public static Color Pink { get { return namedColors["PINK"]; } }
+ public static Color Plum { get { return namedColors["PLUM"]; } }
+ public static Color PowderBlue { get { return namedColors["POWDERBLUE"]; } }
+ public static Color Purple { get { return namedColors["PURPLE"]; } }
+ public static Color RebeccaPurple { get { return namedColors["REBECCAPURPLE"]; } }
+ public static Color Red { get { return namedColors["RED"]; } }
+ public static Color RosyBrown { get { return namedColors["ROSYBROWN"]; } }
+ public static Color RoyalBlue { get { return namedColors["ROYALBLUE"]; } }
+ public static Color SaddleBrown { get { return namedColors["SADDLEBROWN"]; } }
+ public static Color Salmon { get { return namedColors["SALMON"]; } }
+ public static Color SandyBrown { get { return namedColors["SANDYBROWN"]; } }
+ public static Color SeaGreen { get { return namedColors["SEAGREEN"]; } }
+ public static Color Seashell { get { return namedColors["SEASHELL"]; } }
+ public static Color Sienna { get { return namedColors["SIENNA"]; } }
+ public static Color Silver { get { return namedColors["SILVER"]; } }
+ public static Color SkyBlue { get { return namedColors["SKYBLUE"]; } }
+ public static Color SlateBlue { get { return namedColors["SLATEBLUE"]; } }
+ public static Color SlateGray { get { return namedColors["SLATEGRAY"]; } }
+ public static Color Snow { get { return namedColors["SNOW"]; } }
+ public static Color SpringGreen { get { return namedColors["SPRINGGREEN"]; } }
+ public static Color SteelBlue { get { return namedColors["STEELBLUE"]; } }
+ public static Color Tan { get { return namedColors["TAN"]; } }
+ public static Color Teal { get { return namedColors["TEAL"]; } }
+ public static Color Thistle { get { return namedColors["THISTLE"]; } }
+ public static Color Tomato { get { return namedColors["TOMATO"]; } }
+ public static Color Transparent { get { return namedColors["TRANSPARENT"]; } }
+ public static Color Turquoise { get { return namedColors["TURQUOISE"]; } }
+ public static Color Violet { get { return namedColors["VIOLET"]; } }
+ public static Color WebGray { get { return namedColors["WEBGRAY"]; } }
+ public static Color WebGreen { get { return namedColors["WEBGREEN"]; } }
+ public static Color WebMaroon { get { return namedColors["WEBMAROON"]; } }
+ public static Color WebPurple { get { return namedColors["WEBPURPLE"]; } }
+ public static Color Wheat { get { return namedColors["WHEAT"]; } }
+ public static Color White { get { return namedColors["WHITE"]; } }
+ public static Color WhiteSmoke { get { return namedColors["WHITESMOKE"]; } }
+ public static Color Yellow { get { return namedColors["YELLOW"]; } }
+ public static Color YellowGreen { get { return namedColors["YELLOWGREEN"]; } }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
new file mode 100644
index 0000000000..785e87a043
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs
@@ -0,0 +1,395 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+namespace Godot
+{
+ internal static class DelegateUtils
+ {
+ private enum TargetKind : uint
+ {
+ Static,
+ GodotObject,
+ CompilerGenerated
+ }
+
+ internal static bool TrySerializeDelegate(Delegate @delegate, Collections.Array serializedData)
+ {
+ if (@delegate is MulticastDelegate multicastDelegate)
+ {
+ bool someDelegatesSerialized = false;
+
+ Delegate[] invocationList = multicastDelegate.GetInvocationList();
+
+ if (invocationList.Length > 1)
+ {
+ var multiCastData = new Collections.Array();
+
+ foreach (Delegate oneDelegate in invocationList)
+ someDelegatesSerialized |= TrySerializeDelegate(oneDelegate, multiCastData);
+
+ if (!someDelegatesSerialized)
+ return false;
+
+ serializedData.Add(multiCastData);
+ return true;
+ }
+ }
+
+ if (TrySerializeSingleDelegate(@delegate, out byte[] buffer))
+ {
+ serializedData.Add(buffer);
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool TrySerializeSingleDelegate(Delegate @delegate, out byte[] buffer)
+ {
+ buffer = null;
+
+ object target = @delegate.Target;
+
+ switch (target)
+ {
+ case null:
+ {
+ using (var stream = new MemoryStream())
+ using (var writer = new BinaryWriter(stream))
+ {
+ writer.Write((ulong) TargetKind.Static);
+
+ SerializeType(writer, @delegate.GetType());
+
+ if (!TrySerializeMethodInfo(writer, @delegate.Method))
+ return false;
+
+ buffer = stream.ToArray();
+ return true;
+ }
+ }
+ case Godot.Object godotObject:
+ {
+ using (var stream = new MemoryStream())
+ using (var writer = new BinaryWriter(stream))
+ {
+ writer.Write((ulong) TargetKind.GodotObject);
+ writer.Write((ulong) godotObject.GetInstanceId());
+
+ SerializeType(writer, @delegate.GetType());
+
+ if (!TrySerializeMethodInfo(writer, @delegate.Method))
+ return false;
+
+ buffer = stream.ToArray();
+ return true;
+ }
+ }
+ default:
+ {
+ Type targetType = target.GetType();
+
+ if (targetType.GetCustomAttribute(typeof(CompilerGeneratedAttribute), true) != null)
+ {
+ // Compiler generated. Probably a closure. Try to serialize it.
+
+ using (var stream = new MemoryStream())
+ using (var writer = new BinaryWriter(stream))
+ {
+ writer.Write((ulong) TargetKind.CompilerGenerated);
+ SerializeType(writer, targetType);
+
+ SerializeType(writer, @delegate.GetType());
+
+ if (!TrySerializeMethodInfo(writer, @delegate.Method))
+ return false;
+
+ FieldInfo[] fields = targetType.GetFields(BindingFlags.Instance | BindingFlags.Public);
+
+ writer.Write(fields.Length);
+
+ foreach (FieldInfo field in fields)
+ {
+ Type fieldType = field.GetType();
+
+ Variant.Type variantType = GD.TypeToVariantType(fieldType);
+
+ if (variantType == Variant.Type.Nil)
+ return false;
+
+ writer.Write(field.Name);
+ byte[] valueBuffer = GD.Var2Bytes(field.GetValue(target));
+ writer.Write(valueBuffer.Length);
+ writer.Write(valueBuffer);
+ }
+
+ buffer = stream.ToArray();
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+ }
+
+ private static bool TrySerializeMethodInfo(BinaryWriter writer, MethodInfo methodInfo)
+ {
+ if (methodInfo == null)
+ return false;
+
+ SerializeType(writer, methodInfo.DeclaringType);
+
+ writer.Write(methodInfo.Name);
+
+ int flags = 0;
+
+ if (methodInfo.IsPublic)
+ flags |= (int) BindingFlags.Public;
+ else
+ flags |= (int) BindingFlags.NonPublic;
+
+ if (methodInfo.IsStatic)
+ flags |= (int) BindingFlags.Static;
+ else
+ flags |= (int) BindingFlags.Instance;
+
+ writer.Write(flags);
+
+ Type returnType = methodInfo.ReturnType;
+ bool hasReturn = methodInfo.ReturnType != typeof(void);
+
+ writer.Write(hasReturn);
+ if (hasReturn)
+ SerializeType(writer, returnType);
+
+ ParameterInfo[] parameters = methodInfo.GetParameters();
+
+ writer.Write(parameters.Length);
+
+ if (parameters.Length > 0)
+ {
+ for (int i = 0; i < parameters.Length; i++)
+ SerializeType(writer, parameters[i].ParameterType);
+ }
+
+ return true;
+ }
+
+ private static void SerializeType(BinaryWriter writer, Type type)
+ {
+ if (type == null)
+ {
+ int genericArgumentsCount = -1;
+ writer.Write(genericArgumentsCount);
+ }
+ else if (type.IsGenericType)
+ {
+ Type genericTypeDef = type.GetGenericTypeDefinition();
+ Type[] genericArgs = type.GetGenericArguments();
+
+ int genericArgumentsCount = genericArgs.Length;
+ writer.Write(genericArgumentsCount);
+
+ string assemblyQualifiedName = genericTypeDef.AssemblyQualifiedName;
+ Debug.Assert(assemblyQualifiedName != null);
+ writer.Write(assemblyQualifiedName);
+
+ for (int i = 0; i < genericArgs.Length; i++)
+ SerializeType(writer, genericArgs[i]);
+ }
+ else
+ {
+ int genericArgumentsCount = 0;
+ writer.Write(genericArgumentsCount);
+
+ string assemblyQualifiedName = type.AssemblyQualifiedName;
+ Debug.Assert(assemblyQualifiedName != null);
+ writer.Write(assemblyQualifiedName);
+ }
+ }
+
+ private static bool TryDeserializeDelegate(Collections.Array serializedData, out Delegate @delegate)
+ {
+ if (serializedData.Count == 1)
+ {
+ object elem = serializedData[0];
+
+ if (elem is Collections.Array multiCastData)
+ return TryDeserializeDelegate(multiCastData, out @delegate);
+
+ return TryDeserializeSingleDelegate((byte[])elem, out @delegate);
+ }
+
+ @delegate = null;
+
+ var delegates = new List<Delegate>(serializedData.Count);
+
+ foreach (object elem in serializedData)
+ {
+ if (elem is Collections.Array multiCastData)
+ {
+ if (TryDeserializeDelegate(multiCastData, out Delegate oneDelegate))
+ delegates.Add(oneDelegate);
+ }
+ else
+ {
+ if (TryDeserializeSingleDelegate((byte[]) elem, out Delegate oneDelegate))
+ delegates.Add(oneDelegate);
+ }
+ }
+
+ if (delegates.Count <= 0)
+ return false;
+
+ @delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray());
+ return true;
+ }
+
+ private static bool TryDeserializeSingleDelegate(byte[] buffer, out Delegate @delegate)
+ {
+ @delegate = null;
+
+ using (var stream = new MemoryStream(buffer, writable: false))
+ using (var reader = new BinaryReader(stream))
+ {
+ var targetKind = (TargetKind) reader.ReadUInt64();
+
+ switch (targetKind)
+ {
+ case TargetKind.Static:
+ {
+ Type delegateType = DeserializeType(reader);
+ if (delegateType == null)
+ return false;
+
+ if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo))
+ return false;
+
+ @delegate = Delegate.CreateDelegate(delegateType, null, methodInfo);
+ return true;
+ }
+ case TargetKind.GodotObject:
+ {
+ ulong objectId = reader.ReadUInt64();
+ Godot.Object godotObject = GD.InstanceFromId(objectId);
+ if (godotObject == null)
+ return false;
+
+ Type delegateType = DeserializeType(reader);
+ if (delegateType == null)
+ return false;
+
+ if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo))
+ return false;
+
+ @delegate = Delegate.CreateDelegate(delegateType, godotObject, methodInfo);
+ return true;
+ }
+ case TargetKind.CompilerGenerated:
+ {
+ Type targetType = DeserializeType(reader);
+ if (targetType == null)
+ return false;
+
+ Type delegateType = DeserializeType(reader);
+ if (delegateType == null)
+ return false;
+
+ if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo))
+ return false;
+
+ int fieldCount = reader.ReadInt32();
+
+ object recreatedTarget = Activator.CreateInstance(targetType);
+
+ for (int i = 0; i < fieldCount; i++)
+ {
+ string name = reader.ReadString();
+ int valueBufferLength = reader.ReadInt32();
+ byte[] valueBuffer = reader.ReadBytes(valueBufferLength);
+
+ FieldInfo fieldInfo = targetType.GetField(name, BindingFlags.Instance | BindingFlags.Public);
+ fieldInfo?.SetValue(recreatedTarget, GD.Bytes2Var(valueBuffer));
+ }
+
+ @delegate = Delegate.CreateDelegate(delegateType, recreatedTarget, methodInfo);
+ return true;
+ }
+ default:
+ return false;
+ }
+ }
+ }
+
+ private static bool TryDeserializeMethodInfo(BinaryReader reader, out MethodInfo methodInfo)
+ {
+ methodInfo = null;
+
+ Type declaringType = DeserializeType(reader);
+
+ string methodName = reader.ReadString();
+
+ int flags = reader.ReadInt32();
+
+ bool hasReturn = reader.ReadBoolean();
+ Type returnType = hasReturn ? DeserializeType(reader) : typeof(void);
+
+ int parametersCount = reader.ReadInt32();
+
+ if (parametersCount > 0)
+ {
+ 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;
+ }
+
+ methodInfo = declaringType.GetMethod(methodName, (BindingFlags) flags);
+ return methodInfo != null && methodInfo.ReturnType == returnType;
+ }
+
+ private static Type DeserializeType(BinaryReader reader)
+ {
+ int genericArgumentsCount = reader.ReadInt32();
+
+ if (genericArgumentsCount == -1)
+ return null;
+
+ string assemblyQualifiedName = reader.ReadString();
+ var type = Type.GetType(assemblyQualifiedName);
+
+ if (type == null)
+ return null; // Type not found
+
+ if (genericArgumentsCount != 0)
+ {
+ var genericArgumentTypes = new Type[genericArgumentsCount];
+
+ for (int i = 0; i < genericArgumentsCount; i++)
+ {
+ Type genericArgumentType = DeserializeType(reader);
+ if (genericArgumentType == null)
+ return null;
+ genericArgumentTypes[i] = genericArgumentType;
+ }
+
+ type = type.MakeGenericType(genericArgumentTypes);
+ }
+
+ return type;
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
index d72109de92..213fc181c1 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs
@@ -15,10 +15,7 @@ namespace Godot.Collections
public override bool IsInvalid
{
- get
- {
- return handle == IntPtr.Zero;
- }
+ get { return handle == IntPtr.Zero; }
}
protected override bool ReleaseHandle()
@@ -45,7 +42,8 @@ namespace Godot.Collections
if (dictionary == null)
throw new NullReferenceException($"Parameter '{nameof(dictionary)} cannot be null.'");
- MarshalUtils.IDictionaryToDictionary(dictionary, GetPtr());
+ foreach (DictionaryEntry entry in dictionary)
+ Add(entry.Key, entry.Value);
}
internal Dictionary(DictionarySafeHandle handle)
@@ -330,14 +328,8 @@ namespace Godot.Collections
public TValue this[TKey key]
{
- get
- {
- return (TValue)Dictionary.godot_icall_Dictionary_GetValue_Generic(objectDict.GetPtr(), key, valTypeEncoding, valTypeClass);
- }
- set
- {
- objectDict[key] = value;
- }
+ get { return (TValue)Dictionary.godot_icall_Dictionary_GetValue_Generic(objectDict.GetPtr(), key, valTypeEncoding, valTypeClass); }
+ set { objectDict[key] = value; }
}
public ICollection<TKey> Keys
@@ -385,18 +377,12 @@ namespace Godot.Collections
public int Count
{
- get
- {
- return objectDict.Count;
- }
+ get { return objectDict.Count; }
}
public bool IsReadOnly
{
- get
- {
- return objectDict.IsReadOnly;
- }
+ get { return objectDict.IsReadOnly; }
}
public void Add(KeyValuePair<TKey, TValue> item)
@@ -440,7 +426,8 @@ namespace Godot.Collections
public bool Remove(KeyValuePair<TKey, TValue> item)
{
- return Dictionary.godot_icall_Dictionary_Remove(GetPtr(), item.Key, item.Value); ;
+ return Dictionary.godot_icall_Dictionary_Remove(GetPtr(), item.Key, item.Value);
+ ;
}
// IEnumerable<KeyValuePair<TKey, TValue>>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs
index a0f105d55e..c4c911b863 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs
@@ -23,7 +23,7 @@ namespace Godot
/// <example>
/// This sample shows how to use <see cref="Godot.DynamicGodotObject"/> to dynamically access the engine members of a <see cref="Godot.Object"/>.
/// <code>
- /// dynamic sprite = GetNode("Sprite").DynamicGodotObject;
+ /// dynamic sprite = GetNode("Sprite2D").DynamicGodotObject;
/// sprite.add_child(this);
///
/// if ((sprite.hframes * sprite.vframes) &gt; 0)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs
new file mode 100644
index 0000000000..214bbf5179
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/PackedSceneExtensions.cs
@@ -0,0 +1,27 @@
+namespace Godot
+{
+ public partial class PackedScene
+ {
+ /// <summary>
+ /// Instantiates the scene's node hierarchy, erroring on failure.
+ /// Triggers child scene instantiation(s). Triggers a
+ /// `Node.NotificationInstanced` notification on the root node.
+ /// </summary>
+ /// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam>
+ public T Instantiate<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class
+ {
+ return (T)(object)Instantiate(editState);
+ }
+
+ /// <summary>
+ /// Instantiates the scene's node hierarchy, returning null on failure.
+ /// Triggers child scene instantiation(s). Triggers a
+ /// `Node.NotificationInstanced` notification on the root node.
+ /// </summary>
+ /// <typeparam name="T">The type to cast to. Should be a descendant of Node.</typeparam>
+ public T InstantiateOrNull<T>(PackedScene.GenEditState editState = (PackedScene.GenEditState)0) where T : class
+ {
+ return Instantiate(editState) as T;
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs
index 684d160b57..74fa05d1fd 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ResourceLoaderExtensions.cs
@@ -2,9 +2,9 @@ namespace Godot
{
public static partial class ResourceLoader
{
- public static T Load<T>(string path) where T : class
+ public static T Load<T>(string path, string typeHint = null, CacheMode noCache = CacheMode.Reuse) where T : class
{
- return (T)(object)Load(path);
+ return (T)(object)Load(path, typeHint, noCache);
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs
new file mode 100644
index 0000000000..20b11a48dd
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Runtime.CompilerServices;
+using Godot.Collections;
+
+namespace Godot
+{
+ public partial class SceneTree
+ {
+ public Array<T> GetNodesInGroup<T>(StringName group) where T : class
+ {
+ return new Array<T>(godot_icall_SceneTree_get_nodes_in_group_Generic(Object.GetPtr(this), StringName.GetPtr(group), typeof(T)));
+ }
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static IntPtr godot_icall_SceneTree_get_nodes_in_group_Generic(IntPtr obj, IntPtr group, Type elemType);
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
index 19962d418a..6699c5992c 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs
@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using real_t = System.Double;
#else
using real_t = System.Single;
+
#endif
// TODO: Add comments describing what this class does. It is not obvious.
@@ -13,9 +14,9 @@ namespace Godot
{
public static partial class GD
{
- public static object Bytes2Var(byte[] bytes, bool allow_objects = false)
+ public static object Bytes2Var(byte[] bytes, bool allowObjects = false)
{
- return godot_icall_GD_bytes2var(bytes, allow_objects);
+ return godot_icall_GD_bytes2var(bytes, allowObjects);
}
public static object Convert(object what, Variant.Type type)
@@ -25,7 +26,7 @@ namespace Godot
public static real_t Db2Linear(real_t db)
{
- return (real_t)Math.Exp(db * 0.11512925464970228420089957273422);
+ return (real_t) Math.Exp(db * 0.11512925464970228420089957273422);
}
public static real_t DecTime(real_t value, real_t amount, real_t step)
@@ -38,14 +39,6 @@ namespace Godot
return val * sgn;
}
- public static FuncRef FuncRef(Object instance, string funcname)
- {
- var ret = new FuncRef();
- ret.SetInstance(instance);
- ret.SetFunction(funcname);
- return ret;
- }
-
public static int Hash(object var)
{
return godot_icall_GD_hash(var);
@@ -58,7 +51,7 @@ namespace Godot
public static real_t Linear2Db(real_t linear)
{
- return (real_t)(Math.Log(linear) * 8.6858896380650365530225783783321);
+ return (real_t) (Math.Log(linear) * 8.6858896380650365530225783783321);
}
public static Resource Load(string path)
@@ -83,7 +76,7 @@ namespace Godot
public static void Print(params object[] what)
{
- godot_icall_GD_print(Array.ConvertAll(what, x => x.ToString()));
+ godot_icall_GD_print(Array.ConvertAll(what ?? new object[]{"null"}, x => x != null ? x.ToString() : "null"));
}
public static void PrintStack()
@@ -93,22 +86,22 @@ namespace Godot
public static void PrintErr(params object[] what)
{
- godot_icall_GD_printerr(Array.ConvertAll(what, x => x?.ToString()));
+ godot_icall_GD_printerr(Array.ConvertAll(what ?? new object[]{"null"}, x => x != null ? x.ToString() : "null"));
}
public static void PrintRaw(params object[] what)
{
- godot_icall_GD_printraw(Array.ConvertAll(what, x => x?.ToString()));
+ godot_icall_GD_printraw(Array.ConvertAll(what ?? new object[]{"null"}, x => x != null ? x.ToString() : "null"));
}
public static void PrintS(params object[] what)
{
- godot_icall_GD_prints(Array.ConvertAll(what, x => x?.ToString()));
+ godot_icall_GD_prints(Array.ConvertAll(what ?? new object[]{"null"}, x => x != null ? x.ToString() : "null"));
}
public static void PrintT(params object[] what)
{
- godot_icall_GD_printt(Array.ConvertAll(what, x => x?.ToString()));
+ godot_icall_GD_printt(Array.ConvertAll(what ?? new object[]{"null"}, x => x != null ? x.ToString() : "null"));
}
public static float Randf()
@@ -128,7 +121,12 @@ namespace Godot
public static double RandRange(double from, double to)
{
- return godot_icall_GD_rand_range(from, to);
+ return godot_icall_GD_randf_range(from, to);
+ }
+
+ public static int RandRange(int from, int to)
+ {
+ return godot_icall_GD_randi_range(from, to);
}
public static uint RandSeed(ulong seed, out ulong newSeed)
@@ -181,14 +179,14 @@ namespace Godot
return godot_icall_GD_str2var(str);
}
- public static bool TypeExists(string type)
+ public static bool TypeExists(StringName type)
{
- return godot_icall_GD_type_exists(type);
+ return godot_icall_GD_type_exists(StringName.GetPtr(type));
}
- public static byte[] Var2Bytes(object var, bool full_objects = false)
+ public static byte[] Var2Bytes(object var, bool fullObjects = false)
{
- return godot_icall_GD_var2bytes(var, full_objects);
+ return godot_icall_GD_var2bytes(var, fullObjects);
}
public static string Var2Str(object var)
@@ -196,8 +194,13 @@ namespace Godot
return godot_icall_GD_var2str(var);
}
+ public static Variant.Type TypeToVariantType(Type type)
+ {
+ return godot_icall_TypeToVariantType(type);
+ }
+
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static object godot_icall_GD_bytes2var(byte[] bytes, bool allow_objects);
+ internal extern static object godot_icall_GD_bytes2var(byte[] bytes, bool allowObjects);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static object godot_icall_GD_convert(object what, Variant.Type type);
@@ -206,7 +209,7 @@ namespace Godot
internal extern static int godot_icall_GD_hash(object var);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static Object godot_icall_GD_instance_from_id(ulong instance_id);
+ internal extern static Object godot_icall_GD_instance_from_id(ulong instanceId);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_GD_print(object[] what);
@@ -232,9 +235,11 @@ namespace Godot
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_GD_randomize();
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal extern static double godot_icall_GD_randf_range(double from, double to);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static double godot_icall_GD_rand_range(double from, double to);
+ internal extern static int godot_icall_GD_randi_range(int from, int to);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static uint godot_icall_GD_rand_seed(ulong seed, out ulong newSeed);
@@ -249,10 +254,10 @@ namespace Godot
internal extern static object godot_icall_GD_str2var(string str);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static bool godot_icall_GD_type_exists(string type);
+ internal extern static bool godot_icall_GD_type_exists(IntPtr type);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static byte[] godot_icall_GD_var2bytes(object what, bool full_objects);
+ internal extern static byte[] godot_icall_GD_var2bytes(object what, bool fullObjects);
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static string godot_icall_GD_var2str(object var);
@@ -262,5 +267,8 @@ namespace Godot
[MethodImpl(MethodImplOptions.InternalCall)]
internal extern static void godot_icall_GD_pushwarning(string type);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern Variant.Type godot_icall_TypeToVariantType(Type type);
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs
new file mode 100644
index 0000000000..702a6c76ba
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Godot
+{
+ public static partial class GD
+ {
+ /// <summary>
+ /// Fires when an unhandled exception occurs, regardless of project settings.
+ /// </summary>
+ public static event EventHandler<UnhandledExceptionArgs> UnhandledException;
+
+ private static void OnUnhandledException(Exception e)
+ {
+ UnhandledException?.Invoke(null, new UnhandledExceptionArgs(e));
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs
index a1d63a62ef..c59d083080 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs
@@ -16,10 +16,8 @@ namespace Godot
/// <exception cref="System.InvalidOperationException">
/// <paramref name="type"/> is not a generic type. That is, IsGenericType returns false.
/// </exception>
- static bool TypeIsGenericArray(Type type)
- {
- return type.GetGenericTypeDefinition() == typeof(Godot.Collections.Array<>);
- }
+ static bool TypeIsGenericArray(Type type) =>
+ type.GetGenericTypeDefinition() == typeof(Godot.Collections.Array<>);
/// <summary>
/// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/>
@@ -28,10 +26,20 @@ namespace Godot
/// <exception cref="System.InvalidOperationException">
/// <paramref name="type"/> is not a generic type. That is, IsGenericType returns false.
/// </exception>
- static bool TypeIsGenericDictionary(Type type)
- {
- return type.GetGenericTypeDefinition() == typeof(Godot.Collections.Dictionary<,>);
- }
+ static bool TypeIsGenericDictionary(Type type) =>
+ type.GetGenericTypeDefinition() == typeof(Godot.Collections.Dictionary<,>);
+
+ static bool TypeIsSystemGenericList(Type type) =>
+ type.GetGenericTypeDefinition() == typeof(System.Collections.Generic.List<>);
+
+ static bool TypeIsSystemGenericDictionary(Type type) =>
+ type.GetGenericTypeDefinition() == typeof(System.Collections.Generic.Dictionary<,>);
+
+ static bool TypeIsGenericIEnumerable(Type type) => type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
+
+ static bool TypeIsGenericICollection(Type type) => type.GetGenericTypeDefinition() == typeof(ICollection<>);
+
+ static bool TypeIsGenericIDictionary(Type type) => type.GetGenericTypeDefinition() == typeof(IDictionary<,>);
static void ArrayGetElementType(Type arrayType, out Type elementType)
{
@@ -45,105 +53,6 @@ namespace Godot
valueType = genericArgs[1];
}
- static bool GenericIEnumerableIsAssignableFromType(Type type)
- {
- if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
- return true;
-
- foreach (var interfaceType in type.GetInterfaces())
- {
- if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
- return true;
- }
-
- Type baseType = type.BaseType;
-
- if (baseType == null)
- return false;
-
- return GenericIEnumerableIsAssignableFromType(baseType);
- }
-
- static bool GenericIDictionaryIsAssignableFromType(Type type)
- {
- if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>))
- return true;
-
- foreach (var interfaceType in type.GetInterfaces())
- {
- if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
- return true;
- }
-
- Type baseType = type.BaseType;
-
- if (baseType == null)
- return false;
-
- return GenericIDictionaryIsAssignableFromType(baseType);
- }
-
- static bool GenericIEnumerableIsAssignableFromType(Type type, out Type elementType)
- {
- if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
- {
- elementType = type.GetGenericArguments()[0];
- return true;
- }
-
- foreach (var interfaceType in type.GetInterfaces())
- {
- if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
- {
- elementType = interfaceType.GetGenericArguments()[0];
- return true;
- }
- }
-
- Type baseType = type.BaseType;
-
- if (baseType == null)
- {
- elementType = null;
- return false;
- }
-
- return GenericIEnumerableIsAssignableFromType(baseType, out elementType);
- }
-
- static bool GenericIDictionaryIsAssignableFromType(Type type, out Type keyType, out Type valueType)
- {
- if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>))
- {
- var genericArgs = type.GetGenericArguments();
- keyType = genericArgs[0];
- valueType = genericArgs[1];
- return true;
- }
-
- foreach (var interfaceType in type.GetInterfaces())
- {
- if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
- {
- var genericArgs = interfaceType.GetGenericArguments();
- keyType = genericArgs[0];
- valueType = genericArgs[1];
- return true;
- }
- }
-
- Type baseType = type.BaseType;
-
- if (baseType == null)
- {
- keyType = null;
- valueType = null;
- return false;
- }
-
- return GenericIDictionaryIsAssignableFromType(baseType, out keyType, out valueType);
- }
-
static Type MakeGenericArrayType(Type elemType)
{
return typeof(Godot.Collections.Array<>).MakeGenericType(elemType);
@@ -153,60 +62,5 @@ namespace Godot
{
return typeof(Godot.Collections.Dictionary<,>).MakeGenericType(keyType, valueType);
}
-
- // TODO Add support for IEnumerable<T> and IDictionary<TKey, TValue>
- // TODO: EnumerableToArray and IDictionaryToDictionary can be optimized
-
- internal static void EnumerableToArray(IEnumerable enumerable, IntPtr godotArrayPtr)
- {
- if (enumerable is ICollection collection)
- {
- int count = collection.Count;
-
- object[] tempArray = new object[count];
- collection.CopyTo(tempArray, 0);
-
- for (int i = 0; i < count; i++)
- {
- Array.godot_icall_Array_Add(godotArrayPtr, tempArray[i]);
- }
- }
- else
- {
- foreach (object element in enumerable)
- {
- Array.godot_icall_Array_Add(godotArrayPtr, element);
- }
- }
- }
-
- internal static void IDictionaryToDictionary(IDictionary dictionary, IntPtr godotDictionaryPtr)
- {
- foreach (DictionaryEntry entry in dictionary)
- {
- Dictionary.godot_icall_Dictionary_Add(godotDictionaryPtr, entry.Key, entry.Value);
- }
- }
-
- internal static void GenericIDictionaryToDictionary(object dictionary, IntPtr godotDictionaryPtr)
- {
-#if DEBUG
- if (!GenericIDictionaryIsAssignableFromType(dictionary.GetType()))
- throw new InvalidOperationException("The type does not implement IDictionary<,>");
-#endif
-
- // TODO: Can we optimize this?
-
- var keys = ((IEnumerable)dictionary.GetType().GetProperty("Keys").GetValue(dictionary)).GetEnumerator();
- var values = ((IEnumerable)dictionary.GetType().GetProperty("Values").GetValue(dictionary)).GetEnumerator();
-
- while (keys.MoveNext() && values.MoveNext())
- {
- object key = keys.Current;
- object value = values.Current;
-
- Dictionary.godot_icall_Dictionary_Add(godotDictionaryPtr, key, value);
- }
- }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs
index ddfed180b5..c3f372d415 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs
@@ -11,79 +11,185 @@ namespace Godot
{
// Define constants with Decimal precision and cast down to double or float.
+ /// <summary>
+ /// The circle constant, the circumference of the unit circle in radians.
+ /// </summary>
public const real_t Tau = (real_t) 6.2831853071795864769252867666M; // 6.2831855f and 6.28318530717959
+
+ /// <summary>
+ /// Constant that represents how many times the diameter of a circle
+ /// fits around its perimeter. This is equivalent to `Mathf.Tau / 2`.
+ /// </summary>
public const real_t Pi = (real_t) 3.1415926535897932384626433833M; // 3.1415927f and 3.14159265358979
+
+ /// <summary>
+ /// Positive infinity. For negative infinity, use `-Mathf.Inf`.
+ /// </summary>
public const real_t Inf = real_t.PositiveInfinity;
+
+ /// <summary>
+ /// "Not a Number", an invalid value. `NaN` has special properties, including
+ /// that it is not equal to itself. It is output by some invalid operations,
+ /// such as dividing zero by zero.
+ /// </summary>
public const real_t NaN = real_t.NaN;
private const real_t Deg2RadConst = (real_t) 0.0174532925199432957692369077M; // 0.0174532924f and 0.0174532925199433
private const real_t Rad2DegConst = (real_t) 57.295779513082320876798154814M; // 57.29578f and 57.2957795130823
+ /// <summary>
+ /// Returns the absolute value of `s` (i.e. positive value).
+ /// </summary>
+ /// <param name="s">The input number.</param>
+ /// <returns>The absolute value of `s`.</returns>
public static int Abs(int s)
{
return Math.Abs(s);
}
+ /// <summary>
+ /// Returns the absolute value of `s` (i.e. positive value).
+ /// </summary>
+ /// <param name="s">The input number.</param>
+ /// <returns>The absolute value of `s`.</returns>
public static real_t Abs(real_t s)
{
return Math.Abs(s);
}
+ /// <summary>
+ /// Returns the arc cosine of `s` in radians. Use to get the angle of cosine s.
+ /// </summary>
+ /// <param name="s">The input cosine value. Must be on the range of -1.0 to 1.0.</param>
+ /// <returns>An angle that would result in the given cosine value. On the range `0` to `Tau/2`.</returns>
public static real_t Acos(real_t s)
{
return (real_t)Math.Acos(s);
}
+ /// <summary>
+ /// Returns the arc sine of `s` in radians. Use to get the angle of sine s.
+ /// </summary>
+ /// <param name="s">The input sine value. Must be on the range of -1.0 to 1.0.</param>
+ /// <returns>An angle that would result in the given sine value. On the range `-Tau/4` to `Tau/4`.</returns>
public static real_t Asin(real_t s)
{
return (real_t)Math.Asin(s);
}
+ /// <summary>
+ /// Returns the arc tangent of `s` in radians. Use to get the angle of tangent s.
+ ///
+ /// The method cannot know in which quadrant the angle should fall.
+ /// See <see cref="Atan2(real_t, real_t)"/> if you have both `y` and `x`.
+ /// </summary>
+ /// <param name="s">The input tangent value.</param>
+ /// <returns>An angle that would result in the given tangent value. On the range `-Tau/4` to `Tau/4`.</returns>
public static real_t Atan(real_t s)
{
return (real_t)Math.Atan(s);
}
+ /// <summary>
+ /// Returns the arc tangent of `y` and `x` in radians. Use to get the angle
+ /// of the tangent of `y/x`. To compute the value, the method takes into
+ /// account the sign of both arguments in order to determine the quadrant.
+ ///
+ /// Important note: The Y coordinate comes first, by convention.
+ /// </summary>
+ /// <param name="y">The Y coordinate of the point to find the angle to.</param>
+ /// <param name="x">The X coordinate of the point to find the angle to.</param>
+ /// <returns>An angle that would result in the given tangent value. On the range `-Tau/2` to `Tau/2`.</returns>
public static real_t Atan2(real_t y, real_t x)
{
return (real_t)Math.Atan2(y, x);
}
+ /// <summary>
+ /// Converts a 2D point expressed in the cartesian coordinate
+ /// system (X and Y axis) to the polar coordinate system
+ /// (a distance from the origin and an angle).
+ /// </summary>
+ /// <param name="x">The input X coordinate.</param>
+ /// <param name="y">The input Y coordinate.</param>
+ /// <returns>A <see cref="Vector2"/> with X representing the distance and Y representing the angle.</returns>
public static Vector2 Cartesian2Polar(real_t x, real_t y)
{
return new Vector2(Sqrt(x * x + y * y), Atan2(y, x));
}
+ /// <summary>
+ /// Rounds `s` upward (towards positive infinity).
+ /// </summary>
+ /// <param name="s">The number to ceil.</param>
+ /// <returns>The smallest whole number that is not less than `s`.</returns>
public static real_t Ceil(real_t s)
{
return (real_t)Math.Ceiling(s);
}
+ /// <summary>
+ /// Clamps a `value` so that it is not less than `min` and not more than `max`.
+ /// </summary>
+ /// <param name="value">The value to clamp.</param>
+ /// <param name="min">The minimum allowed value.</param>
+ /// <param name="max">The maximum allowed value.</param>
+ /// <returns>The clamped value.</returns>
public static int Clamp(int value, int min, int max)
{
return value < min ? min : value > max ? max : value;
}
+ /// <summary>
+ /// Clamps a `value` so that it is not less than `min` and not more than `max`.
+ /// </summary>
+ /// <param name="value">The value to clamp.</param>
+ /// <param name="min">The minimum allowed value.</param>
+ /// <param name="max">The maximum allowed value.</param>
+ /// <returns>The clamped value.</returns>
public static real_t Clamp(real_t value, real_t min, real_t max)
{
return value < min ? min : value > max ? max : value;
}
+ /// <summary>
+ /// Returns the cosine of angle `s` in radians.
+ /// </summary>
+ /// <param name="s">The angle in radians.</param>
+ /// <returns>The cosine of that angle.</returns>
public static real_t Cos(real_t s)
{
return (real_t)Math.Cos(s);
}
+ /// <summary>
+ /// Returns the hyperbolic cosine of angle `s` in radians.
+ /// </summary>
+ /// <param name="s">The angle in radians.</param>
+ /// <returns>The hyperbolic cosine of that angle.</returns>
public static real_t Cosh(real_t s)
{
return (real_t)Math.Cosh(s);
}
+ /// <summary>
+ /// Converts an angle expressed in degrees to radians.
+ /// </summary>
+ /// <param name="deg">An angle expressed in degrees.</param>
+ /// <returns>The same angle expressed in radians.</returns>
public static real_t Deg2Rad(real_t deg)
{
return deg * Deg2RadConst;
}
+ /// <summary>
+ /// Easing function, based on exponent. The curve values are:
+ /// `0` is constant, `1` is linear, `0` to `1` is ease-in, `1` or more is ease-out.
+ /// Negative values are in-out/out-in.
+ /// </summary>
+ /// <param name="s">The value to ease.</param>
+ /// <param name="curve">`0` is constant, `1` is linear, `0` to `1` is ease-in, `1` or more is ease-out.</param>
+ /// <returns>The eased value.</returns>
public static real_t Ease(real_t s, real_t curve)
{
if (s < 0f)
@@ -118,21 +224,47 @@ namespace Godot
return 0f;
}
+ /// <summary>
+ /// The natural exponential function. It raises the mathematical
+ /// constant `e` to the power of `s` and returns it.
+ /// </summary>
+ /// <param name="s">The exponent to raise `e` to.</param>
+ /// <returns>`e` raised to the power of `s`.</returns>
public static real_t Exp(real_t s)
{
return (real_t)Math.Exp(s);
}
+ /// <summary>
+ /// Rounds `s` downward (towards negative infinity).
+ /// </summary>
+ /// <param name="s">The number to floor.</param>
+ /// <returns>The largest whole number that is not more than `s`.</returns>
public static real_t Floor(real_t s)
{
return (real_t)Math.Floor(s);
}
+ /// <summary>
+ /// Returns a normalized value considering the given range.
+ /// This is the opposite of <see cref="Lerp(real_t, real_t, real_t)"/>.
+ /// </summary>
+ /// <param name="from">The interpolated value.</param>
+ /// <param name="to">The destination value for interpolation.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting value of the inverse interpolation.</returns>
public static real_t InverseLerp(real_t from, real_t to, real_t weight)
{
- return (weight - from) / (to - from);
+ return (weight - from) / (to - from);
}
+ /// <summary>
+ /// Returns true if `a` and `b` are approximately equal to each other.
+ /// The comparison is done using a tolerance calculation with <see cref="Epsilon"/>.
+ /// </summary>
+ /// <param name="a">One of the values.</param>
+ /// <param name="b">The other value.</param>
+ /// <returns>A bool for whether or not the two values are approximately equal.</returns>
public static bool IsEqualApprox(real_t a, real_t b)
{
// Check for exact equality first, required to handle "infinity" values.
@@ -149,26 +281,62 @@ namespace Godot
return Abs(a - b) < tolerance;
}
+ /// <summary>
+ /// Returns whether `s` is an infinity value (either positive infinity or negative infinity).
+ /// </summary>
+ /// <param name="s">The value to check.</param>
+ /// <returns>A bool for whether or not the value is an infinity value.</returns>
public static bool IsInf(real_t s)
{
- return real_t.IsInfinity(s);
+ return real_t.IsInfinity(s);
}
+ /// <summary>
+ /// Returns whether `s` is a `NaN` ("Not a Number" or invalid) value.
+ /// </summary>
+ /// <param name="s">The value to check.</param>
+ /// <returns>A bool for whether or not the value is a `NaN` value.</returns>
public static bool IsNaN(real_t s)
{
- return real_t.IsNaN(s);
+ return real_t.IsNaN(s);
}
+ /// <summary>
+ /// Returns true if `s` is approximately zero.
+ /// The comparison is done using a tolerance calculation with <see cref="Epsilon"/>.
+ ///
+ /// This method is faster than using <see cref="IsEqualApprox(real_t, real_t)"/> with one value as zero.
+ /// </summary>
+ /// <param name="s">The value to check.</param>
+ /// <returns>A bool for whether or not the value is nearly zero.</returns>
public static bool IsZeroApprox(real_t s)
{
return Abs(s) < Epsilon;
}
+ /// <summary>
+ /// Linearly interpolates between two values by a normalized value.
+ /// This is the opposite <see cref="InverseLerp(real_t, real_t, real_t)"/>.
+ /// </summary>
+ /// <param name="from">The start value for interpolation.</param>
+ /// <param name="to">The destination value for interpolation.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting value of the interpolation.</returns>
public static real_t Lerp(real_t from, real_t to, real_t weight)
{
return from + (to - from) * weight;
}
+ /// <summary>
+ /// Linearly interpolates between two angles (in radians) by a normalized value.
+ ///
+ /// Similar to <see cref="Lerp(real_t, real_t, real_t)"/>,
+ /// but interpolates correctly when the angles wrap around <see cref="Tau"/>.
+ /// </summary>
+ /// <param name="from">The start angle for interpolation.</param>
+ /// <param name="to">The destination angle for interpolation.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting angle of the interpolation.</returns>
public static real_t LerpAngle(real_t from, real_t to, real_t weight)
{
real_t difference = (to - from) % Mathf.Tau;
@@ -176,36 +344,81 @@ namespace Godot
return from + distance * weight;
}
+ /// <summary>
+ /// Natural logarithm. The amount of time needed to reach a certain level of continuous growth.
+ ///
+ /// Note: This is not the same as the "log" function on most calculators, which uses a base 10 logarithm.
+ /// </summary>
+ /// <param name="s">The input value.</param>
+ /// <returns>The natural log of `s`.</returns>
public static real_t Log(real_t s)
{
return (real_t)Math.Log(s);
}
+ /// <summary>
+ /// Returns the maximum of two values.
+ /// </summary>
+ /// <param name="a">One of the values.</param>
+ /// <param name="b">The other value.</param>
+ /// <returns>Whichever of the two values is higher.</returns>
public static int Max(int a, int b)
{
return a > b ? a : b;
}
+ /// <summary>
+ /// Returns the maximum of two values.
+ /// </summary>
+ /// <param name="a">One of the values.</param>
+ /// <param name="b">The other value.</param>
+ /// <returns>Whichever of the two values is higher.</returns>
public static real_t Max(real_t a, real_t b)
{
return a > b ? a : b;
}
+ /// <summary>
+ /// Returns the minimum of two values.
+ /// </summary>
+ /// <param name="a">One of the values.</param>
+ /// <param name="b">The other value.</param>
+ /// <returns>Whichever of the two values is lower.</returns>
public static int Min(int a, int b)
{
return a < b ? a : b;
}
+ /// <summary>
+ /// Returns the minimum of two values.
+ /// </summary>
+ /// <param name="a">One of the values.</param>
+ /// <param name="b">The other value.</param>
+ /// <returns>Whichever of the two values is lower.</returns>
public static real_t Min(real_t a, real_t b)
{
return a < b ? a : b;
}
+ /// <summary>
+ /// Moves `from` toward `to` by the `delta` value.
+ ///
+ /// Use a negative delta value to move away.
+ /// </summary>
+ /// <param name="from">The start value.</param>
+ /// <param name="to">The value to move towards.</param>
+ /// <param name="delta">The amount to move by.</param>
+ /// <returns>The value after moving.</returns>
public static real_t MoveToward(real_t from, real_t to, real_t delta)
{
return Abs(to - from) <= delta ? to : from + Sign(to - from) * delta;
}
+ /// <summary>
+ /// Returns the nearest larger power of 2 for the integer `value`.
+ /// </summary>
+ /// <param name="value">The input value.</param>
+ /// <returns>The nearest larger power of 2.</returns>
public static int NearestPo2(int value)
{
value--;
@@ -218,14 +431,25 @@ namespace Godot
return value;
}
+ /// <summary>
+ /// Converts a 2D point expressed in the polar coordinate
+ /// system (a distance from the origin `r` and an angle `th`)
+ /// to the cartesian coordinate system (X and Y axis).
+ /// </summary>
+ /// <param name="r">The distance from the origin.</param>
+ /// <param name="th">The angle of the point.</param>
+ /// <returns>A <see cref="Vector2"/> representing the cartesian coordinate.</returns>
public static Vector2 Polar2Cartesian(real_t r, real_t th)
{
return new Vector2(r * Cos(th), r * Sin(th));
}
/// <summary>
- /// Performs a canonical Modulus operation, where the output is on the range [0, b).
+ /// Performs a canonical Modulus operation, where the output is on the range `[0, b)`.
/// </summary>
+ /// <param name="a">The dividend, the primary input.</param>
+ /// <param name="b">The divisor. The output is on the range `[0, b)`.</param>
+ /// <returns>The resulting output.</returns>
public static int PosMod(int a, int b)
{
int c = a % b;
@@ -237,8 +461,11 @@ namespace Godot
}
/// <summary>
- /// Performs a canonical Modulus operation, where the output is on the range [0, b).
+ /// Performs a canonical Modulus operation, where the output is on the range `[0, b)`.
/// </summary>
+ /// <param name="a">The dividend, the primary input.</param>
+ /// <param name="b">The divisor. The output is on the range `[0, b)`.</param>
+ /// <returns>The resulting output.</returns>
public static real_t PosMod(real_t a, real_t b)
{
real_t c = a % b;
@@ -249,43 +476,89 @@ namespace Godot
return c;
}
+ /// <summary>
+ /// Returns the result of `x` raised to the power of `y`.
+ /// </summary>
+ /// <param name="x">The base.</param>
+ /// <param name="y">The exponent.</param>
+ /// <returns>`x` raised to the power of `y`.</returns>
public static real_t Pow(real_t x, real_t y)
{
return (real_t)Math.Pow(x, y);
}
+ /// <summary>
+ /// Converts an angle expressed in radians to degrees.
+ /// </summary>
+ /// <param name="rad">An angle expressed in radians.</param>
+ /// <returns>The same angle expressed in degrees.</returns>
public static real_t Rad2Deg(real_t rad)
{
return rad * Rad2DegConst;
}
+ /// <summary>
+ /// Rounds `s` to the nearest whole number,
+ /// with halfway cases rounded towards the nearest multiple of two.
+ /// </summary>
+ /// <param name="s">The number to round.</param>
+ /// <returns>The rounded number.</returns>
public static real_t Round(real_t s)
{
return (real_t)Math.Round(s);
}
+ /// <summary>
+ /// Returns the sign of `s`: `-1` or `1`. Returns `0` if `s` is `0`.
+ /// </summary>
+ /// <param name="s">The input number.</param>
+ /// <returns>One of three possible values: `1`, `-1`, or `0`.</returns>
public static int Sign(int s)
{
if (s == 0) return 0;
return s < 0 ? -1 : 1;
}
+ /// <summary>
+ /// Returns the sign of `s`: `-1` or `1`. Returns `0` if `s` is `0`.
+ /// </summary>
+ /// <param name="s">The input number.</param>
+ /// <returns>One of three possible values: `1`, `-1`, or `0`.</returns>
public static int Sign(real_t s)
{
if (s == 0) return 0;
return s < 0 ? -1 : 1;
}
+ /// <summary>
+ /// Returns the sine of angle `s` in radians.
+ /// </summary>
+ /// <param name="s">The angle in radians.</param>
+ /// <returns>The sine of that angle.</returns>
public static real_t Sin(real_t s)
{
return (real_t)Math.Sin(s);
}
+ /// <summary>
+ /// Returns the hyperbolic sine of angle `s` in radians.
+ /// </summary>
+ /// <param name="s">The angle in radians.</param>
+ /// <returns>The hyperbolic sine of that angle.</returns>
public static real_t Sinh(real_t s)
{
return (real_t)Math.Sinh(s);
}
+ /// <summary>
+ /// Returns a number smoothly interpolated between `from` and `to`,
+ /// based on the `weight`. Similar to <see cref="Lerp(real_t, real_t, real_t)"/>,
+ /// but interpolates faster at the beginning and slower at the end.
+ /// </summary>
+ /// <param name="from">The start value for interpolation.</param>
+ /// <param name="to">The destination value for interpolation.</param>
+ /// <param name="weight">A value representing the amount of interpolation.</param>
+ /// <returns>The resulting value of the interpolation.</returns>
public static real_t SmoothStep(real_t from, real_t to, real_t weight)
{
if (IsEqualApprox(from, to))
@@ -296,11 +569,25 @@ namespace Godot
return x * x * (3 - 2 * x);
}
+ /// <summary>
+ /// Returns the square root of `s`, where `s` is a non-negative number.
+ ///
+ /// If you need negative inputs, use `System.Numerics.Complex`.
+ /// </summary>
+ /// <param name="s">The input number. Must not be negative.</param>
+ /// <returns>The square root of `s`.</returns>
public static real_t Sqrt(real_t s)
{
return (real_t)Math.Sqrt(s);
}
+ /// <summary>
+ /// Returns the position of the first non-zero digit, after the
+ /// decimal point. Note that the maximum return value is 10,
+ /// which is a design decision in the implementation.
+ /// </summary>
+ /// <param name="step">The input value.</param>
+ /// <returns>The position of the first non-zero digit.</returns>
public static int StepDecimals(real_t step)
{
double[] sd = new double[] {
@@ -326,32 +613,68 @@ namespace Godot
return 0;
}
- public static real_t Stepify(real_t s, real_t step)
+ /// <summary>
+ /// Snaps float value `s` to a given `step`.
+ /// This can also be used to round a floating point
+ /// number to an arbitrary number of decimals.
+ /// </summary>
+ /// <param name="s">The value to snap.</param>
+ /// <param name="step">The step size to snap to.</param>
+ /// <returns></returns>
+ public static real_t Snapped(real_t s, real_t step)
{
if (step != 0f)
{
- s = Floor(s / step + 0.5f) * step;
+ return Floor(s / step + 0.5f) * step;
}
return s;
}
+ /// <summary>
+ /// Returns the tangent of angle `s` in radians.
+ /// </summary>
+ /// <param name="s">The angle in radians.</param>
+ /// <returns>The tangent of that angle.</returns>
public static real_t Tan(real_t s)
{
return (real_t)Math.Tan(s);
}
+ /// <summary>
+ /// Returns the hyperbolic tangent of angle `s` in radians.
+ /// </summary>
+ /// <param name="s">The angle in radians.</param>
+ /// <returns>The hyperbolic tangent of that angle.</returns>
public static real_t Tanh(real_t s)
{
return (real_t)Math.Tanh(s);
}
+ /// <summary>
+ /// Wraps `value` between `min` and `max`. Usable for creating loop-alike
+ /// behavior or infinite surfaces. If `min` is `0`, this is equivalent
+ /// to <see cref="PosMod(int, int)"/>, so prefer using that instead.
+ /// </summary>
+ /// <param name="value">The value to wrap.</param>
+ /// <param name="min">The minimum allowed value and lower bound of the range.</param>
+ /// <param name="max">The maximum allowed value and upper bound of the range.</param>
+ /// <returns>The wrapped value.</returns>
public static int Wrap(int value, int min, int max)
{
int range = max - min;
return range == 0 ? min : min + ((value - min) % range + range) % range;
}
+ /// <summary>
+ /// Wraps `value` between `min` and `max`. Usable for creating loop-alike
+ /// behavior or infinite surfaces. If `min` is `0`, this is equivalent
+ /// to <see cref="PosMod(real_t, real_t)"/>, so prefer using that instead.
+ /// </summary>
+ /// <param name="value">The value to wrap.</param>
+ /// <param name="min">The minimum allowed value and lower bound of the range.</param>
+ /// <param name="max">The maximum allowed value and upper bound of the range.</param>
+ /// <returns>The wrapped value.</returns>
public static real_t Wrap(real_t value, real_t min, real_t max)
{
real_t range = max - min;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs
index 1b7fd4906f..c2f4701b5f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs
@@ -12,40 +12,89 @@ namespace Godot
{
// Define constants with Decimal precision and cast down to double or float.
+ /// <summary>
+ /// The natural number `e`.
+ /// </summary>
public const real_t E = (real_t) 2.7182818284590452353602874714M; // 2.7182817f and 2.718281828459045
+
+ /// <summary>
+ /// The square root of 2.
+ /// </summary>
public const real_t Sqrt2 = (real_t) 1.4142135623730950488016887242M; // 1.4142136f and 1.414213562373095
+ /// <summary>
+ /// A very small number used for float comparison with error tolerance.
+ /// 1e-06 with single-precision floats, but 1e-14 if `REAL_T_IS_DOUBLE`.
+ /// </summary>
#if REAL_T_IS_DOUBLE
public const real_t Epsilon = 1e-14; // Epsilon size should depend on the precision used.
#else
public const real_t Epsilon = 1e-06f;
#endif
+ /// <summary>
+ /// Returns the amount of digits after the decimal place.
+ /// </summary>
+ /// <param name="s">The input value.</param>
+ /// <returns>The amount of digits.</returns>
public static int DecimalCount(real_t s)
{
return DecimalCount((decimal)s);
}
+ /// <summary>
+ /// Returns the amount of digits after the decimal place.
+ /// </summary>
+ /// <param name="s">The input <see cref="System.Decimal"/> value.</param>
+ /// <returns>The amount of digits.</returns>
public static int DecimalCount(decimal s)
{
return BitConverter.GetBytes(decimal.GetBits(s)[3])[2];
}
+ /// <summary>
+ /// Rounds `s` upward (towards positive infinity).
+ ///
+ /// This is the same as <see cref="Ceil(real_t)"/>, but returns an `int`.
+ /// </summary>
+ /// <param name="s">The number to ceil.</param>
+ /// <returns>The smallest whole number that is not less than `s`.</returns>
public static int CeilToInt(real_t s)
{
return (int)Math.Ceiling(s);
}
+ /// <summary>
+ /// Rounds `s` downward (towards negative infinity).
+ ///
+ /// This is the same as <see cref="Floor(real_t)"/>, but returns an `int`.
+ /// </summary>
+ /// <param name="s">The number to floor.</param>
+ /// <returns>The largest whole number that is not more than `s`.</returns>
public static int FloorToInt(real_t s)
{
return (int)Math.Floor(s);
}
+ /// <summary>
+ ///
+ /// </summary>
+ /// <param name="s"></param>
+ /// <returns></returns>
public static int RoundToInt(real_t s)
{
return (int)Math.Round(s);
}
+ /// <summary>
+ /// Returns true if `a` and `b` are approximately equal to each other.
+ /// The comparison is done using the provided tolerance value.
+ /// If you want the tolerance to be calculated for you, use <see cref="IsEqualApprox(real_t, real_t)"/>.
+ /// </summary>
+ /// <param name="a">One of the values.</param>
+ /// <param name="b">The other value.</param>
+ /// <param name="tolerance">The pre-calculated tolerance value.</param>
+ /// <returns>A bool for whether or not the two values are equal.</returns>
public static bool IsEqualApprox(real_t a, real_t b, real_t tolerance)
{
// Check for exact equality first, required to handle "infinity" values.
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
index 8c5872ba5a..4ecc55f94e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs
@@ -7,7 +7,7 @@ namespace Godot
{
private bool disposed = false;
- internal IntPtr ptr;
+ private IntPtr ptr;
internal static IntPtr GetPtr(NodePath instance)
{
@@ -50,104 +50,93 @@ namespace Godot
this.ptr = ptr;
}
- public IntPtr NativeInstance
- {
- get { return ptr; }
- }
-
public NodePath() : this(string.Empty) {}
public NodePath(string path)
{
- this.ptr = godot_icall_NodePath_Ctor(path);
+ ptr = godot_icall_NodePath_Ctor(path);
}
- public static implicit operator NodePath(string from)
- {
- return new NodePath(from);
- }
+ public static implicit operator NodePath(string from) => new NodePath(from);
- public static implicit operator string(NodePath from)
- {
- return godot_icall_NodePath_operator_String(NodePath.GetPtr(from));
- }
+ public static implicit operator string(NodePath from) => from.ToString();
public override string ToString()
{
- return (string)this;
+ return godot_icall_NodePath_operator_String(GetPtr(this));
}
public NodePath GetAsPropertyPath()
{
- return new NodePath(godot_icall_NodePath_get_as_property_path(NodePath.GetPtr(this)));
+ return new NodePath(godot_icall_NodePath_get_as_property_path(GetPtr(this)));
}
public string GetConcatenatedSubnames()
{
- return godot_icall_NodePath_get_concatenated_subnames(NodePath.GetPtr(this));
+ return godot_icall_NodePath_get_concatenated_subnames(GetPtr(this));
}
public string GetName(int idx)
{
- return godot_icall_NodePath_get_name(NodePath.GetPtr(this), idx);
+ return godot_icall_NodePath_get_name(GetPtr(this), idx);
}
public int GetNameCount()
{
- return godot_icall_NodePath_get_name_count(NodePath.GetPtr(this));
+ return godot_icall_NodePath_get_name_count(GetPtr(this));
}
public string GetSubname(int idx)
{
- return godot_icall_NodePath_get_subname(NodePath.GetPtr(this), idx);
+ return godot_icall_NodePath_get_subname(GetPtr(this), idx);
}
public int GetSubnameCount()
{
- return godot_icall_NodePath_get_subname_count(NodePath.GetPtr(this));
+ return godot_icall_NodePath_get_subname_count(GetPtr(this));
}
public bool IsAbsolute()
{
- return godot_icall_NodePath_is_absolute(NodePath.GetPtr(this));
+ return godot_icall_NodePath_is_absolute(GetPtr(this));
}
public bool IsEmpty()
{
- return godot_icall_NodePath_is_empty(NodePath.GetPtr(this));
+ return godot_icall_NodePath_is_empty(GetPtr(this));
}
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static IntPtr godot_icall_NodePath_Ctor(string path);
+ private static extern IntPtr godot_icall_NodePath_Ctor(string path);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static void godot_icall_NodePath_Dtor(IntPtr ptr);
+ private static extern void godot_icall_NodePath_Dtor(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static string godot_icall_NodePath_operator_String(IntPtr ptr);
+ private static extern string godot_icall_NodePath_operator_String(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static IntPtr godot_icall_NodePath_get_as_property_path(IntPtr ptr);
+ private static extern IntPtr godot_icall_NodePath_get_as_property_path(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static string godot_icall_NodePath_get_concatenated_subnames(IntPtr ptr);
+ private static extern string godot_icall_NodePath_get_concatenated_subnames(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static string godot_icall_NodePath_get_name(IntPtr ptr, int arg1);
+ private static extern string godot_icall_NodePath_get_name(IntPtr ptr, int arg1);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static int godot_icall_NodePath_get_name_count(IntPtr ptr);
+ private static extern int godot_icall_NodePath_get_name_count(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static string godot_icall_NodePath_get_subname(IntPtr ptr, int arg1);
+ private static extern string godot_icall_NodePath_get_subname(IntPtr ptr, int arg1);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static int godot_icall_NodePath_get_subname_count(IntPtr ptr);
+ private static extern int godot_icall_NodePath_get_subname_count(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static bool godot_icall_NodePath_is_absolute(IntPtr ptr);
+ private static extern bool godot_icall_NodePath_is_absolute(IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static bool godot_icall_NodePath_is_empty(IntPtr ptr);
+ private static extern bool godot_icall_NodePath_is_empty(IntPtr ptr);
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
index de80f7fddc..d486d79557 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
@@ -7,7 +7,7 @@ namespace Godot
{
private bool disposed = false;
- private const string nativeName = "Object";
+ private static StringName nativeName = "Object";
internal IntPtr ptr;
internal bool memoryOwn;
@@ -16,6 +16,12 @@ namespace Godot
{
if (ptr == IntPtr.Zero)
ptr = godot_icall_Object_Ctor(this);
+ _InitializeGodotScriptInstanceInternals();
+ }
+
+ internal void _InitializeGodotScriptInstanceInternals()
+ {
+ godot_icall_Object_ConnectEventSignals(ptr);
}
internal Object(bool memoryOwn)
@@ -60,7 +66,7 @@ namespace Godot
if (memoryOwn)
{
memoryOwn = false;
- godot_icall_Reference_Disposed(this, ptr, !disposing);
+ godot_icall_RefCounted_Disposed(this, ptr, !disposing);
}
else
{
@@ -101,7 +107,7 @@ namespace Godot
/// }
/// </code>
/// </example>
- public SignalAwaiter ToSignal(Object source, string signal)
+ public SignalAwaiter ToSignal(Object source, StringName signal)
{
return new SignalAwaiter(source, signal, this);
}
@@ -111,20 +117,28 @@ namespace Godot
/// </summary>
public dynamic DynamicObject => new DynamicGodotObject(this);
+ internal static IntPtr __ClassDB_get_method(StringName type, string method)
+ {
+ return godot_icall_Object_ClassDB_get_method(StringName.GetPtr(type), method);
+ }
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ internal static extern IntPtr godot_icall_Object_Ctor(Object obj);
+
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static IntPtr godot_icall_Object_Ctor(Object obj);
+ internal static extern void godot_icall_Object_Disposed(Object obj, IntPtr ptr);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static void godot_icall_Object_Disposed(Object obj, IntPtr ptr);
+ internal static extern void godot_icall_RefCounted_Disposed(Object obj, IntPtr ptr, bool isFinalizer);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static void godot_icall_Reference_Disposed(Object obj, IntPtr ptr, bool isFinalizer);
+ internal static extern void godot_icall_Object_ConnectEventSignals(IntPtr obj);
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static string godot_icall_Object_ToString(IntPtr ptr);
+ internal static extern string godot_icall_Object_ToString(IntPtr ptr);
// Used by the generated API
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static IntPtr godot_icall_Object_ClassDB_get_method(string type, string method);
+ internal static extern IntPtr godot_icall_Object_ClassDB_get_method(IntPtr type, string method);
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
index 885845e3a4..ad6ca51e8b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs
@@ -8,18 +8,33 @@ using real_t = System.Single;
namespace Godot
{
+ /// <summary>
+ /// Plane represents a normalized plane equation.
+ /// "Over" or "Above" the plane is considered the side of
+ /// the plane towards where the normal is pointing.
+ /// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Plane : IEquatable<Plane>
{
private Vector3 _normal;
+ /// <summary>
+ /// The normal of the plane, which must be normalized.
+ /// In the scalar equation of the plane `ax + by + cz = d`, this is
+ /// the vector `(a, b, c)`, where `d` is the <see cref="D"/> property.
+ /// </summary>
+ /// <value>Equivalent to `x`, `y`, and `z`.</value>
public Vector3 Normal
{
get { return _normal; }
set { _normal = value; }
}
+ /// <summary>
+ /// The X component of the plane's normal vector.
+ /// </summary>
+ /// <value>Equivalent to <see cref="Normal"/>'s X value.</value>
public real_t x
{
get
@@ -32,6 +47,10 @@ namespace Godot
}
}
+ /// <summary>
+ /// The Y component of the plane's normal vector.
+ /// </summary>
+ /// <value>Equivalent to <see cref="Normal"/>'s Y value.</value>
public real_t y
{
get
@@ -44,6 +63,10 @@ namespace Godot
}
}
+ /// <summary>
+ /// The Z component of the plane's normal vector.
+ /// </summary>
+ /// <value>Equivalent to <see cref="Normal"/>'s Z value.</value>
public real_t z
{
get
@@ -56,38 +79,71 @@ namespace Godot
}
}
+ /// <summary>
+ /// The distance from the origin to the plane (in the direction of
+ /// <see cref="Normal"/>). This value is typically non-negative.
+ /// In the scalar equation of the plane `ax + by + cz = d`,
+ /// this is `d`, while the `(a, b, c)` coordinates are represented
+ /// by the <see cref="Normal"/> property.
+ /// </summary>
+ /// <value>The plane's distance from the origin.</value>
public real_t D { get; set; }
+ /// <summary>
+ /// The center of the plane, the point where the normal line intersects the plane.
+ /// </summary>
+ /// <value>Equivalent to <see cref="Normal"/> multiplied by `D`.</value>
public Vector3 Center
{
get
{
return _normal * D;
}
+ set
+ {
+ _normal = value.Normalized();
+ D = value.Length();
+ }
}
+ /// <summary>
+ /// Returns the shortest distance from this plane to the position `point`.
+ /// </summary>
+ /// <param name="point">The position to use for the calculation.</param>
+ /// <returns>The shortest distance.</returns>
public real_t DistanceTo(Vector3 point)
{
return _normal.Dot(point) - D;
}
- public Vector3 GetAnyPoint()
- {
- return _normal * D;
- }
-
+ /// <summary>
+ /// Returns true if point is inside the plane.
+ /// Comparison uses a custom minimum epsilon threshold.
+ /// </summary>
+ /// <param name="point">The point to check.</param>
+ /// <param name="epsilon">The tolerance threshold.</param>
+ /// <returns>A bool for whether or not the plane has the point.</returns>
public bool HasPoint(Vector3 point, real_t epsilon = Mathf.Epsilon)
{
real_t dist = _normal.Dot(point) - D;
return Mathf.Abs(dist) <= epsilon;
}
+ /// <summary>
+ /// Returns the intersection point of the three planes: `b`, `c`,
+ /// and this plane. If no intersection is found, `null` is returned.
+ /// </summary>
+ /// <param name="b">One of the three planes to use in the calculation.</param>
+ /// <param name="c">One of the three planes to use in the calculation.</param>
+ /// <returns>The intersection, or `null` if none is found.</returns>
public Vector3? Intersect3(Plane b, Plane c)
{
real_t denom = _normal.Cross(b._normal).Dot(c._normal);
if (Mathf.IsZeroApprox(denom))
+ {
return null;
+ }
Vector3 result = b._normal.Cross(c._normal) * D +
c._normal.Cross(_normal) * b.D +
@@ -96,54 +152,94 @@ namespace Godot
return result / denom;
}
+ /// <summary>
+ /// Returns the intersection point of a ray consisting of the
+ /// position `from` and the direction normal `dir` with this plane.
+ /// If no intersection is found, `null` is returned.
+ /// </summary>
+ /// <param name="from">The start of the ray.</param>
+ /// <param name="dir">The direction of the ray, normalized.</param>
+ /// <returns>The intersection, or `null` if none is found.</returns>
public Vector3? IntersectRay(Vector3 from, Vector3 dir)
{
real_t den = _normal.Dot(dir);
if (Mathf.IsZeroApprox(den))
+ {
return null;
+ }
real_t dist = (_normal.Dot(from) - D) / den;
// This is a ray, before the emitting pos (from) does not exist
if (dist > Mathf.Epsilon)
+ {
return null;
+ }
return from + dir * -dist;
}
+ /// <summary>
+ /// Returns the intersection point of a line segment from
+ /// position `begin` to position `end` with this plane.
+ /// If no intersection is found, `null` is returned.
+ /// </summary>
+ /// <param name="begin">The start of the line segment.</param>
+ /// <param name="end">The end of the line segment.</param>
+ /// <returns>The intersection, or `null` if none is found.</returns>
public Vector3? IntersectSegment(Vector3 begin, Vector3 end)
{
Vector3 segment = begin - end;
real_t den = _normal.Dot(segment);
if (Mathf.IsZeroApprox(den))
+ {
return null;
+ }
real_t dist = (_normal.Dot(begin) - D) / den;
// Only allow dist to be in the range of 0 to 1, with tolerance.
if (dist < -Mathf.Epsilon || dist > 1.0f + Mathf.Epsilon)
+ {
return null;
+ }
return begin + segment * -dist;
}
+ /// <summary>
+ /// Returns true if `point` is located above the plane.
+ /// </summary>
+ /// <param name="point">The point to check.</param>
+ /// <returns>A bool for whether or not the point is above the plane.</returns>
public bool IsPointOver(Vector3 point)
{
return _normal.Dot(point) > D;
}
+ /// <summary>
+ /// Returns the plane scaled to unit length.
+ /// </summary>
+ /// <returns>A normalized version of the plane.</returns>
public Plane Normalized()
{
real_t len = _normal.Length();
if (len == 0)
+ {
return new Plane(0, 0, 0, 0);
+ }
return new Plane(_normal / len, D / len);
}
+ /// <summary>
+ /// Returns the orthogonal projection of `point` into the plane.
+ /// </summary>
+ /// <param name="point">The point to project.</param>
+ /// <returns>The projected point.</returns>
public Vector3 Project(Vector3 point)
{
return point - _normal * DistanceTo(point);
@@ -154,22 +250,56 @@ namespace Godot
private static readonly Plane _planeXZ = new Plane(0, 1, 0, 0);
private static readonly Plane _planeXY = new Plane(0, 0, 1, 0);
+ /// <summary>
+ /// A plane that extends in the Y and Z axes (normal vector points +X).
+ /// </summary>
+ /// <value>Equivalent to `new Plane(1, 0, 0, 0)`.</value>
public static Plane PlaneYZ { get { return _planeYZ; } }
+
+ /// <summary>
+ /// A plane that extends in the X and Z axes (normal vector points +Y).
+ /// </summary>
+ /// <value>Equivalent to `new Plane(0, 1, 0, 0)`.</value>
public static Plane PlaneXZ { get { return _planeXZ; } }
+
+ /// <summary>
+ /// A plane that extends in the X and Y axes (normal vector points +Z).
+ /// </summary>
+ /// <value>Equivalent to `new Plane(0, 0, 1, 0)`.</value>
public static Plane PlaneXY { get { return _planeXY; } }
- // Constructors
+ /// <summary>
+ /// Constructs a plane from four values. `a`, `b` and `c` become the
+ /// components of the resulting plane's <see cref="Normal"/> vector.
+ /// `d` becomes the plane's distance from the origin.
+ /// </summary>
+ /// <param name="a">The X component of the plane's normal vector.</param>
+ /// <param name="b">The Y component of the plane's normal vector.</param>
+ /// <param name="c">The Z component of the plane's normal vector.</param>
+ /// <param name="d">The plane's distance from the origin. This value is typically non-negative.</param>
public Plane(real_t a, real_t b, real_t c, real_t d)
{
_normal = new Vector3(a, b, c);
this.D = d;
}
+
+ /// <summary>
+ /// Constructs a plane from a normal vector and the plane's distance to the origin.
+ /// </summary>
+ /// <param name="normal">The normal of the plane, must be normalized.</param>
+ /// <param name="d">The plane's distance from the origin. This value is typically non-negative.</param>
public Plane(Vector3 normal, real_t d)
{
this._normal = normal;
this.D = d;
}
+ /// <summary>
+ /// Constructs a plane from the three points, given in clockwise order.
+ /// </summary>
+ /// <param name="v1">The first point.</param>
+ /// <param name="v2">The second point.</param>
+ /// <param name="v3">The third point.</param>
public Plane(Vector3 v1, Vector3 v2, Vector3 v3)
{
_normal = (v1 - v3).Cross(v1 - v2);
@@ -207,6 +337,12 @@ namespace Godot
return _normal == other._normal && D == other.D;
}
+ /// <summary>
+ /// Returns true if this plane and `other` are approximately equal, by running
+ /// <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component.
+ /// </summary>
+ /// <param name="other">The other plane to compare.</param>
+ /// <returns>Whether or not the planes are approximately equal.</returns>
public bool IsEqualApprox(Plane other)
{
return _normal.IsEqualApprox(other._normal) && Mathf.IsEqualApprox(D, other.D);
@@ -219,7 +355,7 @@ namespace Godot
public override string ToString()
{
- return String.Format("({0}, {1})", new object[]
+ return String.Format("{0}, {1}", new object[]
{
_normal.ToString(),
D.ToString()
@@ -228,7 +364,7 @@ namespace Godot
public string ToString(string format)
{
- return String.Format("({0}, {1})", new object[]
+ return String.Format("{0}, {1}", new object[]
{
_normal.ToString(format),
D.ToString(format)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs
deleted file mode 100644
index 6702634c51..0000000000
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quat.cs
+++ /dev/null
@@ -1,414 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-#if REAL_T_IS_DOUBLE
-using real_t = System.Double;
-#else
-using real_t = System.Single;
-#endif
-
-namespace Godot
-{
- [Serializable]
- [StructLayout(LayoutKind.Sequential)]
- public struct Quat : IEquatable<Quat>
- {
- public real_t x;
- public real_t y;
- public real_t z;
- public real_t w;
-
- public real_t this[int index]
- {
- get
- {
- switch (index)
- {
- case 0:
- return x;
- case 1:
- return y;
- case 2:
- return z;
- case 3:
- return w;
- default:
- throw new IndexOutOfRangeException();
- }
- }
- set
- {
- switch (index)
- {
- case 0:
- x = value;
- break;
- case 1:
- y = value;
- break;
- case 2:
- z = value;
- break;
- case 3:
- w = value;
- break;
- default:
- throw new IndexOutOfRangeException();
- }
- }
- }
-
- public real_t Length
- {
- get { return Mathf.Sqrt(LengthSquared); }
- }
-
- public real_t LengthSquared
- {
- get { return Dot(this); }
- }
-
- public Quat CubicSlerp(Quat b, Quat preA, Quat postB, real_t t)
- {
- real_t t2 = (1.0f - t) * t * 2f;
- Quat sp = Slerp(b, t);
- Quat sq = preA.Slerpni(postB, t);
- return sp.Slerpni(sq, t2);
- }
-
- public real_t Dot(Quat b)
- {
- return x * b.x + y * b.y + z * b.z + w * b.w;
- }
-
- public Vector3 GetEuler()
- {
-#if DEBUG
- if (!IsNormalized())
- throw new InvalidOperationException("Quat is not normalized");
-#endif
- var basis = new Basis(this);
- return basis.GetEuler();
- }
-
- public Quat Inverse()
- {
-#if DEBUG
- if (!IsNormalized())
- throw new InvalidOperationException("Quat is not normalized");
-#endif
- return new Quat(-x, -y, -z, w);
- }
-
- public Quat Normalized()
- {
- return this / Length;
- }
-
- [Obsolete("Set is deprecated. Use the Quat(" + nameof(real_t) + ", " + nameof(real_t) + ", " + nameof(real_t) + ", " + nameof(real_t) + ") constructor instead.", error: true)]
- public void Set(real_t x, real_t y, real_t z, real_t w)
- {
- this.x = x;
- this.y = y;
- this.z = z;
- this.w = w;
- }
-
- [Obsolete("Set is deprecated. Use the Quat(" + nameof(Quat) + ") constructor instead.", error: true)]
- public void Set(Quat q)
- {
- this = q;
- }
-
- [Obsolete("SetAxisAngle is deprecated. Use the Quat(" + nameof(Vector3) + ", " + nameof(real_t) + ") constructor instead.", error: true)]
- public void SetAxisAngle(Vector3 axis, real_t angle)
- {
- this = new Quat(axis, angle);
- }
-
- [Obsolete("SetEuler is deprecated. Use the Quat(" + nameof(Vector3) + ") constructor instead.", error: true)]
- public void SetEuler(Vector3 eulerYXZ)
- {
- this = new Quat(eulerYXZ);
- }
-
- public Quat Slerp(Quat b, real_t t)
- {
-#if DEBUG
- if (!IsNormalized())
- throw new InvalidOperationException("Quat is not normalized");
- if (!b.IsNormalized())
- throw new ArgumentException("Argument is not normalized", nameof(b));
-#endif
-
- // Calculate cosine
- real_t cosom = x * b.x + y * b.y + z * b.z + w * b.w;
-
- var to1 = new Quat();
-
- // Adjust signs if necessary
- if (cosom < 0.0)
- {
- cosom = -cosom;
- to1.x = -b.x;
- to1.y = -b.y;
- to1.z = -b.z;
- to1.w = -b.w;
- }
- else
- {
- to1.x = b.x;
- to1.y = b.y;
- to1.z = b.z;
- to1.w = b.w;
- }
-
- real_t sinom, scale0, scale1;
-
- // Calculate coefficients
- if (1.0 - cosom > Mathf.Epsilon)
- {
- // Standard case (Slerp)
- real_t omega = Mathf.Acos(cosom);
- sinom = Mathf.Sin(omega);
- scale0 = Mathf.Sin((1.0f - t) * omega) / sinom;
- scale1 = Mathf.Sin(t * omega) / sinom;
- }
- else
- {
- // Quaternions are very close so we can do a linear interpolation
- scale0 = 1.0f - t;
- scale1 = t;
- }
-
- // Calculate final values
- return new Quat
- (
- scale0 * x + scale1 * to1.x,
- scale0 * y + scale1 * to1.y,
- scale0 * z + scale1 * to1.z,
- scale0 * w + scale1 * to1.w
- );
- }
-
- public Quat Slerpni(Quat b, real_t t)
- {
- real_t dot = Dot(b);
-
- if (Mathf.Abs(dot) > 0.9999f)
- {
- return this;
- }
-
- real_t theta = Mathf.Acos(dot);
- real_t sinT = 1.0f / Mathf.Sin(theta);
- real_t newFactor = Mathf.Sin(t * theta) * sinT;
- real_t invFactor = Mathf.Sin((1.0f - t) * theta) * sinT;
-
- return new Quat
- (
- invFactor * x + newFactor * b.x,
- invFactor * y + newFactor * b.y,
- invFactor * z + newFactor * b.z,
- invFactor * w + newFactor * b.w
- );
- }
-
- public Vector3 Xform(Vector3 v)
- {
-#if DEBUG
- if (!IsNormalized())
- throw new InvalidOperationException("Quat is not normalized");
-#endif
- var u = new Vector3(x, y, z);
- Vector3 uv = u.Cross(v);
- return v + ((uv * w) + u.Cross(uv)) * 2;
- }
-
- // Static Readonly Properties
- public static Quat Identity { get; } = new Quat(0f, 0f, 0f, 1f);
-
- // Constructors
- public Quat(real_t x, real_t y, real_t z, real_t w)
- {
- this.x = x;
- this.y = y;
- this.z = z;
- this.w = w;
- }
-
- public bool IsNormalized()
- {
- return Mathf.Abs(LengthSquared - 1) <= Mathf.Epsilon;
- }
-
- public Quat(Quat q)
- {
- this = q;
- }
-
- public Quat(Basis basis)
- {
- this = basis.Quat();
- }
-
- public Quat(Vector3 eulerYXZ)
- {
- real_t half_a1 = eulerYXZ.y * 0.5f;
- real_t half_a2 = eulerYXZ.x * 0.5f;
- real_t half_a3 = eulerYXZ.z * 0.5f;
-
- // R = Y(a1).X(a2).Z(a3) convention for Euler angles.
- // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6)
- // a3 is the angle of the first rotation, following the notation in this reference.
-
- real_t cos_a1 = Mathf.Cos(half_a1);
- real_t sin_a1 = Mathf.Sin(half_a1);
- real_t cos_a2 = Mathf.Cos(half_a2);
- real_t sin_a2 = Mathf.Sin(half_a2);
- real_t cos_a3 = Mathf.Cos(half_a3);
- real_t sin_a3 = Mathf.Sin(half_a3);
-
- x = sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3;
- y = sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3;
- z = cos_a1 * cos_a2 * sin_a3 - sin_a1 * sin_a2 * cos_a3;
- w = sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3;
- }
-
- public Quat(Vector3 axis, real_t angle)
- {
-#if DEBUG
- if (!axis.IsNormalized())
- throw new ArgumentException("Argument is not normalized", nameof(axis));
-#endif
-
- real_t d = axis.Length();
-
- if (d == 0f)
- {
- x = 0f;
- y = 0f;
- z = 0f;
- w = 0f;
- }
- else
- {
- real_t sinAngle = Mathf.Sin(angle * 0.5f);
- real_t cosAngle = Mathf.Cos(angle * 0.5f);
- real_t s = sinAngle / d;
-
- x = axis.x * s;
- y = axis.y * s;
- z = axis.z * s;
- w = cosAngle;
- }
- }
-
- public static Quat operator *(Quat left, Quat right)
- {
- return new Quat
- (
- left.w * right.x + left.x * right.w + left.y * right.z - left.z * right.y,
- left.w * right.y + left.y * right.w + left.z * right.x - left.x * right.z,
- left.w * right.z + left.z * right.w + left.x * right.y - left.y * right.x,
- left.w * right.w - left.x * right.x - left.y * right.y - left.z * right.z
- );
- }
-
- public static Quat operator +(Quat left, Quat right)
- {
- return new Quat(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w);
- }
-
- public static Quat operator -(Quat left, Quat right)
- {
- return new Quat(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w);
- }
-
- public static Quat operator -(Quat left)
- {
- return new Quat(-left.x, -left.y, -left.z, -left.w);
- }
-
- public static Quat operator *(Quat left, Vector3 right)
- {
- return new Quat
- (
- left.w * right.x + left.y * right.z - left.z * right.y,
- left.w * right.y + left.z * right.x - left.x * right.z,
- left.w * right.z + left.x * right.y - left.y * right.x,
- -left.x * right.x - left.y * right.y - left.z * right.z
- );
- }
-
- public static Quat operator *(Vector3 left, Quat right)
- {
- return new Quat
- (
- right.w * left.x + right.y * left.z - right.z * left.y,
- right.w * left.y + right.z * left.x - right.x * left.z,
- right.w * left.z + right.x * left.y - right.y * left.x,
- -right.x * left.x - right.y * left.y - right.z * left.z
- );
- }
-
- public static Quat operator *(Quat left, real_t right)
- {
- return new Quat(left.x * right, left.y * right, left.z * right, left.w * right);
- }
-
- public static Quat operator *(real_t left, Quat right)
- {
- return new Quat(right.x * left, right.y * left, right.z * left, right.w * left);
- }
-
- public static Quat operator /(Quat left, real_t right)
- {
- return left * (1.0f / right);
- }
-
- public static bool operator ==(Quat left, Quat right)
- {
- return left.Equals(right);
- }
-
- public static bool operator !=(Quat left, Quat right)
- {
- return !left.Equals(right);
- }
-
- public override bool Equals(object obj)
- {
- if (obj is Quat)
- {
- return Equals((Quat)obj);
- }
-
- return false;
- }
-
- public bool Equals(Quat other)
- {
- return x == other.x && y == other.y && z == other.z && w == other.w;
- }
-
- public bool IsEqualApprox(Quat other)
- {
- return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z) && Mathf.IsEqualApprox(w, other.w);
- }
-
- public override int GetHashCode()
- {
- return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode();
- }
-
- public override string ToString()
- {
- return String.Format("({0}, {1}, {2}, {3})", x.ToString(), y.ToString(), z.ToString(), w.ToString());
- }
-
- public string ToString(string format)
- {
- return String.Format("({0}, {1}, {2}, {3})", x.ToString(format), y.ToString(format), z.ToString(format), w.ToString(format));
- }
- }
-}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs
new file mode 100644
index 0000000000..817103994a
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs
@@ -0,0 +1,558 @@
+using System;
+using System.Runtime.InteropServices;
+#if REAL_T_IS_DOUBLE
+using real_t = System.Double;
+#else
+using real_t = System.Single;
+#endif
+
+namespace Godot
+{
+ /// <summary>
+ /// A unit quaternion used for representing 3D rotations.
+ /// Quaternions need to be normalized to be used for rotation.
+ ///
+ /// It is similar to Basis, which implements matrix representation of
+ /// rotations, and can be parametrized using both an axis-angle pair
+ /// or Euler angles. Basis stores rotation, scale, and shearing,
+ /// while Quaternion only stores rotation.
+ ///
+ /// Due to its compactness and the way it is stored in memory, certain
+ /// operations (obtaining axis-angle and performing SLERP, in particular)
+ /// are more efficient and robust against floating-point errors.
+ /// </summary>
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Quaternion : IEquatable<Quaternion>
+ {
+ /// <summary>
+ /// X component of the quaternion (imaginary `i` axis part).
+ /// Quaternion components should usually not be manipulated directly.
+ /// </summary>
+ public real_t x;
+
+ /// <summary>
+ /// Y component of the quaternion (imaginary `j` axis part).
+ /// Quaternion components should usually not be manipulated directly.
+ /// </summary>
+ public real_t y;
+
+ /// <summary>
+ /// Z component of the quaternion (imaginary `k` axis part).
+ /// Quaternion components should usually not be manipulated directly.
+ /// </summary>
+ public real_t z;
+
+ /// <summary>
+ /// W component of the quaternion (real part).
+ /// Quaternion components should usually not be manipulated directly.
+ /// </summary>
+ public real_t w;
+
+ /// <summary>
+ /// Access quaternion components using their index.
+ /// </summary>
+ /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`, `[2]` is equivalent to `.z`, `[3]` is equivalent to `.w`.</value>
+ public real_t this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return x;
+ case 1:
+ return y;
+ case 2:
+ return z;
+ case 3:
+ return w;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ set
+ {
+ switch (index)
+ {
+ case 0:
+ x = value;
+ break;
+ case 1:
+ y = value;
+ break;
+ case 2:
+ z = value;
+ break;
+ case 3:
+ w = value;
+ break;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns the length (magnitude) of the quaternion.
+ /// </summary>
+ /// <value>Equivalent to `Mathf.Sqrt(LengthSquared)`.</value>
+ public real_t Length
+ {
+ get { return Mathf.Sqrt(LengthSquared); }
+ }
+
+ /// <summary>
+ /// Returns the squared length (squared magnitude) of the quaternion.
+ /// This method runs faster than <see cref="Length"/>, so prefer it if
+ /// you need to compare quaternions or need the squared length for some formula.
+ /// </summary>
+ /// <value>Equivalent to `Dot(this)`.</value>
+ public real_t LengthSquared
+ {
+ get { return Dot(this); }
+ }
+
+ /// <summary>
+ /// Returns the angle between this quaternion and `to`.
+ /// This is the magnitude of the angle you would need to rotate
+ /// by to get from one to the other.
+ ///
+ /// Note: This method has an abnormally high amount
+ /// of floating-point error, so methods such as
+ /// <see cref="Mathf.IsZeroApprox"/> will not work reliably.
+ /// </summary>
+ /// <param name="to">The other quaternion.</param>
+ /// <returns>The angle between the quaternions.</returns>
+ public real_t AngleTo(Quaternion to)
+ {
+ real_t dot = Dot(to);
+ return Mathf.Acos(Mathf.Clamp(dot * dot * 2 - 1, -1, 1));
+ }
+
+ /// <summary>
+ /// Performs a cubic spherical interpolation between quaternions `preA`,
+ /// this vector, `b`, and `postB`, by the given amount `t`.
+ /// </summary>
+ /// <param name="b">The destination quaternion.</param>
+ /// <param name="preA">A quaternion before this quaternion.</param>
+ /// <param name="postB">A quaternion after `b`.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The interpolated quaternion.</returns>
+ public Quaternion CubicSlerp(Quaternion b, Quaternion preA, Quaternion postB, real_t weight)
+ {
+ real_t t2 = (1.0f - weight) * weight * 2f;
+ Quaternion sp = Slerp(b, weight);
+ Quaternion sq = preA.Slerpni(postB, weight);
+ return sp.Slerpni(sq, t2);
+ }
+
+ /// <summary>
+ /// Returns the dot product of two quaternions.
+ /// </summary>
+ /// <param name="b">The other quaternion.</param>
+ /// <returns>The dot product.</returns>
+ public real_t Dot(Quaternion b)
+ {
+ return x * b.x + y * b.y + z * b.z + w * b.w;
+ }
+
+ /// <summary>
+ /// Returns Euler angles (in the YXZ convention: when decomposing,
+ /// first Z, then X, and Y last) corresponding to the rotation
+ /// represented by the unit quaternion. Returned vector contains
+ /// the rotation angles in the format (X angle, Y angle, Z angle).
+ /// </summary>
+ /// <returns>The Euler angle representation of this quaternion.</returns>
+ public Vector3 GetEuler()
+ {
+#if DEBUG
+ if (!IsNormalized())
+ {
+ throw new InvalidOperationException("Quaternion is not normalized");
+ }
+#endif
+ var basis = new Basis(this);
+ return basis.GetEuler();
+ }
+
+ /// <summary>
+ /// Returns the inverse of the quaternion.
+ /// </summary>
+ /// <returns>The inverse quaternion.</returns>
+ public Quaternion Inverse()
+ {
+#if DEBUG
+ if (!IsNormalized())
+ {
+ throw new InvalidOperationException("Quaternion is not normalized");
+ }
+#endif
+ return new Quaternion(-x, -y, -z, w);
+ }
+
+ /// <summary>
+ /// Returns whether the quaternion is normalized or not.
+ /// </summary>
+ /// <returns>A bool for whether the quaternion is normalized or not.</returns>
+ public bool IsNormalized()
+ {
+ return Mathf.Abs(LengthSquared - 1) <= Mathf.Epsilon;
+ }
+
+ /// <summary>
+ /// Returns a copy of the quaternion, normalized to unit length.
+ /// </summary>
+ /// <returns>The normalized quaternion.</returns>
+ public Quaternion Normalized()
+ {
+ return this / Length;
+ }
+
+ /// <summary>
+ /// Returns the result of the spherical linear interpolation between
+ /// this quaternion and `to` by amount `weight`.
+ ///
+ /// Note: Both quaternions must be normalized.
+ /// </summary>
+ /// <param name="to">The destination quaternion for interpolation. Must be normalized.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting quaternion of the interpolation.</returns>
+ public Quaternion Slerp(Quaternion to, real_t weight)
+ {
+#if DEBUG
+ if (!IsNormalized())
+ {
+ throw new InvalidOperationException("Quaternion is not normalized");
+ }
+ if (!to.IsNormalized())
+ {
+ throw new ArgumentException("Argument is not normalized", nameof(to));
+ }
+#endif
+
+ // Calculate cosine.
+ real_t cosom = x * to.x + y * to.y + z * to.z + w * to.w;
+
+ var to1 = new Quaternion();
+
+ // Adjust signs if necessary.
+ if (cosom < 0.0)
+ {
+ cosom = -cosom;
+ to1.x = -to.x;
+ to1.y = -to.y;
+ to1.z = -to.z;
+ to1.w = -to.w;
+ }
+ else
+ {
+ to1.x = to.x;
+ to1.y = to.y;
+ to1.z = to.z;
+ to1.w = to.w;
+ }
+
+ real_t sinom, scale0, scale1;
+
+ // Calculate coefficients.
+ if (1.0 - cosom > Mathf.Epsilon)
+ {
+ // Standard case (Slerp).
+ real_t omega = Mathf.Acos(cosom);
+ sinom = Mathf.Sin(omega);
+ scale0 = Mathf.Sin((1.0f - weight) * omega) / sinom;
+ scale1 = Mathf.Sin(weight * omega) / sinom;
+ }
+ else
+ {
+ // Quaternions are very close so we can do a linear interpolation.
+ scale0 = 1.0f - weight;
+ scale1 = weight;
+ }
+
+ // Calculate final values.
+ return new Quaternion
+ (
+ scale0 * x + scale1 * to1.x,
+ scale0 * y + scale1 * to1.y,
+ scale0 * z + scale1 * to1.z,
+ scale0 * w + scale1 * to1.w
+ );
+ }
+
+ /// <summary>
+ /// Returns the result of the spherical linear interpolation between
+ /// this quaternion and `to` by amount `weight`, but without
+ /// checking if the rotation path is not bigger than 90 degrees.
+ /// </summary>
+ /// <param name="to">The destination quaternion for interpolation. Must be normalized.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting quaternion of the interpolation.</returns>
+ public Quaternion Slerpni(Quaternion to, real_t weight)
+ {
+ real_t dot = Dot(to);
+
+ if (Mathf.Abs(dot) > 0.9999f)
+ {
+ return this;
+ }
+
+ real_t theta = Mathf.Acos(dot);
+ real_t sinT = 1.0f / Mathf.Sin(theta);
+ real_t newFactor = Mathf.Sin(weight * theta) * sinT;
+ real_t invFactor = Mathf.Sin((1.0f - weight) * theta) * sinT;
+
+ return new Quaternion
+ (
+ invFactor * x + newFactor * to.x,
+ invFactor * y + newFactor * to.y,
+ invFactor * z + newFactor * to.z,
+ invFactor * w + newFactor * to.w
+ );
+ }
+
+ /// <summary>
+ /// Returns a vector transformed (multiplied) by this quaternion.
+ /// </summary>
+ /// <param name="v">A vector to transform.</param>
+ /// <returns>The transformed vector.</returns>
+ public Vector3 Xform(Vector3 v)
+ {
+#if DEBUG
+ if (!IsNormalized())
+ {
+ throw new InvalidOperationException("Quaternion is not normalized");
+ }
+#endif
+ var u = new Vector3(x, y, z);
+ Vector3 uv = u.Cross(v);
+ return v + ((uv * w) + u.Cross(uv)) * 2;
+ }
+
+ // Constants
+ private static readonly Quaternion _identity = new Quaternion(0, 0, 0, 1);
+
+ /// <summary>
+ /// The identity quaternion, representing no rotation.
+ /// Equivalent to an identity <see cref="Basis"/> matrix. If a vector is transformed by
+ /// an identity quaternion, it will not change.
+ /// </summary>
+ /// <value>Equivalent to `new Quaternion(0, 0, 0, 1)`.</value>
+ public static Quaternion Identity { get { return _identity; } }
+
+ /// <summary>
+ /// Constructs a quaternion defined by the given values.
+ /// </summary>
+ /// <param name="x">X component of the quaternion (imaginary `i` axis part).</param>
+ /// <param name="y">Y component of the quaternion (imaginary `j` axis part).</param>
+ /// <param name="z">Z component of the quaternion (imaginary `k` axis part).</param>
+ /// <param name="w">W component of the quaternion (real part).</param>
+ public Quaternion(real_t x, real_t y, real_t z, real_t w)
+ {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+ }
+
+ /// <summary>
+ /// Constructs a quaternion from the given quaternion.
+ /// </summary>
+ /// <param name="q">The existing quaternion.</param>
+ public Quaternion(Quaternion q)
+ {
+ this = q;
+ }
+
+ /// <summary>
+ /// Constructs a quaternion from the given <see cref="Basis"/>.
+ /// </summary>
+ /// <param name="basis">The basis to construct from.</param>
+ public Quaternion(Basis basis)
+ {
+ this = basis.Quaternion();
+ }
+
+ /// <summary>
+ /// Constructs a quaternion that will perform a rotation specified by
+ /// Euler angles (in the YXZ convention: when decomposing,
+ /// first Z, then X, and Y last),
+ /// given in the vector format as (X angle, Y angle, Z angle).
+ /// </summary>
+ /// <param name="eulerYXZ"></param>
+ public Quaternion(Vector3 eulerYXZ)
+ {
+ real_t half_a1 = eulerYXZ.y * 0.5f;
+ real_t half_a2 = eulerYXZ.x * 0.5f;
+ real_t half_a3 = eulerYXZ.z * 0.5f;
+
+ // R = Y(a1).X(a2).Z(a3) convention for Euler angles.
+ // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6)
+ // a3 is the angle of the first rotation, following the notation in this reference.
+
+ real_t cos_a1 = Mathf.Cos(half_a1);
+ real_t sin_a1 = Mathf.Sin(half_a1);
+ real_t cos_a2 = Mathf.Cos(half_a2);
+ real_t sin_a2 = Mathf.Sin(half_a2);
+ real_t cos_a3 = Mathf.Cos(half_a3);
+ real_t sin_a3 = Mathf.Sin(half_a3);
+
+ x = sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3;
+ y = sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3;
+ z = cos_a1 * cos_a2 * sin_a3 - sin_a1 * sin_a2 * cos_a3;
+ w = sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3;
+ }
+
+ /// <summary>
+ /// Constructs a quaternion that will rotate around the given axis
+ /// by the specified angle. The axis must be a normalized vector.
+ /// </summary>
+ /// <param name="axis">The axis to rotate around. Must be normalized.</param>
+ /// <param name="angle">The angle to rotate, in radians.</param>
+ public Quaternion(Vector3 axis, real_t angle)
+ {
+#if DEBUG
+ if (!axis.IsNormalized())
+ {
+ throw new ArgumentException("Argument is not normalized", nameof(axis));
+ }
+#endif
+
+ real_t d = axis.Length();
+
+ if (d == 0f)
+ {
+ x = 0f;
+ y = 0f;
+ z = 0f;
+ w = 0f;
+ }
+ else
+ {
+ real_t sinAngle = Mathf.Sin(angle * 0.5f);
+ real_t cosAngle = Mathf.Cos(angle * 0.5f);
+ real_t s = sinAngle / d;
+
+ x = axis.x * s;
+ y = axis.y * s;
+ z = axis.z * s;
+ w = cosAngle;
+ }
+ }
+
+ public static Quaternion operator *(Quaternion left, Quaternion right)
+ {
+ return new Quaternion
+ (
+ left.w * right.x + left.x * right.w + left.y * right.z - left.z * right.y,
+ left.w * right.y + left.y * right.w + left.z * right.x - left.x * right.z,
+ left.w * right.z + left.z * right.w + left.x * right.y - left.y * right.x,
+ left.w * right.w - left.x * right.x - left.y * right.y - left.z * right.z
+ );
+ }
+
+ public static Quaternion operator +(Quaternion left, Quaternion right)
+ {
+ return new Quaternion(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w);
+ }
+
+ public static Quaternion operator -(Quaternion left, Quaternion right)
+ {
+ return new Quaternion(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w);
+ }
+
+ public static Quaternion operator -(Quaternion left)
+ {
+ return new Quaternion(-left.x, -left.y, -left.z, -left.w);
+ }
+
+ public static Quaternion operator *(Quaternion left, Vector3 right)
+ {
+ return new Quaternion
+ (
+ left.w * right.x + left.y * right.z - left.z * right.y,
+ left.w * right.y + left.z * right.x - left.x * right.z,
+ left.w * right.z + left.x * right.y - left.y * right.x,
+ -left.x * right.x - left.y * right.y - left.z * right.z
+ );
+ }
+
+ public static Quaternion operator *(Vector3 left, Quaternion right)
+ {
+ return new Quaternion
+ (
+ right.w * left.x + right.y * left.z - right.z * left.y,
+ right.w * left.y + right.z * left.x - right.x * left.z,
+ right.w * left.z + right.x * left.y - right.y * left.x,
+ -right.x * left.x - right.y * left.y - right.z * left.z
+ );
+ }
+
+ public static Quaternion operator *(Quaternion left, real_t right)
+ {
+ return new Quaternion(left.x * right, left.y * right, left.z * right, left.w * right);
+ }
+
+ public static Quaternion operator *(real_t left, Quaternion right)
+ {
+ return new Quaternion(right.x * left, right.y * left, right.z * left, right.w * left);
+ }
+
+ public static Quaternion operator /(Quaternion left, real_t right)
+ {
+ return left * (1.0f / right);
+ }
+
+ public static bool operator ==(Quaternion left, Quaternion right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Quaternion left, Quaternion right)
+ {
+ return !left.Equals(right);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Quaternion)
+ {
+ return Equals((Quaternion)obj);
+ }
+
+ return false;
+ }
+
+ public bool Equals(Quaternion other)
+ {
+ return x == other.x && y == other.y && z == other.z && w == other.w;
+ }
+
+ /// <summary>
+ /// Returns true if this quaternion and `other` are approximately equal, by running
+ /// <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component.
+ /// </summary>
+ /// <param name="other">The other quaternion to compare.</param>
+ /// <returns>Whether or not the quaternions are approximately equal.</returns>
+ public bool IsEqualApprox(Quaternion other)
+ {
+ return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z) && Mathf.IsEqualApprox(w, other.w);
+ }
+
+ public override int GetHashCode()
+ {
+ return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return String.Format("({0}, {1}, {2}, {3})", x.ToString(), y.ToString(), z.ToString(), w.ToString());
+ }
+
+ public string ToString(string format)
+ {
+ return String.Format("({0}, {1}, {2}, {3})", x.ToString(format), y.ToString(format), z.ToString(format), w.ToString(format));
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
index 91e614dc7b..612fb64a3d 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
@@ -8,6 +8,10 @@ using real_t = System.Single;
namespace Godot
{
+ /// <summary>
+ /// 2D axis-aligned bounding box. Rect2 consists of a position, a size, and
+ /// several utility functions. It is typically used for fast overlap tests.
+ /// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Rect2 : IEquatable<Rect2>
@@ -15,29 +19,52 @@ namespace Godot
private Vector2 _position;
private Vector2 _size;
+ /// <summary>
+ /// Beginning corner. Typically has values lower than End.
+ /// </summary>
+ /// <value>Directly uses a private field.</value>
public Vector2 Position
{
get { return _position; }
set { _position = value; }
}
+ /// <summary>
+ /// Size from Position to End. Typically all components are positive.
+ /// If the size is negative, you can use <see cref="Abs"/> to fix it.
+ /// </summary>
+ /// <value>Directly uses a private field.</value>
public Vector2 Size
{
get { return _size; }
set { _size = value; }
}
+ /// <summary>
+ /// Ending corner. This is calculated as <see cref="Position"/> plus
+ /// <see cref="Size"/>. Setting this value will change the size.
+ /// </summary>
+ /// <value>Getting is equivalent to `value = Position + Size`, setting is equivalent to `Size = value - Position`.</value>
public Vector2 End
{
get { return _position + _size; }
set { _size = value - _position; }
}
+ /// <summary>
+ /// The area of this Rect2.
+ /// </summary>
+ /// <value>Equivalent to <see cref="GetArea()"/>.</value>
public real_t Area
{
get { return GetArea(); }
}
+ /// <summary>
+ /// Returns a Rect2 with equivalent position and size, modified so that
+ /// the top-left corner is the origin and width and height are positive.
+ /// </summary>
+ /// <returns>The modified Rect2.</returns>
public Rect2 Abs()
{
Vector2 end = End;
@@ -45,12 +72,20 @@ namespace Godot
return new Rect2(topLeft, _size.Abs());
}
- public Rect2 Clip(Rect2 b)
+ /// <summary>
+ /// Returns the intersection of this Rect2 and `b`.
+ /// If the rectangles do not intersect, an empty Rect2 is returned.
+ /// </summary>
+ /// <param name="b">The other Rect2.</param>
+ /// <returns>The intersection of this Rect2 and `b`, or an empty Rect2 if they do not intersect.</returns>
+ public Rect2 Intersection(Rect2 b)
{
var newRect = b;
if (!Intersects(newRect))
+ {
return new Rect2();
+ }
newRect._position.x = Mathf.Max(b._position.x, _position.x);
newRect._position.y = Mathf.Max(b._position.y, _position.y);
@@ -64,6 +99,11 @@ namespace Godot
return newRect;
}
+ /// <summary>
+ /// Returns true if this Rect2 completely encloses another one.
+ /// </summary>
+ /// <param name="b">The other Rect2 that may be enclosed.</param>
+ /// <returns>A bool for whether or not this Rect2 encloses `b`.</returns>
public bool Encloses(Rect2 b)
{
return b._position.x >= _position.x && b._position.y >= _position.y &&
@@ -71,6 +111,11 @@ namespace Godot
b._position.y + b._size.y < _position.y + _size.y;
}
+ /// <summary>
+ /// Returns this Rect2 expanded to include a given point.
+ /// </summary>
+ /// <param name="to">The point to include.</param>
+ /// <returns>The expanded Rect2.</returns>
public Rect2 Expand(Vector2 to)
{
var expanded = this;
@@ -79,14 +124,22 @@ namespace Godot
Vector2 end = expanded._position + expanded._size;
if (to.x < begin.x)
+ {
begin.x = to.x;
+ }
if (to.y < begin.y)
+ {
begin.y = to.y;
+ }
if (to.x > end.x)
+ {
end.x = to.x;
+ }
if (to.y > end.y)
+ {
end.y = to.y;
+ }
expanded._position = begin;
expanded._size = end - begin;
@@ -94,11 +147,20 @@ namespace Godot
return expanded;
}
+ /// <summary>
+ /// Returns the area of the Rect2.
+ /// </summary>
+ /// <returns>The area.</returns>
public real_t GetArea()
{
return _size.x * _size.y;
}
+ /// <summary>
+ /// Returns a copy of the Rect2 grown by the specified amount on all sides.
+ /// </summary>
+ /// <param name="by">The amount to grow by.</param>
+ /// <returns>The grown Rect2.</returns>
public Rect2 Grow(real_t by)
{
var g = this;
@@ -111,6 +173,14 @@ namespace Godot
return g;
}
+ /// <summary>
+ /// Returns a copy of the Rect2 grown by the specified amount on each side individually.
+ /// </summary>
+ /// <param name="left">The amount to grow by on the left side.</param>
+ /// <param name="top">The amount to grow by on the top side.</param>
+ /// <param name="right">The amount to grow by on the right side.</param>
+ /// <param name="bottom">The amount to grow by on the bottom side.</param>
+ /// <returns>The grown Rect2.</returns>
public Rect2 GrowIndividual(real_t left, real_t top, real_t right, real_t bottom)
{
var g = this;
@@ -123,23 +193,38 @@ namespace Godot
return g;
}
- public Rect2 GrowMargin(Margin margin, real_t by)
+ /// <summary>
+ /// Returns a copy of the Rect2 grown by the specified amount on the specified Side.
+ /// </summary>
+ /// <param name="side">The side to grow.</param>
+ /// <param name="by">The amount to grow by.</param>
+ /// <returns>The grown Rect2.</returns>
+ public Rect2 GrowSide(Side side, real_t by)
{
var g = this;
- g.GrowIndividual(Margin.Left == margin ? by : 0,
- Margin.Top == margin ? by : 0,
- Margin.Right == margin ? by : 0,
- Margin.Bottom == margin ? by : 0);
+ g = g.GrowIndividual(Side.Left == side ? by : 0,
+ Side.Top == side ? by : 0,
+ Side.Right == side ? by : 0,
+ Side.Bottom == side ? by : 0);
return g;
}
+ /// <summary>
+ /// Returns true if the Rect2 is flat or empty, or false otherwise.
+ /// </summary>
+ /// <returns>A bool for whether or not the Rect2 has area.</returns>
public bool HasNoArea()
{
return _size.x <= 0 || _size.y <= 0;
}
+ /// <summary>
+ /// Returns true if the Rect2 contains a point, or false otherwise.
+ /// </summary>
+ /// <param name="point">The point to check.</param>
+ /// <returns>A bool for whether or not the Rect2 contains `point`.</returns>
public bool HasPoint(Vector2 point)
{
if (point.x < _position.x)
@@ -155,20 +240,65 @@ namespace Godot
return true;
}
- public bool Intersects(Rect2 b)
+ /// <summary>
+ /// Returns true if the Rect2 overlaps with `b`
+ /// (i.e. they have at least one point in common).
+ ///
+ /// If `includeBorders` is true, they will also be considered overlapping
+ /// if their borders touch, even without intersection.
+ /// </summary>
+ /// <param name="b">The other Rect2 to check for intersections with.</param>
+ /// <param name="includeBorders">Whether or not to consider borders.</param>
+ /// <returns>A bool for whether or not they are intersecting.</returns>
+ public bool Intersects(Rect2 b, bool includeBorders = false)
{
- if (_position.x >= b._position.x + b._size.x)
- return false;
- if (_position.x + _size.x <= b._position.x)
- return false;
- if (_position.y >= b._position.y + b._size.y)
- return false;
- if (_position.y + _size.y <= b._position.y)
- return false;
+ if (includeBorders)
+ {
+ if (_position.x > b._position.x + b._size.x)
+ {
+ return false;
+ }
+ if (_position.x + _size.x < b._position.x)
+ {
+ return false;
+ }
+ if (_position.y > b._position.y + b._size.y)
+ {
+ return false;
+ }
+ if (_position.y + _size.y < b._position.y)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (_position.x >= b._position.x + b._size.x)
+ {
+ return false;
+ }
+ if (_position.x + _size.x <= b._position.x)
+ {
+ return false;
+ }
+ if (_position.y >= b._position.y + b._size.y)
+ {
+ return false;
+ }
+ if (_position.y + _size.y <= b._position.y)
+ {
+ return false;
+ }
+ }
return true;
}
+ /// <summary>
+ /// Returns a larger Rect2 that contains this Rect2 and `b`.
+ /// </summary>
+ /// <param name="b">The other Rect2.</param>
+ /// <returns>The merged Rect2.</returns>
public Rect2 Merge(Rect2 b)
{
Rect2 newRect;
@@ -179,27 +309,53 @@ namespace Godot
newRect._size.x = Mathf.Max(b._position.x + b._size.x, _position.x + _size.x);
newRect._size.y = Mathf.Max(b._position.y + b._size.y, _position.y + _size.y);
- newRect._size = newRect._size - newRect._position; // Make relative again
+ newRect._size -= newRect._position; // Make relative again
return newRect;
}
- // Constructors
+ /// <summary>
+ /// Constructs a Rect2 from a position and size.
+ /// </summary>
+ /// <param name="position">The position.</param>
+ /// <param name="size">The size.</param>
public Rect2(Vector2 position, Vector2 size)
{
_position = position;
_size = size;
}
+
+ /// <summary>
+ /// Constructs a Rect2 from a position, width, and height.
+ /// </summary>
+ /// <param name="position">The position.</param>
+ /// <param name="width">The width.</param>
+ /// <param name="height">The height.</param>
public Rect2(Vector2 position, real_t width, real_t height)
{
_position = position;
_size = new Vector2(width, height);
}
+
+ /// <summary>
+ /// Constructs a Rect2 from x, y, and size.
+ /// </summary>
+ /// <param name="x">The position's X coordinate.</param>
+ /// <param name="y">The position's Y coordinate.</param>
+ /// <param name="size">The size.</param>
public Rect2(real_t x, real_t y, Vector2 size)
{
_position = new Vector2(x, y);
_size = size;
}
+
+ /// <summary>
+ /// Constructs a Rect2 from x, y, width, and height.
+ /// </summary>
+ /// <param name="x">The position's X coordinate.</param>
+ /// <param name="y">The position's Y coordinate.</param>
+ /// <param name="width">The width.</param>
+ /// <param name="height">The height.</param>
public Rect2(real_t x, real_t y, real_t width, real_t height)
{
_position = new Vector2(x, y);
@@ -231,6 +387,12 @@ namespace Godot
return _position.Equals(other._position) && _size.Equals(other._size);
}
+ /// <summary>
+ /// Returns true if this Rect2 and `other` are approximately equal, by running
+ /// <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component.
+ /// </summary>
+ /// <param name="other">The other Rect2 to compare.</param>
+ /// <returns>Whether or not the Rect2s are approximately equal.</returns>
public bool IsEqualApprox(Rect2 other)
{
return _position.IsEqualApprox(other._position) && _size.IsEqualApprox(other.Size);
@@ -243,7 +405,7 @@ namespace Godot
public override string ToString()
{
- return String.Format("({0}, {1})", new object[]
+ return String.Format("{0}, {1}", new object[]
{
_position.ToString(),
_size.ToString()
@@ -252,7 +414,7 @@ namespace Godot
public string ToString(string format)
{
- return String.Format("({0}, {1})", new object[]
+ return String.Format("{0}, {1}", new object[]
{
_position.ToString(format),
_size.ToString(format)
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs
new file mode 100644
index 0000000000..c27af74866
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2i.cs
@@ -0,0 +1,402 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Godot
+{
+ /// <summary>
+ /// 2D axis-aligned bounding box using integers. Rect2i consists of a position, a size, and
+ /// several utility functions. It is typically used for fast overlap tests.
+ /// </summary>
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Rect2i : IEquatable<Rect2i>
+ {
+ private Vector2i _position;
+ private Vector2i _size;
+
+ /// <summary>
+ /// Beginning corner. Typically has values lower than End.
+ /// </summary>
+ /// <value>Directly uses a private field.</value>
+ public Vector2i Position
+ {
+ get { return _position; }
+ set { _position = value; }
+ }
+
+ /// <summary>
+ /// Size from Position to End. Typically all components are positive.
+ /// If the size is negative, you can use <see cref="Abs"/> to fix it.
+ /// </summary>
+ /// <value>Directly uses a private field.</value>
+ public Vector2i Size
+ {
+ get { return _size; }
+ set { _size = value; }
+ }
+
+ /// <summary>
+ /// Ending corner. This is calculated as <see cref="Position"/> plus
+ /// <see cref="Size"/>. Setting this value will change the size.
+ /// </summary>
+ /// <value>Getting is equivalent to `value = Position + Size`, setting is equivalent to `Size = value - Position`.</value>
+ public Vector2i End
+ {
+ get { return _position + _size; }
+ set { _size = value - _position; }
+ }
+
+ /// <summary>
+ /// The area of this Rect2i.
+ /// </summary>
+ /// <value>Equivalent to <see cref="GetArea()"/>.</value>
+ public int Area
+ {
+ get { return GetArea(); }
+ }
+
+ /// <summary>
+ /// Returns a Rect2i with equivalent position and size, modified so that
+ /// the top-left corner is the origin and width and height are positive.
+ /// </summary>
+ /// <returns>The modified Rect2i.</returns>
+ public Rect2i Abs()
+ {
+ Vector2i end = End;
+ Vector2i topLeft = new Vector2i(Mathf.Min(_position.x, end.x), Mathf.Min(_position.y, end.y));
+ return new Rect2i(topLeft, _size.Abs());
+ }
+
+ /// <summary>
+ /// Returns the intersection of this Rect2i and `b`.
+ /// If the rectangles do not intersect, an empty Rect2i is returned.
+ /// </summary>
+ /// <param name="b">The other Rect2i.</param>
+ /// <returns>The intersection of this Rect2i and `b`, or an empty Rect2i if they do not intersect.</returns>
+ public Rect2i Intersection(Rect2i b)
+ {
+ var newRect = b;
+
+ if (!Intersects(newRect))
+ {
+ return new Rect2i();
+ }
+
+ newRect._position.x = Mathf.Max(b._position.x, _position.x);
+ newRect._position.y = Mathf.Max(b._position.y, _position.y);
+
+ Vector2i bEnd = b._position + b._size;
+ Vector2i end = _position + _size;
+
+ newRect._size.x = Mathf.Min(bEnd.x, end.x) - newRect._position.x;
+ newRect._size.y = Mathf.Min(bEnd.y, end.y) - newRect._position.y;
+
+ return newRect;
+ }
+
+ /// <summary>
+ /// Returns true if this Rect2i completely encloses another one.
+ /// </summary>
+ /// <param name="b">The other Rect2i that may be enclosed.</param>
+ /// <returns>A bool for whether or not this Rect2i encloses `b`.</returns>
+ public bool Encloses(Rect2i b)
+ {
+ return b._position.x >= _position.x && b._position.y >= _position.y &&
+ b._position.x + b._size.x < _position.x + _size.x &&
+ b._position.y + b._size.y < _position.y + _size.y;
+ }
+
+ /// <summary>
+ /// Returns this Rect2i expanded to include a given point.
+ /// </summary>
+ /// <param name="to">The point to include.</param>
+ /// <returns>The expanded Rect2i.</returns>
+ public Rect2i Expand(Vector2i to)
+ {
+ var expanded = this;
+
+ Vector2i begin = expanded._position;
+ Vector2i end = expanded._position + expanded._size;
+
+ if (to.x < begin.x)
+ {
+ begin.x = to.x;
+ }
+ if (to.y < begin.y)
+ {
+ begin.y = to.y;
+ }
+
+ if (to.x > end.x)
+ {
+ end.x = to.x;
+ }
+ if (to.y > end.y)
+ {
+ end.y = to.y;
+ }
+
+ expanded._position = begin;
+ expanded._size = end - begin;
+
+ return expanded;
+ }
+
+ /// <summary>
+ /// Returns the area of the Rect2.
+ /// </summary>
+ /// <returns>The area.</returns>
+ public int GetArea()
+ {
+ return _size.x * _size.y;
+ }
+
+ /// <summary>
+ /// Returns a copy of the Rect2i grown by the specified amount on all sides.
+ /// </summary>
+ /// <param name="by">The amount to grow by.</param>
+ /// <returns>The grown Rect2i.</returns>
+ public Rect2i Grow(int by)
+ {
+ var g = this;
+
+ g._position.x -= by;
+ g._position.y -= by;
+ g._size.x += by * 2;
+ g._size.y += by * 2;
+
+ return g;
+ }
+
+ /// <summary>
+ /// Returns a copy of the Rect2i grown by the specified amount on each side individually.
+ /// </summary>
+ /// <param name="left">The amount to grow by on the left side.</param>
+ /// <param name="top">The amount to grow by on the top side.</param>
+ /// <param name="right">The amount to grow by on the right side.</param>
+ /// <param name="bottom">The amount to grow by on the bottom side.</param>
+ /// <returns>The grown Rect2i.</returns>
+ public Rect2i GrowIndividual(int left, int top, int right, int bottom)
+ {
+ var g = this;
+
+ g._position.x -= left;
+ g._position.y -= top;
+ g._size.x += left + right;
+ g._size.y += top + bottom;
+
+ return g;
+ }
+
+ /// <summary>
+ /// Returns a copy of the Rect2i grown by the specified amount on the specified Side.
+ /// </summary>
+ /// <param name="side">The side to grow.</param>
+ /// <param name="by">The amount to grow by.</param>
+ /// <returns>The grown Rect2i.</returns>
+ public Rect2i GrowSide(Side side, int by)
+ {
+ var g = this;
+
+ g = g.GrowIndividual(Side.Left == side ? by : 0,
+ Side.Top == side ? by : 0,
+ Side.Right == side ? by : 0,
+ Side.Bottom == side ? by : 0);
+
+ return g;
+ }
+
+ /// <summary>
+ /// Returns true if the Rect2i is flat or empty, or false otherwise.
+ /// </summary>
+ /// <returns>A bool for whether or not the Rect2i has area.</returns>
+ public bool HasNoArea()
+ {
+ return _size.x <= 0 || _size.y <= 0;
+ }
+
+ /// <summary>
+ /// Returns true if the Rect2i contains a point, or false otherwise.
+ /// </summary>
+ /// <param name="point">The point to check.</param>
+ /// <returns>A bool for whether or not the Rect2i contains `point`.</returns>
+ public bool HasPoint(Vector2i point)
+ {
+ if (point.x < _position.x)
+ return false;
+ if (point.y < _position.y)
+ return false;
+
+ if (point.x >= _position.x + _size.x)
+ return false;
+ if (point.y >= _position.y + _size.y)
+ return false;
+
+ return true;
+ }
+
+ /// <summary>
+ /// Returns true if the Rect2i overlaps with `b`
+ /// (i.e. they have at least one point in common).
+ ///
+ /// If `includeBorders` is true, they will also be considered overlapping
+ /// if their borders touch, even without intersection.
+ /// </summary>
+ /// <param name="b">The other Rect2i to check for intersections with.</param>
+ /// <param name="includeBorders">Whether or not to consider borders.</param>
+ /// <returns>A bool for whether or not they are intersecting.</returns>
+ public bool Intersects(Rect2i b, bool includeBorders = false)
+ {
+ if (includeBorders)
+ {
+ if (_position.x > b._position.x + b._size.x)
+ return false;
+ if (_position.x + _size.x < b._position.x)
+ return false;
+ if (_position.y > b._position.y + b._size.y)
+ return false;
+ if (_position.y + _size.y < b._position.y)
+ return false;
+ }
+ else
+ {
+ if (_position.x >= b._position.x + b._size.x)
+ return false;
+ if (_position.x + _size.x <= b._position.x)
+ return false;
+ if (_position.y >= b._position.y + b._size.y)
+ return false;
+ if (_position.y + _size.y <= b._position.y)
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Returns a larger Rect2i that contains this Rect2i and `b`.
+ /// </summary>
+ /// <param name="b">The other Rect2i.</param>
+ /// <returns>The merged Rect2i.</returns>
+ public Rect2i Merge(Rect2i b)
+ {
+ Rect2i newRect;
+
+ newRect._position.x = Mathf.Min(b._position.x, _position.x);
+ newRect._position.y = Mathf.Min(b._position.y, _position.y);
+
+ newRect._size.x = Mathf.Max(b._position.x + b._size.x, _position.x + _size.x);
+ newRect._size.y = Mathf.Max(b._position.y + b._size.y, _position.y + _size.y);
+
+ newRect._size -= newRect._position; // Make relative again
+
+ return newRect;
+ }
+
+ /// <summary>
+ /// Constructs a Rect2i from a position and size.
+ /// </summary>
+ /// <param name="position">The position.</param>
+ /// <param name="size">The size.</param>
+ public Rect2i(Vector2i position, Vector2i size)
+ {
+ _position = position;
+ _size = size;
+ }
+
+ /// <summary>
+ /// Constructs a Rect2i from a position, width, and height.
+ /// </summary>
+ /// <param name="position">The position.</param>
+ /// <param name="width">The width.</param>
+ /// <param name="height">The height.</param>
+ public Rect2i(Vector2i position, int width, int height)
+ {
+ _position = position;
+ _size = new Vector2i(width, height);
+ }
+
+ /// <summary>
+ /// Constructs a Rect2i from x, y, and size.
+ /// </summary>
+ /// <param name="x">The position's X coordinate.</param>
+ /// <param name="y">The position's Y coordinate.</param>
+ /// <param name="size">The size.</param>
+ public Rect2i(int x, int y, Vector2i size)
+ {
+ _position = new Vector2i(x, y);
+ _size = size;
+ }
+
+ /// <summary>
+ /// Constructs a Rect2i from x, y, width, and height.
+ /// </summary>
+ /// <param name="x">The position's X coordinate.</param>
+ /// <param name="y">The position's Y coordinate.</param>
+ /// <param name="width">The width.</param>
+ /// <param name="height">The height.</param>
+ public Rect2i(int x, int y, int width, int height)
+ {
+ _position = new Vector2i(x, y);
+ _size = new Vector2i(width, height);
+ }
+
+ public static bool operator ==(Rect2i left, Rect2i right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Rect2i left, Rect2i right)
+ {
+ return !left.Equals(right);
+ }
+
+ public static implicit operator Rect2(Rect2i value)
+ {
+ return new Rect2(value._position, value._size);
+ }
+
+ public static explicit operator Rect2i(Rect2 value)
+ {
+ return new Rect2i((Vector2i)value.Position, (Vector2i)value.Size);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Rect2i)
+ {
+ return Equals((Rect2i)obj);
+ }
+
+ return false;
+ }
+
+ public bool Equals(Rect2i other)
+ {
+ return _position.Equals(other._position) && _size.Equals(other._size);
+ }
+
+ public override int GetHashCode()
+ {
+ return _position.GetHashCode() ^ _size.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0}, {1}", new object[]
+ {
+ _position.ToString(),
+ _size.ToString()
+ });
+ }
+
+ public string ToString(string format)
+ {
+ return String.Format("{0}, {1}", new object[]
+ {
+ _position.ToString(format),
+ _size.ToString(format)
+ });
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs
index 9483b6ffb4..4dc630238b 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs
@@ -9,13 +9,13 @@ namespace Godot
private object[] result;
private Action action;
- public SignalAwaiter(Object source, string signal, Object target)
+ public SignalAwaiter(Object source, StringName signal, Object target)
{
- godot_icall_SignalAwaiter_connect(Object.GetPtr(source), signal, Object.GetPtr(target), this);
+ godot_icall_SignalAwaiter_connect(Object.GetPtr(source), StringName.GetPtr(signal), Object.GetPtr(target), this);
}
[MethodImpl(MethodImplOptions.InternalCall)]
- internal extern static Error godot_icall_SignalAwaiter_connect(IntPtr source, string signal, IntPtr target, SignalAwaiter awaiter);
+ internal extern static Error godot_icall_SignalAwaiter_connect(IntPtr source, IntPtr signal, IntPtr target, SignalAwaiter awaiter);
public bool IsCompleted
{
@@ -50,11 +50,5 @@ namespace Godot
action();
}
}
-
- internal void FailureCallback()
- {
- action = null;
- completed = true;
- }
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs
new file mode 100644
index 0000000000..dc92de7a61
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs
@@ -0,0 +1,17 @@
+namespace Godot
+{
+ public struct SignalInfo
+ {
+ private readonly Object _owner;
+ private readonly StringName _signalName;
+
+ public Object Owner => _owner;
+ public StringName Name => _signalName;
+
+ public SignalInfo(Object owner, StringName name)
+ {
+ _owner = owner;
+ _signalName = name;
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
index b926037e5a..98efa89ef0 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs
@@ -12,7 +12,7 @@ namespace Godot
{
private static int GetSliceCount(this string instance, string splitter)
{
- if (instance.Empty() || splitter.Empty())
+ if (string.IsNullOrEmpty(instance) || string.IsNullOrEmpty(splitter))
return 0;
int pos = 0;
@@ -29,7 +29,7 @@ namespace Godot
private static string GetSliceCharacter(this string instance, char splitter, int slice)
{
- if (!instance.Empty() && slice >= 0)
+ if (!string.IsNullOrEmpty(instance) && slice >= 0)
{
int i = 0;
int prev = 0;
@@ -97,6 +97,36 @@ namespace Godot
return b;
}
+ /// <summary>
+ /// Converts a string containing a binary number into an integer.
+ /// Binary strings can either be prefixed with `0b` or not,
+ /// and they can also start with a `-` before the optional prefix.
+ /// </summary>
+ /// <param name="instance">The string to convert.</param>
+ /// <returns>The converted string.</returns>
+ public static int BinToInt(this string instance)
+ {
+ if (instance.Length == 0)
+ {
+ return 0;
+ }
+
+ int sign = 1;
+
+ if (instance[0] == '-')
+ {
+ sign = -1;
+ instance = instance.Substring(1);
+ }
+
+ if (instance.StartsWith("0b"))
+ {
+ instance = instance.Substring(2);
+ }
+
+ return sign * Convert.ToInt32(instance, 2);;
+ }
+
// <summary>
// Return the amount of substrings in string.
// </summary>
@@ -237,10 +267,10 @@ namespace Godot
// </summary>
public static int CompareTo(this string instance, string to, bool caseSensitive = true)
{
- if (instance.Empty())
- return to.Empty() ? 0 : -1;
+ if (string.IsNullOrEmpty(instance))
+ return string.IsNullOrEmpty(to) ? 0 : -1;
- if (to.Empty())
+ if (string.IsNullOrEmpty(to))
return 1;
int instanceIndex = 0;
@@ -264,7 +294,8 @@ namespace Godot
instanceIndex++;
toIndex++;
}
- } else
+ }
+ else
{
while (true)
{
@@ -286,14 +317,6 @@ namespace Godot
}
// <summary>
- // Return true if the string is empty.
- // </summary>
- public static bool Empty(this string instance)
- {
- return string.IsNullOrEmpty(instance);
- }
-
- // <summary>
// Return true if the strings ends with the given string.
// </summary>
public static bool EndsWith(this string instance, string text)
@@ -329,6 +352,15 @@ namespace Godot
return instance.IndexOf(what, from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
}
+ /// <summary>Find the first occurrence of a char. Optionally, the search starting position can be passed.</summary>
+ /// <returns>The first instance of the char, or -1 if not found.</returns>
+ public static int Find(this string instance, char what, int from = 0, bool caseSensitive = true)
+ {
+ // TODO: Could be more efficient if we get a char version of `IndexOf`.
+ // See https://github.com/dotnet/runtime/issues/44116
+ return instance.IndexOf(what.ToString(), from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
+ }
+
/// <summary>Find the last occurrence of a substring.</summary>
/// <returns>The starting position of the substring, or -1 if not found.</returns>
public static int FindLast(this string instance, string what, bool caseSensitive = true)
@@ -400,26 +432,111 @@ namespace Godot
return instance.Substring(sep + 1);
}
+ /// <summary>
+ /// Converts the given byte array of ASCII encoded text to a string.
+ /// Faster alternative to <see cref="GetStringFromUTF8"/> if the
+ /// content is ASCII-only. Unlike the UTF-8 function this function
+ /// maps every byte to a character in the array. Multibyte sequences
+ /// will not be interpreted correctly. For parsing user input always
+ /// use <see cref="GetStringFromUTF8"/>.
+ /// </summary>
+ /// <param name="bytes">A byte array of ASCII characters (on the range of 0-127).</param>
+ /// <returns>A string created from the bytes.</returns>
+ public static string GetStringFromASCII(this byte[] bytes)
+ {
+ return Encoding.ASCII.GetString(bytes);
+ }
+
+ /// <summary>
+ /// Converts the given byte array of UTF-8 encoded text to a string.
+ /// Slower than <see cref="GetStringFromASCII"/> but supports UTF-8
+ /// encoded data. Use this function if you are unsure about the
+ /// source of the data. For user input this function
+ /// should always be preferred.
+ /// </summary>
+ /// <param name="bytes">A byte array of UTF-8 characters (a character may take up multiple bytes).</param>
+ /// <returns>A string created from the bytes.</returns>
+ public static string GetStringFromUTF8(this byte[] bytes)
+ {
+ return Encoding.UTF8.GetString(bytes);
+ }
+
// <summary>
- // Hash the string and return a 32 bits integer.
+ // Hash the string and return a 32 bits unsigned integer.
// </summary>
- public static int Hash(this string instance)
+ public static uint Hash(this string instance)
{
- int index = 0;
- int hashv = 5381;
- int c;
+ uint hash = 5381;
- while ((c = instance[index++]) != 0)
- hashv = (hashv << 5) + hashv + c; // hash * 33 + c
+ foreach(uint c in instance)
+ {
+ hash = (hash << 5) + hash + c; // hash * 33 + c
+ }
- return hashv;
+ return hash;
}
- // <summary>
- // Convert a string containing an hexadecimal number into an int.
- // </summary>
+ /// <summary>
+ /// Returns a hexadecimal representation of this byte as a string.
+ /// </summary>
+ /// <param name="b">The byte to encode.</param>
+ /// <returns>The hexadecimal representation of this byte.</returns>
+ internal static string HexEncode(this byte b)
+ {
+ var ret = string.Empty;
+
+ for (int i = 0; i < 2; i++)
+ {
+ char c;
+ int lv = b & 0xF;
+
+ if (lv < 10)
+ {
+ c = (char)('0' + lv);
+ }
+ else
+ {
+ c = (char)('a' + lv - 10);
+ }
+
+ b >>= 4;
+ ret = c + ret;
+ }
+
+ return ret;
+ }
+
+ /// <summary>
+ /// Returns a hexadecimal representation of this byte array as a string.
+ /// </summary>
+ /// <param name="bytes">The byte array to encode.</param>
+ /// <returns>The hexadecimal representation of this byte array.</returns>
+ public static string HexEncode(this byte[] bytes)
+ {
+ var ret = string.Empty;
+
+ foreach (byte b in bytes)
+ {
+ ret += b.HexEncode();
+ }
+
+ return ret;
+ }
+
+ /// <summary>
+ /// Converts a string containing a hexadecimal number into an integer.
+ /// Hexadecimal strings can either be prefixed with `0x` or not,
+ /// and they can also start with a `-` before the optional prefix.
+ /// </summary>
+ /// <param name="instance">The string to convert.</param>
+ /// <returns>The converted string.</returns>
public static int HexToInt(this string instance)
{
+ if (instance.Length == 0)
+ {
+ return 0;
+ }
+
int sign = 1;
if (instance[0] == '-')
@@ -428,10 +545,12 @@ namespace Godot
instance = instance.Substring(1);
}
- if (!instance.StartsWith("0x"))
- return 0;
+ if (instance.StartsWith("0x"))
+ {
+ instance = instance.Substring(2);
+ }
- return sign * int.Parse(instance.Substring(2), NumberStyles.HexNumber);
+ return sign * int.Parse(instance, NumberStyles.HexNumber);
}
// <summary>
@@ -447,7 +566,12 @@ namespace Godot
// </summary>
public static bool IsAbsPath(this string instance)
{
- return System.IO.Path.IsPathRooted(instance);
+ if (string.IsNullOrEmpty(instance))
+ return false;
+ else if (instance.Length > 1)
+ return instance[0] == '/' || instance[0] == '\\' || instance.Contains(":/") || instance.Contains(":\\");
+ else
+ return instance[0] == '/' || instance[0] == '\\';
}
// <summary>
@@ -455,7 +579,7 @@ namespace Godot
// </summary>
public static bool IsRelPath(this string instance)
{
- return !System.IO.Path.IsPathRooted(instance);
+ return !IsAbsPath(instance);
}
// <summary>
@@ -474,7 +598,7 @@ namespace Godot
int source = 0;
int target = 0;
- while (instance[source] != 0 && text[target] != 0)
+ while (source < len && target < text.Length)
{
bool match;
@@ -491,7 +615,7 @@ namespace Godot
if (match)
{
source++;
- if (instance[source] == 0)
+ if (source >= len)
return true;
}
@@ -631,41 +755,73 @@ namespace Godot
return instance.Length;
}
- // <summary>
- // Do a simple expression match, where '*' matches zero or more arbitrary characters and '?' matches any single character except '.'.
- // </summary>
- public static bool ExprMatch(this string instance, string expr, bool caseSensitive)
+ /// <summary>
+ /// Returns a copy of the string with characters removed from the left.
+ /// </summary>
+ /// <param name="instance">The string to remove characters from.</param>
+ /// <param name="chars">The characters to be removed.</param>
+ /// <returns>A copy of the string with characters removed from the left.</returns>
+ public static string LStrip(this string instance, string chars)
{
- if (expr.Length == 0 || instance.Length == 0)
- return false;
+ int len = instance.Length;
+ int beg;
+
+ for (beg = 0; beg < len; beg++)
+ {
+ if (chars.Find(instance[beg]) == -1)
+ {
+ break;
+ }
+ }
+
+ if (beg == 0)
+ {
+ return instance;
+ }
+
+ return instance.Substr(beg, len - beg);
+ }
+
+ /// <summary>
+ /// Do a simple expression match, where '*' matches zero or more arbitrary characters and '?' matches any single character except '.'.
+ /// </summary>
+ private static bool ExprMatch(this string instance, string expr, bool caseSensitive)
+ {
+ // case '\0':
+ if (expr.Length == 0)
+ return instance.Length == 0;
switch (expr[0])
{
- case '\0':
- return instance[0] == 0;
case '*':
- return ExprMatch(expr + 1, instance, caseSensitive) || instance[0] != 0 && ExprMatch(expr, instance + 1, caseSensitive);
+ return ExprMatch(instance, expr.Substring(1), caseSensitive) || (instance.Length > 0 && ExprMatch(instance.Substring(1), expr, caseSensitive));
case '?':
- return instance[0] != 0 && instance[0] != '.' && ExprMatch(expr + 1, instance + 1, caseSensitive);
+ return instance.Length > 0 && instance[0] != '.' && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive);
default:
- return (caseSensitive ? instance[0] == expr[0] : char.ToUpper(instance[0]) == char.ToUpper(expr[0])) &&
- ExprMatch(expr + 1, instance + 1, caseSensitive);
+ if (instance.Length == 0) return false;
+ return (caseSensitive ? instance[0] == expr[0] : char.ToUpper(instance[0]) == char.ToUpper(expr[0])) && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive);
}
}
- // <summary>
- // Do a simple case sensitive expression match, using ? and * wildcards (see [method expr_match]).
- // </summary>
+ /// <summary>
+ /// Do a simple case sensitive expression match, using ? and * wildcards (see [method expr_match]).
+ /// </summary>
public static bool Match(this string instance, string expr, bool caseSensitive = true)
{
+ if (instance.Length == 0 || expr.Length == 0)
+ return false;
+
return instance.ExprMatch(expr, caseSensitive);
}
- // <summary>
- // Do a simple case insensitive expression match, using ? and * wildcards (see [method expr_match]).
- // </summary>
+ /// <summary>
+ /// Do a simple case insensitive expression match, using ? and * wildcards (see [method expr_match]).
+ /// </summary>
public static bool MatchN(this string instance, string expr)
{
+ if (instance.Length == 0 || expr.Length == 0)
+ return false;
+
return instance.ExprMatch(expr, caseSensitive: false);
}
@@ -777,22 +933,6 @@ namespace Godot
}
// <summary>
- // Decode a percent-encoded string. See [method percent_encode].
- // </summary>
- public static string PercentDecode(this string instance)
- {
- return Uri.UnescapeDataString(instance);
- }
-
- // <summary>
- // Percent-encode a string. This is meant to encode parameters in a URL when sending a HTTP GET request and bodies of form-urlencoded POST request.
- // </summary>
- public static string PercentEncode(this string instance)
- {
- return Uri.EscapeDataString(instance);
- }
-
- // <summary>
// If the string is a path, this concatenates [code]file[/code] at the end of the string as a subpath. E.g. [code]"this/is".plus_file("path") == "this/is/path"[/code].
// </summary>
public static string PlusFile(this string instance, string file)
@@ -854,6 +994,33 @@ namespace Godot
return instance.Substring(pos, instance.Length - pos);
}
+ /// <summary>
+ /// Returns a copy of the string with characters removed from the right.
+ /// </summary>
+ /// <param name="instance">The string to remove characters from.</param>
+ /// <param name="chars">The characters to be removed.</param>
+ /// <returns>A copy of the string with characters removed from the right.</returns>
+ public static string RStrip(this string instance, string chars)
+ {
+ int len = instance.Length;
+ int end;
+
+ for (end = len - 1; end >= 0; end--)
+ {
+ if (chars.Find(instance[end]) == -1)
+ {
+ break;
+ }
+ }
+
+ if (end == len - 1)
+ {
+ return instance;
+ }
+
+ return instance.Substr(0, end + 1);
+ }
+
public static byte[] SHA256Buffer(this string instance)
{
return godot_icall_String_sha256_buffer(instance);
@@ -980,7 +1147,7 @@ namespace Godot
}
// <summary>
- // Convert the String (which is a character array) to PoolByteArray (which is an array of bytes). The conversion is speeded up in comparison to to_utf8() with the assumption that all the characters the String contains are only ASCII characters.
+ // Convert the String (which is a character array) to PackedByteArray (which is an array of bytes). The conversion is speeded up in comparison to to_utf8() with the assumption that all the characters the String contains are only ASCII characters.
// </summary>
public static byte[] ToAscii(this string instance)
{
@@ -1020,13 +1187,40 @@ namespace Godot
}
// <summary>
- // Convert the String (which is an array of characters) to PoolByteArray (which is an array of bytes). The conversion is a bit slower than to_ascii(), but supports all UTF-8 characters. Therefore, you should prefer this function over to_ascii().
+ // Convert the String (which is an array of characters) to PackedByteArray (which is an array of bytes). The conversion is a bit slower than to_ascii(), but supports all UTF-8 characters. Therefore, you should prefer this function over to_ascii().
// </summary>
public static byte[] ToUTF8(this string instance)
{
return Encoding.UTF8.GetBytes(instance);
}
+ /// <summary>
+ /// Decodes a string in URL encoded format. This is meant to
+ /// decode parameters in a URL when receiving an HTTP request.
+ /// This mostly wraps around `System.Uri.UnescapeDataString()`,
+ /// but also handles `+`.
+ /// See <see cref="URIEncode"/> for encoding.
+ /// </summary>
+ /// <param name="instance">The string to decode.</param>
+ /// <returns>The unescaped string.</returns>
+ public static string URIDecode(this string instance)
+ {
+ return Uri.UnescapeDataString(instance.Replace("+", "%20"));
+ }
+
+ /// <summary>
+ /// Encodes a string to URL friendly format. This is meant to
+ /// encode parameters in a URL when sending an HTTP request.
+ /// This wraps around `System.Uri.EscapeDataString()`.
+ /// See <see cref="URIDecode"/> for decoding.
+ /// </summary>
+ /// <param name="instance">The string to encode.</param>
+ /// <returns>The escaped string.</returns>
+ public static string URIEncode(this string instance)
+ {
+ return Uri.EscapeDataString(instance);
+ }
+
// <summary>
// Return a copy of the string with special characters escaped using the XML standard.
// </summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
new file mode 100644
index 0000000000..7700b6d4ed
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Godot
+{
+ public sealed partial class StringName : IDisposable
+ {
+ private IntPtr ptr;
+
+ internal static IntPtr GetPtr(StringName instance)
+ {
+ if (instance == null)
+ throw new NullReferenceException($"The instance of type {nameof(StringName)} is null.");
+
+ if (instance.ptr == IntPtr.Zero)
+ throw new ObjectDisposedException(instance.GetType().FullName);
+
+ return instance.ptr;
+ }
+
+ ~StringName()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (ptr != IntPtr.Zero)
+ {
+ godot_icall_StringName_Dtor(ptr);
+ ptr = IntPtr.Zero;
+ }
+ }
+
+ internal StringName(IntPtr ptr)
+ {
+ this.ptr = ptr;
+ }
+
+ public StringName()
+ {
+ ptr = IntPtr.Zero;
+ }
+
+ public StringName(string path)
+ {
+ ptr = path == null ? IntPtr.Zero : godot_icall_StringName_Ctor(path);
+ }
+
+ public static implicit operator StringName(string from) => new StringName(from);
+
+ public static implicit operator string(StringName from) => from.ToString();
+
+ public override string ToString()
+ {
+ return ptr == IntPtr.Zero ? string.Empty : godot_icall_StringName_operator_String(GetPtr(this));
+ }
+
+ public bool IsEmpty()
+ {
+ return ptr == IntPtr.Zero || godot_icall_StringName_is_empty(GetPtr(this));
+ }
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern IntPtr godot_icall_StringName_Ctor(string path);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern void godot_icall_StringName_Dtor(IntPtr ptr);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string godot_icall_StringName_operator_String(IntPtr ptr);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern bool godot_icall_StringName_is_empty(IntPtr ptr);
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs
deleted file mode 100644
index 0b84050f07..0000000000
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform.cs
+++ /dev/null
@@ -1,216 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-#if REAL_T_IS_DOUBLE
-using real_t = System.Double;
-#else
-using real_t = System.Single;
-#endif
-
-namespace Godot
-{
- [Serializable]
- [StructLayout(LayoutKind.Sequential)]
- public struct Transform : IEquatable<Transform>
- {
- public Basis basis;
- public Vector3 origin;
-
- public Transform AffineInverse()
- {
- Basis basisInv = basis.Inverse();
- return new Transform(basisInv, basisInv.Xform(-origin));
- }
-
- public Transform InterpolateWith(Transform transform, real_t c)
- {
- /* not sure if very "efficient" but good enough? */
-
- Vector3 sourceScale = basis.Scale;
- Quat sourceRotation = basis.RotationQuat();
- Vector3 sourceLocation = origin;
-
- Vector3 destinationScale = transform.basis.Scale;
- Quat destinationRotation = transform.basis.RotationQuat();
- Vector3 destinationLocation = transform.origin;
-
- var interpolated = new Transform();
- interpolated.basis.SetQuatScale(sourceRotation.Slerp(destinationRotation, c).Normalized(), sourceScale.LinearInterpolate(destinationScale, c));
- interpolated.origin = sourceLocation.LinearInterpolate(destinationLocation, c);
-
- return interpolated;
- }
-
- public Transform Inverse()
- {
- Basis basisTr = basis.Transposed();
- return new Transform(basisTr, basisTr.Xform(-origin));
- }
-
- public Transform LookingAt(Vector3 target, Vector3 up)
- {
- var t = this;
- t.SetLookAt(origin, target, up);
- return t;
- }
-
- public Transform Orthonormalized()
- {
- return new Transform(basis.Orthonormalized(), origin);
- }
-
- public Transform Rotated(Vector3 axis, real_t phi)
- {
- return new Transform(new Basis(axis, phi), new Vector3()) * this;
- }
-
- public Transform Scaled(Vector3 scale)
- {
- return new Transform(basis.Scaled(scale), origin * scale);
- }
-
- public void SetLookAt(Vector3 eye, Vector3 target, Vector3 up)
- {
- // Make rotation matrix
- // Z vector
- Vector3 column2 = eye - target;
-
- column2.Normalize();
-
- Vector3 column1 = up;
-
- Vector3 column0 = column1.Cross(column2);
-
- // Recompute Y = Z cross X
- column1 = column2.Cross(column0);
-
- column0.Normalize();
- column1.Normalize();
-
- basis = new Basis(column0, column1, column2);
-
- origin = eye;
- }
-
- public Transform Translated(Vector3 ofs)
- {
- return new Transform(basis, new Vector3
- (
- origin[0] += basis.Row0.Dot(ofs),
- origin[1] += basis.Row1.Dot(ofs),
- origin[2] += basis.Row2.Dot(ofs)
- ));
- }
-
- public Vector3 Xform(Vector3 v)
- {
- return new Vector3
- (
- basis.Row0.Dot(v) + origin.x,
- basis.Row1.Dot(v) + origin.y,
- basis.Row2.Dot(v) + origin.z
- );
- }
-
- public Vector3 XformInv(Vector3 v)
- {
- Vector3 vInv = v - origin;
-
- return new Vector3
- (
- basis.Row0[0] * vInv.x + basis.Row1[0] * vInv.y + basis.Row2[0] * vInv.z,
- basis.Row0[1] * vInv.x + basis.Row1[1] * vInv.y + basis.Row2[1] * vInv.z,
- basis.Row0[2] * vInv.x + basis.Row1[2] * vInv.y + basis.Row2[2] * vInv.z
- );
- }
-
- // Constants
- private static readonly Transform _identity = new Transform(Basis.Identity, Vector3.Zero);
- private static readonly Transform _flipX = new Transform(new Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1), Vector3.Zero);
- private static readonly Transform _flipY = new Transform(new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1), Vector3.Zero);
- private static readonly Transform _flipZ = new Transform(new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1), Vector3.Zero);
-
- public static Transform Identity { get { return _identity; } }
- public static Transform FlipX { get { return _flipX; } }
- public static Transform FlipY { get { return _flipY; } }
- public static Transform FlipZ { get { return _flipZ; } }
-
- // Constructors
- public Transform(Vector3 column0, Vector3 column1, Vector3 column2, Vector3 origin)
- {
- basis = new Basis(column0, column1, column2);
- this.origin = origin;
- }
-
- public Transform(Quat quat, Vector3 origin)
- {
- basis = new Basis(quat);
- this.origin = origin;
- }
-
- public Transform(Basis basis, Vector3 origin)
- {
- this.basis = basis;
- this.origin = origin;
- }
-
- public static Transform operator *(Transform left, Transform right)
- {
- left.origin = left.Xform(right.origin);
- left.basis *= right.basis;
- return left;
- }
-
- public static bool operator ==(Transform left, Transform right)
- {
- return left.Equals(right);
- }
-
- public static bool operator !=(Transform left, Transform right)
- {
- return !left.Equals(right);
- }
-
- public override bool Equals(object obj)
- {
- if (obj is Transform)
- {
- return Equals((Transform)obj);
- }
-
- return false;
- }
-
- public bool Equals(Transform other)
- {
- return basis.Equals(other.basis) && origin.Equals(other.origin);
- }
-
- public bool IsEqualApprox(Transform other)
- {
- return basis.IsEqualApprox(other.basis) && origin.IsEqualApprox(other.origin);
- }
-
- public override int GetHashCode()
- {
- return basis.GetHashCode() ^ origin.GetHashCode();
- }
-
- public override string ToString()
- {
- return String.Format("{0} - {1}", new object[]
- {
- basis.ToString(),
- origin.ToString()
- });
- }
-
- public string ToString(string format)
- {
- return String.Format("{0} - {1}", new object[]
- {
- basis.ToString(format),
- origin.ToString(format)
- });
- }
- }
-}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
index 77ea3e5830..fe93592667 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
@@ -8,25 +8,44 @@ using real_t = System.Single;
namespace Godot
{
+ /// <summary>
+ /// 2×3 matrix (2 rows, 3 columns) used for 2D linear transformations.
+ /// It can represent transformations such as translation, rotation, or scaling.
+ /// It consists of a three <see cref="Vector2"/> values: x, y, and the origin.
+ ///
+ /// For more information, read this documentation article:
+ /// https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html
+ /// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct Transform2D : IEquatable<Transform2D>
{
+ /// <summary>
+ /// The basis matrix's X vector (column 0). Equivalent to array index `[0]`.
+ /// </summary>
+ /// <value></value>
public Vector2 x;
+
+ /// <summary>
+ /// The basis matrix's Y vector (column 1). Equivalent to array index `[1]`.
+ /// </summary>
public Vector2 y;
+
+ /// <summary>
+ /// The origin vector (column 2, the third column). Equivalent to array index `[2]`.
+ /// The origin vector represents translation.
+ /// </summary>
public Vector2 origin;
+ /// <summary>
+ /// The rotation of this transformation matrix.
+ /// </summary>
+ /// <value>Getting is equivalent to calling <see cref="Mathf.Atan2(real_t, real_t)"/> with the values of <see cref="x"/>.</value>
public real_t Rotation
{
get
{
- real_t det = BasisDeterminant();
- Transform2D t = Orthonormalized();
- if (det < 0)
- {
- t.ScaleBasis(new Vector2(1, -1));
- }
- return Mathf.Atan2(t.x.y, t.x.x);
+ return Mathf.Atan2(x.y, x.x);
}
set
{
@@ -38,6 +57,10 @@ namespace Godot
}
}
+ /// <summary>
+ /// The scale of this transformation matrix.
+ /// </summary>
+ /// <value>Equivalent to the lengths of each column vector, but Y is negative if the determinant is negative.</value>
public Vector2 Scale
{
get
@@ -47,18 +70,21 @@ namespace Godot
}
set
{
- x = x.Normalized();
- y = y.Normalized();
+ value /= Scale; // Value becomes what's called "delta_scale" in core.
x *= value.x;
y *= value.y;
}
}
- public Vector2 this[int rowIndex]
+ /// <summary>
+ /// Access whole columns in the form of Vector2. The third column is the origin vector.
+ /// </summary>
+ /// <param name="column">Which column vector.</param>
+ public Vector2 this[int column]
{
get
{
- switch (rowIndex)
+ switch (column)
{
case 0:
return x;
@@ -72,7 +98,7 @@ namespace Godot
}
set
{
- switch (rowIndex)
+ switch (column)
{
case 0:
x = value;
@@ -89,41 +115,30 @@ namespace Godot
}
}
- public real_t this[int rowIndex, int columnIndex]
+ /// <summary>
+ /// Access matrix elements in column-major order. The third column is the origin vector.
+ /// </summary>
+ /// <param name="column">Which column, the matrix horizontal position.</param>
+ /// <param name="row">Which row, the matrix vertical position.</param>
+ public real_t this[int column, int row]
{
get
{
- switch (rowIndex)
- {
- case 0:
- return x[columnIndex];
- case 1:
- return y[columnIndex];
- case 2:
- return origin[columnIndex];
- default:
- throw new IndexOutOfRangeException();
- }
+ return this[column][row];
}
set
{
- switch (rowIndex)
- {
- case 0:
- x[columnIndex] = value;
- return;
- case 1:
- y[columnIndex] = value;
- return;
- case 2:
- origin[columnIndex] = value;
- return;
- default:
- throw new IndexOutOfRangeException();
- }
+ Vector2 columnVector = this[column];
+ columnVector[row] = value;
+ this[column] = columnVector;
}
}
+ /// <summary>
+ /// Returns the inverse of the transform, under the assumption that
+ /// the transformation is composed of rotation, scaling, and translation.
+ /// </summary>
+ /// <returns>The inverse transformation matrix.</returns>
public Transform2D AffineInverse()
{
real_t det = BasisDeterminant();
@@ -147,28 +162,58 @@ namespace Godot
return inv;
}
+ /// <summary>
+ /// Returns the determinant of the basis matrix. If the basis is
+ /// uniformly scaled, its determinant is the square of the scale.
+ ///
+ /// A negative determinant means the Y scale is negative.
+ /// A zero determinant means the basis isn't invertible,
+ /// and is usually considered invalid.
+ /// </summary>
+ /// <returns>The determinant of the basis matrix.</returns>
private real_t BasisDeterminant()
{
return x.x * y.y - x.y * y.x;
}
+ /// <summary>
+ /// Returns a vector transformed (multiplied) by the basis matrix.
+ /// This method does not account for translation (the origin vector).
+ /// </summary>
+ /// <param name="v">A vector to transform.</param>
+ /// <returns>The transformed vector.</returns>
public Vector2 BasisXform(Vector2 v)
{
return new Vector2(Tdotx(v), Tdoty(v));
}
+ /// <summary>
+ /// Returns a vector transformed (multiplied) by the inverse basis matrix.
+ /// This method does not account for translation (the origin vector).
+ ///
+ /// Note: This results in a multiplication by the inverse of the
+ /// basis matrix only if it represents a rotation-reflection.
+ /// </summary>
+ /// <param name="v">A vector to inversely transform.</param>
+ /// <returns>The inversely transformed vector.</returns>
public Vector2 BasisXformInv(Vector2 v)
{
return new Vector2(x.Dot(v), y.Dot(v));
}
- public Transform2D InterpolateWith(Transform2D m, real_t c)
+ /// <summary>
+ /// Interpolates this transform to the other `transform` by `weight`.
+ /// </summary>
+ /// <param name="transform">The other transform.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The interpolated transform.</returns>
+ public Transform2D InterpolateWith(Transform2D transform, real_t weight)
{
real_t r1 = Rotation;
- real_t r2 = m.Rotation;
+ real_t r2 = transform.Rotation;
Vector2 s1 = Scale;
- Vector2 s2 = m.Scale;
+ Vector2 s2 = transform.Scale;
// Slerp rotation
var v1 = new Vector2(Mathf.Cos(r1), Mathf.Sin(r1));
@@ -176,36 +221,41 @@ namespace Godot
real_t dot = v1.Dot(v2);
- // Clamp dot to [-1, 1]
- dot = dot < -1.0f ? -1.0f : (dot > 1.0f ? 1.0f : dot);
+ dot = Mathf.Clamp(dot, -1.0f, 1.0f);
Vector2 v;
if (dot > 0.9995f)
{
// Linearly interpolate to avoid numerical precision issues
- v = v1.LinearInterpolate(v2, c).Normalized();
+ v = v1.Lerp(v2, weight).Normalized();
}
else
{
- real_t angle = c * Mathf.Acos(dot);
+ real_t angle = weight * Mathf.Acos(dot);
Vector2 v3 = (v2 - v1 * dot).Normalized();
v = v1 * Mathf.Cos(angle) + v3 * Mathf.Sin(angle);
}
// Extract parameters
Vector2 p1 = origin;
- Vector2 p2 = m.origin;
+ Vector2 p2 = transform.origin;
// Construct matrix
- var res = new Transform2D(Mathf.Atan2(v.y, v.x), p1.LinearInterpolate(p2, c));
- Vector2 scale = s1.LinearInterpolate(s2, c);
+ var res = new Transform2D(Mathf.Atan2(v.y, v.x), p1.Lerp(p2, weight));
+ Vector2 scale = s1.Lerp(s2, weight);
res.x *= scale;
res.y *= scale;
return res;
}
+ /// <summary>
+ /// Returns the inverse of the transform, under the assumption that
+ /// the transformation is composed of rotation and translation
+ /// (no scaling, use <see cref="AffineInverse"/> for transforms with scaling).
+ /// </summary>
+ /// <returns>The inverse matrix.</returns>
public Transform2D Inverse()
{
var inv = this;
@@ -220,6 +270,11 @@ namespace Godot
return inv;
}
+ /// <summary>
+ /// Returns the transform with the basis orthogonal (90 degrees),
+ /// and normalized axis vectors (scale of 1 or -1).
+ /// </summary>
+ /// <returns>The orthonormalized transform.</returns>
public Transform2D Orthonormalized()
{
var on = this;
@@ -237,11 +292,21 @@ namespace Godot
return on;
}
+ /// <summary>
+ /// Rotates the transform by `phi` (in radians), using matrix multiplication.
+ /// </summary>
+ /// <param name="phi">The angle to rotate, in radians.</param>
+ /// <returns>The rotated transformation matrix.</returns>
public Transform2D Rotated(real_t phi)
{
return this * new Transform2D(phi, new Vector2());
}
+ /// <summary>
+ /// Scales the transform by the given scaling factor, using matrix multiplication.
+ /// </summary>
+ /// <param name="scale">The scale to introduce.</param>
+ /// <returns>The scaled transformation matrix.</returns>
public Transform2D Scaled(Vector2 scale)
{
var copy = this;
@@ -269,6 +334,15 @@ namespace Godot
return this[0, 1] * with[0] + this[1, 1] * with[1];
}
+ /// <summary>
+ /// Translates the transform by the given `offset`,
+ /// relative to the transform's basis vectors.
+ ///
+ /// Unlike <see cref="Rotated"/> and <see cref="Scaled"/>,
+ /// this does not use matrix multiplication.
+ /// </summary>
+ /// <param name="offset">The offset to translate by.</param>
+ /// <returns>The translated matrix.</returns>
public Transform2D Translated(Vector2 offset)
{
var copy = this;
@@ -276,11 +350,21 @@ namespace Godot
return copy;
}
+ /// <summary>
+ /// Returns a vector transformed (multiplied) by this transformation matrix.
+ /// </summary>
+ /// <param name="v">A vector to transform.</param>
+ /// <returns>The transformed vector.</returns>
public Vector2 Xform(Vector2 v)
{
return new Vector2(Tdotx(v), Tdoty(v)) + origin;
}
+ /// <summary>
+ /// Returns a vector transformed (multiplied) by the inverse transformation matrix.
+ /// </summary>
+ /// <param name="v">A vector to inversely transform.</param>
+ /// <returns>The inversely transformed vector.</returns>
public Vector2 XformInv(Vector2 v)
{
Vector2 vInv = v - origin;
@@ -292,11 +376,30 @@ namespace Godot
private static readonly Transform2D _flipX = new Transform2D(-1, 0, 0, 1, 0, 0);
private static readonly Transform2D _flipY = new Transform2D(1, 0, 0, -1, 0, 0);
- public static Transform2D Identity => _identity;
- public static Transform2D FlipX => _flipX;
- public static Transform2D FlipY => _flipY;
-
- // Constructors
+ /// <summary>
+ /// The identity transform, with no translation, rotation, or scaling applied.
+ /// This is used as a replacement for `Transform2D()` in GDScript.
+ /// Do not use `new Transform2D()` with no arguments in C#, because it sets all values to zero.
+ /// </summary>
+ /// <value>Equivalent to `new Transform2D(Vector2.Right, Vector2.Down, Vector2.Zero)`.</value>
+ public static Transform2D Identity { get { return _identity; } }
+ /// <summary>
+ /// The transform that will flip something along the X axis.
+ /// </summary>
+ /// <value>Equivalent to `new Transform2D(Vector2.Left, Vector2.Down, Vector2.Zero)`.</value>
+ public static Transform2D FlipX { get { return _flipX; } }
+ /// <summary>
+ /// The transform that will flip something along the Y axis.
+ /// </summary>
+ /// <value>Equivalent to `new Transform2D(Vector2.Right, Vector2.Up, Vector2.Zero)`.</value>
+ public static Transform2D FlipY { get { return _flipY; } }
+
+ /// <summary>
+ /// Constructs a transformation matrix from 3 vectors (matrix columns).
+ /// </summary>
+ /// <param name="xAxis">The X vector, or column index 0.</param>
+ /// <param name="yAxis">The Y vector, or column index 1.</param>
+ /// <param name="originPos">The origin vector, or column index 2.</param>
public Transform2D(Vector2 xAxis, Vector2 yAxis, Vector2 originPos)
{
x = xAxis;
@@ -304,7 +407,16 @@ namespace Godot
origin = originPos;
}
- // Arguments are named such that xy is equal to calling x.y
+ /// <summary>
+ /// Constructs a transformation matrix from the given components.
+ /// Arguments are named such that xy is equal to calling x.y
+ /// </summary>
+ /// <param name="xx">The X component of the X column vector, accessed via `t.x.x` or `[0][0]`</param>
+ /// <param name="xy">The Y component of the X column vector, accessed via `t.x.y` or `[0][1]`</param>
+ /// <param name="yx">The X component of the Y column vector, accessed via `t.y.x` or `[1][0]`</param>
+ /// <param name="yy">The Y component of the Y column vector, accessed via `t.y.y` or `[1][1]`</param>
+ /// <param name="ox">The X component of the origin vector, accessed via `t.origin.x` or `[2][0]`</param>
+ /// <param name="oy">The Y component of the origin vector, accessed via `t.origin.y` or `[2][1]`</param>
public Transform2D(real_t xx, real_t xy, real_t yx, real_t yy, real_t ox, real_t oy)
{
x = new Vector2(xx, xy);
@@ -312,6 +424,11 @@ namespace Godot
origin = new Vector2(ox, oy);
}
+ /// <summary>
+ /// Constructs a transformation matrix from a rotation value and origin vector.
+ /// </summary>
+ /// <param name="rot">The rotation of the new transform, in radians.</param>
+ /// <param name="pos">The origin vector, or column index 2.</param>
public Transform2D(real_t rot, Vector2 pos)
{
x.x = y.y = Mathf.Cos(rot);
@@ -357,6 +474,12 @@ namespace Godot
return x.Equals(other.x) && y.Equals(other.y) && origin.Equals(other.origin);
}
+ /// <summary>
+ /// Returns true if this transform and `other` are approximately equal, by running
+ /// <see cref="Vector2.IsEqualApprox(Vector2)"/> on each component.
+ /// </summary>
+ /// <param name="other">The other transform to compare.</param>
+ /// <returns>Whether or not the matrices are approximately equal.</returns>
public bool IsEqualApprox(Transform2D other)
{
return x.IsEqualApprox(other.x) && y.IsEqualApprox(other.y) && origin.IsEqualApprox(other.origin);
@@ -369,22 +492,16 @@ namespace Godot
public override string ToString()
{
- return String.Format("({0}, {1}, {2})", new object[]
- {
- x.ToString(),
- y.ToString(),
- origin.ToString()
- });
+ return "[X: " + x.ToString() +
+ ", Y: " + y.ToString() +
+ ", O: " + origin.ToString() + "]";
}
public string ToString(string format)
{
- return String.Format("({0}, {1}, {2})", new object[]
- {
- x.ToString(format),
- y.ToString(format),
- origin.ToString(format)
- });
+ return "[X: " + x.ToString(format) +
+ ", Y: " + y.ToString(format) +
+ ", O: " + origin.ToString(format) + "]";
}
}
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs
new file mode 100644
index 0000000000..26b1a9e8b2
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs
@@ -0,0 +1,410 @@
+using System;
+using System.Runtime.InteropServices;
+#if REAL_T_IS_DOUBLE
+using real_t = System.Double;
+#else
+using real_t = System.Single;
+#endif
+
+namespace Godot
+{
+ /// <summary>
+ /// 3×4 matrix (3 rows, 4 columns) used for 3D linear transformations.
+ /// It can represent transformations such as translation, rotation, or scaling.
+ /// It consists of a <see cref="Basis"/> (first 3 columns) and a
+ /// <see cref="Vector3"/> for the origin (last column).
+ ///
+ /// For more information, read this documentation article:
+ /// https://docs.godotengine.org/en/latest/tutorials/math/matrices_and_transforms.html
+ /// </summary>
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Transform3D : IEquatable<Transform3D>
+ {
+ /// <summary>
+ /// The <see cref="Basis"/> of this transform. Contains the X, Y, and Z basis
+ /// vectors (columns 0 to 2) and is responsible for rotation and scale.
+ /// </summary>
+ public Basis basis;
+
+ /// <summary>
+ /// The origin vector (column 3, the fourth column). Equivalent to array index `[3]`.
+ /// </summary>
+ public Vector3 origin;
+
+ /// <summary>
+ /// Access whole columns in the form of Vector3. The fourth column is the origin vector.
+ /// </summary>
+ /// <param name="column">Which column vector.</param>
+ public Vector3 this[int column]
+ {
+ get
+ {
+ switch (column)
+ {
+ case 0:
+ return basis.Column0;
+ case 1:
+ return basis.Column1;
+ case 2:
+ return basis.Column2;
+ case 3:
+ return origin;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ set
+ {
+ switch (column)
+ {
+ case 0:
+ basis.Column0 = value;
+ return;
+ case 1:
+ basis.Column1 = value;
+ return;
+ case 2:
+ basis.Column2 = value;
+ return;
+ case 3:
+ origin = value;
+ return;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Access matrix elements in column-major order. The fourth column is the origin vector.
+ /// </summary>
+ /// <param name="column">Which column, the matrix horizontal position.</param>
+ /// <param name="row">Which row, the matrix vertical position.</param>
+ public real_t this[int column, int row]
+ {
+ get
+ {
+ if (column == 3)
+ {
+ return origin[row];
+ }
+ return basis[column, row];
+ }
+ set
+ {
+ if (column == 3)
+ {
+ origin[row] = value;
+ return;
+ }
+ basis[column, row] = value;
+ }
+ }
+
+ /// <summary>
+ /// Returns the inverse of the transform, under the assumption that
+ /// the transformation is composed of rotation, scaling, and translation.
+ /// </summary>
+ /// <returns>The inverse transformation matrix.</returns>
+ public Transform3D AffineInverse()
+ {
+ Basis basisInv = basis.Inverse();
+ return new Transform3D(basisInv, basisInv.Xform(-origin));
+ }
+
+ /// <summary>
+ /// Interpolates this transform to the other `transform` by `weight`.
+ /// </summary>
+ /// <param name="transform">The other transform.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The interpolated transform.</returns>
+ public Transform3D InterpolateWith(Transform3D transform, real_t weight)
+ {
+ /* not sure if very "efficient" but good enough? */
+
+ Vector3 sourceScale = basis.Scale;
+ Quaternion sourceRotation = basis.RotationQuaternion();
+ Vector3 sourceLocation = origin;
+
+ Vector3 destinationScale = transform.basis.Scale;
+ Quaternion destinationRotation = transform.basis.RotationQuaternion();
+ Vector3 destinationLocation = transform.origin;
+
+ var interpolated = new Transform3D();
+ interpolated.basis.SetQuaternionScale(sourceRotation.Slerp(destinationRotation, weight).Normalized(), sourceScale.Lerp(destinationScale, weight));
+ interpolated.origin = sourceLocation.Lerp(destinationLocation, weight);
+
+ return interpolated;
+ }
+
+ /// <summary>
+ /// Returns the inverse of the transform, under the assumption that
+ /// the transformation is composed of rotation and translation
+ /// (no scaling, use <see cref="AffineInverse"/> for transforms with scaling).
+ /// </summary>
+ /// <returns>The inverse matrix.</returns>
+ public Transform3D Inverse()
+ {
+ Basis basisTr = basis.Transposed();
+ return new Transform3D(basisTr, basisTr.Xform(-origin));
+ }
+
+ /// <summary>
+ /// Returns a copy of the transform rotated such that its
+ /// -Z axis (forward) points towards the target position.
+ ///
+ /// The transform will first be rotated around the given up vector,
+ /// and then fully aligned to the target by a further rotation around
+ /// an axis perpendicular to both the target and up vectors.
+ ///
+ /// Operations take place in global space.
+ /// </summary>
+ /// <param name="target">The object to look at.</param>
+ /// <param name="up">The relative up direction</param>
+ /// <returns>The resulting transform.</returns>
+ public Transform3D LookingAt(Vector3 target, Vector3 up)
+ {
+ var t = this;
+ t.SetLookAt(origin, target, up);
+ return t;
+ }
+
+ /// <summary>
+ /// Returns the transform with the basis orthogonal (90 degrees),
+ /// and normalized axis vectors (scale of 1 or -1).
+ /// </summary>
+ /// <returns>The orthonormalized transform.</returns>
+ public Transform3D Orthonormalized()
+ {
+ return new Transform3D(basis.Orthonormalized(), origin);
+ }
+
+ /// <summary>
+ /// Rotates the transform around the given `axis` by `phi` (in radians),
+ /// using matrix multiplication. The axis must be a normalized vector.
+ /// </summary>
+ /// <param name="axis">The axis to rotate around. Must be normalized.</param>
+ /// <param name="phi">The angle to rotate, in radians.</param>
+ /// <returns>The rotated transformation matrix.</returns>
+ public Transform3D Rotated(Vector3 axis, real_t phi)
+ {
+ return new Transform3D(new Basis(axis, phi), new Vector3()) * this;
+ }
+
+ /// <summary>
+ /// Scales the transform by the given 3D scaling factor, using matrix multiplication.
+ /// </summary>
+ /// <param name="scale">The scale to introduce.</param>
+ /// <returns>The scaled transformation matrix.</returns>
+ public Transform3D Scaled(Vector3 scale)
+ {
+ return new Transform3D(basis.Scaled(scale), origin * scale);
+ }
+
+ private void SetLookAt(Vector3 eye, Vector3 target, Vector3 up)
+ {
+ // Make rotation matrix
+ // Z vector
+ Vector3 column2 = eye - target;
+
+ column2.Normalize();
+
+ Vector3 column1 = up;
+
+ Vector3 column0 = column1.Cross(column2);
+
+ // Recompute Y = Z cross X
+ column1 = column2.Cross(column0);
+
+ column0.Normalize();
+ column1.Normalize();
+
+ basis = new Basis(column0, column1, column2);
+
+ origin = eye;
+ }
+
+ /// <summary>
+ /// Translates the transform by the given `offset`,
+ /// relative to the transform's basis vectors.
+ ///
+ /// Unlike <see cref="Rotated"/> and <see cref="Scaled"/>,
+ /// this does not use matrix multiplication.
+ /// </summary>
+ /// <param name="offset">The offset to translate by.</param>
+ /// <returns>The translated matrix.</returns>
+ public Transform3D Translated(Vector3 offset)
+ {
+ return new Transform3D(basis, new Vector3
+ (
+ origin[0] += basis.Row0.Dot(offset),
+ origin[1] += basis.Row1.Dot(offset),
+ origin[2] += basis.Row2.Dot(offset)
+ ));
+ }
+
+ /// <summary>
+ /// Returns a vector transformed (multiplied) by this transformation matrix.
+ /// </summary>
+ /// <param name="v">A vector to transform.</param>
+ /// <returns>The transformed vector.</returns>
+ public Vector3 Xform(Vector3 v)
+ {
+ return new Vector3
+ (
+ basis.Row0.Dot(v) + origin.x,
+ basis.Row1.Dot(v) + origin.y,
+ basis.Row2.Dot(v) + origin.z
+ );
+ }
+
+ /// <summary>
+ /// Returns a vector transformed (multiplied) by the transposed transformation matrix.
+ ///
+ /// Note: This results in a multiplication by the inverse of the
+ /// transformation matrix only if it represents a rotation-reflection.
+ /// </summary>
+ /// <param name="v">A vector to inversely transform.</param>
+ /// <returns>The inversely transformed vector.</returns>
+ public Vector3 XformInv(Vector3 v)
+ {
+ Vector3 vInv = v - origin;
+
+ return new Vector3
+ (
+ basis.Row0[0] * vInv.x + basis.Row1[0] * vInv.y + basis.Row2[0] * vInv.z,
+ basis.Row0[1] * vInv.x + basis.Row1[1] * vInv.y + basis.Row2[1] * vInv.z,
+ basis.Row0[2] * vInv.x + basis.Row1[2] * vInv.y + basis.Row2[2] * vInv.z
+ );
+ }
+
+ // Constants
+ private static readonly Transform3D _identity = new Transform3D(Basis.Identity, Vector3.Zero);
+ private static readonly Transform3D _flipX = new Transform3D(new Basis(-1, 0, 0, 0, 1, 0, 0, 0, 1), Vector3.Zero);
+ private static readonly Transform3D _flipY = new Transform3D(new Basis(1, 0, 0, 0, -1, 0, 0, 0, 1), Vector3.Zero);
+ private static readonly Transform3D _flipZ = new Transform3D(new Basis(1, 0, 0, 0, 1, 0, 0, 0, -1), Vector3.Zero);
+
+ /// <summary>
+ /// The identity transform, with no translation, rotation, or scaling applied.
+ /// This is used as a replacement for `Transform()` in GDScript.
+ /// Do not use `new Transform()` with no arguments in C#, because it sets all values to zero.
+ /// </summary>
+ /// <value>Equivalent to `new Transform(Vector3.Right, Vector3.Up, Vector3.Back, Vector3.Zero)`.</value>
+ public static Transform3D Identity { get { return _identity; } }
+ /// <summary>
+ /// The transform that will flip something along the X axis.
+ /// </summary>
+ /// <value>Equivalent to `new Transform(Vector3.Left, Vector3.Up, Vector3.Back, Vector3.Zero)`.</value>
+ public static Transform3D FlipX { get { return _flipX; } }
+ /// <summary>
+ /// The transform that will flip something along the Y axis.
+ /// </summary>
+ /// <value>Equivalent to `new Transform(Vector3.Right, Vector3.Down, Vector3.Back, Vector3.Zero)`.</value>
+ public static Transform3D FlipY { get { return _flipY; } }
+ /// <summary>
+ /// The transform that will flip something along the Z axis.
+ /// </summary>
+ /// <value>Equivalent to `new Transform(Vector3.Right, Vector3.Up, Vector3.Forward, Vector3.Zero)`.</value>
+ public static Transform3D FlipZ { get { return _flipZ; } }
+
+ /// <summary>
+ /// Constructs a transformation matrix from 4 vectors (matrix columns).
+ /// </summary>
+ /// <param name="column0">The X vector, or column index 0.</param>
+ /// <param name="column1">The Y vector, or column index 1.</param>
+ /// <param name="column2">The Z vector, or column index 2.</param>
+ /// <param name="origin">The origin vector, or column index 3.</param>
+ public Transform3D(Vector3 column0, Vector3 column1, Vector3 column2, Vector3 origin)
+ {
+ basis = new Basis(column0, column1, column2);
+ this.origin = origin;
+ }
+
+ /// <summary>
+ /// Constructs a transformation matrix from the given quaternion and origin vector.
+ /// </summary>
+ /// <param name="quaternion">The <see cref="Godot.Quaternion"/> to create the basis from.</param>
+ /// <param name="origin">The origin vector, or column index 3.</param>
+ public Transform3D(Quaternion quaternion, Vector3 origin)
+ {
+ basis = new Basis(quaternion);
+ this.origin = origin;
+ }
+
+ /// <summary>
+ /// Constructs a transformation matrix from the given basis and origin vector.
+ /// </summary>
+ /// <param name="basis">The <see cref="Godot.Basis"/> to create the basis from.</param>
+ /// <param name="origin">The origin vector, or column index 3.</param>
+ public Transform3D(Basis basis, Vector3 origin)
+ {
+ this.basis = basis;
+ this.origin = origin;
+ }
+
+ public static Transform3D operator *(Transform3D left, Transform3D right)
+ {
+ left.origin = left.Xform(right.origin);
+ left.basis *= right.basis;
+ return left;
+ }
+
+ public static bool operator ==(Transform3D left, Transform3D right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Transform3D left, Transform3D right)
+ {
+ return !left.Equals(right);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Transform3D)
+ {
+ return Equals((Transform3D)obj);
+ }
+
+ return false;
+ }
+
+ public bool Equals(Transform3D other)
+ {
+ return basis.Equals(other.basis) && origin.Equals(other.origin);
+ }
+
+ /// <summary>
+ /// Returns true if this transform and `other` are approximately equal, by running
+ /// <see cref="Vector3.IsEqualApprox(Vector3)"/> on each component.
+ /// </summary>
+ /// <param name="other">The other transform to compare.</param>
+ /// <returns>Whether or not the matrices are approximately equal.</returns>
+ public bool IsEqualApprox(Transform3D other)
+ {
+ return basis.IsEqualApprox(other.basis) && origin.IsEqualApprox(other.origin);
+ }
+
+ public override int GetHashCode()
+ {
+ return basis.GetHashCode() ^ origin.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return "[X: " + basis.x.ToString() +
+ ", Y: " + basis.y.ToString() +
+ ", Z: " + basis.z.ToString() +
+ ", O: " + origin.ToString() + "]";
+ }
+
+ public string ToString(string format)
+ {
+ return "[X: " + basis.x.ToString(format) +
+ ", Y: " + basis.y.ToString(format) +
+ ", Z: " + basis.z.ToString(format) +
+ ", O: " + origin.ToString(format) + "]";
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs
new file mode 100644
index 0000000000..be01674568
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Godot
+{
+ /// <summary>
+ /// Event arguments for when unhandled exceptions occur.
+ /// </summary>
+ public class UnhandledExceptionArgs
+ {
+ /// <summary>
+ /// Exception object
+ /// </summary>
+ public Exception Exception { get; private set; }
+
+ internal UnhandledExceptionArgs(Exception exception)
+ {
+ Exception = exception;
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
index f92453f546..af053bd263 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs
@@ -21,15 +21,29 @@ namespace Godot
[StructLayout(LayoutKind.Sequential)]
public struct Vector2 : IEquatable<Vector2>
{
+ /// <summary>
+ /// Enumerated index values for the axes.
+ /// Returned by <see cref="MaxAxis"/> and <see cref="MinAxis"/>.
+ /// </summary>
public enum Axis
{
X = 0,
Y
}
+ /// <summary>
+ /// The vector's X component. Also accessible by using the index position `[0]`.
+ /// </summary>
public real_t x;
+ /// <summary>
+ /// The vector's Y component. Also accessible by using the index position `[1]`.
+ /// </summary>
public real_t y;
+ /// <summary>
+ /// Access vector components using their index.
+ /// </summary>
+ /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`.</value>
public real_t this[int index]
{
get
@@ -76,67 +90,118 @@ namespace Godot
}
}
- public real_t Cross(Vector2 b)
- {
- return x * b.y - y * b.x;
- }
-
+ /// <summary>
+ /// Returns a new vector with all components in absolute values (i.e. positive).
+ /// </summary>
+ /// <returns>A vector with <see cref="Mathf.Abs(real_t)"/> called on each component.</returns>
public Vector2 Abs()
{
return new Vector2(Mathf.Abs(x), Mathf.Abs(y));
}
+ /// <summary>
+ /// Returns this vector's angle with respect to the X axis, or (1, 0) vector, in radians.
+ ///
+ /// Equivalent to the result of <see cref="Mathf.Atan2(real_t, real_t)"/> when
+ /// called with the vector's `y` and `x` as parameters: `Mathf.Atan2(v.y, v.x)`.
+ /// </summary>
+ /// <returns>The angle of this vector, in radians.</returns>
public real_t Angle()
{
return Mathf.Atan2(y, x);
}
+ /// <summary>
+ /// Returns the angle to the given vector, in radians.
+ /// </summary>
+ /// <param name="to">The other vector to compare this vector to.</param>
+ /// <returns>The angle between the two vectors, in radians.</returns>
public real_t AngleTo(Vector2 to)
{
return Mathf.Atan2(Cross(to), Dot(to));
}
+ /// <summary>
+ /// Returns the angle between the line connecting the two points and the X axis, in radians.
+ /// </summary>
+ /// <param name="to">The other vector to compare this vector to.</param>
+ /// <returns>The angle between the two vectors, in radians.</returns>
public real_t AngleToPoint(Vector2 to)
{
return Mathf.Atan2(y - to.y, x - to.x);
}
+ /// <summary>
+ /// Returns the aspect ratio of this vector, the ratio of `x` to `y`.
+ /// </summary>
+ /// <returns>The `x` component divided by the `y` component.</returns>
public real_t Aspect()
{
return x / y;
}
- public Vector2 Bounce(Vector2 n)
+ /// <summary>
+ /// Returns the vector "bounced off" from a plane defined by the given normal.
+ /// </summary>
+ /// <param name="normal">The normal vector defining the plane to bounce off. Must be normalized.</param>
+ /// <returns>The bounced vector.</returns>
+ public Vector2 Bounce(Vector2 normal)
{
- return -Reflect(n);
+ return -Reflect(normal);
}
+ /// <summary>
+ /// Returns a new vector with all components rounded up (towards positive infinity).
+ /// </summary>
+ /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns>
public Vector2 Ceil()
{
return new Vector2(Mathf.Ceil(x), Mathf.Ceil(y));
}
- public Vector2 Clamped(real_t length)
+ /// <summary>
+ /// Returns a new vector with all components clamped between the
+ /// components of `min` and `max` using
+ /// <see cref="Mathf.Clamp(real_t, real_t, real_t)"/>.
+ /// </summary>
+ /// <param name="min">The vector with minimum allowed values.</param>
+ /// <param name="max">The vector with maximum allowed values.</param>
+ /// <returns>The vector with all components clamped.</returns>
+ public Vector2 Clamp(Vector2 min, Vector2 max)
+ {
+ return new Vector2
+ (
+ Mathf.Clamp(x, min.x, max.x),
+ Mathf.Clamp(y, min.y, max.y)
+ );
+ }
+
+ /// <summary>
+ /// Returns the cross product of this vector and `b`.
+ /// </summary>
+ /// <param name="b">The other vector.</param>
+ /// <returns>The cross product value.</returns>
+ public real_t Cross(Vector2 b)
{
- var v = this;
- real_t l = Length();
-
- if (l > 0 && length < l)
- {
- v /= l;
- v *= length;
- }
-
- return v;
+ return x * b.y - y * b.x;
}
- public Vector2 CubicInterpolate(Vector2 b, Vector2 preA, Vector2 postB, real_t t)
+ /// <summary>
+ /// Performs a cubic interpolation between vectors `preA`, this vector, `b`, and `postB`, by the given amount `t`.
+ /// </summary>
+ /// <param name="b">The destination vector.</param>
+ /// <param name="preA">A vector before this vector.</param>
+ /// <param name="postB">A vector after `b`.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The interpolated vector.</returns>
+ public Vector2 CubicInterpolate(Vector2 b, Vector2 preA, Vector2 postB, real_t weight)
{
- var p0 = preA;
- var p1 = this;
- var p2 = b;
- var p3 = postB;
+ Vector2 p0 = preA;
+ Vector2 p1 = this;
+ Vector2 p2 = b;
+ Vector2 p3 = postB;
+ real_t t = weight;
real_t t2 = t * t;
real_t t3 = t2 * t;
@@ -146,56 +211,172 @@ namespace Godot
(-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
}
+ /// <summary>
+ /// Returns the normalized vector pointing from this vector to `b`.
+ /// </summary>
+ /// <param name="b">The other vector to point towards.</param>
+ /// <returns>The direction from this vector to `b`.</returns>
public Vector2 DirectionTo(Vector2 b)
{
return new Vector2(b.x - x, b.y - y).Normalized();
}
+ /// <summary>
+ /// Returns the squared distance between this vector and `to`.
+ /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if
+ /// you need to compare vectors or need the squared distance for some formula.
+ /// </summary>
+ /// <param name="to">The other vector to use.</param>
+ /// <returns>The squared distance between the two vectors.</returns>
public real_t DistanceSquaredTo(Vector2 to)
{
return (x - to.x) * (x - to.x) + (y - to.y) * (y - to.y);
}
+ /// <summary>
+ /// Returns the distance between this vector and `to`.
+ /// </summary>
+ /// <param name="to">The other vector to use.</param>
+ /// <returns>The distance between the two vectors.</returns>
public real_t DistanceTo(Vector2 to)
{
return Mathf.Sqrt((x - to.x) * (x - to.x) + (y - to.y) * (y - to.y));
}
+ /// <summary>
+ /// Returns the dot product of this vector and `with`.
+ /// </summary>
+ /// <param name="with">The other vector to use.</param>
+ /// <returns>The dot product of the two vectors.</returns>
public real_t Dot(Vector2 with)
{
return x * with.x + y * with.y;
}
+ /// <summary>
+ /// Returns a new vector with all components rounded down (towards negative infinity).
+ /// </summary>
+ /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns>
public Vector2 Floor()
{
return new Vector2(Mathf.Floor(x), Mathf.Floor(y));
}
+ /// <summary>
+ /// Returns the inverse of this vector. This is the same as `new Vector2(1 / v.x, 1 / v.y)`.
+ /// </summary>
+ /// <returns>The inverse of this vector.</returns>
+ public Vector2 Inverse()
+ {
+ return new Vector2(1 / x, 1 / y);
+ }
+
+ /// <summary>
+ /// Returns true if the vector is normalized, and false otherwise.
+ /// </summary>
+ /// <returns>A bool indicating whether or not the vector is normalized.</returns>
public bool IsNormalized()
{
return Mathf.Abs(LengthSquared() - 1.0f) < Mathf.Epsilon;
}
+ /// <summary>
+ /// Returns the length (magnitude) of this vector.
+ /// </summary>
+ /// <returns>The length of this vector.</returns>
public real_t Length()
{
return Mathf.Sqrt(x * x + y * y);
}
+ /// <summary>
+ /// Returns the squared length (squared magnitude) of this vector.
+ /// This method runs faster than <see cref="Length"/>, so prefer it if
+ /// you need to compare vectors or need the squared length for some formula.
+ /// </summary>
+ /// <returns>The squared length of this vector.</returns>
public real_t LengthSquared()
{
return x * x + y * y;
}
- public Vector2 LinearInterpolate(Vector2 b, real_t t)
- {
- var res = this;
+ /// <summary>
+ /// Returns the result of the linear interpolation between
+ /// this vector and `to` by amount `weight`.
+ /// </summary>
+ /// <param name="to">The destination vector for interpolation.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting vector of the interpolation.</returns>
+ public Vector2 Lerp(Vector2 to, real_t weight)
+ {
+ return new Vector2
+ (
+ Mathf.Lerp(x, to.x, weight),
+ Mathf.Lerp(y, to.y, weight)
+ );
+ }
+
+ /// <summary>
+ /// Returns the result of the linear interpolation between
+ /// this vector and `to` by the vector amount `weight`.
+ /// </summary>
+ /// <param name="to">The destination vector for interpolation.</param>
+ /// <param name="weight">A vector with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting vector of the interpolation.</returns>
+ public Vector2 Lerp(Vector2 to, Vector2 weight)
+ {
+ return new Vector2
+ (
+ Mathf.Lerp(x, to.x, weight.x),
+ Mathf.Lerp(y, to.y, weight.y)
+ );
+ }
+
+ /// <summary>
+ /// Returns the vector with a maximum length by limiting its length to `length`.
+ /// </summary>
+ /// <param name="length">The length to limit to.</param>
+ /// <returns>The vector with its length limited.</returns>
+ public Vector2 LimitLength(real_t length = 1.0f)
+ {
+ Vector2 v = this;
+ real_t l = Length();
- res.x += t * (b.x - x);
- res.y += t * (b.y - y);
+ if (l > 0 && length < l)
+ {
+ v /= l;
+ v *= length;
+ }
- return res;
+ return v;
}
+ /// <summary>
+ /// Returns the axis of the vector's largest value. See <see cref="Axis"/>.
+ /// If both components are equal, this method returns <see cref="Axis.X"/>.
+ /// </summary>
+ /// <returns>The index of the largest axis.</returns>
+ public Axis MaxAxis()
+ {
+ return x < y ? Axis.Y : Axis.X;
+ }
+
+ /// <summary>
+ /// Returns the axis of the vector's smallest value. See <see cref="Axis"/>.
+ /// If both components are equal, this method returns <see cref="Axis.Y"/>.
+ /// </summary>
+ /// <returns>The index of the smallest axis.</returns>
+ public Axis MinAxis()
+ {
+ return x < y ? Axis.X : Axis.Y;
+ }
+
+ /// <summary>
+ /// Moves this vector toward `to` by the fixed `delta` amount.
+ /// </summary>
+ /// <param name="to">The vector to move towards.</param>
+ /// <param name="delta">The amount to move towards by.</param>
+ /// <returns>The resulting vector.</returns>
public Vector2 MoveToward(Vector2 to, real_t delta)
{
var v = this;
@@ -204,6 +385,10 @@ namespace Godot
return len <= delta || len < Mathf.Epsilon ? to : v + vd / len * delta;
}
+ /// <summary>
+ /// Returns the vector scaled to unit length. Equivalent to `v / v.Length()`.
+ /// </summary>
+ /// <returns>A normalized version of the vector.</returns>
public Vector2 Normalized()
{
var v = this;
@@ -211,6 +396,11 @@ namespace Godot
return v;
}
+ /// <summary>
+ /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `mod`.
+ /// </summary>
+ /// <param name="mod">A value representing the divisor of the operation.</param>
+ /// <returns>A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by `mod`.</returns>
public Vector2 PosMod(real_t mod)
{
Vector2 v;
@@ -219,6 +409,11 @@ namespace Godot
return v;
}
+ /// <summary>
+ /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `modv`'s components.
+ /// </summary>
+ /// <param name="modv">A vector representing the divisors of the operation.</param>
+ /// <returns>A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by `modv`'s components.</returns>
public Vector2 PosMod(Vector2 modv)
{
Vector2 v;
@@ -227,40 +422,62 @@ namespace Godot
return v;
}
+ /// <summary>
+ /// Returns this vector projected onto another vector `b`.
+ /// </summary>
+ /// <param name="onNormal">The vector to project onto.</param>
+ /// <returns>The projected vector.</returns>
public Vector2 Project(Vector2 onNormal)
{
return onNormal * (Dot(onNormal) / onNormal.LengthSquared());
}
- public Vector2 Reflect(Vector2 n)
+ /// <summary>
+ /// Returns this vector reflected from a plane defined by the given `normal`.
+ /// </summary>
+ /// <param name="normal">The normal vector defining the plane to reflect from. Must be normalized.</param>
+ /// <returns>The reflected vector.</returns>
+ public Vector2 Reflect(Vector2 normal)
{
- return 2.0f * n * Dot(n) - this;
+#if DEBUG
+ if (!normal.IsNormalized())
+ {
+ throw new ArgumentException("Argument is not normalized", nameof(normal));
+ }
+#endif
+ return 2 * Dot(normal) * normal - this;
}
+ /// <summary>
+ /// Rotates this vector by `phi` radians.
+ /// </summary>
+ /// <param name="phi">The angle to rotate by, in radians.</param>
+ /// <returns>The rotated vector.</returns>
public Vector2 Rotated(real_t phi)
{
- real_t rads = Angle() + phi;
- return new Vector2(Mathf.Cos(rads), Mathf.Sin(rads)) * Length();
+ real_t sine = Mathf.Sin(phi);
+ real_t cosi = Mathf.Cos(phi);
+ return new Vector2(
+ x * cosi - y * sine,
+ x * sine + y * cosi);
}
+ /// <summary>
+ /// Returns this vector with all components rounded to the nearest integer,
+ /// with halfway cases rounded towards the nearest multiple of two.
+ /// </summary>
+ /// <returns>The rounded vector.</returns>
public Vector2 Round()
{
return new Vector2(Mathf.Round(x), Mathf.Round(y));
}
- [Obsolete("Set is deprecated. Use the Vector2(" + nameof(real_t) + ", " + nameof(real_t) + ") constructor instead.", error: true)]
- public void Set(real_t x, real_t y)
- {
- this.x = x;
- this.y = y;
- }
- [Obsolete("Set is deprecated. Use the Vector2(" + nameof(Vector2) + ") constructor instead.", error: true)]
- public void Set(Vector2 v)
- {
- x = v.x;
- y = v.y;
- }
-
+ /// <summary>
+ /// Returns a vector with each component set to one or negative one, depending
+ /// on the signs of this vector's components, or zero if the component is zero,
+ /// by calling <see cref="Mathf.Sign(real_t)"/> on each component.
+ /// </summary>
+ /// <returns>A vector with all components as either `1`, `-1`, or `0`.</returns>
public Vector2 Sign()
{
Vector2 v;
@@ -269,23 +486,57 @@ namespace Godot
return v;
}
- public Vector2 Slerp(Vector2 b, real_t t)
+ /// <summary>
+ /// Returns the result of the spherical linear interpolation between
+ /// this vector and `to` by amount `weight`.
+ ///
+ /// Note: Both vectors must be normalized.
+ /// </summary>
+ /// <param name="to">The destination vector for interpolation. Must be normalized.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting vector of the interpolation.</returns>
+ public Vector2 Slerp(Vector2 to, real_t weight)
{
- real_t theta = AngleTo(b);
- return Rotated(theta * t);
+#if DEBUG
+ if (!IsNormalized())
+ {
+ throw new InvalidOperationException("Vector2.Slerp: From vector is not normalized.");
+ }
+ if (!to.IsNormalized())
+ {
+ throw new InvalidOperationException("Vector2.Slerp: `to` is not normalized.");
+ }
+#endif
+ return Rotated(AngleTo(to) * weight);
}
- public Vector2 Slide(Vector2 n)
+ /// <summary>
+ /// Returns this vector slid along a plane defined by the given normal.
+ /// </summary>
+ /// <param name="normal">The normal vector defining the plane to slide on.</param>
+ /// <returns>The slid vector.</returns>
+ public Vector2 Slide(Vector2 normal)
{
- return this - n * Dot(n);
+ return this - normal * Dot(normal);
}
- public Vector2 Snapped(Vector2 by)
+ /// <summary>
+ /// Returns this vector with each component snapped to the nearest multiple of `step`.
+ /// This can also be used to round to an arbitrary number of decimals.
+ /// </summary>
+ /// <param name="step">A vector value representing the step size to snap to.</param>
+ /// <returns>The snapped vector.</returns>
+ public Vector2 Snapped(Vector2 step)
{
- return new Vector2(Mathf.Stepify(x, by.x), Mathf.Stepify(y, by.y));
+ return new Vector2(Mathf.Snapped(x, step.x), Mathf.Snapped(y, step.y));
}
- public Vector2 Tangent()
+ /// <summary>
+ /// Returns a perpendicular vector rotated 90 degrees counter-clockwise
+ /// compared to the original, with the same length.
+ /// </summary>
+ /// <returns>The perpendicular vector.</returns>
+ public Vector2 Orthogonal()
{
return new Vector2(y, -x);
}
@@ -293,7 +544,6 @@ namespace Godot
// Constants
private static readonly Vector2 _zero = new Vector2(0, 0);
private static readonly Vector2 _one = new Vector2(1, 1);
- private static readonly Vector2 _negOne = new Vector2(-1, -1);
private static readonly Vector2 _inf = new Vector2(Mathf.Inf, Mathf.Inf);
private static readonly Vector2 _up = new Vector2(0, -1);
@@ -301,22 +551,58 @@ namespace Godot
private static readonly Vector2 _right = new Vector2(1, 0);
private static readonly Vector2 _left = new Vector2(-1, 0);
+ /// <summary>
+ /// Zero vector, a vector with all components set to `0`.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2(0, 0)`</value>
public static Vector2 Zero { get { return _zero; } }
- public static Vector2 NegOne { get { return _negOne; } }
+ /// <summary>
+ /// One vector, a vector with all components set to `1`.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2(1, 1)`</value>
public static Vector2 One { get { return _one; } }
+ /// <summary>
+ /// Infinity vector, a vector with all components set to `Mathf.Inf`.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2(Mathf.Inf, Mathf.Inf)`</value>
public static Vector2 Inf { get { return _inf; } }
+ /// <summary>
+ /// Up unit vector. Y is down in 2D, so this vector points -Y.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2(0, -1)`</value>
public static Vector2 Up { get { return _up; } }
+ /// <summary>
+ /// Down unit vector. Y is down in 2D, so this vector points +Y.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2(0, 1)`</value>
public static Vector2 Down { get { return _down; } }
+ /// <summary>
+ /// Right unit vector. Represents the direction of right.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2(1, 0)`</value>
public static Vector2 Right { get { return _right; } }
+ /// <summary>
+ /// Left unit vector. Represents the direction of left.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2(-1, 0)`</value>
public static Vector2 Left { get { return _left; } }
- // Constructors
+ /// <summary>
+ /// Constructs a new <see cref="Vector2"/> with the given components.
+ /// </summary>
+ /// <param name="x">The vector's X component.</param>
+ /// <param name="y">The vector's Y component.</param>
public Vector2(real_t x, real_t y)
{
this.x = x;
this.y = y;
}
+
+ /// <summary>
+ /// Constructs a new <see cref="Vector2"/> from an existing <see cref="Vector2"/>.
+ /// </summary>
+ /// <param name="v">The existing <see cref="Vector2"/>.</param>
public Vector2(Vector2 v)
{
x = v.x;
@@ -365,18 +651,18 @@ namespace Godot
return left;
}
- public static Vector2 operator /(Vector2 vec, real_t scale)
+ public static Vector2 operator /(Vector2 vec, real_t divisor)
{
- vec.x /= scale;
- vec.y /= scale;
+ vec.x /= divisor;
+ vec.y /= divisor;
return vec;
}
- public static Vector2 operator /(Vector2 left, Vector2 right)
+ public static Vector2 operator /(Vector2 vec, Vector2 divisorv)
{
- left.x /= right.x;
- left.y /= right.y;
- return left;
+ vec.x /= divisorv.x;
+ vec.y /= divisorv.y;
+ return vec;
}
public static Vector2 operator %(Vector2 vec, real_t divisor)
@@ -405,41 +691,37 @@ namespace Godot
public static bool operator <(Vector2 left, Vector2 right)
{
- if (Mathf.IsEqualApprox(left.x, right.x))
+ if (left.x == right.x)
{
return left.y < right.y;
}
-
return left.x < right.x;
}
public static bool operator >(Vector2 left, Vector2 right)
{
- if (Mathf.IsEqualApprox(left.x, right.x))
+ if (left.x == right.x)
{
return left.y > right.y;
}
-
return left.x > right.x;
}
public static bool operator <=(Vector2 left, Vector2 right)
{
- if (Mathf.IsEqualApprox(left.x, right.x))
+ if (left.x == right.x)
{
return left.y <= right.y;
}
-
return left.x <= right.x;
}
public static bool operator >=(Vector2 left, Vector2 right)
{
- if (Mathf.IsEqualApprox(left.x, right.x))
+ if (left.x == right.x)
{
return left.y >= right.y;
}
-
return left.x >= right.x;
}
@@ -449,7 +731,6 @@ namespace Godot
{
return Equals((Vector2)obj);
}
-
return false;
}
@@ -458,6 +739,12 @@ namespace Godot
return x == other.x && y == other.y;
}
+ /// <summary>
+ /// Returns true if this vector and `other` are approximately equal, by running
+ /// <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component.
+ /// </summary>
+ /// <param name="other">The other vector to compare.</param>
+ /// <returns>Whether or not the vectors are approximately equal.</returns>
public bool IsEqualApprox(Vector2 other)
{
return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs
new file mode 100644
index 0000000000..9068593fd8
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs
@@ -0,0 +1,528 @@
+using System;
+using System.Runtime.InteropServices;
+
+#if REAL_T_IS_DOUBLE
+using real_t = System.Double;
+#else
+using real_t = System.Single;
+#endif
+
+namespace Godot
+{
+ /// <summary>
+ /// 2-element structure that can be used to represent 2D grid coordinates or pairs of integers.
+ /// </summary>
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Vector2i : IEquatable<Vector2i>
+ {
+ /// <summary>
+ /// Enumerated index values for the axes.
+ /// Returned by <see cref="MaxAxis"/> and <see cref="MinAxis"/>.
+ /// </summary>
+ public enum Axis
+ {
+ X = 0,
+ Y
+ }
+
+ /// <summary>
+ /// The vector's X component. Also accessible by using the index position `[0]`.
+ /// </summary>
+ public int x;
+ /// <summary>
+ /// The vector's Y component. Also accessible by using the index position `[1]`.
+ /// </summary>
+ public int y;
+
+ /// <summary>
+ /// Access vector components using their index.
+ /// </summary>
+ /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`.</value>
+ public int this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return x;
+ case 1:
+ return y;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ set
+ {
+ switch (index)
+ {
+ case 0:
+ x = value;
+ return;
+ case 1:
+ y = value;
+ return;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns a new vector with all components in absolute values (i.e. positive).
+ /// </summary>
+ /// <returns>A vector with <see cref="Mathf.Abs(int)"/> called on each component.</returns>
+ public Vector2i Abs()
+ {
+ return new Vector2i(Mathf.Abs(x), Mathf.Abs(y));
+ }
+
+ /// <summary>
+ /// Returns this vector's angle with respect to the X axis, or (1, 0) vector, in radians.
+ ///
+ /// Equivalent to the result of <see cref="Mathf.Atan2(real_t, real_t)"/> when
+ /// called with the vector's `y` and `x` as parameters: `Mathf.Atan2(v.y, v.x)`.
+ /// </summary>
+ /// <returns>The angle of this vector, in radians.</returns>
+ public real_t Angle()
+ {
+ return Mathf.Atan2(y, x);
+ }
+
+ /// <summary>
+ /// Returns the angle to the given vector, in radians.
+ /// </summary>
+ /// <param name="to">The other vector to compare this vector to.</param>
+ /// <returns>The angle between the two vectors, in radians.</returns>
+ public real_t AngleTo(Vector2i to)
+ {
+ return Mathf.Atan2(Cross(to), Dot(to));
+ }
+
+ /// <summary>
+ /// Returns the angle between the line connecting the two points and the X axis, in radians.
+ /// </summary>
+ /// <param name="to">The other vector to compare this vector to.</param>
+ /// <returns>The angle between the two vectors, in radians.</returns>
+ public real_t AngleToPoint(Vector2i to)
+ {
+ return Mathf.Atan2(y - to.y, x - to.x);
+ }
+
+ /// <summary>
+ /// Returns the aspect ratio of this vector, the ratio of `x` to `y`.
+ /// </summary>
+ /// <returns>The `x` component divided by the `y` component.</returns>
+ public real_t Aspect()
+ {
+ return x / (real_t)y;
+ }
+
+ /// <summary>
+ /// Returns a new vector with all components clamped between the
+ /// components of `min` and `max` using
+ /// <see cref="Mathf.Clamp(int, int, int)"/>.
+ /// </summary>
+ /// <param name="min">The vector with minimum allowed values.</param>
+ /// <param name="max">The vector with maximum allowed values.</param>
+ /// <returns>The vector with all components clamped.</returns>
+ public Vector2i Clamp(Vector2i min, Vector2i max)
+ {
+ return new Vector2i
+ (
+ Mathf.Clamp(x, min.x, max.x),
+ Mathf.Clamp(y, min.y, max.y)
+ );
+ }
+
+ /// <summary>
+ /// Returns the cross product of this vector and `b`.
+ /// </summary>
+ /// <param name="b">The other vector.</param>
+ /// <returns>The cross product vector.</returns>
+ public int Cross(Vector2i b)
+ {
+ return x * b.y - y * b.x;
+ }
+
+ /// <summary>
+ /// Returns the squared distance between this vector and `b`.
+ /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if
+ /// you need to compare vectors or need the squared distance for some formula.
+ /// </summary>
+ /// <param name="b">The other vector to use.</param>
+ /// <returns>The squared distance between the two vectors.</returns>
+ public int DistanceSquaredTo(Vector2i b)
+ {
+ return (b - this).LengthSquared();
+ }
+
+ /// <summary>
+ /// Returns the distance between this vector and `b`.
+ /// </summary>
+ /// <param name="b">The other vector to use.</param>
+ /// <returns>The distance between the two vectors.</returns>
+ public real_t DistanceTo(Vector2i b)
+ {
+ return (b - this).Length();
+ }
+
+ /// <summary>
+ /// Returns the dot product of this vector and `b`.
+ /// </summary>
+ /// <param name="b">The other vector to use.</param>
+ /// <returns>The dot product of the two vectors.</returns>
+ public int Dot(Vector2i b)
+ {
+ return x * b.x + y * b.y;
+ }
+
+ /// <summary>
+ /// Returns the length (magnitude) of this vector.
+ /// </summary>
+ /// <returns>The length of this vector.</returns>
+ public real_t Length()
+ {
+ int x2 = x * x;
+ int y2 = y * y;
+
+ return Mathf.Sqrt(x2 + y2);
+ }
+
+ /// <summary>
+ /// Returns the squared length (squared magnitude) of this vector.
+ /// This method runs faster than <see cref="Length"/>, so prefer it if
+ /// you need to compare vectors or need the squared length for some formula.
+ /// </summary>
+ /// <returns>The squared length of this vector.</returns>
+ public int LengthSquared()
+ {
+ int x2 = x * x;
+ int y2 = y * y;
+
+ return x2 + y2;
+ }
+
+ /// <summary>
+ /// Returns the axis of the vector's largest value. See <see cref="Axis"/>.
+ /// If both components are equal, this method returns <see cref="Axis.X"/>.
+ /// </summary>
+ /// <returns>The index of the largest axis.</returns>
+ public Axis MaxAxis()
+ {
+ return x < y ? Axis.Y : Axis.X;
+ }
+
+ /// <summary>
+ /// Returns the axis of the vector's smallest value. See <see cref="Axis"/>.
+ /// If both components are equal, this method returns <see cref="Axis.Y"/>.
+ /// </summary>
+ /// <returns>The index of the smallest axis.</returns>
+ public Axis MinAxis()
+ {
+ return x < y ? Axis.X : Axis.Y;
+ }
+
+ /// <summary>
+ /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `mod`.
+ /// </summary>
+ /// <param name="mod">A value representing the divisor of the operation.</param>
+ /// <returns>A vector with each component <see cref="Mathf.PosMod(int, int)"/> by `mod`.</returns>
+ public Vector2i PosMod(int mod)
+ {
+ Vector2i v = this;
+ v.x = Mathf.PosMod(v.x, mod);
+ v.y = Mathf.PosMod(v.y, mod);
+ return v;
+ }
+
+ /// <summary>
+ /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `modv`'s components.
+ /// </summary>
+ /// <param name="modv">A vector representing the divisors of the operation.</param>
+ /// <returns>A vector with each component <see cref="Mathf.PosMod(int, int)"/> by `modv`'s components.</returns>
+ public Vector2i PosMod(Vector2i modv)
+ {
+ Vector2i v = this;
+ v.x = Mathf.PosMod(v.x, modv.x);
+ v.y = Mathf.PosMod(v.y, modv.y);
+ return v;
+ }
+
+ /// <summary>
+ /// Returns a vector with each component set to one or negative one, depending
+ /// on the signs of this vector's components, or zero if the component is zero,
+ /// by calling <see cref="Mathf.Sign(int)"/> on each component.
+ /// </summary>
+ /// <returns>A vector with all components as either `1`, `-1`, or `0`.</returns>
+ public Vector2i Sign()
+ {
+ Vector2i v = this;
+ v.x = Mathf.Sign(v.x);
+ v.y = Mathf.Sign(v.y);
+ return v;
+ }
+
+ /// <summary>
+ /// Returns a perpendicular vector rotated 90 degrees counter-clockwise
+ /// compared to the original, with the same length.
+ /// </summary>
+ /// <returns>The perpendicular vector.</returns>
+ public Vector2i Orthogonal()
+ {
+ return new Vector2i(y, -x);
+ }
+
+ // Constants
+ private static readonly Vector2i _zero = new Vector2i(0, 0);
+ private static readonly Vector2i _one = new Vector2i(1, 1);
+
+ private static readonly Vector2i _up = new Vector2i(0, -1);
+ private static readonly Vector2i _down = new Vector2i(0, 1);
+ private static readonly Vector2i _right = new Vector2i(1, 0);
+ private static readonly Vector2i _left = new Vector2i(-1, 0);
+
+ /// <summary>
+ /// Zero vector, a vector with all components set to `0`.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2i(0, 0)`</value>
+ public static Vector2i Zero { get { return _zero; } }
+ /// <summary>
+ /// One vector, a vector with all components set to `1`.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2i(1, 1)`</value>
+ public static Vector2i One { get { return _one; } }
+
+ /// <summary>
+ /// Up unit vector. Y is down in 2D, so this vector points -Y.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2i(0, -1)`</value>
+ public static Vector2i Up { get { return _up; } }
+ /// <summary>
+ /// Down unit vector. Y is down in 2D, so this vector points +Y.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2i(0, 1)`</value>
+ public static Vector2i Down { get { return _down; } }
+ /// <summary>
+ /// Right unit vector. Represents the direction of right.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2i(1, 0)`</value>
+ public static Vector2i Right { get { return _right; } }
+ /// <summary>
+ /// Left unit vector. Represents the direction of left.
+ /// </summary>
+ /// <value>Equivalent to `new Vector2i(-1, 0)`</value>
+ public static Vector2i Left { get { return _left; } }
+
+ /// <summary>
+ /// Constructs a new <see cref="Vector2i"/> with the given components.
+ /// </summary>
+ /// <param name="x">The vector's X component.</param>
+ /// <param name="y">The vector's Y component.</param>
+ public Vector2i(int x, int y)
+ {
+ this.x = x;
+ this.y = y;
+ }
+
+ /// <summary>
+ /// Constructs a new <see cref="Vector2i"/> from an existing <see cref="Vector2i"/>.
+ /// </summary>
+ /// <param name="vi">The existing <see cref="Vector2i"/>.</param>
+ public Vector2i(Vector2i vi)
+ {
+ this.x = vi.x;
+ this.y = vi.y;
+ }
+
+ /// <summary>
+ /// Constructs a new <see cref="Vector2i"/> from an existing <see cref="Vector2"/>
+ /// by rounding the components via <see cref="Mathf.RoundToInt(real_t)"/>.
+ /// </summary>
+ /// <param name="v">The <see cref="Vector2"/> to convert.</param>
+ public Vector2i(Vector2 v)
+ {
+ this.x = Mathf.RoundToInt(v.x);
+ this.y = Mathf.RoundToInt(v.y);
+ }
+
+ public static Vector2i operator +(Vector2i left, Vector2i right)
+ {
+ left.x += right.x;
+ left.y += right.y;
+ return left;
+ }
+
+ public static Vector2i operator -(Vector2i left, Vector2i right)
+ {
+ left.x -= right.x;
+ left.y -= right.y;
+ return left;
+ }
+
+ public static Vector2i operator -(Vector2i vec)
+ {
+ vec.x = -vec.x;
+ vec.y = -vec.y;
+ return vec;
+ }
+
+ public static Vector2i operator *(Vector2i vec, int scale)
+ {
+ vec.x *= scale;
+ vec.y *= scale;
+ return vec;
+ }
+
+ public static Vector2i operator *(int scale, Vector2i vec)
+ {
+ vec.x *= scale;
+ vec.y *= scale;
+ return vec;
+ }
+
+ public static Vector2i operator *(Vector2i left, Vector2i right)
+ {
+ left.x *= right.x;
+ left.y *= right.y;
+ return left;
+ }
+
+ public static Vector2i operator /(Vector2i vec, int divisor)
+ {
+ vec.x /= divisor;
+ vec.y /= divisor;
+ return vec;
+ }
+
+ public static Vector2i operator /(Vector2i vec, Vector2i divisorv)
+ {
+ vec.x /= divisorv.x;
+ vec.y /= divisorv.y;
+ return vec;
+ }
+
+ public static Vector2i operator %(Vector2i vec, int divisor)
+ {
+ vec.x %= divisor;
+ vec.y %= divisor;
+ return vec;
+ }
+
+ public static Vector2i operator %(Vector2i vec, Vector2i divisorv)
+ {
+ vec.x %= divisorv.x;
+ vec.y %= divisorv.y;
+ return vec;
+ }
+
+ public static Vector2i operator &(Vector2i vec, int and)
+ {
+ vec.x &= and;
+ vec.y &= and;
+ return vec;
+ }
+
+ public static Vector2i operator &(Vector2i vec, Vector2i andv)
+ {
+ vec.x &= andv.x;
+ vec.y &= andv.y;
+ return vec;
+ }
+
+ public static bool operator ==(Vector2i left, Vector2i right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Vector2i left, Vector2i right)
+ {
+ return !left.Equals(right);
+ }
+
+ public static bool operator <(Vector2i left, Vector2i right)
+ {
+ if (left.x.Equals(right.x))
+ {
+ return left.y < right.y;
+ }
+ return left.x < right.x;
+ }
+
+ public static bool operator >(Vector2i left, Vector2i right)
+ {
+ if (left.x.Equals(right.x))
+ {
+ return left.y > right.y;
+ }
+ return left.x > right.x;
+ }
+
+ public static bool operator <=(Vector2i left, Vector2i right)
+ {
+ if (left.x.Equals(right.x))
+ {
+ return left.y <= right.y;
+ }
+ return left.x <= right.x;
+ }
+
+ public static bool operator >=(Vector2i left, Vector2i right)
+ {
+ if (left.x.Equals(right.x))
+ {
+ return left.y >= right.y;
+ }
+ return left.x >= right.x;
+ }
+
+ public static implicit operator Vector2(Vector2i value)
+ {
+ return new Vector2(value.x, value.y);
+ }
+
+ public static explicit operator Vector2i(Vector2 value)
+ {
+ return new Vector2i(value);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Vector2i)
+ {
+ return Equals((Vector2i)obj);
+ }
+
+ return false;
+ }
+
+ public bool Equals(Vector2i other)
+ {
+ return x == other.x && y == other.y;
+ }
+
+ public override int GetHashCode()
+ {
+ return y.GetHashCode() ^ x.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return String.Format("({0}, {1})", new object[]
+ {
+ this.x.ToString(),
+ this.y.ToString()
+ });
+ }
+
+ public string ToString(string format)
+ {
+ return String.Format("({0}, {1})", new object[]
+ {
+ this.x.ToString(format),
+ this.y.ToString(format)
+ });
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
index fded34002d..31a9af2d9e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs
@@ -21,6 +21,10 @@ namespace Godot
[StructLayout(LayoutKind.Sequential)]
public struct Vector3 : IEquatable<Vector3>
{
+ /// <summary>
+ /// Enumerated index values for the axes.
+ /// Returned by <see cref="MaxAxis"/> and <see cref="MinAxis"/>.
+ /// </summary>
public enum Axis
{
X = 0,
@@ -28,10 +32,23 @@ namespace Godot
Z
}
+ /// <summary>
+ /// The vector's X component. Also accessible by using the index position `[0]`.
+ /// </summary>
public real_t x;
+ /// <summary>
+ /// The vector's Y component. Also accessible by using the index position `[1]`.
+ /// </summary>
public real_t y;
+ /// <summary>
+ /// The vector's Z component. Also accessible by using the index position `[2]`.
+ /// </summary>
public real_t z;
+ /// <summary>
+ /// Access vector components using their index.
+ /// </summary>
+ /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`, `[2]` is equivalent to `.z`.</value>
public real_t this[int index]
{
get
@@ -84,26 +101,67 @@ namespace Godot
}
}
+ /// <summary>
+ /// Returns a new vector with all components in absolute values (i.e. positive).
+ /// </summary>
+ /// <returns>A vector with <see cref="Mathf.Abs(real_t)"/> called on each component.</returns>
public Vector3 Abs()
{
return new Vector3(Mathf.Abs(x), Mathf.Abs(y), Mathf.Abs(z));
}
+ /// <summary>
+ /// Returns the unsigned minimum angle to the given vector, in radians.
+ /// </summary>
+ /// <param name="to">The other vector to compare this vector to.</param>
+ /// <returns>The unsigned angle between the two vectors, in radians.</returns>
public real_t AngleTo(Vector3 to)
{
return Mathf.Atan2(Cross(to).Length(), Dot(to));
}
- public Vector3 Bounce(Vector3 n)
+ /// <summary>
+ /// Returns this vector "bounced off" from a plane defined by the given normal.
+ /// </summary>
+ /// <param name="normal">The normal vector defining the plane to bounce off. Must be normalized.</param>
+ /// <returns>The bounced vector.</returns>
+ public Vector3 Bounce(Vector3 normal)
{
- return -Reflect(n);
+ return -Reflect(normal);
}
+ /// <summary>
+ /// Returns a new vector with all components rounded up (towards positive infinity).
+ /// </summary>
+ /// <returns>A vector with <see cref="Mathf.Ceil"/> called on each component.</returns>
public Vector3 Ceil()
{
return new Vector3(Mathf.Ceil(x), Mathf.Ceil(y), Mathf.Ceil(z));
}
+ /// <summary>
+ /// Returns a new vector with all components clamped between the
+ /// components of `min` and `max` using
+ /// <see cref="Mathf.Clamp(real_t, real_t, real_t)"/>.
+ /// </summary>
+ /// <param name="min">The vector with minimum allowed values.</param>
+ /// <param name="max">The vector with maximum allowed values.</param>
+ /// <returns>The vector with all components clamped.</returns>
+ public Vector3 Clamp(Vector3 min, Vector3 max)
+ {
+ return new Vector3
+ (
+ Mathf.Clamp(x, min.x, max.x),
+ Mathf.Clamp(y, min.y, max.y),
+ Mathf.Clamp(z, min.z, max.z)
+ );
+ }
+
+ /// <summary>
+ /// Returns the cross product of this vector and `b`.
+ /// </summary>
+ /// <param name="b">The other vector.</param>
+ /// <returns>The cross product vector.</returns>
public Vector3 Cross(Vector3 b)
{
return new Vector3
@@ -114,13 +172,23 @@ namespace Godot
);
}
- public Vector3 CubicInterpolate(Vector3 b, Vector3 preA, Vector3 postB, real_t t)
+ /// <summary>
+ /// Performs a cubic interpolation between vectors `preA`, this vector,
+ /// `b`, and `postB`, by the given amount `t`.
+ /// </summary>
+ /// <param name="b">The destination vector.</param>
+ /// <param name="preA">A vector before this vector.</param>
+ /// <param name="postB">A vector after `b`.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The interpolated vector.</returns>
+ public Vector3 CubicInterpolate(Vector3 b, Vector3 preA, Vector3 postB, real_t weight)
{
- var p0 = preA;
- var p1 = this;
- var p2 = b;
- var p3 = postB;
+ Vector3 p0 = preA;
+ Vector3 p1 = this;
+ Vector3 p2 = b;
+ Vector3 p3 = postB;
+ real_t t = weight;
real_t t2 = t * t;
real_t t3 = t2 * t;
@@ -131,41 +199,79 @@ namespace Godot
);
}
+ /// <summary>
+ /// Returns the normalized vector pointing from this vector to `b`.
+ /// </summary>
+ /// <param name="b">The other vector to point towards.</param>
+ /// <returns>The direction from this vector to `b`.</returns>
public Vector3 DirectionTo(Vector3 b)
{
return new Vector3(b.x - x, b.y - y, b.z - z).Normalized();
}
+ /// <summary>
+ /// Returns the squared distance between this vector and `b`.
+ /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if
+ /// you need to compare vectors or need the squared distance for some formula.
+ /// </summary>
+ /// <param name="b">The other vector to use.</param>
+ /// <returns>The squared distance between the two vectors.</returns>
public real_t DistanceSquaredTo(Vector3 b)
{
return (b - this).LengthSquared();
}
+ /// <summary>
+ /// Returns the distance between this vector and `b`.
+ /// </summary>
+ /// <param name="b">The other vector to use.</param>
+ /// <returns>The distance between the two vectors.</returns>
public real_t DistanceTo(Vector3 b)
{
return (b - this).Length();
}
+ /// <summary>
+ /// Returns the dot product of this vector and `b`.
+ /// </summary>
+ /// <param name="b">The other vector to use.</param>
+ /// <returns>The dot product of the two vectors.</returns>
public real_t Dot(Vector3 b)
{
return x * b.x + y * b.y + z * b.z;
}
+ /// <summary>
+ /// Returns a new vector with all components rounded down (towards negative infinity).
+ /// </summary>
+ /// <returns>A vector with <see cref="Mathf.Floor"/> called on each component.</returns>
public Vector3 Floor()
{
return new Vector3(Mathf.Floor(x), Mathf.Floor(y), Mathf.Floor(z));
}
+ /// <summary>
+ /// Returns the inverse of this vector. This is the same as `new Vector3(1 / v.x, 1 / v.y, 1 / v.z)`.
+ /// </summary>
+ /// <returns>The inverse of this vector.</returns>
public Vector3 Inverse()
{
- return new Vector3(1.0f / x, 1.0f / y, 1.0f / z);
+ return new Vector3(1 / x, 1 / y, 1 / z);
}
+ /// <summary>
+ /// Returns true if the vector is normalized, and false otherwise.
+ /// </summary>
+ /// <returns>A bool indicating whether or not the vector is normalized.</returns>
public bool IsNormalized()
{
return Mathf.Abs(LengthSquared() - 1.0f) < Mathf.Epsilon;
}
+ /// <summary>
+ /// Returns the length (magnitude) of this vector.
+ /// </summary>
+ /// <returns>The length of this vector.</returns>
public real_t Length()
{
real_t x2 = x * x;
@@ -175,6 +281,12 @@ namespace Godot
return Mathf.Sqrt(x2 + y2 + z2);
}
+ /// <summary>
+ /// Returns the squared length (squared magnitude) of this vector.
+ /// This method runs faster than <see cref="Length"/>, so prefer it if
+ /// you need to compare vectors or need the squared length for some formula.
+ /// </summary>
+ /// <returns>The squared length of this vector.</returns>
public real_t LengthSquared()
{
real_t x2 = x * x;
@@ -184,34 +296,97 @@ namespace Godot
return x2 + y2 + z2;
}
- public Vector3 LinearInterpolate(Vector3 b, real_t t)
+ /// <summary>
+ /// Returns the result of the linear interpolation between
+ /// this vector and `to` by amount `weight`.
+ /// </summary>
+ /// <param name="to">The destination vector for interpolation.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting vector of the interpolation.</returns>
+ public Vector3 Lerp(Vector3 to, real_t weight)
{
return new Vector3
(
- x + t * (b.x - x),
- y + t * (b.y - y),
- z + t * (b.z - z)
+ Mathf.Lerp(x, to.x, weight),
+ Mathf.Lerp(y, to.y, weight),
+ Mathf.Lerp(z, to.z, weight)
);
}
- public Vector3 MoveToward(Vector3 to, real_t delta)
+ /// <summary>
+ /// Returns the result of the linear interpolation between
+ /// this vector and `to` by the vector amount `weight`.
+ /// </summary>
+ /// <param name="to">The destination vector for interpolation.</param>
+ /// <param name="weight">A vector with components on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting vector of the interpolation.</returns>
+ public Vector3 Lerp(Vector3 to, Vector3 weight)
{
- var v = this;
- var vd = to - v;
- var len = vd.Length();
- return len <= delta || len < Mathf.Epsilon ? to : v + vd / len * delta;
+ return new Vector3
+ (
+ Mathf.Lerp(x, to.x, weight.x),
+ Mathf.Lerp(y, to.y, weight.y),
+ Mathf.Lerp(z, to.z, weight.z)
+ );
}
+ /// <summary>
+ /// Returns the vector with a maximum length by limiting its length to `length`.
+ /// </summary>
+ /// <param name="length">The length to limit to.</param>
+ /// <returns>The vector with its length limited.</returns>
+ public Vector3 LimitLength(real_t length = 1.0f)
+ {
+ Vector3 v = this;
+ real_t l = Length();
+
+ if (l > 0 && length < l)
+ {
+ v /= l;
+ v *= length;
+ }
+
+ return v;
+ }
+
+ /// <summary>
+ /// Returns the axis of the vector's largest value. See <see cref="Axis"/>.
+ /// If all components are equal, this method returns <see cref="Axis.X"/>.
+ /// </summary>
+ /// <returns>The index of the largest axis.</returns>
public Axis MaxAxis()
{
return x < y ? (y < z ? Axis.Z : Axis.Y) : (x < z ? Axis.Z : Axis.X);
}
+ /// <summary>
+ /// Returns the axis of the vector's smallest value. See <see cref="Axis"/>.
+ /// If all components are equal, this method returns <see cref="Axis.Z"/>.
+ /// </summary>
+ /// <returns>The index of the smallest axis.</returns>
public Axis MinAxis()
{
return x < y ? (x < z ? Axis.X : Axis.Z) : (y < z ? Axis.Y : Axis.Z);
}
+ /// <summary>
+ /// Moves this vector toward `to` by the fixed `delta` amount.
+ /// </summary>
+ /// <param name="to">The vector to move towards.</param>
+ /// <param name="delta">The amount to move towards by.</param>
+ /// <returns>The resulting vector.</returns>
+ public Vector3 MoveToward(Vector3 to, real_t delta)
+ {
+ var v = this;
+ var vd = to - v;
+ var len = vd.Length();
+ return len <= delta || len < Mathf.Epsilon ? to : v + vd / len * delta;
+ }
+
+ /// <summary>
+ /// Returns the vector scaled to unit length. Equivalent to `v / v.Length()`.
+ /// </summary>
+ /// <returns>A normalized version of the vector.</returns>
public Vector3 Normalized()
{
var v = this;
@@ -219,6 +394,11 @@ namespace Godot
return v;
}
+ /// <summary>
+ /// Returns the outer product with `b`.
+ /// </summary>
+ /// <param name="b">The other vector.</param>
+ /// <returns>A <see cref="Basis"/> representing the outer product matrix.</returns>
public Basis Outer(Vector3 b)
{
return new Basis(
@@ -228,6 +408,11 @@ namespace Godot
);
}
+ /// <summary>
+ /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `mod`.
+ /// </summary>
+ /// <param name="mod">A value representing the divisor of the operation.</param>
+ /// <returns>A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by `mod`.</returns>
public Vector3 PosMod(real_t mod)
{
Vector3 v;
@@ -237,6 +422,11 @@ namespace Godot
return v;
}
+ /// <summary>
+ /// Returns a vector composed of the <see cref="Mathf.PosMod(real_t, real_t)"/> of this vector's components and `modv`'s components.
+ /// </summary>
+ /// <param name="modv">A vector representing the divisors of the operation.</param>
+ /// <returns>A vector with each component <see cref="Mathf.PosMod(real_t, real_t)"/> by `modv`'s components.</returns>
public Vector3 PosMod(Vector3 modv)
{
Vector3 v;
@@ -246,45 +436,66 @@ namespace Godot
return v;
}
+ /// <summary>
+ /// Returns this vector projected onto another vector `b`.
+ /// </summary>
+ /// <param name="onNormal">The vector to project onto.</param>
+ /// <returns>The projected vector.</returns>
public Vector3 Project(Vector3 onNormal)
{
return onNormal * (Dot(onNormal) / onNormal.LengthSquared());
}
- public Vector3 Reflect(Vector3 n)
+ /// <summary>
+ /// Returns this vector reflected from a plane defined by the given `normal`.
+ /// </summary>
+ /// <param name="normal">The normal vector defining the plane to reflect from. Must be normalized.</param>
+ /// <returns>The reflected vector.</returns>
+ public Vector3 Reflect(Vector3 normal)
{
#if DEBUG
- if (!n.IsNormalized())
- throw new ArgumentException("Argument is not normalized", nameof(n));
+ if (!normal.IsNormalized())
+ {
+ throw new ArgumentException("Argument is not normalized", nameof(normal));
+ }
#endif
- return 2.0f * n * Dot(n) - this;
- }
-
- public Vector3 Round()
- {
- return new Vector3(Mathf.Round(x), Mathf.Round(y), Mathf.Round(z));
+ return 2.0f * Dot(normal) * normal - this;
}
+ /// <summary>
+ /// Rotates this vector around a given `axis` vector by `phi` radians.
+ /// The `axis` vector must be a normalized vector.
+ /// </summary>
+ /// <param name="axis">The vector to rotate around. Must be normalized.</param>
+ /// <param name="phi">The angle to rotate by, in radians.</param>
+ /// <returns>The rotated vector.</returns>
public Vector3 Rotated(Vector3 axis, real_t phi)
{
+#if DEBUG
+ if (!axis.IsNormalized())
+ {
+ throw new ArgumentException("Argument is not normalized", nameof(axis));
+ }
+#endif
return new Basis(axis, phi).Xform(this);
}
- [Obsolete("Set is deprecated. Use the Vector3(" + nameof(real_t) + ", " + nameof(real_t) + ", " + nameof(real_t) + ") constructor instead.", error: true)]
- public void Set(real_t x, real_t y, real_t z)
- {
- this.x = x;
- this.y = y;
- this.z = z;
- }
- [Obsolete("Set is deprecated. Use the Vector3(" + nameof(Vector3) + ") constructor instead.", error: true)]
- public void Set(Vector3 v)
+ /// <summary>
+ /// Returns this vector with all components rounded to the nearest integer,
+ /// with halfway cases rounded towards the nearest multiple of two.
+ /// </summary>
+ /// <returns>The rounded vector.</returns>
+ public Vector3 Round()
{
- x = v.x;
- y = v.y;
- z = v.z;
+ return new Vector3(Mathf.Round(x), Mathf.Round(y), Mathf.Round(z));
}
+ /// <summary>
+ /// Returns a vector with each component set to one or negative one, depending
+ /// on the signs of this vector's components, or zero if the component is zero,
+ /// by calling <see cref="Mathf.Sign(real_t)"/> on each component.
+ /// </summary>
+ /// <returns>A vector with all components as either `1`, `-1`, or `0`.</returns>
public Vector3 Sign()
{
Vector3 v;
@@ -294,44 +505,93 @@ namespace Godot
return v;
}
- public Vector3 Slerp(Vector3 b, real_t t)
+ /// <summary>
+ /// Returns the signed angle to the given vector, in radians.
+ /// The sign of the angle is positive in a counter-clockwise
+ /// direction and negative in a clockwise direction when viewed
+ /// from the side specified by the `axis`.
+ /// </summary>
+ /// <param name="to">The other vector to compare this vector to.</param>
+ /// <param name="axis">The reference axis to use for the angle sign.</param>
+ /// <returns>The signed angle between the two vectors, in radians.</returns>
+ public real_t SignedAngleTo(Vector3 to, Vector3 axis)
+ {
+ Vector3 crossTo = Cross(to);
+ real_t unsignedAngle = Mathf.Atan2(crossTo.Length(), Dot(to));
+ real_t sign = crossTo.Dot(axis);
+ return (sign < 0) ? -unsignedAngle : unsignedAngle;
+ }
+
+ /// <summary>
+ /// Returns the result of the spherical linear interpolation between
+ /// this vector and `to` by amount `weight`.
+ ///
+ /// Note: Both vectors must be normalized.
+ /// </summary>
+ /// <param name="to">The destination vector for interpolation. Must be normalized.</param>
+ /// <param name="weight">A value on the range of 0.0 to 1.0, representing the amount of interpolation.</param>
+ /// <returns>The resulting vector of the interpolation.</returns>
+ public Vector3 Slerp(Vector3 to, real_t weight)
{
#if DEBUG
if (!IsNormalized())
- throw new InvalidOperationException("Vector3 is not normalized");
+ {
+ throw new InvalidOperationException("Vector3.Slerp: From vector is not normalized.");
+ }
+ if (!to.IsNormalized())
+ {
+ throw new InvalidOperationException("Vector3.Slerp: `to` is not normalized.");
+ }
#endif
- real_t theta = AngleTo(b);
- return Rotated(Cross(b), theta * t);
+ real_t theta = AngleTo(to);
+ return Rotated(Cross(to), theta * weight);
}
- public Vector3 Slide(Vector3 n)
+ /// <summary>
+ /// Returns this vector slid along a plane defined by the given normal.
+ /// </summary>
+ /// <param name="normal">The normal vector defining the plane to slide on.</param>
+ /// <returns>The slid vector.</returns>
+ public Vector3 Slide(Vector3 normal)
{
- return this - n * Dot(n);
+ return this - normal * Dot(normal);
}
- public Vector3 Snapped(Vector3 by)
+ /// <summary>
+ /// Returns this vector with each component snapped to the nearest multiple of `step`.
+ /// This can also be used to round to an arbitrary number of decimals.
+ /// </summary>
+ /// <param name="step">A vector value representing the step size to snap to.</param>
+ /// <returns>The snapped vector.</returns>
+ public Vector3 Snapped(Vector3 step)
{
return new Vector3
(
- Mathf.Stepify(x, by.x),
- Mathf.Stepify(y, by.y),
- Mathf.Stepify(z, by.z)
+ Mathf.Snapped(x, step.x),
+ Mathf.Snapped(y, step.y),
+ Mathf.Snapped(z, step.z)
);
}
+ /// <summary>
+ /// Returns a diagonal matrix with the vector as main diagonal.
+ ///
+ /// This is equivalent to a Basis with no rotation or shearing and
+ /// this vector's components set as the scale.
+ /// </summary>
+ /// <returns>A Basis with the vector as its main diagonal.</returns>
public Basis ToDiagonalMatrix()
{
return new Basis(
- x, 0f, 0f,
- 0f, y, 0f,
- 0f, 0f, z
+ x, 0, 0,
+ 0, y, 0,
+ 0, 0, z
);
}
// Constants
private static readonly Vector3 _zero = new Vector3(0, 0, 0);
private static readonly Vector3 _one = new Vector3(1, 1, 1);
- private static readonly Vector3 _negOne = new Vector3(-1, -1, -1);
private static readonly Vector3 _inf = new Vector3(Mathf.Inf, Mathf.Inf, Mathf.Inf);
private static readonly Vector3 _up = new Vector3(0, 1, 0);
@@ -341,25 +601,74 @@ namespace Godot
private static readonly Vector3 _forward = new Vector3(0, 0, -1);
private static readonly Vector3 _back = new Vector3(0, 0, 1);
+ /// <summary>
+ /// Zero vector, a vector with all components set to `0`.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3(0, 0, 0)`</value>
public static Vector3 Zero { get { return _zero; } }
+ /// <summary>
+ /// One vector, a vector with all components set to `1`.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3(1, 1, 1)`</value>
public static Vector3 One { get { return _one; } }
- public static Vector3 NegOne { get { return _negOne; } }
+ /// <summary>
+ /// Infinity vector, a vector with all components set to `Mathf.Inf`.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3(Mathf.Inf, Mathf.Inf, Mathf.Inf)`</value>
public static Vector3 Inf { get { return _inf; } }
+ /// <summary>
+ /// Up unit vector.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3(0, 1, 0)`</value>
public static Vector3 Up { get { return _up; } }
+ /// <summary>
+ /// Down unit vector.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3(0, -1, 0)`</value>
public static Vector3 Down { get { return _down; } }
+ /// <summary>
+ /// Right unit vector. Represents the local direction of right,
+ /// and the global direction of east.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3(1, 0, 0)`</value>
public static Vector3 Right { get { return _right; } }
+ /// <summary>
+ /// Left unit vector. Represents the local direction of left,
+ /// and the global direction of west.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3(-1, 0, 0)`</value>
public static Vector3 Left { get { return _left; } }
+ /// <summary>
+ /// Forward unit vector. Represents the local direction of forward,
+ /// and the global direction of north.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3(0, 0, -1)`</value>
public static Vector3 Forward { get { return _forward; } }
+ /// <summary>
+ /// Back unit vector. Represents the local direction of back,
+ /// and the global direction of south.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3(0, 0, 1)`</value>
public static Vector3 Back { get { return _back; } }
- // Constructors
+ /// <summary>
+ /// Constructs a new <see cref="Vector3"/> with the given components.
+ /// </summary>
+ /// <param name="x">The vector's X component.</param>
+ /// <param name="y">The vector's Y component.</param>
+ /// <param name="z">The vector's Z component.</param>
public Vector3(real_t x, real_t y, real_t z)
{
this.x = x;
this.y = y;
this.z = z;
}
+
+ /// <summary>
+ /// Constructs a new <see cref="Vector3"/> from an existing <see cref="Vector3"/>.
+ /// </summary>
+ /// <param name="v">The existing <see cref="Vector3"/>.</param>
public Vector3(Vector3 v)
{
x = v.x;
@@ -415,20 +724,20 @@ namespace Godot
return left;
}
- public static Vector3 operator /(Vector3 vec, real_t scale)
+ public static Vector3 operator /(Vector3 vec, real_t divisor)
{
- vec.x /= scale;
- vec.y /= scale;
- vec.z /= scale;
+ vec.x /= divisor;
+ vec.y /= divisor;
+ vec.z /= divisor;
return vec;
}
- public static Vector3 operator /(Vector3 left, Vector3 right)
+ public static Vector3 operator /(Vector3 vec, Vector3 divisorv)
{
- left.x /= right.x;
- left.y /= right.y;
- left.z /= right.z;
- return left;
+ vec.x /= divisorv.x;
+ vec.y /= divisorv.y;
+ vec.z /= divisorv.z;
+ return vec;
}
public static Vector3 operator %(Vector3 vec, real_t divisor)
@@ -459,49 +768,53 @@ namespace Godot
public static bool operator <(Vector3 left, Vector3 right)
{
- if (Mathf.IsEqualApprox(left.x, right.x))
+ if (left.x == right.x)
{
- if (Mathf.IsEqualApprox(left.y, right.y))
+ if (left.y == right.y)
+ {
return left.z < right.z;
+ }
return left.y < right.y;
}
-
return left.x < right.x;
}
public static bool operator >(Vector3 left, Vector3 right)
{
- if (Mathf.IsEqualApprox(left.x, right.x))
+ if (left.x == right.x)
{
- if (Mathf.IsEqualApprox(left.y, right.y))
+ if (left.y == right.y)
+ {
return left.z > right.z;
+ }
return left.y > right.y;
}
-
return left.x > right.x;
}
public static bool operator <=(Vector3 left, Vector3 right)
{
- if (Mathf.IsEqualApprox(left.x, right.x))
+ if (left.x == right.x)
{
- if (Mathf.IsEqualApprox(left.y, right.y))
+ if (left.y == right.y)
+ {
return left.z <= right.z;
+ }
return left.y < right.y;
}
-
return left.x < right.x;
}
public static bool operator >=(Vector3 left, Vector3 right)
{
- if (Mathf.IsEqualApprox(left.x, right.x))
+ if (left.x == right.x)
{
- if (Mathf.IsEqualApprox(left.y, right.y))
+ if (left.y == right.y)
+ {
return left.z >= right.z;
+ }
return left.y > right.y;
}
-
return left.x > right.x;
}
@@ -520,6 +833,12 @@ namespace Godot
return x == other.x && y == other.y && z == other.z;
}
+ /// <summary>
+ /// Returns true if this vector and `other` are approximately equal, by running
+ /// <see cref="Mathf.IsEqualApprox(real_t, real_t)"/> on each component.
+ /// </summary>
+ /// <param name="other">The other vector to compare.</param>
+ /// <returns>Whether or not the vectors are approximately equal.</returns>
public bool IsEqualApprox(Vector3 other)
{
return Mathf.IsEqualApprox(x, other.x) && Mathf.IsEqualApprox(y, other.y) && Mathf.IsEqualApprox(z, other.z);
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs
new file mode 100644
index 0000000000..e727afa3ff
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs
@@ -0,0 +1,533 @@
+using System;
+using System.Runtime.InteropServices;
+
+#if REAL_T_IS_DOUBLE
+using real_t = System.Double;
+#else
+using real_t = System.Single;
+#endif
+
+namespace Godot
+{
+ /// <summary>
+ /// 3-element structure that can be used to represent 3D grid coordinates or sets of integers.
+ /// </summary>
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Vector3i : IEquatable<Vector3i>
+ {
+ /// <summary>
+ /// Enumerated index values for the axes.
+ /// Returned by <see cref="MaxAxis"/> and <see cref="MinAxis"/>.
+ /// </summary>
+ public enum Axis
+ {
+ X = 0,
+ Y,
+ Z
+ }
+
+ /// <summary>
+ /// The vector's X component. Also accessible by using the index position `[0]`.
+ /// </summary>
+ public int x;
+ /// <summary>
+ /// The vector's Y component. Also accessible by using the index position `[1]`.
+ /// </summary>
+ public int y;
+ /// <summary>
+ /// The vector's Z component. Also accessible by using the index position `[2]`.
+ /// </summary>
+ public int z;
+
+ /// <summary>
+ /// Access vector components using their index.
+ /// </summary>
+ /// <value>`[0]` is equivalent to `.x`, `[1]` is equivalent to `.y`, `[2]` is equivalent to `.z`.</value>
+ public int this[int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return x;
+ case 1:
+ return y;
+ case 2:
+ return z;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ set
+ {
+ switch (index)
+ {
+ case 0:
+ x = value;
+ return;
+ case 1:
+ y = value;
+ return;
+ case 2:
+ z = value;
+ return;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns a new vector with all components in absolute values (i.e. positive).
+ /// </summary>
+ /// <returns>A vector with <see cref="Mathf.Abs(int)"/> called on each component.</returns>
+ public Vector3i Abs()
+ {
+ return new Vector3i(Mathf.Abs(x), Mathf.Abs(y), Mathf.Abs(z));
+ }
+
+ /// <summary>
+ /// Returns a new vector with all components clamped between the
+ /// components of `min` and `max` using
+ /// <see cref="Mathf.Clamp(int, int, int)"/>.
+ /// </summary>
+ /// <param name="min">The vector with minimum allowed values.</param>
+ /// <param name="max">The vector with maximum allowed values.</param>
+ /// <returns>The vector with all components clamped.</returns>
+ public Vector3i Clamp(Vector3i min, Vector3i max)
+ {
+ return new Vector3i
+ (
+ Mathf.Clamp(x, min.x, max.x),
+ Mathf.Clamp(y, min.y, max.y),
+ Mathf.Clamp(z, min.z, max.z)
+ );
+ }
+
+ /// <summary>
+ /// Returns the squared distance between this vector and `b`.
+ /// This method runs faster than <see cref="DistanceTo"/>, so prefer it if
+ /// you need to compare vectors or need the squared distance for some formula.
+ /// </summary>
+ /// <param name="b">The other vector to use.</param>
+ /// <returns>The squared distance between the two vectors.</returns>
+ public int DistanceSquaredTo(Vector3i b)
+ {
+ return (b - this).LengthSquared();
+ }
+
+ /// <summary>
+ /// Returns the distance between this vector and `b`.
+ /// </summary>
+ /// <param name="b">The other vector to use.</param>
+ /// <returns>The distance between the two vectors.</returns>
+ public real_t DistanceTo(Vector3i b)
+ {
+ return (b - this).Length();
+ }
+
+ /// <summary>
+ /// Returns the dot product of this vector and `b`.
+ /// </summary>
+ /// <param name="b">The other vector to use.</param>
+ /// <returns>The dot product of the two vectors.</returns>
+ public int Dot(Vector3i b)
+ {
+ return x * b.x + y * b.y + z * b.z;
+ }
+
+ /// <summary>
+ /// Returns the length (magnitude) of this vector.
+ /// </summary>
+ /// <returns>The length of this vector.</returns>
+ public real_t Length()
+ {
+ int x2 = x * x;
+ int y2 = y * y;
+ int z2 = z * z;
+
+ return Mathf.Sqrt(x2 + y2 + z2);
+ }
+
+ /// <summary>
+ /// Returns the squared length (squared magnitude) of this vector.
+ /// This method runs faster than <see cref="Length"/>, so prefer it if
+ /// you need to compare vectors or need the squared length for some formula.
+ /// </summary>
+ /// <returns>The squared length of this vector.</returns>
+ public int LengthSquared()
+ {
+ int x2 = x * x;
+ int y2 = y * y;
+ int z2 = z * z;
+
+ return x2 + y2 + z2;
+ }
+
+ /// <summary>
+ /// Returns the axis of the vector's largest value. See <see cref="Axis"/>.
+ /// If all components are equal, this method returns <see cref="Axis.X"/>.
+ /// </summary>
+ /// <returns>The index of the largest axis.</returns>
+ public Axis MaxAxis()
+ {
+ return x < y ? (y < z ? Axis.Z : Axis.Y) : (x < z ? Axis.Z : Axis.X);
+ }
+
+ /// <summary>
+ /// Returns the axis of the vector's smallest value. See <see cref="Axis"/>.
+ /// If all components are equal, this method returns <see cref="Axis.Z"/>.
+ /// </summary>
+ /// <returns>The index of the smallest axis.</returns>
+ public Axis MinAxis()
+ {
+ return x < y ? (x < z ? Axis.X : Axis.Z) : (y < z ? Axis.Y : Axis.Z);
+ }
+
+ /// <summary>
+ /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `mod`.
+ /// </summary>
+ /// <param name="mod">A value representing the divisor of the operation.</param>
+ /// <returns>A vector with each component <see cref="Mathf.PosMod(int, int)"/> by `mod`.</returns>
+ public Vector3i PosMod(int mod)
+ {
+ Vector3i v = this;
+ v.x = Mathf.PosMod(v.x, mod);
+ v.y = Mathf.PosMod(v.y, mod);
+ v.z = Mathf.PosMod(v.z, mod);
+ return v;
+ }
+
+ /// <summary>
+ /// Returns a vector composed of the <see cref="Mathf.PosMod(int, int)"/> of this vector's components and `modv`'s components.
+ /// </summary>
+ /// <param name="modv">A vector representing the divisors of the operation.</param>
+ /// <returns>A vector with each component <see cref="Mathf.PosMod(int, int)"/> by `modv`'s components.</returns>
+ public Vector3i PosMod(Vector3i modv)
+ {
+ Vector3i v = this;
+ v.x = Mathf.PosMod(v.x, modv.x);
+ v.y = Mathf.PosMod(v.y, modv.y);
+ v.z = Mathf.PosMod(v.z, modv.z);
+ return v;
+ }
+
+ /// <summary>
+ /// Returns a vector with each component set to one or negative one, depending
+ /// on the signs of this vector's components, or zero if the component is zero,
+ /// by calling <see cref="Mathf.Sign(int)"/> on each component.
+ /// </summary>
+ /// <returns>A vector with all components as either `1`, `-1`, or `0`.</returns>
+ public Vector3i Sign()
+ {
+ Vector3i v = this;
+ v.x = Mathf.Sign(v.x);
+ v.y = Mathf.Sign(v.y);
+ v.z = Mathf.Sign(v.z);
+ return v;
+ }
+
+ // Constants
+ private static readonly Vector3i _zero = new Vector3i(0, 0, 0);
+ private static readonly Vector3i _one = new Vector3i(1, 1, 1);
+
+ private static readonly Vector3i _up = new Vector3i(0, 1, 0);
+ private static readonly Vector3i _down = new Vector3i(0, -1, 0);
+ private static readonly Vector3i _right = new Vector3i(1, 0, 0);
+ private static readonly Vector3i _left = new Vector3i(-1, 0, 0);
+ private static readonly Vector3i _forward = new Vector3i(0, 0, -1);
+ private static readonly Vector3i _back = new Vector3i(0, 0, 1);
+
+ /// <summary>
+ /// Zero vector, a vector with all components set to `0`.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3i(0, 0, 0)`</value>
+ public static Vector3i Zero { get { return _zero; } }
+ /// <summary>
+ /// One vector, a vector with all components set to `1`.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3i(1, 1, 1)`</value>
+ public static Vector3i One { get { return _one; } }
+
+ /// <summary>
+ /// Up unit vector.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3i(0, 1, 0)`</value>
+ public static Vector3i Up { get { return _up; } }
+ /// <summary>
+ /// Down unit vector.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3i(0, -1, 0)`</value>
+ public static Vector3i Down { get { return _down; } }
+ /// <summary>
+ /// Right unit vector. Represents the local direction of right,
+ /// and the global direction of east.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3i(1, 0, 0)`</value>
+ public static Vector3i Right { get { return _right; } }
+ /// <summary>
+ /// Left unit vector. Represents the local direction of left,
+ /// and the global direction of west.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3i(-1, 0, 0)`</value>
+ public static Vector3i Left { get { return _left; } }
+ /// <summary>
+ /// Forward unit vector. Represents the local direction of forward,
+ /// and the global direction of north.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3i(0, 0, -1)`</value>
+ public static Vector3i Forward { get { return _forward; } }
+ /// <summary>
+ /// Back unit vector. Represents the local direction of back,
+ /// and the global direction of south.
+ /// </summary>
+ /// <value>Equivalent to `new Vector3i(0, 0, 1)`</value>
+ public static Vector3i Back { get { return _back; } }
+
+ /// <summary>
+ /// Constructs a new <see cref="Vector3i"/> with the given components.
+ /// </summary>
+ /// <param name="x">The vector's X component.</param>
+ /// <param name="y">The vector's Y component.</param>
+ /// <param name="z">The vector's Z component.</param>
+ public Vector3i(int x, int y, int z)
+ {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ /// <summary>
+ /// Constructs a new <see cref="Vector3i"/> from an existing <see cref="Vector3i"/>.
+ /// </summary>
+ /// <param name="vi">The existing <see cref="Vector3i"/>.</param>
+ public Vector3i(Vector3i vi)
+ {
+ this.x = vi.x;
+ this.y = vi.y;
+ this.z = vi.z;
+ }
+
+ /// <summary>
+ /// Constructs a new <see cref="Vector3i"/> from an existing <see cref="Vector3"/>
+ /// by rounding the components via <see cref="Mathf.RoundToInt(real_t)"/>.
+ /// </summary>
+ /// <param name="v">The <see cref="Vector3"/> to convert.</param>
+ public Vector3i(Vector3 v)
+ {
+ this.x = Mathf.RoundToInt(v.x);
+ this.y = Mathf.RoundToInt(v.y);
+ this.z = Mathf.RoundToInt(v.z);
+ }
+
+ public static Vector3i operator +(Vector3i left, Vector3i right)
+ {
+ left.x += right.x;
+ left.y += right.y;
+ left.z += right.z;
+ return left;
+ }
+
+ public static Vector3i operator -(Vector3i left, Vector3i right)
+ {
+ left.x -= right.x;
+ left.y -= right.y;
+ left.z -= right.z;
+ return left;
+ }
+
+ public static Vector3i operator -(Vector3i vec)
+ {
+ vec.x = -vec.x;
+ vec.y = -vec.y;
+ vec.z = -vec.z;
+ return vec;
+ }
+
+ public static Vector3i operator *(Vector3i vec, int scale)
+ {
+ vec.x *= scale;
+ vec.y *= scale;
+ vec.z *= scale;
+ return vec;
+ }
+
+ public static Vector3i operator *(int scale, Vector3i vec)
+ {
+ vec.x *= scale;
+ vec.y *= scale;
+ vec.z *= scale;
+ return vec;
+ }
+
+ public static Vector3i operator *(Vector3i left, Vector3i right)
+ {
+ left.x *= right.x;
+ left.y *= right.y;
+ left.z *= right.z;
+ return left;
+ }
+
+ public static Vector3i operator /(Vector3i vec, int divisor)
+ {
+ vec.x /= divisor;
+ vec.y /= divisor;
+ vec.z /= divisor;
+ return vec;
+ }
+
+ public static Vector3i operator /(Vector3i vec, Vector3i divisorv)
+ {
+ vec.x /= divisorv.x;
+ vec.y /= divisorv.y;
+ vec.z /= divisorv.z;
+ return vec;
+ }
+
+ public static Vector3i operator %(Vector3i vec, int divisor)
+ {
+ vec.x %= divisor;
+ vec.y %= divisor;
+ vec.z %= divisor;
+ return vec;
+ }
+
+ public static Vector3i operator %(Vector3i vec, Vector3i divisorv)
+ {
+ vec.x %= divisorv.x;
+ vec.y %= divisorv.y;
+ vec.z %= divisorv.z;
+ return vec;
+ }
+
+ public static Vector3i operator &(Vector3i vec, int and)
+ {
+ vec.x &= and;
+ vec.y &= and;
+ vec.z &= and;
+ return vec;
+ }
+
+ public static Vector3i operator &(Vector3i vec, Vector3i andv)
+ {
+ vec.x &= andv.x;
+ vec.y &= andv.y;
+ vec.z &= andv.z;
+ return vec;
+ }
+
+ public static bool operator ==(Vector3i left, Vector3i right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Vector3i left, Vector3i right)
+ {
+ return !left.Equals(right);
+ }
+
+ public static bool operator <(Vector3i left, Vector3i right)
+ {
+ if (left.x == right.x)
+ {
+ if (left.y == right.y)
+ return left.z < right.z;
+ else
+ return left.y < right.y;
+ }
+
+ return left.x < right.x;
+ }
+
+ public static bool operator >(Vector3i left, Vector3i right)
+ {
+ if (left.x == right.x)
+ {
+ if (left.y == right.y)
+ return left.z > right.z;
+ else
+ return left.y > right.y;
+ }
+
+ return left.x > right.x;
+ }
+
+ public static bool operator <=(Vector3i left, Vector3i right)
+ {
+ if (left.x == right.x)
+ {
+ if (left.y == right.y)
+ return left.z <= right.z;
+ else
+ return left.y < right.y;
+ }
+
+ return left.x < right.x;
+ }
+
+ public static bool operator >=(Vector3i left, Vector3i right)
+ {
+ if (left.x == right.x)
+ {
+ if (left.y == right.y)
+ return left.z >= right.z;
+ else
+ return left.y > right.y;
+ }
+
+ return left.x > right.x;
+ }
+
+ public static implicit operator Vector3(Vector3i value)
+ {
+ return new Vector3(value.x, value.y, value.z);
+ }
+
+ public static explicit operator Vector3i(Vector3 value)
+ {
+ return new Vector3i(value);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Vector3i)
+ {
+ return Equals((Vector3i)obj);
+ }
+
+ return false;
+ }
+
+ public bool Equals(Vector3i other)
+ {
+ return x == other.x && y == other.y && z == other.z;
+ }
+
+ public override int GetHashCode()
+ {
+ return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return String.Format("({0}, {1}, {2})", new object[]
+ {
+ this.x.ToString(),
+ this.y.ToString(),
+ this.z.ToString()
+ });
+ }
+
+ public string ToString(string format)
+ {
+ return String.Format("({0}, {1}, {2})", new object[]
+ {
+ this.x.ToString(format),
+ this.y.ToString(format),
+ this.z.ToString(format)
+ });
+ }
+ }
+}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index 5419cd06e6..1fcfe74c86 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -1,59 +1,46 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{AEBF0036-DA76-4341-B651-A3F2856AB2FA}</ProjectGuid>
- <OutputType>Library</OutputType>
<OutputPath>bin/$(Configuration)</OutputPath>
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace>Godot</RootNamespace>
- <AssemblyName>GodotSharp</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFramework>netstandard2.1</TargetFramework>
<DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile>
- <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
+ <EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
- <Optimize>false</Optimize>
- <DefineConstants>$(GodotDefineConstants);GODOT;DEBUG;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <DebugType>portable</DebugType>
- <Optimize>true</Optimize>
- <DefineConstants>$(GodotDefineConstants);GODOT;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
+ <PropertyGroup>
+ <DefineConstants>$(DefineConstants);GODOT</DefineConstants>
</PropertyGroup>
<ItemGroup>
- <Reference Include="System" />
- </ItemGroup>
- <ItemGroup>
<Compile Include="Core\AABB.cs" />
<Compile Include="Core\Array.cs" />
+ <Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" />
+ <Compile Include="Core\Attributes\DisableGodotGeneratorsAttribute.cs" />
<Compile Include="Core\Attributes\ExportAttribute.cs" />
<Compile Include="Core\Attributes\GodotMethodAttribute.cs" />
<Compile Include="Core\Attributes\RPCAttributes.cs" />
+ <Compile Include="Core\Attributes\ScriptPathAttribute.cs" />
<Compile Include="Core\Attributes\SignalAttribute.cs" />
<Compile Include="Core\Attributes\ToolAttribute.cs" />
<Compile Include="Core\Basis.cs" />
+ <Compile Include="Core\Callable.cs" />
<Compile Include="Core\Color.cs" />
<Compile Include="Core\Colors.cs" />
<Compile Include="Core\DebuggingUtils.cs" />
+ <Compile Include="Core\DelegateUtils.cs" />
<Compile Include="Core\Dictionary.cs" />
<Compile Include="Core\Dispatcher.cs" />
<Compile Include="Core\DynamicObject.cs" />
<Compile Include="Core\Extensions\NodeExtensions.cs" />
<Compile Include="Core\Extensions\ObjectExtensions.cs" />
+ <Compile Include="Core\Extensions\PackedSceneExtensions.cs" />
<Compile Include="Core\Extensions\ResourceLoaderExtensions.cs" />
+ <Compile Include="Core\Extensions\SceneTreeExtensions.cs" />
<Compile Include="Core\GD.cs" />
<Compile Include="Core\GodotSynchronizationContext.cs" />
<Compile Include="Core\GodotTaskScheduler.cs" />
<Compile Include="Core\GodotTraceListener.cs" />
+ <Compile Include="Core\GodotUnhandledExceptionEvent.cs" />
<Compile Include="Core\Interfaces\IAwaitable.cs" />
<Compile Include="Core\Interfaces\IAwaiter.cs" />
<Compile Include="Core\Interfaces\ISerializationListener.cs" />
@@ -63,15 +50,21 @@
<Compile Include="Core\NodePath.cs" />
<Compile Include="Core\Object.base.cs" />
<Compile Include="Core\Plane.cs" />
- <Compile Include="Core\Quat.cs" />
+ <Compile Include="Core\Quaternion.cs" />
<Compile Include="Core\Rect2.cs" />
+ <Compile Include="Core\Rect2i.cs" />
<Compile Include="Core\RID.cs" />
+ <Compile Include="Core\SignalInfo.cs" />
<Compile Include="Core\SignalAwaiter.cs" />
<Compile Include="Core\StringExtensions.cs" />
- <Compile Include="Core\Transform.cs" />
+ <Compile Include="Core\StringName.cs" />
<Compile Include="Core\Transform2D.cs" />
+ <Compile Include="Core\Transform3D.cs" />
+ <Compile Include="Core\UnhandledExceptionArgs.cs" />
<Compile Include="Core\Vector2.cs" />
+ <Compile Include="Core\Vector2i.cs" />
<Compile Include="Core\Vector3.cs" />
+ <Compile Include="Core\Vector3i.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<!--
@@ -81,5 +74,4 @@
Fortunately code completion, go to definition and such still work.
-->
<Import Project="Generated\GeneratedIncludes.props" />
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
index f84e0183f6..da6f293871 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
@@ -1,27 +1,3 @@
-using System.Reflection;
using System.Runtime.CompilerServices;
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle("GodotSharp")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-
-[assembly: AssemblyVersion("1.0.*")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
[assembly: InternalsVisibleTo("GodotSharpEditor")]
diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
index 22853797c1..a8c4ba96b5 100644
--- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
@@ -1,45 +1,26 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8FBEC238-D944-4074-8548-B3B524305905}</ProjectGuid>
- <OutputType>Library</OutputType>
<OutputPath>bin/$(Configuration)</OutputPath>
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace>Godot</RootNamespace>
- <AssemblyName>GodotSharpEditor</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFramework>netstandard2.1</TargetFramework>
<DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile>
- <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
+ <EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
- <Optimize>false</Optimize>
- <DefineConstants>$(GodotDefineConstants);GODOT;DEBUG;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <DebugType>portable</DebugType>
- <Optimize>true</Optimize>
- <DefineConstants>$(GodotDefineConstants);GODOT;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
+ <PropertyGroup>
+ <DefineConstants>$(DefineConstants);GODOT</DefineConstants>
</PropertyGroup>
<ItemGroup>
- <Reference Include="System" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="Properties\AssemblyInfo.cs" />
- </ItemGroup>
- <Import Project="Generated\GeneratedIncludes.props" />
- <ItemGroup>
<ProjectReference Include="..\GodotSharp\GodotSharp.csproj">
- <Private>False</Private>
+ <Private>false</Private>
</ProjectReference>
</ItemGroup>
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <!--
+ We import a props file with auto-generated includes. This works well with Rider.
+ However, Visual Studio and MonoDevelop won't list them in the solution explorer.
+ We can't use wildcards as there may be undesired old files still hanging around.
+ Fortunately code completion, go to definition and such still work.
+ -->
+ <Import Project="Generated\GeneratedIncludes.props" />
</Project>
diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs
deleted file mode 100644
index 3684b7a3cb..0000000000
--- a/modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System.Reflection;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle("GodotSharpEditor")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-
-[assembly: AssemblyVersion("1.0.*")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
diff --git a/modules/mono/glue/arguments_vector.h b/modules/mono/glue/arguments_vector.h
index aeb466ba72..9ba6a05ac6 100644
--- a/modules/mono/glue/arguments_vector.h
+++ b/modules/mono/glue/arguments_vector.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -35,14 +35,13 @@
template <typename T, int POOL_SIZE = 5>
struct ArgumentsVector {
-
private:
T pool[POOL_SIZE];
T *_ptr;
int size;
- ArgumentsVector();
- ArgumentsVector(const ArgumentsVector &);
+ ArgumentsVector() = delete;
+ ArgumentsVector(const ArgumentsVector &) = delete;
public:
T *ptr() { return _ptr; }
diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp
index 02246b2f2f..2b87c2d9a4 100644
--- a/modules/mono/glue/base_object_glue.cpp
+++ b/modules/mono/glue/base_object_glue.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,17 +28,17 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "base_object_glue.h"
-
#ifdef MONO_GLUE_ENABLED
-#include "core/reference.h"
-#include "core/string_name.h"
+#include "core/object/class_db.h"
+#include "core/object/ref_counted.h"
+#include "core/string/string_name.h"
#include "../csharp_script.h"
#include "../mono_gd/gd_mono_cache.h"
#include "../mono_gd/gd_mono_class.h"
#include "../mono_gd/gd_mono_internals.h"
+#include "../mono_gd/gd_mono_marshal.h"
#include "../mono_gd/gd_mono_utils.h"
#include "../signal_awaiter_utils.h"
#include "arguments_vector.h"
@@ -51,7 +51,7 @@ Object *godot_icall_Object_Ctor(MonoObject *p_obj) {
void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) {
#ifdef DEBUG_ENABLED
- CRASH_COND(p_ptr == NULL);
+ CRASH_COND(p_ptr == nullptr);
#endif
if (p_ptr->get_script_instance()) {
@@ -59,7 +59,7 @@ void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) {
if (cs_instance) {
if (!cs_instance->is_destructing_script_instance()) {
cs_instance->mono_object_disposed(p_obj);
- p_ptr->set_script_instance(NULL);
+ p_ptr->set_script_instance(nullptr);
}
return;
}
@@ -70,25 +70,25 @@ void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) {
if (data) {
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
if (script_binding.inited) {
- Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
- if (gchandle.is_valid()) {
+ MonoGCHandleData &gchandle = script_binding.gchandle;
+ if (!gchandle.is_released()) {
CSharpLanguage::release_script_gchandle(p_obj, gchandle);
}
}
}
}
-void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolean p_is_finalizer) {
+void godot_icall_RefCounted_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolean p_is_finalizer) {
#ifdef DEBUG_ENABLED
- CRASH_COND(p_ptr == NULL);
- // This is only called with Reference derived classes
- CRASH_COND(!Object::cast_to<Reference>(p_ptr));
+ CRASH_COND(p_ptr == nullptr);
+ // This is only called with RefCounted derived classes
+ CRASH_COND(!Object::cast_to<RefCounted>(p_ptr));
#endif
- Reference *ref = static_cast<Reference *>(p_ptr);
+ RefCounted *rc = static_cast<RefCounted *>(p_ptr);
- if (ref->get_script_instance()) {
- CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(ref->get_script_instance());
+ if (rc->get_script_instance()) {
+ CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(rc->get_script_instance());
if (cs_instance) {
if (!cs_instance->is_destructing_script_instance()) {
bool delete_owner;
@@ -97,9 +97,9 @@ void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolea
cs_instance->mono_object_disposed_baseref(p_obj, p_is_finalizer, delete_owner, remove_script_instance);
if (delete_owner) {
- memdelete(ref);
+ memdelete(rc);
} else if (remove_script_instance) {
- ref->set_script_instance(NULL);
+ rc->set_script_instance(nullptr);
}
}
return;
@@ -108,17 +108,17 @@ void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolea
// Unsafe refcount decrement. The managed instance also counts as a reference.
// See: CSharpLanguage::alloc_instance_binding_data(Object *p_object)
- CSharpLanguage::get_singleton()->pre_unsafe_unreference(ref);
- if (ref->unreference()) {
- memdelete(ref);
+ CSharpLanguage::get_singleton()->pre_unsafe_unreference(rc);
+ if (rc->unreference()) {
+ memdelete(rc);
} else {
- void *data = ref->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
+ void *data = rc->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
if (data) {
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
if (script_binding.inited) {
- Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
- if (gchandle.is_valid()) {
+ MonoGCHandleData &gchandle = script_binding.gchandle;
+ if (!gchandle.is_released()) {
CSharpLanguage::release_script_gchandle(p_obj, gchandle);
}
}
@@ -126,37 +126,46 @@ void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolea
}
}
-MethodBind *godot_icall_Object_ClassDB_get_method(MonoString *p_type, MonoString *p_method) {
- StringName type(GDMonoMarshal::mono_string_to_godot(p_type));
+void godot_icall_Object_ConnectEventSignals(Object *p_ptr) {
+ CSharpInstance *csharp_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance());
+ if (csharp_instance) {
+ csharp_instance->connect_event_signals();
+ }
+}
+
+MethodBind *godot_icall_Object_ClassDB_get_method(StringName *p_type, MonoString *p_method) {
+ StringName type = p_type ? *p_type : StringName();
StringName method(GDMonoMarshal::mono_string_to_godot(p_method));
return ClassDB::get_method(type, method);
}
-MonoObject *godot_icall_Object_weakref(Object *p_obj) {
- if (!p_obj)
- return NULL;
+MonoObject *godot_icall_Object_weakref(Object *p_ptr) {
+ if (!p_ptr) {
+ return nullptr;
+ }
Ref<WeakRef> wref;
- Reference *ref = Object::cast_to<Reference>(p_obj);
+ RefCounted *rc = Object::cast_to<RefCounted>(p_ptr);
- if (ref) {
- REF r = ref;
- if (!r.is_valid())
- return NULL;
+ if (rc) {
+ REF r = rc;
+ if (!r.is_valid()) {
+ return nullptr;
+ }
- wref.instance();
+ wref.instantiate();
wref->set_ref(r);
} else {
- wref.instance();
- wref->set_obj(p_obj);
+ wref.instantiate();
+ wref->set_obj(p_ptr);
}
return GDMonoUtils::unmanaged_get_managed(wref.ptr());
}
-Error godot_icall_SignalAwaiter_connect(Object *p_source, MonoString *p_signal, Object *p_target, MonoObject *p_awaiter) {
- String signal = GDMonoMarshal::mono_string_to_godot(p_signal);
- return SignalAwaiterUtils::connect_signal_awaiter(p_source, signal, p_target, p_awaiter);
+int32_t godot_icall_SignalAwaiter_connect(Object *p_source, StringName *p_signal, Object *p_target, MonoObject *p_awaiter) {
+ StringName signal = p_signal ? *p_signal : StringName();
+ return (int32_t)gd_mono_connect_signal_awaiter(p_source, signal, p_target, p_awaiter);
}
MonoArray *godot_icall_DynamicGodotObject_SetMemberList(Object *p_ptr) {
@@ -189,12 +198,12 @@ MonoBoolean godot_icall_DynamicGodotObject_InvokeMember(Object *p_ptr, MonoStrin
args.set(i, &arg_store.get(i));
}
- Variant::CallError error;
+ Callable::CallError error;
Variant result = p_ptr->call(StringName(name), args.ptr(), argc, error);
*r_result = GDMonoMarshal::variant_to_mono_object(result);
- return error.error == Variant::CallError::CALL_OK;
+ return error.error == Callable::CallError::CALL_OK;
}
MonoBoolean godot_icall_DynamicGodotObject_GetMember(Object *p_ptr, MonoString *p_name, MonoObject **r_result) {
@@ -223,30 +232,26 @@ MonoBoolean godot_icall_DynamicGodotObject_SetMember(Object *p_ptr, MonoString *
MonoString *godot_icall_Object_ToString(Object *p_ptr) {
#ifdef DEBUG_ENABLED
// Cannot happen in C#; would get an ObjectDisposedException instead.
- CRASH_COND(p_ptr == NULL);
-
- if (ScriptDebugger::get_singleton() && !Object::cast_to<Reference>(p_ptr)) { // Only if debugging!
- // Cannot happen either in C#; the handle is nullified when the object is destroyed
- CRASH_COND(!ObjectDB::instance_validate(p_ptr));
- }
+ CRASH_COND(p_ptr == nullptr);
#endif
-
+ // Can't call 'Object::to_string()' here, as that can end up calling 'ToString' again resulting in an endless circular loop.
String result = "[" + p_ptr->get_class() + ":" + itos(p_ptr->get_instance_id()) + "]";
return GDMonoMarshal::mono_string_from_godot(result);
}
void godot_register_object_icalls() {
- mono_add_internal_call("Godot.Object::godot_icall_Object_Ctor", (void *)godot_icall_Object_Ctor);
- mono_add_internal_call("Godot.Object::godot_icall_Object_Disposed", (void *)godot_icall_Object_Disposed);
- mono_add_internal_call("Godot.Object::godot_icall_Reference_Disposed", (void *)godot_icall_Reference_Disposed);
- mono_add_internal_call("Godot.Object::godot_icall_Object_ClassDB_get_method", (void *)godot_icall_Object_ClassDB_get_method);
- mono_add_internal_call("Godot.Object::godot_icall_Object_ToString", (void *)godot_icall_Object_ToString);
- mono_add_internal_call("Godot.Object::godot_icall_Object_weakref", (void *)godot_icall_Object_weakref);
- mono_add_internal_call("Godot.SignalAwaiter::godot_icall_SignalAwaiter_connect", (void *)godot_icall_SignalAwaiter_connect);
- mono_add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMemberList", (void *)godot_icall_DynamicGodotObject_SetMemberList);
- mono_add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_InvokeMember", (void *)godot_icall_DynamicGodotObject_InvokeMember);
- mono_add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_GetMember", (void *)godot_icall_DynamicGodotObject_GetMember);
- mono_add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMember", (void *)godot_icall_DynamicGodotObject_SetMember);
+ GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_Ctor", godot_icall_Object_Ctor);
+ GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_Disposed", godot_icall_Object_Disposed);
+ GDMonoUtils::add_internal_call("Godot.Object::godot_icall_RefCounted_Disposed", godot_icall_RefCounted_Disposed);
+ GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_ConnectEventSignals", godot_icall_Object_ConnectEventSignals);
+ GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_ClassDB_get_method", godot_icall_Object_ClassDB_get_method);
+ GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_ToString", godot_icall_Object_ToString);
+ GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_weakref", godot_icall_Object_weakref);
+ GDMonoUtils::add_internal_call("Godot.SignalAwaiter::godot_icall_SignalAwaiter_connect", godot_icall_SignalAwaiter_connect);
+ GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMemberList", godot_icall_DynamicGodotObject_SetMemberList);
+ GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_InvokeMember", godot_icall_DynamicGodotObject_InvokeMember);
+ GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_GetMember", godot_icall_DynamicGodotObject_GetMember);
+ GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMember", godot_icall_DynamicGodotObject_SetMember);
}
#endif // MONO_GLUE_ENABLED
diff --git a/modules/mono/glue/base_object_glue.h b/modules/mono/glue/base_object_glue.h
deleted file mode 100644
index 22532dcff9..0000000000
--- a/modules/mono/glue/base_object_glue.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*************************************************************************/
-/* base_object_glue.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef BASE_OBJECT_GLUE_H
-#define BASE_OBJECT_GLUE_H
-
-#ifdef MONO_GLUE_ENABLED
-
-#include "core/class_db.h"
-#include "core/object.h"
-
-#include "../mono_gd/gd_mono_marshal.h"
-
-Object *godot_icall_Object_Ctor(MonoObject *p_obj);
-
-void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr);
-
-void godot_icall_Reference_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolean p_is_finalizer);
-
-MethodBind *godot_icall_Object_ClassDB_get_method(MonoString *p_type, MonoString *p_method);
-
-MonoObject *godot_icall_Object_weakref(Object *p_obj);
-
-Error godot_icall_SignalAwaiter_connect(Object *p_source, MonoString *p_signal, Object *p_target, MonoObject *p_awaiter);
-
-// DynamicGodotObject
-
-MonoArray *godot_icall_DynamicGodotObject_SetMemberList(Object *p_ptr);
-
-MonoBoolean godot_icall_DynamicGodotObject_InvokeMember(Object *p_ptr, MonoString *p_name, MonoArray *p_args, MonoObject **r_result);
-
-MonoBoolean godot_icall_DynamicGodotObject_GetMember(Object *p_ptr, MonoString *p_name, MonoObject **r_result);
-
-MonoBoolean godot_icall_DynamicGodotObject_SetMember(Object *p_ptr, MonoString *p_name, MonoObject *p_value);
-
-MonoString *godot_icall_Object_ToString(Object *p_ptr);
-
-// Register internal calls
-
-void godot_register_object_icalls();
-
-#endif // MONO_GLUE_ENABLED
-
-#endif // BASE_OBJECT_GLUE_H
diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp
index b7fa7fcab2..191f863350 100644
--- a/modules/mono/glue/collections_glue.cpp
+++ b/modules/mono/glue/collections_glue.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,14 +28,15 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "collections_glue.h"
-
#ifdef MONO_GLUE_ENABLED
#include <mono/metadata/exception.h>
+#include "core/variant/array.h"
+
#include "../mono_gd/gd_mono_cache.h"
#include "../mono_gd/gd_mono_class.h"
+#include "../mono_gd/gd_mono_marshal.h"
#include "../mono_gd/gd_mono_utils.h"
Array *godot_icall_Array_Ctor() {
@@ -46,23 +47,23 @@ void godot_icall_Array_Dtor(Array *ptr) {
memdelete(ptr);
}
-MonoObject *godot_icall_Array_At(Array *ptr, int index) {
+MonoObject *godot_icall_Array_At(Array *ptr, int32_t index) {
if (index < 0 || index >= ptr->size()) {
GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
- return NULL;
+ return nullptr;
}
return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index));
}
-MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_encoding, GDMonoClass *type_class) {
+MonoObject *godot_icall_Array_At_Generic(Array *ptr, int32_t index, uint32_t type_encoding, GDMonoClass *type_class) {
if (index < 0 || index >= ptr->size()) {
GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
- return NULL;
+ return nullptr;
}
return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index), ManagedType(type_encoding, type_class));
}
-void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value) {
+void godot_icall_Array_SetAt(Array *ptr, int32_t index, MonoObject *value) {
if (index < 0 || index >= ptr->size()) {
GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
return;
@@ -70,11 +71,11 @@ void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value) {
ptr->operator[](index) = GDMonoMarshal::mono_object_to_variant(value);
}
-int godot_icall_Array_Count(Array *ptr) {
+int32_t godot_icall_Array_Count(Array *ptr) {
return ptr->size();
}
-int godot_icall_Array_Add(Array *ptr, MonoObject *item) {
+int32_t godot_icall_Array_Add(Array *ptr, MonoObject *item) {
ptr->append(GDMonoMarshal::mono_object_to_variant(item));
return ptr->size();
}
@@ -87,7 +88,7 @@ MonoBoolean godot_icall_Array_Contains(Array *ptr, MonoObject *item) {
return ptr->find(GDMonoMarshal::mono_object_to_variant(item)) != -1;
}
-void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index) {
+void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int32_t array_index) {
unsigned int count = ptr->size();
if (mono_array_length(array) < (array_index + count)) {
@@ -103,15 +104,36 @@ void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index) {
}
}
+Array *godot_icall_Array_Ctor_MonoArray(MonoArray *mono_array) {
+ Array *godot_array = memnew(Array);
+ unsigned int count = mono_array_length(mono_array);
+ godot_array->resize(count);
+ for (unsigned int i = 0; i < count; i++) {
+ MonoObject *item = mono_array_get(mono_array, MonoObject *, i);
+ godot_icall_Array_SetAt(godot_array, i, item);
+ }
+ return godot_array;
+}
+
Array *godot_icall_Array_Duplicate(Array *ptr, MonoBoolean deep) {
return memnew(Array(ptr->duplicate(deep)));
}
-int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) {
+Array *godot_icall_Array_Concatenate(Array *left, Array *right) {
+ int count = left->size() + right->size();
+ Array *new_array = memnew(Array(left->duplicate(false)));
+ new_array->resize(count);
+ for (unsigned int i = 0; i < (unsigned int)right->size(); i++) {
+ new_array->operator[](i + left->size()) = right->operator[](i);
+ }
+ return new_array;
+}
+
+int32_t godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) {
return ptr->find(GDMonoMarshal::mono_object_to_variant(item));
}
-void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item) {
+void godot_icall_Array_Insert(Array *ptr, int32_t index, MonoObject *item) {
if (index < 0 || index > ptr->size()) {
GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
return;
@@ -128,7 +150,7 @@ MonoBoolean godot_icall_Array_Remove(Array *ptr, MonoObject *item) {
return false;
}
-void godot_icall_Array_RemoveAt(Array *ptr, int index) {
+void godot_icall_Array_RemoveAt(Array *ptr, int32_t index) {
if (index < 0 || index >= ptr->size()) {
GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range());
return;
@@ -136,8 +158,12 @@ void godot_icall_Array_RemoveAt(Array *ptr, int index) {
ptr->remove(index);
}
-Error godot_icall_Array_Resize(Array *ptr, int new_size) {
- return ptr->resize(new_size);
+int32_t godot_icall_Array_Resize(Array *ptr, int32_t new_size) {
+ return (int32_t)ptr->resize(new_size);
+}
+
+void godot_icall_Array_Shuffle(Array *ptr) {
+ ptr->shuffle();
}
void godot_icall_Array_Generic_GetElementTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class) {
@@ -162,28 +188,28 @@ void godot_icall_Dictionary_Dtor(Dictionary *ptr) {
MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key) {
Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
- if (ret == NULL) {
+ if (ret == nullptr) {
MonoObject *exc = mono_object_new(mono_domain_get(), CACHED_CLASS(KeyNotFoundException)->get_mono_ptr());
#ifdef DEBUG_ENABLED
CRASH_COND(!exc);
#endif
GDMonoUtils::runtime_object_init(exc, CACHED_CLASS(KeyNotFoundException));
GDMonoUtils::set_pending_exception((MonoException *)exc);
- return NULL;
+ return nullptr;
}
return GDMonoMarshal::variant_to_mono_object(ret);
}
MonoObject *godot_icall_Dictionary_GetValue_Generic(Dictionary *ptr, MonoObject *key, uint32_t type_encoding, GDMonoClass *type_class) {
Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
- if (ret == NULL) {
+ if (ret == nullptr) {
MonoObject *exc = mono_object_new(mono_domain_get(), CACHED_CLASS(KeyNotFoundException)->get_mono_ptr());
#ifdef DEBUG_ENABLED
CRASH_COND(!exc);
#endif
GDMonoUtils::runtime_object_init(exc, CACHED_CLASS(KeyNotFoundException));
GDMonoUtils::set_pending_exception((MonoException *)exc);
- return NULL;
+ return nullptr;
}
return GDMonoMarshal::variant_to_mono_object(ret, ManagedType(type_encoding, type_class));
}
@@ -200,14 +226,14 @@ Array *godot_icall_Dictionary_Values(Dictionary *ptr) {
return memnew(Array(ptr->values()));
}
-int godot_icall_Dictionary_Count(Dictionary *ptr) {
+int32_t godot_icall_Dictionary_Count(Dictionary *ptr) {
return ptr->size();
}
void godot_icall_Dictionary_Add(Dictionary *ptr, MonoObject *key, MonoObject *value) {
Variant varKey = GDMonoMarshal::mono_object_to_variant(key);
Variant *ret = ptr->getptr(varKey);
- if (ret != NULL) {
+ if (ret != nullptr) {
GDMonoUtils::set_pending_exception(mono_get_exception_argument("key", "An element with the same key already exists"));
return;
}
@@ -221,7 +247,7 @@ void godot_icall_Dictionary_Clear(Dictionary *ptr) {
MonoBoolean godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value) {
// no dupes
Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
- return ret != NULL && *ret == GDMonoMarshal::mono_object_to_variant(value);
+ return ret != nullptr && *ret == GDMonoMarshal::mono_object_to_variant(value);
}
MonoBoolean godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key) {
@@ -241,7 +267,7 @@ MonoBoolean godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, Mono
// no dupes
Variant *ret = ptr->getptr(varKey);
- if (ret != NULL && *ret == GDMonoMarshal::mono_object_to_variant(value)) {
+ if (ret != nullptr && *ret == GDMonoMarshal::mono_object_to_variant(value)) {
ptr->erase(varKey);
return true;
}
@@ -251,8 +277,8 @@ MonoBoolean godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, Mono
MonoBoolean godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value) {
Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
- if (ret == NULL) {
- *value = NULL;
+ if (ret == nullptr) {
+ *value = nullptr;
return false;
}
*value = GDMonoMarshal::variant_to_mono_object(ret);
@@ -261,8 +287,8 @@ MonoBoolean godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key,
MonoBoolean godot_icall_Dictionary_TryGetValue_Generic(Dictionary *ptr, MonoObject *key, MonoObject **value, uint32_t type_encoding, GDMonoClass *type_class) {
Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key));
- if (ret == NULL) {
- *value = NULL;
+ if (ret == nullptr) {
+ *value = nullptr;
return false;
}
*value = GDMonoMarshal::variant_to_mono_object(ret, ManagedType(type_encoding, type_class));
@@ -282,44 +308,47 @@ MonoString *godot_icall_Dictionary_ToString(Dictionary *ptr) {
}
void godot_register_collections_icalls() {
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor", (void *)godot_icall_Array_Ctor);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Dtor", (void *)godot_icall_Array_Dtor);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_At", (void *)godot_icall_Array_At);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_At_Generic", (void *)godot_icall_Array_At_Generic);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_SetAt", (void *)godot_icall_Array_SetAt);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Count", (void *)godot_icall_Array_Count);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Add", (void *)godot_icall_Array_Add);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Clear", (void *)godot_icall_Array_Clear);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Contains", (void *)godot_icall_Array_Contains);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_CopyTo", (void *)godot_icall_Array_CopyTo);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Duplicate", (void *)godot_icall_Array_Duplicate);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_IndexOf", (void *)godot_icall_Array_IndexOf);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Insert", (void *)godot_icall_Array_Insert);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Remove", (void *)godot_icall_Array_Remove);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_RemoveAt", (void *)godot_icall_Array_RemoveAt);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Resize", (void *)godot_icall_Array_Resize);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_Generic_GetElementTypeInfo", (void *)godot_icall_Array_Generic_GetElementTypeInfo);
- mono_add_internal_call("Godot.Collections.Array::godot_icall_Array_ToString", (void *)godot_icall_Array_ToString);
-
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Ctor", (void *)godot_icall_Dictionary_Ctor);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Dtor", (void *)godot_icall_Dictionary_Dtor);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue", (void *)godot_icall_Dictionary_GetValue);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue_Generic", (void *)godot_icall_Dictionary_GetValue_Generic);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_SetValue", (void *)godot_icall_Dictionary_SetValue);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Keys", (void *)godot_icall_Dictionary_Keys);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Values", (void *)godot_icall_Dictionary_Values);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Count", (void *)godot_icall_Dictionary_Count);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Add", (void *)godot_icall_Dictionary_Add);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Clear", (void *)godot_icall_Dictionary_Clear);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Contains", (void *)godot_icall_Dictionary_Contains);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ContainsKey", (void *)godot_icall_Dictionary_ContainsKey);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Duplicate", (void *)godot_icall_Dictionary_Duplicate);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_RemoveKey", (void *)godot_icall_Dictionary_RemoveKey);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Remove", (void *)godot_icall_Dictionary_Remove);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue", (void *)godot_icall_Dictionary_TryGetValue);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue_Generic", (void *)godot_icall_Dictionary_TryGetValue_Generic);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Generic_GetValueTypeInfo", (void *)godot_icall_Dictionary_Generic_GetValueTypeInfo);
- mono_add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ToString", (void *)godot_icall_Dictionary_ToString);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor", godot_icall_Array_Ctor);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor_MonoArray", godot_icall_Array_Ctor_MonoArray);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Dtor", godot_icall_Array_Dtor);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_At", godot_icall_Array_At);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_At_Generic", godot_icall_Array_At_Generic);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_SetAt", godot_icall_Array_SetAt);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Count", godot_icall_Array_Count);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Add", godot_icall_Array_Add);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Clear", godot_icall_Array_Clear);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Concatenate", godot_icall_Array_Concatenate);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Contains", godot_icall_Array_Contains);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_CopyTo", godot_icall_Array_CopyTo);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Duplicate", godot_icall_Array_Duplicate);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_IndexOf", godot_icall_Array_IndexOf);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Insert", godot_icall_Array_Insert);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Remove", godot_icall_Array_Remove);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_RemoveAt", godot_icall_Array_RemoveAt);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Resize", godot_icall_Array_Resize);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Shuffle", godot_icall_Array_Shuffle);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Generic_GetElementTypeInfo", godot_icall_Array_Generic_GetElementTypeInfo);
+ GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_ToString", godot_icall_Array_ToString);
+
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Ctor", godot_icall_Dictionary_Ctor);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Dtor", godot_icall_Dictionary_Dtor);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue", godot_icall_Dictionary_GetValue);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue_Generic", godot_icall_Dictionary_GetValue_Generic);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_SetValue", godot_icall_Dictionary_SetValue);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Keys", godot_icall_Dictionary_Keys);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Values", godot_icall_Dictionary_Values);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Count", godot_icall_Dictionary_Count);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Add", godot_icall_Dictionary_Add);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Clear", godot_icall_Dictionary_Clear);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Contains", godot_icall_Dictionary_Contains);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ContainsKey", godot_icall_Dictionary_ContainsKey);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Duplicate", godot_icall_Dictionary_Duplicate);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_RemoveKey", godot_icall_Dictionary_RemoveKey);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Remove", godot_icall_Dictionary_Remove);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue", godot_icall_Dictionary_TryGetValue);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue_Generic", godot_icall_Dictionary_TryGetValue_Generic);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Generic_GetValueTypeInfo", godot_icall_Dictionary_Generic_GetValueTypeInfo);
+ GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ToString", godot_icall_Dictionary_ToString);
}
#endif // MONO_GLUE_ENABLED
diff --git a/modules/mono/glue/collections_glue.h b/modules/mono/glue/collections_glue.h
deleted file mode 100644
index f8351a1fc7..0000000000
--- a/modules/mono/glue/collections_glue.h
+++ /dev/null
@@ -1,124 +0,0 @@
-/*************************************************************************/
-/* collections_glue.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef COLLECTIONS_GLUE_H
-#define COLLECTIONS_GLUE_H
-
-#ifdef MONO_GLUE_ENABLED
-
-#include "core/array.h"
-
-#include "../mono_gd/gd_mono_marshal.h"
-
-// Array
-
-Array *godot_icall_Array_Ctor();
-
-void godot_icall_Array_Dtor(Array *ptr);
-
-MonoObject *godot_icall_Array_At(Array *ptr, int index);
-
-MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_encoding, GDMonoClass *type_class);
-
-void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value);
-
-int godot_icall_Array_Count(Array *ptr);
-
-int godot_icall_Array_Add(Array *ptr, MonoObject *item);
-
-void godot_icall_Array_Clear(Array *ptr);
-
-MonoBoolean godot_icall_Array_Contains(Array *ptr, MonoObject *item);
-
-void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int array_index);
-
-Array *godot_icall_Array_Duplicate(Array *ptr, MonoBoolean deep);
-
-int godot_icall_Array_IndexOf(Array *ptr, MonoObject *item);
-
-void godot_icall_Array_Insert(Array *ptr, int index, MonoObject *item);
-
-MonoBoolean godot_icall_Array_Remove(Array *ptr, MonoObject *item);
-
-void godot_icall_Array_RemoveAt(Array *ptr, int index);
-
-Error godot_icall_Array_Resize(Array *ptr, int new_size);
-
-void godot_icall_Array_Generic_GetElementTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class);
-
-MonoString *godot_icall_Array_ToString(Array *ptr);
-
-// Dictionary
-
-Dictionary *godot_icall_Dictionary_Ctor();
-
-void godot_icall_Dictionary_Dtor(Dictionary *ptr);
-
-MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key);
-
-MonoObject *godot_icall_Dictionary_GetValue_Generic(Dictionary *ptr, MonoObject *key, uint32_t type_encoding, GDMonoClass *type_class);
-
-void godot_icall_Dictionary_SetValue(Dictionary *ptr, MonoObject *key, MonoObject *value);
-
-Array *godot_icall_Dictionary_Keys(Dictionary *ptr);
-
-Array *godot_icall_Dictionary_Values(Dictionary *ptr);
-
-int godot_icall_Dictionary_Count(Dictionary *ptr);
-
-void godot_icall_Dictionary_Add(Dictionary *ptr, MonoObject *key, MonoObject *value);
-
-void godot_icall_Dictionary_Clear(Dictionary *ptr);
-
-MonoBoolean godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value);
-
-MonoBoolean godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key);
-
-Dictionary *godot_icall_Dictionary_Duplicate(Dictionary *ptr, MonoBoolean deep);
-
-MonoBoolean godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key);
-
-MonoBoolean godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value);
-
-MonoBoolean godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value);
-
-MonoBoolean godot_icall_Dictionary_TryGetValue_Generic(Dictionary *ptr, MonoObject *key, MonoObject **value, uint32_t type_encoding, GDMonoClass *type_class);
-
-void godot_icall_Dictionary_Generic_GetValueTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class);
-
-MonoString *godot_icall_Dictionary_ToString(Dictionary *ptr);
-
-// Register internal calls
-
-void godot_register_collections_icalls();
-
-#endif // MONO_GLUE_ENABLED
-
-#endif // COLLECTIONS_GLUE_H
diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp
index 9bea625450..a2ff868f65 100644
--- a/modules/mono/glue/gd_glue.cpp
+++ b/modules/mono/glue/gd_glue.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,25 +28,23 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "gd_glue.h"
-
#ifdef MONO_GLUE_ENABLED
-#include "core/array.h"
#include "core/io/marshalls.h"
#include "core/os/os.h"
-#include "core/ustring.h"
-#include "core/variant.h"
-#include "core/variant_parser.h"
+#include "core/string/ustring.h"
+#include "core/variant/array.h"
+#include "core/variant/variant.h"
+#include "core/variant/variant_parser.h"
#include "../mono_gd/gd_mono_cache.h"
+#include "../mono_gd/gd_mono_marshal.h"
#include "../mono_gd/gd_mono_utils.h"
MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes, MonoBoolean p_allow_objects) {
Variant ret;
- PoolByteArray varr = GDMonoMarshal::mono_array_to_PoolByteArray(p_bytes);
- PoolByteArray::Read r = varr.read();
- Error err = decode_variant(ret, r.ptr(), varr.size(), NULL, p_allow_objects);
+ PackedByteArray varr = GDMonoMarshal::mono_array_to_PackedByteArray(p_bytes);
+ Error err = decode_variant(ret, varr.ptr(), varr.size(), nullptr, p_allow_objects);
if (err != OK) {
ret = RTR("Not enough bytes for decoding bytes, or invalid format.");
}
@@ -56,9 +54,10 @@ MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes, MonoBoolean p_allow_obj
MonoObject *godot_icall_GD_convert(MonoObject *p_what, int32_t p_type) {
Variant what = GDMonoMarshal::mono_object_to_variant(p_what);
const Variant *args[1] = { &what };
- Variant::CallError ce;
- Variant ret = Variant::construct(Variant::Type(p_type), args, 1, ce);
- ERR_FAIL_COND_V(ce.error != Variant::CallError::CALL_OK, NULL);
+ Callable::CallError ce;
+ Variant ret;
+ Variant::construct(Variant::Type(p_type), ret, args, 1, ce);
+ ERR_FAIL_COND_V(ce.error != Callable::CallError::CALL_OK, nullptr);
return GDMonoMarshal::variant_to_mono_object(ret);
}
@@ -67,7 +66,7 @@ int godot_icall_GD_hash(MonoObject *p_var) {
}
MonoObject *godot_icall_GD_instance_from_id(uint64_t p_instance_id) {
- return GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(p_instance_id));
+ return GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(ObjectID(p_instance_id)));
}
void godot_icall_GD_print(MonoArray *p_what) {
@@ -77,7 +76,7 @@ void godot_icall_GD_print(MonoArray *p_what) {
for (int i = 0; i < length; i++) {
MonoObject *elem = mono_array_get(p_what, MonoObject *, i);
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc);
if (exc) {
@@ -92,14 +91,13 @@ void godot_icall_GD_print(MonoArray *p_what) {
}
void godot_icall_GD_printerr(MonoArray *p_what) {
-
String str;
int length = mono_array_length(p_what);
for (int i = 0; i < length; i++) {
MonoObject *elem = mono_array_get(p_what, MonoObject *, i);
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc);
if (exc) {
@@ -120,7 +118,7 @@ void godot_icall_GD_printraw(MonoArray *p_what) {
for (int i = 0; i < length; i++) {
MonoObject *elem = mono_array_get(p_what, MonoObject *, i);
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc);
if (exc) {
@@ -141,7 +139,7 @@ void godot_icall_GD_prints(MonoArray *p_what) {
for (int i = 0; i < length; i++) {
MonoObject *elem = mono_array_get(p_what, MonoObject *, i);
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc);
if (exc) {
@@ -149,8 +147,9 @@ void godot_icall_GD_prints(MonoArray *p_what) {
return;
}
- if (i)
+ if (i) {
str += " ";
+ }
str += elem_str;
}
@@ -165,7 +164,7 @@ void godot_icall_GD_printt(MonoArray *p_what) {
for (int i = 0; i < length; i++) {
MonoObject *elem = mono_array_get(p_what, MonoObject *, i);
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc);
if (exc) {
@@ -173,8 +172,9 @@ void godot_icall_GD_printt(MonoArray *p_what) {
return;
}
- if (i)
+ if (i) {
str += "\t";
+ }
str += elem_str;
}
@@ -194,12 +194,16 @@ void godot_icall_GD_randomize() {
Math::randomize();
}
-double godot_icall_GD_rand_range(double from, double to) {
+double godot_icall_GD_randf_range(double from, double to) {
+ return Math::random(from, to);
+}
+
+int32_t godot_icall_GD_randi_range(int32_t from, int32_t to) {
return Math::random(from, to);
}
uint32_t godot_icall_GD_rand_seed(uint64_t seed, uint64_t *newSeed) {
- int ret = Math::rand_from_seed(&seed);
+ uint32_t ret = Math::rand_from_seed(&seed);
*newSeed = seed;
return ret;
}
@@ -215,10 +219,11 @@ MonoString *godot_icall_GD_str(MonoArray *p_what) {
for (int i = 0; i < what.size(); i++) {
String os = what[i].operator String();
- if (i == 0)
+ if (i == 0) {
str = os;
- else
+ } else {
str += os;
+ }
}
return GDMonoMarshal::mono_string_from_godot(str);
@@ -235,40 +240,38 @@ MonoObject *godot_icall_GD_str2var(MonoString *p_str) {
Error err = VariantParser::parse(&ss, ret, errs, line);
if (err != OK) {
String err_str = "Parse error at line " + itos(line) + ": " + errs + ".";
- ERR_PRINTS(err_str);
+ ERR_PRINT(err_str);
ret = err_str;
}
return GDMonoMarshal::variant_to_mono_object(ret);
}
-MonoBoolean godot_icall_GD_type_exists(MonoString *p_type) {
- return ClassDB::class_exists(GDMonoMarshal::mono_string_to_godot(p_type));
+MonoBoolean godot_icall_GD_type_exists(StringName *p_type) {
+ StringName type = p_type ? *p_type : StringName();
+ return ClassDB::class_exists(type);
}
void godot_icall_GD_pusherror(MonoString *p_str) {
- ERR_PRINTS(GDMonoMarshal::mono_string_to_godot(p_str));
+ ERR_PRINT(GDMonoMarshal::mono_string_to_godot(p_str));
}
void godot_icall_GD_pushwarning(MonoString *p_str) {
- WARN_PRINTS(GDMonoMarshal::mono_string_to_godot(p_str));
+ WARN_PRINT(GDMonoMarshal::mono_string_to_godot(p_str));
}
MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_objects) {
Variant var = GDMonoMarshal::mono_object_to_variant(p_var);
- PoolByteArray barr;
+ PackedByteArray barr;
int len;
- Error err = encode_variant(var, NULL, len, p_full_objects);
- ERR_FAIL_COND_V_MSG(err != OK, NULL, "Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID).");
+ Error err = encode_variant(var, nullptr, len, p_full_objects);
+ ERR_FAIL_COND_V_MSG(err != OK, nullptr, "Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID).");
barr.resize(len);
- {
- PoolByteArray::Write w = barr.write();
- encode_variant(var, w.ptr(), len, p_full_objects);
- }
+ encode_variant(var, barr.ptrw(), len, p_full_objects);
- return GDMonoMarshal::PoolByteArray_to_mono_array(barr);
+ return GDMonoMarshal::PackedByteArray_to_mono_array(barr);
}
MonoString *godot_icall_GD_var2str(MonoObject *p_var) {
@@ -277,36 +280,42 @@ MonoString *godot_icall_GD_var2str(MonoObject *p_var) {
return GDMonoMarshal::mono_string_from_godot(vars);
}
+uint32_t godot_icall_TypeToVariantType(MonoReflectionType *p_refl_type) {
+ return (uint32_t)GDMonoMarshal::managed_to_variant_type(ManagedType::from_reftype(p_refl_type));
+}
+
MonoObject *godot_icall_DefaultGodotTaskScheduler() {
return GDMonoCache::cached_data.task_scheduler_handle->get_target();
}
void godot_register_gd_icalls() {
- mono_add_internal_call("Godot.GD::godot_icall_GD_bytes2var", (void *)godot_icall_GD_bytes2var);
- mono_add_internal_call("Godot.GD::godot_icall_GD_convert", (void *)godot_icall_GD_convert);
- mono_add_internal_call("Godot.GD::godot_icall_GD_hash", (void *)godot_icall_GD_hash);
- mono_add_internal_call("Godot.GD::godot_icall_GD_instance_from_id", (void *)godot_icall_GD_instance_from_id);
- mono_add_internal_call("Godot.GD::godot_icall_GD_pusherror", (void *)godot_icall_GD_pusherror);
- mono_add_internal_call("Godot.GD::godot_icall_GD_pushwarning", (void *)godot_icall_GD_pushwarning);
- mono_add_internal_call("Godot.GD::godot_icall_GD_print", (void *)godot_icall_GD_print);
- mono_add_internal_call("Godot.GD::godot_icall_GD_printerr", (void *)godot_icall_GD_printerr);
- mono_add_internal_call("Godot.GD::godot_icall_GD_printraw", (void *)godot_icall_GD_printraw);
- mono_add_internal_call("Godot.GD::godot_icall_GD_prints", (void *)godot_icall_GD_prints);
- mono_add_internal_call("Godot.GD::godot_icall_GD_printt", (void *)godot_icall_GD_printt);
- mono_add_internal_call("Godot.GD::godot_icall_GD_randf", (void *)godot_icall_GD_randf);
- mono_add_internal_call("Godot.GD::godot_icall_GD_randi", (void *)godot_icall_GD_randi);
- mono_add_internal_call("Godot.GD::godot_icall_GD_randomize", (void *)godot_icall_GD_randomize);
- mono_add_internal_call("Godot.GD::godot_icall_GD_rand_range", (void *)godot_icall_GD_rand_range);
- mono_add_internal_call("Godot.GD::godot_icall_GD_rand_seed", (void *)godot_icall_GD_rand_seed);
- mono_add_internal_call("Godot.GD::godot_icall_GD_seed", (void *)godot_icall_GD_seed);
- mono_add_internal_call("Godot.GD::godot_icall_GD_str", (void *)godot_icall_GD_str);
- mono_add_internal_call("Godot.GD::godot_icall_GD_str2var", (void *)godot_icall_GD_str2var);
- mono_add_internal_call("Godot.GD::godot_icall_GD_type_exists", (void *)godot_icall_GD_type_exists);
- mono_add_internal_call("Godot.GD::godot_icall_GD_var2bytes", (void *)godot_icall_GD_var2bytes);
- mono_add_internal_call("Godot.GD::godot_icall_GD_var2str", (void *)godot_icall_GD_var2str);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_bytes2var", godot_icall_GD_bytes2var);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_convert", godot_icall_GD_convert);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_hash", godot_icall_GD_hash);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_instance_from_id", godot_icall_GD_instance_from_id);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_pusherror", godot_icall_GD_pusherror);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_pushwarning", godot_icall_GD_pushwarning);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_print", godot_icall_GD_print);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printerr", godot_icall_GD_printerr);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printraw", godot_icall_GD_printraw);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_prints", godot_icall_GD_prints);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printt", godot_icall_GD_printt);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randf", godot_icall_GD_randf);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randi", godot_icall_GD_randi);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randomize", godot_icall_GD_randomize);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randf_range", godot_icall_GD_randf_range);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randi_range", godot_icall_GD_randi_range);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_rand_seed", godot_icall_GD_rand_seed);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_seed", godot_icall_GD_seed);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_str", godot_icall_GD_str);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_str2var", godot_icall_GD_str2var);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_type_exists", godot_icall_GD_type_exists);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_var2bytes", godot_icall_GD_var2bytes);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_var2str", godot_icall_GD_var2str);
+ GDMonoUtils::add_internal_call("Godot.GD::godot_icall_TypeToVariantType", godot_icall_TypeToVariantType);
// Dispatcher
- mono_add_internal_call("Godot.Dispatcher::godot_icall_DefaultGodotTaskScheduler", (void *)godot_icall_DefaultGodotTaskScheduler);
+ GDMonoUtils::add_internal_call("Godot.Dispatcher::godot_icall_DefaultGodotTaskScheduler", godot_icall_DefaultGodotTaskScheduler);
}
#endif // MONO_GLUE_ENABLED
diff --git a/modules/mono/glue/gd_glue.h b/modules/mono/glue/gd_glue.h
deleted file mode 100644
index f00e2efc5d..0000000000
--- a/modules/mono/glue/gd_glue.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/*************************************************************************/
-/* gd_glue.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef GD_GLUE_H
-#define GD_GLUE_H
-
-#ifdef MONO_GLUE_ENABLED
-
-#include "../mono_gd/gd_mono_marshal.h"
-
-MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes, MonoBoolean p_allow_objects);
-
-MonoObject *godot_icall_GD_convert(MonoObject *p_what, int32_t p_type);
-
-int godot_icall_GD_hash(MonoObject *p_var);
-
-MonoObject *godot_icall_GD_instance_from_id(uint64_t p_instance_id);
-
-void godot_icall_GD_print(MonoArray *p_what);
-
-void godot_icall_GD_printerr(MonoArray *p_what);
-
-void godot_icall_GD_printraw(MonoArray *p_what);
-
-void godot_icall_GD_prints(MonoArray *p_what);
-
-void godot_icall_GD_printt(MonoArray *p_what);
-
-float godot_icall_GD_randf();
-
-uint32_t godot_icall_GD_randi();
-
-void godot_icall_GD_randomize();
-
-double godot_icall_GD_rand_range(double from, double to);
-
-uint32_t godot_icall_GD_rand_seed(uint64_t seed, uint64_t *newSeed);
-
-void godot_icall_GD_seed(uint64_t p_seed);
-
-MonoString *godot_icall_GD_str(MonoArray *p_what);
-
-MonoObject *godot_icall_GD_str2var(MonoString *p_str);
-
-MonoBoolean godot_icall_GD_type_exists(MonoString *p_type);
-
-MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_objects);
-
-MonoString *godot_icall_GD_var2str(MonoObject *p_var);
-
-MonoObject *godot_icall_DefaultGodotTaskScheduler();
-
-// Register internal calls
-
-void godot_register_gd_icalls();
-
-#endif // MONO_GLUE_ENABLED
-
-#endif // GD_GLUE_H
diff --git a/modules/mono/glue/glue_header.h b/modules/mono/glue/glue_header.h
index 758b71f719..eed3bd2167 100644
--- a/modules/mono/glue/glue_header.h
+++ b/modules/mono/glue/glue_header.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,12 +30,16 @@
#ifdef MONO_GLUE_ENABLED
-#include "base_object_glue.h"
-#include "collections_glue.h"
-#include "gd_glue.h"
-#include "nodepath_glue.h"
-#include "rid_glue.h"
-#include "string_glue.h"
+#include "../mono_gd/gd_mono_marshal.h"
+
+void godot_register_collections_icalls();
+void godot_register_gd_icalls();
+void godot_register_string_name_icalls();
+void godot_register_nodepath_icalls();
+void godot_register_object_icalls();
+void godot_register_rid_icalls();
+void godot_register_string_icalls();
+void godot_register_scene_tree_icalls();
/**
* Registers internal calls that were not generated. This function is called
@@ -44,31 +48,32 @@
void godot_register_glue_header_icalls() {
godot_register_collections_icalls();
godot_register_gd_icalls();
+ godot_register_string_name_icalls();
godot_register_nodepath_icalls();
godot_register_object_icalls();
godot_register_rid_icalls();
godot_register_string_icalls();
+ godot_register_scene_tree_icalls();
}
// Used by the generated glue
-#include "core/array.h"
-#include "core/class_db.h"
-#include "core/dictionary.h"
-#include "core/engine.h"
-#include "core/method_bind.h"
-#include "core/node_path.h"
-#include "core/object.h"
-#include "core/reference.h"
+#include "core/config/engine.h"
+#include "core/object/class_db.h"
+#include "core/object/method_bind.h"
+#include "core/object/ref_counted.h"
+#include "core/string/node_path.h"
+#include "core/string/ustring.h"
#include "core/typedefs.h"
-#include "core/ustring.h"
+#include "core/variant/array.h"
+#include "core/variant/dictionary.h"
#include "../mono_gd/gd_mono_class.h"
#include "../mono_gd/gd_mono_internals.h"
#include "../mono_gd/gd_mono_utils.h"
#define GODOTSHARP_INSTANCE_OBJECT(m_instance, m_type) \
- static ClassDB::ClassInfo *ci = NULL; \
+ static ClassDB::ClassInfo *ci = nullptr; \
if (!ci) { \
ci = ClassDB::classes.getptr(m_type); \
} \
diff --git a/modules/mono/glue/nodepath_glue.cpp b/modules/mono/glue/nodepath_glue.cpp
index e413f404d8..4ddb94e1a8 100644
--- a/modules/mono/glue/nodepath_glue.cpp
+++ b/modules/mono/glue/nodepath_glue.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,11 +28,12 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "nodepath_glue.h"
-
#ifdef MONO_GLUE_ENABLED
-#include "core/ustring.h"
+#include "core/string/node_path.h"
+#include "core/string/ustring.h"
+
+#include "../mono_gd/gd_mono_marshal.h"
NodePath *godot_icall_NodePath_Ctor(MonoString *p_path) {
return memnew(NodePath(GDMonoMarshal::mono_string_to_godot(p_path)));
@@ -51,7 +52,7 @@ MonoBoolean godot_icall_NodePath_is_absolute(NodePath *p_ptr) {
return (MonoBoolean)p_ptr->is_absolute();
}
-uint32_t godot_icall_NodePath_get_name_count(NodePath *p_ptr) {
+int32_t godot_icall_NodePath_get_name_count(NodePath *p_ptr) {
return p_ptr->get_name_count();
}
@@ -59,7 +60,7 @@ MonoString *godot_icall_NodePath_get_name(NodePath *p_ptr, uint32_t p_idx) {
return GDMonoMarshal::mono_string_from_godot(p_ptr->get_name(p_idx));
}
-uint32_t godot_icall_NodePath_get_subname_count(NodePath *p_ptr) {
+int32_t godot_icall_NodePath_get_subname_count(NodePath *p_ptr) {
return p_ptr->get_subname_count();
}
@@ -80,17 +81,17 @@ MonoBoolean godot_icall_NodePath_is_empty(NodePath *p_ptr) {
}
void godot_register_nodepath_icalls() {
- mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_Ctor", (void *)godot_icall_NodePath_Ctor);
- mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_Dtor", (void *)godot_icall_NodePath_Dtor);
- mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_operator_String", (void *)godot_icall_NodePath_operator_String);
- mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_as_property_path", (void *)godot_icall_NodePath_get_as_property_path);
- mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_concatenated_subnames", (void *)godot_icall_NodePath_get_concatenated_subnames);
- mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name", (void *)godot_icall_NodePath_get_name);
- mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name_count", (void *)godot_icall_NodePath_get_name_count);
- mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname", (void *)godot_icall_NodePath_get_subname);
- mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname_count", (void *)godot_icall_NodePath_get_subname_count);
- mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_is_absolute", (void *)godot_icall_NodePath_is_absolute);
- mono_add_internal_call("Godot.NodePath::godot_icall_NodePath_is_empty", (void *)godot_icall_NodePath_is_empty);
+ GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_Ctor", godot_icall_NodePath_Ctor);
+ GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_Dtor", godot_icall_NodePath_Dtor);
+ GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_operator_String", godot_icall_NodePath_operator_String);
+ GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_as_property_path", godot_icall_NodePath_get_as_property_path);
+ GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_concatenated_subnames", godot_icall_NodePath_get_concatenated_subnames);
+ GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name", godot_icall_NodePath_get_name);
+ GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name_count", godot_icall_NodePath_get_name_count);
+ GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname", godot_icall_NodePath_get_subname);
+ GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname_count", godot_icall_NodePath_get_subname_count);
+ GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_is_absolute", godot_icall_NodePath_is_absolute);
+ GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_is_empty", godot_icall_NodePath_is_empty);
}
#endif // MONO_GLUE_ENABLED
diff --git a/modules/mono/glue/rid_glue.cpp b/modules/mono/glue/rid_glue.cpp
index 66a49d8fec..f464e63a81 100644
--- a/modules/mono/glue/rid_glue.cpp
+++ b/modules/mono/glue/rid_glue.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,17 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "rid_glue.h"
-
#ifdef MONO_GLUE_ENABLED
-#include "core/resource.h"
+#include "core/io/resource.h"
+#include "core/object/class_db.h"
+#include "core/templates/rid.h"
+
+#include "../mono_gd/gd_mono_marshal.h"
RID *godot_icall_RID_Ctor(Object *p_from) {
Resource *res_from = Object::cast_to<Resource>(p_from);
- if (res_from)
+ if (res_from) {
return memnew(RID(res_from->get_rid()));
+ }
return memnew(RID);
}
@@ -53,9 +56,9 @@ uint32_t godot_icall_RID_get_id(RID *p_ptr) {
}
void godot_register_rid_icalls() {
- mono_add_internal_call("Godot.RID::godot_icall_RID_Ctor", (void *)godot_icall_RID_Ctor);
- mono_add_internal_call("Godot.RID::godot_icall_RID_Dtor", (void *)godot_icall_RID_Dtor);
- mono_add_internal_call("Godot.RID::godot_icall_RID_get_id", (void *)godot_icall_RID_get_id);
+ GDMonoUtils::add_internal_call("Godot.RID::godot_icall_RID_Ctor", godot_icall_RID_Ctor);
+ GDMonoUtils::add_internal_call("Godot.RID::godot_icall_RID_Dtor", godot_icall_RID_Dtor);
+ GDMonoUtils::add_internal_call("Godot.RID::godot_icall_RID_get_id", godot_icall_RID_get_id);
}
#endif // MONO_GLUE_ENABLED
diff --git a/modules/mono/glue/string_glue.h b/modules/mono/glue/scene_tree_glue.cpp
index a5e833ba61..5a6fd69db8 100644
--- a/modules/mono/glue/string_glue.h
+++ b/modules/mono/glue/scene_tree_glue.cpp
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* string_glue.h */
+/* scene_tree_glue.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,29 +28,59 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef STRING_GLUE_H
-#define STRING_GLUE_H
-
#ifdef MONO_GLUE_ENABLED
-#include "../mono_gd/gd_mono_marshal.h"
+#include "core/object/class_db.h"
+#include "core/string/string_name.h"
+#include "core/variant/array.h"
+#include "scene/main/node.h"
+#include "scene/main/scene_tree.h"
-MonoArray *godot_icall_String_md5_buffer(MonoString *p_str);
+#include "../csharp_script.h"
+#include "../mono_gd/gd_mono_marshal.h"
+#include "../mono_gd/gd_mono_utils.h"
-MonoString *godot_icall_String_md5_text(MonoString *p_str);
+Array *godot_icall_SceneTree_get_nodes_in_group_Generic(SceneTree *ptr, StringName *group, MonoReflectionType *refltype) {
+ List<Node *> nodes;
+ Array ret;
-int godot_icall_String_rfind(MonoString *p_str, MonoString *p_what, int p_from);
+ // Retrieve all the nodes in the group
+ ptr->get_nodes_in_group(*group, &nodes);
-int godot_icall_String_rfindn(MonoString *p_str, MonoString *p_what, int p_from);
+ // No need to bother if the group is empty
+ if (!nodes.is_empty()) {
+ MonoType *elem_type = mono_reflection_type_get_type(refltype);
+ MonoClass *mono_class = mono_class_from_mono_type(elem_type);
+ GDMonoClass *klass = GDMono::get_singleton()->get_class(mono_class);
-MonoArray *godot_icall_String_sha256_buffer(MonoString *p_str);
+ if (klass == GDMonoUtils::get_class_native_base(klass)) {
+ // If we're trying to get native objects, just check the inheritance list
+ StringName native_class_name = GDMonoUtils::get_native_godot_class_name(klass);
+ for (int i = 0; i < nodes.size(); ++i) {
+ if (ClassDB::is_parent_class(nodes[i]->get_class(), native_class_name)) {
+ ret.push_back(nodes[i]);
+ }
+ }
+ } else {
+ // If we're trying to get csharpscript instances, get the mono object and compare the classes
+ for (int i = 0; i < nodes.size(); ++i) {
+ CSharpInstance *si = CAST_CSHARP_INSTANCE(nodes[i]->get_script_instance());
-MonoString *godot_icall_String_sha256_text(MonoString *p_str);
+ if (si != nullptr) {
+ MonoObject *obj = si->get_mono_object();
+ if (obj != nullptr && mono_object_get_class(obj) == mono_class) {
+ ret.push_back(nodes[i]);
+ }
+ }
+ }
+ }
+ }
-// Register internal calls
+ return memnew(Array(ret));
+}
-void godot_register_string_icalls();
+void godot_register_scene_tree_icalls() {
+ GDMonoUtils::add_internal_call("Godot.SceneTree::godot_icall_SceneTree_get_nodes_in_group_Generic", godot_icall_SceneTree_get_nodes_in_group_Generic);
+}
#endif // MONO_GLUE_ENABLED
-
-#endif // STRING_GLUE_H
diff --git a/modules/mono/glue/string_glue.cpp b/modules/mono/glue/string_glue.cpp
index e407a70db9..18a9221f89 100644
--- a/modules/mono/glue/string_glue.cpp
+++ b/modules/mono/glue/string_glue.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,13 +28,13 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "string_glue.h"
-
#ifdef MONO_GLUE_ENABLED
-#include "core/ustring.h"
-#include "core/variant.h"
-#include "core/vector.h"
+#include "core/string/ustring.h"
+#include "core/templates/vector.h"
+#include "core/variant/variant.h"
+
+#include "../mono_gd/gd_mono_marshal.h"
MonoArray *godot_icall_String_md5_buffer(MonoString *p_str) {
Vector<uint8_t> ret = GDMonoMarshal::mono_string_to_godot(p_str).md5_buffer();
@@ -68,12 +68,12 @@ MonoString *godot_icall_String_sha256_text(MonoString *p_str) {
}
void godot_register_string_icalls() {
- mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", (void *)godot_icall_String_md5_buffer);
- mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", (void *)godot_icall_String_md5_text);
- mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfind", (void *)godot_icall_String_rfind);
- mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", (void *)godot_icall_String_rfindn);
- mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", (void *)godot_icall_String_sha256_buffer);
- mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", (void *)godot_icall_String_sha256_text);
+ GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", godot_icall_String_md5_buffer);
+ GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", godot_icall_String_md5_text);
+ GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_rfind", godot_icall_String_rfind);
+ GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", godot_icall_String_rfindn);
+ GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", godot_icall_String_sha256_buffer);
+ GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", godot_icall_String_sha256_text);
}
#endif // MONO_GLUE_ENABLED
diff --git a/modules/mono/glue/nodepath_glue.h b/modules/mono/glue/string_name_glue.cpp
index 727679c278..f537896559 100644
--- a/modules/mono/glue/nodepath_glue.h
+++ b/modules/mono/glue/string_name_glue.cpp
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* nodepath_glue.h */
+/* string_name_glue.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,41 +28,35 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef NODEPATH_GLUE_H
-#define NODEPATH_GLUE_H
-
#ifdef MONO_GLUE_ENABLED
-#include "core/node_path.h"
+#include "core/string/string_name.h"
+#include "core/string/ustring.h"
#include "../mono_gd/gd_mono_marshal.h"
-NodePath *godot_icall_NodePath_Ctor(MonoString *p_path);
-
-void godot_icall_NodePath_Dtor(NodePath *p_ptr);
-
-MonoString *godot_icall_NodePath_operator_String(NodePath *p_np);
-
-MonoBoolean godot_icall_NodePath_is_absolute(NodePath *p_ptr);
-
-uint32_t godot_icall_NodePath_get_name_count(NodePath *p_ptr);
+StringName *godot_icall_StringName_Ctor(MonoString *p_path) {
+ return memnew(StringName(GDMonoMarshal::mono_string_to_godot(p_path)));
+}
-MonoString *godot_icall_NodePath_get_name(NodePath *p_ptr, uint32_t p_idx);
+void godot_icall_StringName_Dtor(StringName *p_ptr) {
+ ERR_FAIL_NULL(p_ptr);
+ memdelete(p_ptr);
+}
-uint32_t godot_icall_NodePath_get_subname_count(NodePath *p_ptr);
+MonoString *godot_icall_StringName_operator_String(StringName *p_np) {
+ return GDMonoMarshal::mono_string_from_godot(p_np->operator String());
+}
-MonoString *godot_icall_NodePath_get_subname(NodePath *p_ptr, uint32_t p_idx);
+MonoBoolean godot_icall_StringName_is_empty(StringName *p_ptr) {
+ return (MonoBoolean)(*p_ptr == StringName());
+}
-MonoString *godot_icall_NodePath_get_concatenated_subnames(NodePath *p_ptr);
-
-NodePath *godot_icall_NodePath_get_as_property_path(NodePath *p_ptr);
-
-MonoBoolean godot_icall_NodePath_is_empty(NodePath *p_ptr);
-
-// Register internal calls
-
-void godot_register_nodepath_icalls();
+void godot_register_string_name_icalls() {
+ GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_Ctor", godot_icall_StringName_Ctor);
+ GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_Dtor", godot_icall_StringName_Dtor);
+ GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_operator_String", godot_icall_StringName_operator_String);
+ GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_is_empty", godot_icall_StringName_is_empty);
+}
#endif // MONO_GLUE_ENABLED
-
-#endif // NODEPATH_GLUE_H
diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h
index 7d57d0fac3..273dba52f9 100644
--- a/modules/mono/godotsharp_defs.h
+++ b/modules/mono/godotsharp_defs.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 47eb432490..375ad413c4 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,9 +30,9 @@
#include "godotsharp_dirs.h"
-#include "core/os/dir_access.h"
+#include "core/config/project_settings.h"
+#include "core/io/dir_access.h"
#include "core/os/os.h"
-#include "core/project_settings.h"
#ifdef TOOLS_ENABLED
#include "core/version.h"
@@ -40,7 +40,7 @@
#endif
#ifdef ANDROID_ENABLED
-#include "mono_gd/gd_mono_android.h"
+#include "mono_gd/support/android_support.h"
#endif
#include "mono_gd/gd_mono.h"
@@ -49,13 +49,13 @@ namespace GodotSharpDirs {
String _get_expected_build_config() {
#ifdef TOOLS_ENABLED
- return "Tools";
+ return "Debug";
#else
#ifdef DEBUG_ENABLED
- return "Debug";
+ return "ExportDebug";
#else
- return "Release";
+ return "ExportRelease";
#endif
#endif
@@ -63,8 +63,8 @@ String _get_expected_build_config() {
String _get_mono_user_dir() {
#ifdef TOOLS_ENABLED
- if (EditorSettings::get_singleton()) {
- return EditorSettings::get_singleton()->get_data_dir().plus_file("mono");
+ if (EditorPaths::get_singleton()) {
+ return EditorPaths::get_singleton()->get_data_dir().plus_file("mono");
} else {
String settings_path;
@@ -86,7 +86,6 @@ String _get_mono_user_dir() {
}
class _GodotSharpDirs {
-
public:
String res_data_dir;
String res_metadata_dir;
@@ -123,7 +122,7 @@ public:
private:
_GodotSharpDirs() {
- res_data_dir = "res://.mono";
+ res_data_dir = "res://.godot/mono";
res_metadata_dir = res_data_dir.plus_file("metadata");
res_assemblies_base_dir = res_data_dir.plus_file("assemblies");
res_assemblies_dir = res_assemblies_base_dir.plus_file(GDMono::get_expected_api_build_config());
@@ -147,7 +146,7 @@ private:
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
- if (appname_safe.empty()) {
+ if (appname_safe.is_empty()) {
appname_safe = "UnnamedProject";
}
@@ -169,7 +168,7 @@ private:
data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
#ifdef ANDROID_ENABLED
- data_mono_lib_dir = GDMonoAndroid::get_app_native_lib_dir();
+ data_mono_lib_dir = gdmono::android::support::get_app_native_lib_dir();
#else
data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
#endif
@@ -180,16 +179,16 @@ private:
#ifdef OSX_ENABLED
if (!DirAccess::exists(data_editor_tools_dir)) {
- data_editor_tools_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Tools");
+ data_editor_tools_dir = exe_dir.plus_file("../Resources/GodotSharp/Tools");
}
if (!DirAccess::exists(data_editor_prebuilt_api_dir)) {
- data_editor_prebuilt_api_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Api");
+ data_editor_prebuilt_api_dir = exe_dir.plus_file("../Resources/GodotSharp/Api");
}
if (!DirAccess::exists(data_mono_root_dir)) {
data_mono_etc_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/etc");
- data_mono_lib_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Mono/lib");
+ data_mono_lib_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/lib");
}
#endif
@@ -206,7 +205,7 @@ private:
data_mono_etc_dir = data_mono_root_dir.plus_file("etc");
#ifdef ANDROID_ENABLED
- data_mono_lib_dir = GDMonoAndroid::get_app_native_lib_dir();
+ data_mono_lib_dir = gdmono::android::support::get_app_native_lib_dir();
#else
data_mono_lib_dir = data_mono_root_dir.plus_file("lib");
data_game_assemblies_dir = data_dir_root.plus_file("Assemblies");
@@ -219,11 +218,11 @@ private:
#ifdef OSX_ENABLED
if (!DirAccess::exists(data_mono_root_dir)) {
data_mono_etc_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/etc");
- data_mono_lib_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Mono/lib");
+ data_mono_lib_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/lib");
}
if (!DirAccess::exists(data_game_assemblies_dir)) {
- data_game_assemblies_dir = exe_dir.plus_file("../Frameworks/GodotSharp/Assemblies");
+ data_game_assemblies_dir = exe_dir.plus_file("../Resources/GodotSharp/Assemblies");
}
#endif
@@ -323,5 +322,4 @@ String get_data_mono_bin_dir() {
return _GodotSharpDirs::get_singleton().data_mono_bin_dir;
}
#endif
-
} // namespace GodotSharpDirs
diff --git a/modules/mono/godotsharp_dirs.h b/modules/mono/godotsharp_dirs.h
index 2ab4b0e309..3a3c6f980e 100644
--- a/modules/mono/godotsharp_dirs.h
+++ b/modules/mono/godotsharp_dirs.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,7 +31,7 @@
#ifndef GODOTSHARP_DIRS_H
#define GODOTSHARP_DIRS_H
-#include "core/ustring.h"
+#include "core/string/ustring.h"
namespace GodotSharpDirs {
@@ -66,7 +66,6 @@ String get_data_mono_lib_dir();
#ifdef WINDOWS_ENABLED
String get_data_mono_bin_dir();
#endif
-
} // namespace GodotSharpDirs
#endif // GODOTSHARP_DIRS_H
diff --git a/modules/mono/icons/CSharpScript.svg b/modules/mono/icons/CSharpScript.svg
new file mode 100644
index 0000000000..0b2cc840f8
--- /dev/null
+++ b/modules/mono/icons/CSharpScript.svg
@@ -0,0 +1 @@
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m6 1046.4c-1.6569 0-3 1.3431-3 3s1.3431 3 3 3h1v-2h-1c-.55228 0-1-.4478-1-1 0-.5523.44772-1 1-1h1v-2zm1-9-.56445 2.2578c-.23643.076-.46689.1692-.68945.2793l-1.9883-1.1933-1.4141 1.414 1.1953 1.9942c-.11191.2211-.20723.4502-.28516.6855l-2.2539.5625v2h5.2715c-.17677-.3037-.27041-.6486-.27148-1 .0000096-1.1046.89543-2 2-2s2 .8954 2 2c-.0004817.3512-.093442.6961-.26953 1h5.2695v-2l-2.2578-.5645c-.07594-.2357-.1693-.4655-.2793-.6875l1.1934-1.9902-1.4141-1.414-1.9941 1.1953c-.22113-.1119-.45028-.2073-.68555-.2852l-.5625-2.2539zm4 9c-.71466-.0001-1.3751.3811-1.7324 1-.35727.6188-.35727 1.3812 0 2 .35733.6189 1.0178 1.0001 1.7324 1h-2v2h2c.71466.0001 1.3751-.3811 1.7324-1 .35727-.6188.35727-1.3812 0-2-.35733-.6189-1.0178-1.0001-1.7324-1h2v-2z" fill="#e0e0e0" transform="translate(0 -1036.4)"/></svg>
diff --git a/modules/mono/icons/icon_c_sharp_script.svg b/modules/mono/icons/icon_c_sharp_script.svg
deleted file mode 100644
index 69664ca553..0000000000
--- a/modules/mono/icons/icon_c_sharp_script.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
-<g transform="translate(0 -1036.4)">
-<path d="m6 1046.4c-1.6569 0-3 1.3431-3 3s1.3431 3 3 3h1v-2h-1c-0.55228 0-1-0.4478-1-1 0-0.5523 0.44772-1 1-1h1v-2zm1-9-0.56445 2.2578c-0.23643 0.076-0.46689 0.1692-0.68945 0.2793l-1.9883-1.1933-1.4141 1.414 1.1953 1.9942c-0.11191 0.2211-0.20723 0.4502-0.28516 0.6855l-2.2539 0.5625v2h5.2715c-0.17677-0.3037-0.27041-0.6486-0.27148-1 9.6e-6 -1.1046 0.89543-2 2-2s2 0.8954 2 2c-4.817e-4 0.3512-0.093442 0.6961-0.26953 1h5.2695v-2l-2.2578-0.5645c-0.07594-0.2357-0.1693-0.4655-0.2793-0.6875l1.1934-1.9902-1.4141-1.414-1.9941 1.1953c-0.22113-0.1119-0.45028-0.2073-0.68555-0.2852l-0.5625-2.2539zm4 9c-0.71466-1e-4 -1.3751 0.3811-1.7324 1-0.35727 0.6188-0.35727 1.3812 0 2 0.35733 0.6189 1.0178 1.0001 1.7324 1h-2v2h2c0.71466 1e-4 1.3751-0.3811 1.7324-1 0.35727-0.6188 0.35727-1.3812 0-2-0.35733-0.6189-1.0178-1.0001-1.7324-1h2v-2z" fill="#e0e0e0"/>
-</g>
-</svg>
diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp
new file mode 100644
index 0000000000..6d868b527c
--- /dev/null
+++ b/modules/mono/managed_callable.cpp
@@ -0,0 +1,148 @@
+/*************************************************************************/
+/* managed_callable.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "managed_callable.h"
+
+#include "csharp_script.h"
+#include "mono_gd/gd_mono_marshal.h"
+#include "mono_gd/gd_mono_utils.h"
+
+#ifdef GD_MONO_HOT_RELOAD
+SelfList<ManagedCallable>::List ManagedCallable::instances;
+Map<ManagedCallable *, Array> ManagedCallable::instances_pending_reload;
+Mutex ManagedCallable::instances_mutex;
+#endif
+
+bool ManagedCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
+ const ManagedCallable *a = static_cast<const ManagedCallable *>(p_a);
+ const ManagedCallable *b = static_cast<const ManagedCallable *>(p_b);
+
+ MonoDelegate *delegate_a = (MonoDelegate *)a->delegate_handle.get_target();
+ MonoDelegate *delegate_b = (MonoDelegate *)b->delegate_handle.get_target();
+
+ if (!delegate_a || !delegate_b) {
+ if (!delegate_a && !delegate_b) {
+ return true;
+ }
+ return false;
+ }
+
+ // Call Delegate's 'Equals'
+ return GDMonoUtils::mono_delegate_equal(delegate_a, delegate_b);
+}
+
+bool ManagedCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
+ if (compare_equal(p_a, p_b)) {
+ return false;
+ }
+ return p_a < p_b;
+}
+
+uint32_t ManagedCallable::hash() const {
+ // hmm
+ uint32_t hash = delegate_invoke->get_name().hash();
+ return hash_djb2_one_64(delegate_handle.handle, hash);
+}
+
+String ManagedCallable::get_as_text() const {
+ return "Delegate::Invoke";
+}
+
+CallableCustom::CompareEqualFunc ManagedCallable::get_compare_equal_func() const {
+ return compare_equal_func_ptr;
+}
+
+CallableCustom::CompareLessFunc ManagedCallable::get_compare_less_func() const {
+ return compare_less_func_ptr;
+}
+
+ObjectID ManagedCallable::get_object() const {
+ // TODO: If the delegate target extends Godot.Object, use that instead!
+ return CSharpLanguage::get_singleton()->get_managed_callable_middleman()->get_instance_id();
+}
+
+void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
+ r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better
+ r_return_value = Variant();
+
+#ifdef GD_MONO_HOT_RELOAD
+ // Lost during hot-reload
+ ERR_FAIL_NULL(delegate_invoke);
+ ERR_FAIL_COND(delegate_handle.is_released());
+#endif
+
+ ERR_FAIL_COND(delegate_invoke->get_parameters_count() < p_argcount);
+
+ MonoObject *delegate = delegate_handle.get_target();
+
+ MonoException *exc = nullptr;
+ MonoObject *ret = delegate_invoke->invoke(delegate, p_arguments, &exc);
+
+ if (exc) {
+ GDMonoUtils::set_pending_exception(exc);
+ } else {
+ r_return_value = GDMonoMarshal::mono_object_to_variant(ret);
+ r_call_error.error = Callable::CallError::CALL_OK;
+ }
+}
+
+void ManagedCallable::set_delegate(MonoDelegate *p_delegate) {
+ delegate_handle = MonoGCHandleData::new_strong_handle((MonoObject *)p_delegate);
+ MonoMethod *delegate_invoke_raw = mono_get_delegate_invoke(mono_object_get_class((MonoObject *)p_delegate));
+ const StringName &delegate_invoke_name = CSharpLanguage::get_singleton()->get_string_names().delegate_invoke_method_name;
+ delegate_invoke = memnew(GDMonoMethod(delegate_invoke_name, delegate_invoke_raw)); // TODO: Use pooling for this GDMonoMethod instances
+}
+
+ManagedCallable::ManagedCallable(MonoDelegate *p_delegate) {
+#ifdef DEBUG_ENABLED
+ CRASH_COND(p_delegate == nullptr);
+#endif
+
+ set_delegate(p_delegate);
+
+#ifdef GD_MONO_HOT_RELOAD
+ {
+ MutexLock lock(instances_mutex);
+ instances.add(&self_instance);
+ }
+#endif
+}
+
+ManagedCallable::~ManagedCallable() {
+#ifdef GD_MONO_HOT_RELOAD
+ {
+ MutexLock lock(instances_mutex);
+ instances.remove(&self_instance);
+ instances_pending_reload.erase(this);
+ }
+#endif
+
+ delegate_handle.release();
+}
diff --git a/modules/mono/utils/mutex_utils.h b/modules/mono/managed_callable.h
index bafd875395..c620eee60d 100644
--- a/modules/mono/utils/mutex_utils.h
+++ b/modules/mono/managed_callable.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* mutex_utils.h */
+/* managed_callable.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,40 +28,50 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef MUTEX_UTILS_H
-#define MUTEX_UTILS_H
+#ifndef MANAGED_CALLABLE_H
+#define MANAGED_CALLABLE_H
+
+#include <mono/metadata/object.h>
-#include "core/error_macros.h"
#include "core/os/mutex.h"
+#include "core/templates/self_list.h"
+#include "core/variant/callable.h"
-#include "macros.h"
+#include "mono_gc_handle.h"
+#include "mono_gd/gd_mono_method.h"
-class ScopedMutexLock {
- Mutex *mutex;
+class ManagedCallable : public CallableCustom {
+ friend class CSharpLanguage;
+ MonoGCHandleData delegate_handle;
+ GDMonoMethod *delegate_invoke;
-public:
- ScopedMutexLock(Mutex *mutex) {
- this->mutex = mutex;
-#ifndef NO_THREADS
-#ifdef DEBUG_ENABLED
- CRASH_COND(!mutex);
-#endif
- this->mutex->lock();
+#ifdef GD_MONO_HOT_RELOAD
+ SelfList<ManagedCallable> self_instance = this;
+ static SelfList<ManagedCallable>::List instances;
+ static Map<ManagedCallable *, Array> instances_pending_reload;
+ static Mutex instances_mutex;
#endif
- }
- ~ScopedMutexLock() {
-#ifndef NO_THREADS
-#ifdef DEBUG_ENABLED
- CRASH_COND(!mutex);
-#endif
- mutex->unlock();
-#endif
- }
-};
+public:
+ uint32_t hash() const override;
+ String get_as_text() const override;
+ CompareEqualFunc get_compare_equal_func() const override;
+ CompareLessFunc get_compare_less_func() const override;
+ ObjectID get_object() const override;
+ void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
+
+ _FORCE_INLINE_ MonoDelegate *get_delegate() { return (MonoDelegate *)delegate_handle.get_target(); }
+
+ void set_delegate(MonoDelegate *p_delegate);
-#define SCOPED_MUTEX_LOCK(m_mutex) ScopedMutexLock GD_UNIQUE_NAME(__scoped_mutex_lock__)(m_mutex);
+ static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
+ static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
-// TODO: Add version that receives a lambda instead, once C++11 is allowed
+ static constexpr CompareEqualFunc compare_equal_func_ptr = &ManagedCallable::compare_equal;
+ static constexpr CompareEqualFunc compare_less_func_ptr = &ManagedCallable::compare_less;
+
+ ManagedCallable(MonoDelegate *p_delegate);
+ ~ManagedCallable();
+};
-#endif // MUTEX_UTILS_H
+#endif // MANAGED_CALLABLE_H
diff --git a/modules/mono/mono_gc_handle.cpp b/modules/mono/mono_gc_handle.cpp
index feeea848ee..8583065016 100644
--- a/modules/mono/mono_gc_handle.cpp
+++ b/modules/mono/mono_gc_handle.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -32,56 +32,33 @@
#include "mono_gd/gd_mono.h"
-uint32_t MonoGCHandle::new_strong_handle(MonoObject *p_object) {
-
- return mono_gchandle_new(p_object, /* pinned: */ false);
-}
-
-uint32_t MonoGCHandle::new_strong_handle_pinned(MonoObject *p_object) {
-
- return mono_gchandle_new(p_object, /* pinned: */ true);
-}
-
-uint32_t MonoGCHandle::new_weak_handle(MonoObject *p_object) {
-
- return mono_gchandle_new_weakref(p_object, /* track_resurrection: */ false);
-}
-
-void MonoGCHandle::free_handle(uint32_t p_gchandle) {
+void MonoGCHandleData::release() {
+#ifdef DEBUG_ENABLED
+ CRASH_COND(handle && GDMono::get_singleton() == nullptr);
+#endif
- mono_gchandle_free(p_gchandle);
+ if (handle && GDMono::get_singleton()->is_runtime_initialized()) {
+ GDMonoUtils::free_gchandle(handle);
+ handle = 0;
+ }
}
-Ref<MonoGCHandle> MonoGCHandle::create_strong(MonoObject *p_object) {
-
- return memnew(MonoGCHandle(new_strong_handle(p_object), STRONG_HANDLE));
+MonoGCHandleData MonoGCHandleData::new_strong_handle(MonoObject *p_object) {
+ return MonoGCHandleData(GDMonoUtils::new_strong_gchandle(p_object), gdmono::GCHandleType::STRONG_HANDLE);
}
-Ref<MonoGCHandle> MonoGCHandle::create_weak(MonoObject *p_object) {
-
- return memnew(MonoGCHandle(new_weak_handle(p_object), WEAK_HANDLE));
+MonoGCHandleData MonoGCHandleData::new_strong_handle_pinned(MonoObject *p_object) {
+ return MonoGCHandleData(GDMonoUtils::new_strong_gchandle_pinned(p_object), gdmono::GCHandleType::STRONG_HANDLE);
}
-void MonoGCHandle::release() {
-
-#ifdef DEBUG_ENABLED
- CRASH_COND(!released && GDMono::get_singleton() == NULL);
-#endif
-
- if (!released && GDMono::get_singleton()->is_runtime_initialized()) {
- free_handle(handle);
- released = true;
- }
+MonoGCHandleData MonoGCHandleData::new_weak_handle(MonoObject *p_object) {
+ return MonoGCHandleData(GDMonoUtils::new_weak_gchandle(p_object), gdmono::GCHandleType::WEAK_HANDLE);
}
-MonoGCHandle::MonoGCHandle(uint32_t p_handle, HandleType p_handle_type) {
-
- released = false;
- weak = p_handle_type == WEAK_HANDLE;
- handle = p_handle;
+Ref<MonoGCHandleRef> MonoGCHandleRef::create_strong(MonoObject *p_object) {
+ return memnew(MonoGCHandleRef(MonoGCHandleData::new_strong_handle(p_object)));
}
-MonoGCHandle::~MonoGCHandle() {
-
- release();
+Ref<MonoGCHandleRef> MonoGCHandleRef::create_weak(MonoObject *p_object) {
+ return memnew(MonoGCHandleRef(MonoGCHandleData::new_weak_handle(p_object)));
}
diff --git a/modules/mono/mono_gc_handle.h b/modules/mono/mono_gc_handle.h
index 37fc7d8a17..d0e51d159f 100644
--- a/modules/mono/mono_gc_handle.h
+++ b/modules/mono/mono_gc_handle.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -33,44 +33,76 @@
#include <mono/jit/jit.h>
-#include "core/reference.h"
+#include "core/object/ref_counted.h"
-class MonoGCHandle : public Reference {
+namespace gdmono {
- GDCLASS(MonoGCHandle, Reference);
+enum class GCHandleType : char {
+ NIL,
+ STRONG_HANDLE,
+ WEAK_HANDLE
+};
+}
- bool released;
- bool weak;
- uint32_t handle;
+// Manual release of the GC handle must be done when using this struct
+struct MonoGCHandleData {
+ uint32_t handle = 0;
+ gdmono::GCHandleType type = gdmono::GCHandleType::NIL;
-public:
- enum HandleType {
- STRONG_HANDLE,
- WEAK_HANDLE
- };
+ _FORCE_INLINE_ bool is_released() const { return !handle; }
+ _FORCE_INLINE_ bool is_weak() const { return type == gdmono::GCHandleType::WEAK_HANDLE; }
- static uint32_t new_strong_handle(MonoObject *p_object);
- static uint32_t new_strong_handle_pinned(MonoObject *p_object);
- static uint32_t new_weak_handle(MonoObject *p_object);
- static void free_handle(uint32_t p_gchandle);
+ _FORCE_INLINE_ MonoObject *get_target() const { return handle ? mono_gchandle_get_target(handle) : nullptr; }
- static Ref<MonoGCHandle> create_strong(MonoObject *p_object);
- static Ref<MonoGCHandle> create_weak(MonoObject *p_object);
+ void release();
+
+ MonoGCHandleData &operator=(const MonoGCHandleData &p_other) {
+#ifdef DEBUG_ENABLED
+ CRASH_COND(!is_released());
+#endif
+ handle = p_other.handle;
+ type = p_other.type;
+ return *this;
+ }
- _FORCE_INLINE_ bool is_released() { return released; }
- _FORCE_INLINE_ bool is_weak() { return weak; }
+ MonoGCHandleData(const MonoGCHandleData &) = default;
- _FORCE_INLINE_ MonoObject *get_target() const { return released ? NULL : mono_gchandle_get_target(handle); }
+ MonoGCHandleData() {}
- _FORCE_INLINE_ void set_handle(uint32_t p_handle, HandleType p_handle_type) {
- released = false;
- weak = p_handle_type == WEAK_HANDLE;
- handle = p_handle;
+ MonoGCHandleData(uint32_t p_handle, gdmono::GCHandleType p_type) :
+ handle(p_handle),
+ type(p_type) {
}
- void release();
- MonoGCHandle(uint32_t p_handle, HandleType p_handle_type);
- ~MonoGCHandle();
+ static MonoGCHandleData new_strong_handle(MonoObject *p_object);
+ static MonoGCHandleData new_strong_handle_pinned(MonoObject *p_object);
+ static MonoGCHandleData new_weak_handle(MonoObject *p_object);
+};
+
+class MonoGCHandleRef : public RefCounted {
+ GDCLASS(MonoGCHandleRef, RefCounted);
+
+ MonoGCHandleData data;
+
+public:
+ static Ref<MonoGCHandleRef> create_strong(MonoObject *p_object);
+ static Ref<MonoGCHandleRef> create_weak(MonoObject *p_object);
+
+ _FORCE_INLINE_ bool is_released() const { return data.is_released(); }
+ _FORCE_INLINE_ bool is_weak() const { return data.is_weak(); }
+
+ _FORCE_INLINE_ MonoObject *get_target() const { return data.get_target(); }
+
+ void release() { data.release(); }
+
+ _FORCE_INLINE_ void set_handle(uint32_t p_handle, gdmono::GCHandleType p_handle_type) {
+ data = MonoGCHandleData(p_handle, p_handle_type);
+ }
+
+ MonoGCHandleRef(const MonoGCHandleData &p_gc_handle_data) :
+ data(p_gc_handle_data) {
+ }
+ ~MonoGCHandleRef() { release(); }
};
#endif // CSHARP_GC_HANDLE_H
diff --git a/modules/mono/mono_gd/android_mono_config.h b/modules/mono/mono_gd/android_mono_config.h
index 93f708bba0..9d7cfe1b7c 100644
--- a/modules/mono/mono_gd/android_mono_config.h
+++ b/modules/mono/mono_gd/android_mono_config.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -33,7 +33,7 @@
#ifdef ANDROID_ENABLED
-#include "core/ustring.h"
+#include "core/string/ustring.h"
// This function is defined in an auto-generated source file
String get_godot_android_mono_config();
diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp
index 60008f8fab..02d875f669 100644
--- a/modules/mono/mono_gd/gd_mono.cpp
+++ b/modules/mono/mono_gd/gd_mono.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -37,11 +37,12 @@
#include <mono/metadata/mono-gc.h>
#include <mono/metadata/profiler.h>
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
+#include "core/config/project_settings.h"
+#include "core/debugger/engine_debugger.h"
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
#include "core/os/os.h"
#include "core/os/thread.h"
-#include "core/project_settings.h"
#include "../csharp_script.h"
#include "../godotsharp_dirs.h"
@@ -57,14 +58,25 @@
#ifdef ANDROID_ENABLED
#include "android_mono_config.h"
-#include "gd_mono_android.h"
+#include "support/android_support.h"
+#elif defined(IPHONE_ENABLED)
+#include "support/ios_support.h"
+#endif
+
+#if defined(TOOL_ENABLED) && defined(GD_MONO_SINGLE_APPDOMAIN)
+// This will no longer be the case if we replace appdomains with AssemblyLoadContext
+#error "Editor build requires support for multiple appdomains"
+#endif
+
+#if defined(GD_MONO_HOT_RELOAD) && defined(GD_MONO_SINGLE_APPDOMAIN)
+#error "Hot reloading requires multiple appdomains"
#endif
// TODO:
// This has turn into a gigantic mess. There's too much going on here. Too much #ifdef as well.
// It's just painful to read... It needs to be re-structured. Please, clean this up, future me.
-GDMono *GDMono::singleton = NULL;
+GDMono *GDMono::singleton = nullptr;
namespace {
@@ -107,34 +119,34 @@ void gd_mono_profiler_init() {
const String env_var_name = "MONO_ENV_OPTIONS";
if (OS::get_singleton()->has_environment(env_var_name)) {
- const auto mono_env_ops = OS::get_singleton()->get_environment(env_var_name);
+ const String mono_env_ops = OS::get_singleton()->get_environment(env_var_name);
// Usually MONO_ENV_OPTIONS looks like: --profile=jb:prof=timeline,ctl=remote,host=127.0.0.1:55467
const String prefix = "--profile=";
if (mono_env_ops.begins_with(prefix)) {
- const auto ops = mono_env_ops.substr(prefix.length(), mono_env_ops.length());
+ const String ops = mono_env_ops.substr(prefix.length(), mono_env_ops.length());
mono_profiler_load(ops.utf8());
}
}
}
-#if defined(DEBUG_ENABLED)
-
void gd_mono_debug_init() {
-
- mono_debug_init(MONO_DEBUG_FORMAT_MONO);
-
CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
+ if (da_args.length()) {
+ OS::get_singleton()->set_environment("GODOT_MONO_DEBUGGER_AGENT", String());
+ }
+
#ifdef TOOLS_ENABLED
int da_port = GLOBAL_DEF("mono/debugger_agent/port", 23685);
bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false);
int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000);
if (Engine::get_singleton()->is_editor_hint() ||
- ProjectSettings::get_singleton()->get_resource_path().empty() ||
+ ProjectSettings::get_singleton()->get_resource_path().is_empty() ||
Main::is_project_manager()) {
- if (da_args.size() == 0)
+ if (da_args.size() == 0) {
return;
+ }
}
if (da_args.length() == 0) {
@@ -147,6 +159,10 @@ void gd_mono_debug_init() {
return; // Exported games don't use the project settings to setup the debugger agent
#endif
+ // Debugging enabled
+
+ mono_debug_init(MONO_DEBUG_FORMAT_MONO);
+
// --debugger-agent=help
const char *options[] = {
"--soft-breakpoints",
@@ -155,7 +171,6 @@ void gd_mono_debug_init() {
mono_jit_parse_options(2, (char **)options);
}
-#endif // defined(DEBUG_ENABLED)
#endif // !defined(JAVASCRIPT_ENABLED)
#if defined(JAVASCRIPT_ENABLED)
@@ -163,6 +178,7 @@ MonoDomain *gd_initialize_mono_runtime() {
const char *vfs_prefix = "managed";
int enable_debugging = 0;
+ // TODO: Provide a way to enable debugging on WASM release builds.
#ifdef DEBUG_ENABLED
enable_debugging = 1;
#endif
@@ -173,14 +189,18 @@ MonoDomain *gd_initialize_mono_runtime() {
}
#else
MonoDomain *gd_initialize_mono_runtime() {
-#ifdef DEBUG_ENABLED
gd_mono_debug_init();
+
+#if defined(IPHONE_ENABLED) || defined(ANDROID_ENABLED)
+ // I don't know whether this actually matters or not
+ const char *runtime_version = "mobile";
+#else
+ const char *runtime_version = "v4.0.30319";
#endif
- return mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319");
+ return mono_jit_init_version("GodotEngine.RootDomain", runtime_version);
}
#endif
-
} // namespace
void GDMono::add_mono_shared_libs_dir_to_path() {
@@ -230,7 +250,6 @@ void GDMono::add_mono_shared_libs_dir_to_path() {
}
void GDMono::determine_mono_dirs(String &r_assembly_rootdir, String &r_config_dir) {
-
String bundled_assembly_rootdir = GodotSharpDirs::get_data_mono_lib_dir();
String bundled_config_dir = GodotSharpDirs::get_data_mono_etc_dir();
@@ -278,7 +297,7 @@ void GDMono::determine_mono_dirs(String &r_assembly_rootdir, String &r_config_di
}
#ifdef WINDOWS_ENABLED
- if (r_assembly_rootdir.empty() || r_config_dir.empty()) {
+ if (r_assembly_rootdir.is_empty() || r_config_dir.is_empty()) {
ERR_PRINT("Cannot find Mono in the registry.");
// Assertion: if they are not set, then they weren't found in the registry
CRASH_COND(mono_reg_info.assembly_dir.length() > 0 || mono_reg_info.config_dir.length() > 0);
@@ -293,7 +312,6 @@ void GDMono::determine_mono_dirs(String &r_assembly_rootdir, String &r_config_di
}
void GDMono::initialize() {
-
ERR_FAIL_NULL(Engine::get_singleton());
print_verbose("Mono: Initializing module...");
@@ -313,14 +331,22 @@ void GDMono::initialize() {
determine_mono_dirs(assembly_rootdir, config_dir);
// Leak if we call mono_set_dirs more than once
- mono_set_dirs(assembly_rootdir.length() ? assembly_rootdir.utf8().get_data() : NULL,
- config_dir.length() ? config_dir.utf8().get_data() : NULL);
+ mono_set_dirs(assembly_rootdir.length() ? assembly_rootdir.utf8().get_data() : nullptr,
+ config_dir.length() ? config_dir.utf8().get_data() : nullptr);
add_mono_shared_libs_dir_to_path();
#endif
+#ifdef ANDROID_ENABLED
+ mono_config_parse_memory(get_godot_android_mono_config().utf8().get_data());
+#else
+ mono_config_parse(nullptr);
+#endif
+
#if defined(ANDROID_ENABLED)
- GDMonoAndroid::initialize();
+ gdmono::android::support::initialize();
+#elif defined(IPHONE_ENABLED)
+ gdmono::ios::support::initialize();
#endif
GDMonoAssembly::initialize();
@@ -329,18 +355,12 @@ void GDMono::initialize() {
gd_mono_profiler_init();
#endif
-#ifdef ANDROID_ENABLED
- mono_config_parse_memory(get_godot_android_mono_config().utf8().get_data());
-#else
- mono_config_parse(NULL);
-#endif
-
- mono_install_unhandled_exception_hook(&unhandled_exception_hook, NULL);
+ mono_install_unhandled_exception_hook(&unhandled_exception_hook, nullptr);
#ifndef TOOLS_ENABLED
// Exported games that don't use C# must still work. They likely don't ship with mscorlib.
// We only initialize the Mono runtime if we can find mscorlib. Otherwise it would crash.
- if (GDMonoAssembly::find_assembly("mscorlib.dll").empty()) {
+ if (GDMonoAssembly::find_assembly("mscorlib.dll").is_empty()) {
print_verbose("Mono: Skipping runtime initialization because 'mscorlib.dll' could not be found");
return;
}
@@ -354,7 +374,7 @@ void GDMono::initialize() {
#endif
// NOTE: Internal calls must be registered after the Mono runtime initialization.
- // Otherwise registration fails with the error: 'assertion 'hash != NULL' failed'.
+ // Otherwise registration fails with the error: 'assertion 'hash != nullptr' failed'.
root_domain = gd_initialize_mono_runtime();
ERR_FAIL_NULL_MSG(root_domain, "Mono: Failed to initialize runtime.");
@@ -370,15 +390,19 @@ void GDMono::initialize() {
print_verbose("Mono: Runtime initialized");
#if defined(ANDROID_ENABLED)
- GDMonoAndroid::register_internal_calls();
+ gdmono::android::support::register_internal_calls();
#endif
// mscorlib assembly MUST be present at initialization
bool corlib_loaded = _load_corlib_assembly();
ERR_FAIL_COND_MSG(!corlib_loaded, "Mono: Failed to load mscorlib assembly.");
+#ifndef GD_MONO_SINGLE_APPDOMAIN
Error domain_load_err = _load_scripts_domain();
ERR_FAIL_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain.");
+#else
+ scripts_domain = root_domain;
+#endif
_register_internal_calls();
@@ -386,7 +410,6 @@ void GDMono::initialize() {
}
void GDMono::initialize_load_assemblies() {
-
#ifndef MONO_GLUE_ENABLED
CRASH_NOW_MSG("Mono: This binary was built with 'mono_glue=no'; cannot load assemblies.");
#endif
@@ -399,22 +422,28 @@ void GDMono::initialize_load_assemblies() {
#if defined(TOOLS_ENABLED)
bool tool_assemblies_loaded = _load_tools_assemblies();
CRASH_COND_MSG(!tool_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies.");
+
+ if (Main::is_project_manager()) {
+ return;
+ }
#endif
// Load the project's main assembly. This doesn't necessarily need to succeed.
// The game may not be using .NET at all, or if the project does use .NET and
// we're running in the editor, it may just happen to be it wasn't built yet.
if (!_load_project_assembly()) {
- if (OS::get_singleton()->is_stdout_verbose())
+ if (OS::get_singleton()->is_stdout_verbose()) {
print_error("Mono: Failed to load project assembly");
+ }
}
}
bool GDMono::_are_api_assemblies_out_of_sync() {
bool out_of_sync = core_api_assembly.assembly && (core_api_assembly.out_of_sync || !GDMonoCache::cached_data.godot_api_cache_updated);
#ifdef TOOLS_ENABLED
- if (!out_of_sync)
+ if (!out_of_sync) {
out_of_sync = editor_api_assembly.assembly && editor_api_assembly.out_of_sync;
+ }
#endif
return out_of_sync;
}
@@ -444,6 +473,7 @@ uint64_t get_editor_api_hash() {
uint32_t get_bindings_version() {
GD_UNREACHABLE();
}
+
uint32_t get_cs_glue_version() {
GD_UNREACHABLE();
}
@@ -485,21 +515,25 @@ void GDMono::_init_exception_policy() {
}
}
-void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) {
-
+void GDMono::add_assembly(int32_t p_domain_id, GDMonoAssembly *p_assembly) {
assemblies[p_domain_id][p_assembly->get_name()] = p_assembly;
}
-GDMonoAssembly **GDMono::get_loaded_assembly(const String &p_name) {
+GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) {
+ if (p_name == "mscorlib" && corlib_assembly) {
+ return corlib_assembly;
+ }
MonoDomain *domain = mono_domain_get();
- uint32_t domain_id = domain ? mono_domain_get_id(domain) : 0;
- return assemblies[domain_id].getptr(p_name);
+ int32_t domain_id = domain ? mono_domain_get_id(domain) : 0;
+ GDMonoAssembly **result = assemblies[domain_id].getptr(p_name);
+ return result ? *result : nullptr;
}
bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) {
-
+#ifdef DEBUG_ENABLED
CRASH_COND(!r_assembly);
+#endif
MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8());
bool result = load_assembly(p_name, aname, r_assembly, p_refonly);
@@ -510,27 +544,27 @@ bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bo
}
bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) {
+#ifdef DEBUG_ENABLED
+ CRASH_COND(!r_assembly);
+#endif
+
+ return load_assembly(p_name, p_aname, r_assembly, p_refonly, GDMonoAssembly::get_default_search_dirs());
+}
+bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs) {
+#ifdef DEBUG_ENABLED
CRASH_COND(!r_assembly);
+#endif
print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");
- MonoImageOpenStatus status = MONO_IMAGE_OK;
- MonoAssembly *assembly = mono_assembly_load_full(p_aname, NULL, &status, p_refonly);
+ GDMonoAssembly *assembly = GDMonoAssembly::load(p_name, p_aname, p_refonly, p_search_dirs);
- if (!assembly)
+ if (!assembly) {
return false;
+ }
- ERR_FAIL_COND_V(status != MONO_IMAGE_OK, false);
-
- uint32_t domain_id = mono_domain_get_id(mono_domain_get());
-
- GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name);
-
- ERR_FAIL_COND_V(stored_assembly == NULL, false);
- ERR_FAIL_COND_V((*stored_assembly)->get_assembly() != assembly, false);
-
- *r_assembly = *stored_assembly;
+ *r_assembly = assembly;
print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path());
@@ -538,23 +572,15 @@ bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMo
}
bool GDMono::load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly) {
-
CRASH_COND(!r_assembly);
print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");
GDMonoAssembly *assembly = GDMonoAssembly::load_from(p_name, p_path, p_refonly);
- if (!assembly)
+ if (!assembly) {
return false;
-
-#ifdef DEBUG_ENABLED
- uint32_t domain_id = mono_domain_get_id(mono_domain_get());
- GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name);
-
- ERR_FAIL_COND_V(stored_assembly == NULL, false);
- ERR_FAIL_COND_V(*stored_assembly != assembly, false);
-#endif
+ }
*r_assembly = assembly;
@@ -567,23 +593,26 @@ ApiAssemblyInfo::Version ApiAssemblyInfo::Version::get_from_loaded_assembly(GDMo
ApiAssemblyInfo::Version api_assembly_version;
const char *nativecalls_name = p_api_type == ApiAssemblyInfo::API_CORE ?
- BINDINGS_CLASS_NATIVECALLS :
- BINDINGS_CLASS_NATIVECALLS_EDITOR;
+ BINDINGS_CLASS_NATIVECALLS :
+ BINDINGS_CLASS_NATIVECALLS_EDITOR;
GDMonoClass *nativecalls_klass = p_api_assembly->get_class(BINDINGS_NAMESPACE, nativecalls_name);
if (nativecalls_klass) {
GDMonoField *api_hash_field = nativecalls_klass->get_field("godot_api_hash");
- if (api_hash_field)
- api_assembly_version.godot_api_hash = GDMonoMarshal::unbox<uint64_t>(api_hash_field->get_value(NULL));
+ if (api_hash_field) {
+ api_assembly_version.godot_api_hash = GDMonoMarshal::unbox<uint64_t>(api_hash_field->get_value(nullptr));
+ }
GDMonoField *binds_ver_field = nativecalls_klass->get_field("bindings_version");
- if (binds_ver_field)
- api_assembly_version.bindings_version = GDMonoMarshal::unbox<uint32_t>(binds_ver_field->get_value(NULL));
+ if (binds_ver_field) {
+ api_assembly_version.bindings_version = GDMonoMarshal::unbox<uint32_t>(binds_ver_field->get_value(nullptr));
+ }
GDMonoField *cs_glue_ver_field = nativecalls_klass->get_field("cs_glue_version");
- if (cs_glue_ver_field)
- api_assembly_version.cs_glue_version = GDMonoMarshal::unbox<uint32_t>(cs_glue_ver_field->get_value(NULL));
+ if (cs_glue_ver_field) {
+ api_assembly_version.cs_glue_version = GDMonoMarshal::unbox<uint32_t>(cs_glue_ver_field->get_value(nullptr));
+ }
}
return api_assembly_version;
@@ -594,21 +623,21 @@ String ApiAssemblyInfo::to_string(ApiAssemblyInfo::Type p_type) {
}
bool GDMono::_load_corlib_assembly() {
-
- if (corlib_assembly)
+ if (corlib_assembly) {
return true;
+ }
bool success = load_assembly("mscorlib", &corlib_assembly);
- if (success)
+ if (success) {
GDMonoCache::update_corlib_cache();
+ }
return success;
}
#ifdef TOOLS_ENABLED
bool GDMono::copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config) {
-
String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
String dst_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config);
@@ -621,7 +650,7 @@ bool GDMono::copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const
memdelete(da);
if (err != OK) {
- ERR_PRINTS("Failed to create destination directory for the API assemblies. Error: " + itos(err) + ".");
+ ERR_PRINT("Failed to create destination directory for the API assemblies. Error: " + itos(err) + ".");
return false;
}
}
@@ -629,16 +658,18 @@ bool GDMono::copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
String xml_file = assembly_name + ".xml";
- if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK)
- WARN_PRINTS("Failed to copy '" + xml_file + "'.");
+ if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK) {
+ WARN_PRINT("Failed to copy '" + xml_file + "'.");
+ }
String pdb_file = assembly_name + ".pdb";
- if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK)
- WARN_PRINTS("Failed to copy '" + pdb_file + "'.");
+ if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK) {
+ WARN_PRINT("Failed to copy '" + pdb_file + "'.");
+ }
String assembly_file = assembly_name + ".dll";
if (da->copy(src_dir.plus_file(assembly_file), dst_dir.plus_file(assembly_file)) != OK) {
- ERR_PRINTS("Failed to copy '" + assembly_file + "'.");
+ ERR_PRINT("Failed to copy '" + assembly_file + "'.");
return false;
}
@@ -649,16 +680,18 @@ static bool try_get_cached_api_hash_for(const String &p_api_assemblies_dir, bool
String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
- if (!FileAccess::exists(core_api_assembly_path) || !FileAccess::exists(editor_api_assembly_path))
+ if (!FileAccess::exists(core_api_assembly_path) || !FileAccess::exists(editor_api_assembly_path)) {
return false;
+ }
String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg");
- if (!FileAccess::exists(cached_api_hash_path))
+ if (!FileAccess::exists(cached_api_hash_path)) {
return false;
+ }
Ref<ConfigFile> cfg;
- cfg.instance();
+ cfg.instantiate();
Error cfg_err = cfg->load(cached_api_hash_path);
ERR_FAIL_COND_V(cfg_err != OK, false);
@@ -679,13 +712,12 @@ static bool try_get_cached_api_hash_for(const String &p_api_assemblies_dir, bool
}
static void create_cached_api_hash_for(const String &p_api_assemblies_dir) {
-
String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg");
Ref<ConfigFile> cfg;
- cfg.instance();
+ cfg.instantiate();
cfg->set_value("core", "modified_time", FileAccess::get_modified_time(core_api_assembly_path));
cfg->set_value("editor", "modified_time", FileAccess::get_modified_time(editor_api_assembly_path));
@@ -714,7 +746,7 @@ bool GDMono::_temp_domain_load_are_assemblies_out_of_sync(const String &p_config
GDMono::LoadedApiAssembly temp_editor_api_assembly;
if (!_try_load_api_assemblies(temp_core_api_assembly, temp_editor_api_assembly,
- p_config, /* refonly: */ true, /* loaded_callback: */ NULL)) {
+ p_config, /* refonly: */ true, /* loaded_callback: */ nullptr)) {
return temp_core_api_assembly.out_of_sync || temp_editor_api_assembly.out_of_sync;
}
@@ -722,15 +754,14 @@ bool GDMono::_temp_domain_load_are_assemblies_out_of_sync(const String &p_config
}
String GDMono::update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync, const bool *p_editor_api_out_of_sync) {
-
#define FAIL_REASON(m_out_of_sync, m_prebuilt_exists) \
( \
(m_out_of_sync ? \
- String("The assembly is invalidated ") : \
- String("The assembly was not found ")) + \
+ String("The assembly is invalidated ") : \
+ String("The assembly was not found ")) + \
(m_prebuilt_exists ? \
- String("and the prebuilt assemblies are missing.") : \
- String("and we failed to copy the prebuilt assemblies.")))
+ String("and the prebuilt assemblies are missing.") : \
+ String("and we failed to copy the prebuilt assemblies.")))
String dst_assemblies_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config);
@@ -750,8 +781,9 @@ String GDMono::update_api_assemblies_from_prebuilt(const String &p_config, const
// Note: Even if only one of the assemblies if missing or out of sync, we update both
- if (!api_assemblies_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path))
+ if (!api_assemblies_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) {
return String(); // No update needed
+ }
print_verbose("Updating '" + p_config + "' API assemblies");
@@ -779,17 +811,17 @@ String GDMono::update_api_assemblies_from_prebuilt(const String &p_config, const
#endif
bool GDMono::_load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) {
-
- if (r_loaded_api_assembly.assembly)
+ if (r_loaded_api_assembly.assembly) {
return true;
+ }
#ifdef TOOLS_ENABLED
// For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date
// If running the project manager, load it from the prebuilt API directory
String assembly_dir = !Main::is_project_manager() ?
- GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) :
- GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
+ GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) :
+ GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
String assembly_path = assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
@@ -813,16 +845,16 @@ bool GDMono::_load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, c
#ifdef TOOLS_ENABLED
bool GDMono::_load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) {
-
- if (r_loaded_api_assembly.assembly)
+ if (r_loaded_api_assembly.assembly) {
return true;
+ }
// For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date
// If running the project manager, load it from the prebuilt API directory
String assembly_dir = !Main::is_project_manager() ?
- GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) :
- GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
+ GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) :
+ GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config);
String assembly_path = assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
@@ -845,30 +877,35 @@ bool GDMono::_load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly,
bool GDMono::_try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly,
const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback) {
if (!_load_core_api_assembly(r_core_api_assembly, p_config, p_refonly)) {
- if (OS::get_singleton()->is_stdout_verbose())
+ if (OS::get_singleton()->is_stdout_verbose()) {
print_error("Mono: Failed to load Core API assembly");
+ }
return false;
}
#ifdef TOOLS_ENABLED
if (!_load_editor_api_assembly(r_editor_api_assembly, p_config, p_refonly)) {
- if (OS::get_singleton()->is_stdout_verbose())
+ if (OS::get_singleton()->is_stdout_verbose()) {
print_error("Mono: Failed to load Editor API assembly");
+ }
return false;
}
- if (r_editor_api_assembly.out_of_sync)
+ if (r_editor_api_assembly.out_of_sync) {
return false;
+ }
#endif
// Check if the core API assembly is out of sync only after trying to load the
// editor API assembly. Otherwise, if both assemblies are out of sync, we would
// only update the former as we won't know the latter also needs to be updated.
- if (r_core_api_assembly.out_of_sync)
+ if (r_core_api_assembly.out_of_sync) {
return false;
+ }
- if (p_callback)
+ if (p_callback) {
return p_callback();
+ }
return true;
}
@@ -876,8 +913,9 @@ bool GDMono::_try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, Lo
bool GDMono::_on_core_api_assembly_loaded() {
GDMonoCache::update_godot_api_cache();
- if (!GDMonoCache::cached_data.godot_api_cache_updated)
+ if (!GDMonoCache::cached_data.godot_api_cache_updated) {
return false;
+ }
get_singleton()->_install_trace_listener();
@@ -890,11 +928,10 @@ bool GDMono::_try_load_api_assemblies_preset() {
}
void GDMono::_load_api_assemblies() {
-
bool api_assemblies_loaded = _try_load_api_assemblies_preset();
+#if defined(TOOLS_ENABLED) && !defined(GD_MONO_SINGLE_APPDOMAIN)
if (!api_assemblies_loaded) {
-#ifdef TOOLS_ENABLED
// The API assemblies are out of sync or some other error happened. Fine, try one more time, but
// this time update them from the prebuilt assemblies directory before trying to load them again.
@@ -907,7 +944,7 @@ void GDMono::_load_api_assemblies() {
// 2. Update the API assemblies
String update_error = update_api_assemblies_from_prebuilt("Debug", &core_api_assembly.out_of_sync, &editor_api_assembly.out_of_sync);
- CRASH_COND_MSG(!update_error.empty(), update_error);
+ CRASH_COND_MSG(!update_error.is_empty(), update_error);
// 3. Load the scripts domain again
Error domain_load_err = _load_scripts_domain();
@@ -915,8 +952,8 @@ void GDMono::_load_api_assemblies() {
// 4. Try loading the updated assemblies
api_assemblies_loaded = _try_load_api_assemblies_preset();
-#endif
}
+#endif
if (!api_assemblies_loaded) {
// welp... too bad
@@ -943,9 +980,9 @@ void GDMono::_load_api_assemblies() {
#ifdef TOOLS_ENABLED
bool GDMono::_load_tools_assemblies() {
-
- if (tools_assembly && tools_project_editor_assembly)
+ if (tools_assembly && tools_project_editor_assembly) {
return true;
+ }
bool success = load_assembly(TOOLS_ASM_NAME, &tools_assembly) &&
load_assembly(TOOLS_PROJECT_EDITOR_ASM_NAME, &tools_project_editor_assembly);
@@ -955,13 +992,13 @@ bool GDMono::_load_tools_assemblies() {
#endif
bool GDMono::_load_project_assembly() {
-
- if (project_assembly)
+ if (project_assembly) {
return true;
+ }
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
- if (appname_safe.empty()) {
+ if (appname_safe.is_empty()) {
appname_safe = "UnnamedProject";
}
@@ -969,20 +1006,20 @@ bool GDMono::_load_project_assembly() {
if (success) {
mono_assembly_set_main(project_assembly->get_assembly());
+ CSharpLanguage::get_singleton()->lookup_scripts_in_assembly(project_assembly);
}
return success;
}
void GDMono::_install_trace_listener() {
-
#ifdef DEBUG_ENABLED
// Install the trace listener now before the project assembly is loaded
GDMonoClass *debug_utils = get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, "DebuggingUtils");
GDMonoMethod *install_func = debug_utils->get_method("InstallTraceListener");
- MonoException *exc = NULL;
- install_func->invoke_raw(NULL, NULL, &exc);
+ MonoException *exc = nullptr;
+ install_func->invoke_raw(nullptr, nullptr, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener.");
@@ -990,9 +1027,9 @@ void GDMono::_install_trace_listener() {
#endif
}
+#ifndef GD_MONO_SINGLE_APPDOMAIN
Error GDMono::_load_scripts_domain() {
-
- ERR_FAIL_COND_V(scripts_domain != NULL, ERR_BUG);
+ ERR_FAIL_COND_V(scripts_domain != nullptr, ERR_BUG);
print_verbose("Mono: Loading scripts domain...");
@@ -1006,13 +1043,13 @@ Error GDMono::_load_scripts_domain() {
}
Error GDMono::_unload_scripts_domain() {
-
ERR_FAIL_NULL_V(scripts_domain, ERR_BUG);
- print_verbose("Mono: Unloading scripts domain...");
+ print_verbose("Mono: Finalizing scripts domain...");
- if (mono_domain_get() != root_domain)
+ if (mono_domain_get() != root_domain) {
mono_domain_set(root_domain, true);
+ }
finalizing_scripts_domain = true;
@@ -1028,21 +1065,23 @@ Error GDMono::_unload_scripts_domain() {
_domain_assemblies_cleanup(mono_domain_get_id(scripts_domain));
- core_api_assembly.assembly = NULL;
+ core_api_assembly.assembly = nullptr;
#ifdef TOOLS_ENABLED
- editor_api_assembly.assembly = NULL;
+ editor_api_assembly.assembly = nullptr;
#endif
- project_assembly = NULL;
+ project_assembly = nullptr;
#ifdef TOOLS_ENABLED
- tools_assembly = NULL;
- tools_project_editor_assembly = NULL;
+ tools_assembly = nullptr;
+ tools_project_editor_assembly = nullptr;
#endif
MonoDomain *domain = scripts_domain;
- scripts_domain = NULL;
+ scripts_domain = nullptr;
- MonoException *exc = NULL;
+ print_verbose("Mono: Unloading scripts domain...");
+
+ MonoException *exc = nullptr;
mono_domain_try_unload(domain, (MonoObject **)&exc);
if (exc) {
@@ -1053,10 +1092,10 @@ Error GDMono::_unload_scripts_domain() {
return OK;
}
+#endif
#ifdef GD_MONO_HOT_RELOAD
Error GDMono::reload_scripts_domain() {
-
ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG);
if (scripts_domain) {
@@ -1091,17 +1130,18 @@ Error GDMono::reload_scripts_domain() {
}
#endif
+#ifndef GD_MONO_SINGLE_APPDOMAIN
Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
-
- CRASH_COND(p_domain == NULL);
+ CRASH_COND(p_domain == nullptr);
CRASH_COND(p_domain == GDMono::get_singleton()->get_scripts_domain()); // Should use _unload_scripts_domain() instead
String domain_name = mono_domain_get_friendly_name(p_domain);
print_verbose("Mono: Unloading domain '" + domain_name + "'...");
- if (mono_domain_get() == p_domain)
+ if (mono_domain_get() == p_domain) {
mono_domain_set(root_domain, true);
+ }
if (!mono_domain_finalize(p_domain, 2000)) {
ERR_PRINT("Mono: Domain finalization timeout.");
@@ -1111,63 +1151,68 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
_domain_assemblies_cleanup(mono_domain_get_id(p_domain));
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
mono_domain_try_unload(p_domain, (MonoObject **)&exc);
if (exc) {
- ERR_PRINTS("Exception thrown when unloading domain '" + domain_name + "'.");
+ ERR_PRINT("Exception thrown when unloading domain '" + domain_name + "'.");
GDMonoUtils::debug_print_unhandled_exception(exc);
return FAILED;
}
return OK;
}
+#endif
GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) {
-
MonoImage *image = mono_class_get_image(p_raw_class);
- if (image == corlib_assembly->get_image())
+ if (image == corlib_assembly->get_image()) {
return corlib_assembly->get_class(p_raw_class);
+ }
- uint32_t domain_id = mono_domain_get_id(mono_domain_get());
+ int32_t domain_id = mono_domain_get_id(mono_domain_get());
HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id];
- const String *k = NULL;
+ const String *k = nullptr;
while ((k = domain_assemblies.next(k))) {
GDMonoAssembly *assembly = domain_assemblies.get(*k);
if (assembly->get_image() == image) {
GDMonoClass *klass = assembly->get_class(p_raw_class);
-
- if (klass)
+ if (klass) {
return klass;
+ }
}
}
- return NULL;
+ return nullptr;
}
GDMonoClass *GDMono::get_class(const StringName &p_namespace, const StringName &p_name) {
+ GDMonoClass *klass = corlib_assembly->get_class(p_namespace, p_name);
+ if (klass) {
+ return klass;
+ }
- uint32_t domain_id = mono_domain_get_id(mono_domain_get());
+ int32_t domain_id = mono_domain_get_id(mono_domain_get());
HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id];
- const String *k = NULL;
+ const String *k = nullptr;
while ((k = domain_assemblies.next(k))) {
GDMonoAssembly *assembly = domain_assemblies.get(*k);
- GDMonoClass *klass = assembly->get_class(p_namespace, p_name);
- if (klass)
+ klass = assembly->get_class(p_namespace, p_name);
+ if (klass) {
return klass;
+ }
}
- return NULL;
+ return nullptr;
}
-void GDMono::_domain_assemblies_cleanup(uint32_t p_domain_id) {
-
+void GDMono::_domain_assemblies_cleanup(int32_t p_domain_id) {
HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[p_domain_id];
- const String *k = NULL;
+ const String *k = nullptr;
while ((k = domain_assemblies.next(k))) {
memdelete(domain_assemblies.get(*k));
}
@@ -1176,15 +1221,15 @@ void GDMono::_domain_assemblies_cleanup(uint32_t p_domain_id) {
}
void GDMono::unhandled_exception_hook(MonoObject *p_exc, void *) {
-
// This method will be called by the runtime when a thrown exception is not handled.
// It won't be called when we manually treat a thrown exception as unhandled.
// We assume the exception was already printed before calling this hook.
#ifdef DEBUG_ENABLED
GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc);
- if (ScriptDebugger::get_singleton())
- ScriptDebugger::get_singleton()->idle_poll();
+ if (EngineDebugger::is_active()) {
+ EngineDebugger::get_singleton()->poll_events(false);
+ }
#endif
exit(mono_environment_exitcode_get());
@@ -1193,7 +1238,6 @@ void GDMono::unhandled_exception_hook(MonoObject *p_exc, void *) {
}
GDMono::GDMono() {
-
singleton = this;
gdmono_log = memnew(GDMonoLog);
@@ -1201,14 +1245,14 @@ GDMono::GDMono() {
runtime_initialized = false;
finalizing_scripts_domain = false;
- root_domain = NULL;
- scripts_domain = NULL;
+ root_domain = nullptr;
+ scripts_domain = nullptr;
- corlib_assembly = NULL;
- project_assembly = NULL;
+ corlib_assembly = nullptr;
+ project_assembly = nullptr;
#ifdef TOOLS_ENABLED
- tools_assembly = NULL;
- tools_project_editor_assembly = NULL;
+ tools_assembly = nullptr;
+ tools_project_editor_assembly = nullptr;
#endif
api_core_hash = 0;
@@ -1220,20 +1264,51 @@ GDMono::GDMono() {
}
GDMono::~GDMono() {
-
if (is_runtime_initialized()) {
+#ifndef GD_MONO_SINGLE_APPDOMAIN
if (scripts_domain) {
Error err = _unload_scripts_domain();
if (err != OK) {
ERR_PRINT("Mono: Failed to unload scripts domain.");
}
}
+#else
+ CRASH_COND(scripts_domain != root_domain);
+
+ print_verbose("Mono: Finalizing scripts domain...");
+
+ if (mono_domain_get() != root_domain)
+ mono_domain_set(root_domain, true);
- const uint32_t *k = NULL;
+ finalizing_scripts_domain = true;
+
+ if (!mono_domain_finalize(root_domain, 2000)) {
+ ERR_PRINT("Mono: Domain finalization timeout.");
+ }
+
+ finalizing_scripts_domain = false;
+
+ mono_gc_collect(mono_gc_max_generation());
+
+ GDMonoCache::clear_godot_api_cache();
+
+ _domain_assemblies_cleanup(mono_domain_get_id(root_domain));
+
+ core_api_assembly.assembly = nullptr;
+
+ project_assembly = nullptr;
+
+ root_domain = nullptr;
+ scripts_domain = nullptr;
+
+ // Leave the rest to 'mono_jit_cleanup'
+#endif
+
+ const int32_t *k = nullptr;
while ((k = assemblies.next(k))) {
HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies.get(*k);
- const String *kk = NULL;
+ const String *kk = nullptr;
while ((kk = domain_assemblies.next(kk))) {
memdelete(domain_assemblies.get(*kk));
}
@@ -1244,94 +1319,95 @@ GDMono::~GDMono() {
mono_jit_cleanup(root_domain);
-#if defined(ANDROID_ENABLED)
- GDMonoAndroid::cleanup();
-#endif
-
print_verbose("Mono: Finalized");
runtime_initialized = false;
}
- if (gdmono_log)
+#if defined(ANDROID_ENABLED)
+ gdmono::android::support::cleanup();
+#endif
+
+ if (gdmono_log) {
memdelete(gdmono_log);
+ }
- singleton = NULL;
+ singleton = nullptr;
}
-_GodotSharp *_GodotSharp::singleton = NULL;
+_GodotSharp *_GodotSharp::singleton = nullptr;
void _GodotSharp::attach_thread() {
-
GDMonoUtils::attach_current_thread();
}
void _GodotSharp::detach_thread() {
-
GDMonoUtils::detach_current_thread();
}
int32_t _GodotSharp::get_domain_id() {
-
MonoDomain *domain = mono_domain_get();
- CRASH_COND(!domain); // User must check if runtime is initialized before calling this method
+ ERR_FAIL_NULL_V(domain, -1);
return mono_domain_get_id(domain);
}
int32_t _GodotSharp::get_scripts_domain_id() {
-
+ ERR_FAIL_NULL_V_MSG(GDMono::get_singleton(),
+ -1, "The Mono runtime is not initialized");
MonoDomain *domain = GDMono::get_singleton()->get_scripts_domain();
- CRASH_COND(!domain); // User must check if scripts domain is loaded before calling this method
+ ERR_FAIL_NULL_V(domain, -1);
return mono_domain_get_id(domain);
}
bool _GodotSharp::is_scripts_domain_loaded() {
-
- return GDMono::get_singleton()->is_runtime_initialized() && GDMono::get_singleton()->get_scripts_domain() != NULL;
+ return GDMono::get_singleton() != nullptr &&
+ GDMono::get_singleton()->is_runtime_initialized() &&
+ GDMono::get_singleton()->get_scripts_domain() != nullptr;
}
bool _GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) {
-
return is_domain_finalizing_for_unload(p_domain_id);
}
-bool _GodotSharp::is_domain_finalizing_for_unload() {
-
- return is_domain_finalizing_for_unload(mono_domain_get());
-}
-
bool _GodotSharp::is_domain_finalizing_for_unload(int32_t p_domain_id) {
-
return is_domain_finalizing_for_unload(mono_domain_get_by_id(p_domain_id));
}
bool _GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) {
+ GDMono *gd_mono = GDMono::get_singleton();
- if (!p_domain)
- return true;
- if (p_domain == GDMono::get_singleton()->get_scripts_domain() && GDMono::get_singleton()->is_finalizing_scripts_domain())
+ ERR_FAIL_COND_V_MSG(!gd_mono || !gd_mono->is_runtime_initialized(),
+ false, "The Mono runtime is not initialized");
+
+ ERR_FAIL_NULL_V(p_domain, true);
+
+ if (p_domain == gd_mono->get_scripts_domain() && gd_mono->is_finalizing_scripts_domain()) {
return true;
+ }
+
return mono_domain_is_unloading(p_domain);
}
bool _GodotSharp::is_runtime_shutting_down() {
-
return mono_runtime_is_shutting_down();
}
bool _GodotSharp::is_runtime_initialized() {
-
- return GDMono::get_singleton()->is_runtime_initialized();
+ return GDMono::get_singleton() != nullptr && GDMono::get_singleton()->is_runtime_initialized();
}
void _GodotSharp::_reload_assemblies(bool p_soft_reload) {
#ifdef GD_MONO_HOT_RELOAD
- CSharpLanguage::get_singleton()->reload_assemblies(p_soft_reload);
+ CRASH_COND(CSharpLanguage::get_singleton() == nullptr);
+ // This method may be called more than once with `call_deferred`, so we need to check
+ // again if reloading is needed to avoid reloading multiple times unnecessarily.
+ if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
+ CSharpLanguage::get_singleton()->reload_assemblies(p_soft_reload);
+ }
#endif
}
void _GodotSharp::_bind_methods() {
-
ClassDB::bind_method(D_METHOD("attach_thread"), &_GodotSharp::attach_thread);
ClassDB::bind_method(D_METHOD("detach_thread"), &_GodotSharp::detach_thread);
@@ -1346,11 +1422,9 @@ void _GodotSharp::_bind_methods() {
}
_GodotSharp::_GodotSharp() {
-
singleton = this;
}
_GodotSharp::~_GodotSharp() {
-
- singleton = NULL;
+ singleton = nullptr;
}
diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h
index 306fa15f12..5accc21f8e 100644
--- a/modules/mono/mono_gd/gd_mono.h
+++ b/modules/mono/mono_gd/gd_mono.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -48,9 +48,9 @@ enum Type {
};
struct Version {
- uint64_t godot_api_hash;
- uint32_t bindings_version;
- uint32_t cs_glue_version;
+ uint64_t godot_api_hash = 0;
+ uint32_t bindings_version = 0;
+ uint32_t cs_glue_version = 0;
bool operator==(const Version &p_other) const {
return godot_api_hash == p_other.godot_api_hash &&
@@ -58,11 +58,7 @@ struct Version {
cs_glue_version == p_other.cs_glue_version;
}
- Version() :
- godot_api_hash(0),
- bindings_version(0),
- cs_glue_version(0) {
- }
+ Version() {}
Version(uint64_t p_godot_api_hash,
uint32_t p_bindings_version,
@@ -79,7 +75,6 @@ String to_string(Type p_type);
} // namespace ApiAssemblyInfo
class GDMono {
-
public:
enum UnhandledExceptionPolicy {
POLICY_TERMINATE_APP,
@@ -87,13 +82,10 @@ public:
};
struct LoadedApiAssembly {
- GDMonoAssembly *assembly;
- bool out_of_sync;
+ GDMonoAssembly *assembly = nullptr;
+ bool out_of_sync = false;
- LoadedApiAssembly() :
- assembly(NULL),
- out_of_sync(false) {
- }
+ LoadedApiAssembly() {}
};
private:
@@ -105,7 +97,7 @@ private:
MonoDomain *root_domain;
MonoDomain *scripts_domain;
- HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies;
+ HashMap<int32_t, HashMap<String, GDMonoAssembly *>> assemblies;
GDMonoAssembly *corlib_assembly;
GDMonoAssembly *project_assembly;
@@ -144,10 +136,12 @@ private:
void _register_internal_calls();
+#ifndef GD_MONO_SINGLE_APPDOMAIN
Error _load_scripts_domain();
Error _unload_scripts_domain();
+#endif
- void _domain_assemblies_cleanup(uint32_t p_domain_id);
+ void _domain_assemblies_cleanup(int32_t p_domain_id);
uint64_t api_core_hash;
#ifdef TOOLS_ENABLED
@@ -171,14 +165,16 @@ protected:
public:
#ifdef DEBUG_METHODS_ENABLED
uint64_t get_api_core_hash() {
- if (api_core_hash == 0)
+ if (api_core_hash == 0) {
api_core_hash = ClassDB::get_api_hash(ClassDB::API_CORE);
+ }
return api_core_hash;
}
#ifdef TOOLS_ENABLED
uint64_t get_api_editor_hash() {
- if (api_editor_hash == 0)
+ if (api_editor_hash == 0) {
api_editor_hash = ClassDB::get_api_hash(ClassDB::API_EDITOR);
+ }
return api_editor_hash;
}
#endif // TOOLS_ENABLED
@@ -198,18 +194,18 @@ public:
#ifdef TOOLS_ENABLED
bool copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config);
- String update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync = NULL, const bool *p_editor_api_out_of_sync = NULL);
+ String update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync = nullptr, const bool *p_editor_api_out_of_sync = nullptr);
#endif
static GDMono *get_singleton() { return singleton; }
- GD_NORETURN static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data);
+ [[noreturn]] static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data);
UnhandledExceptionPolicy get_unhandled_exception_policy() const { return unhandled_exception_policy; }
// Do not use these, unless you know what you're doing
- void add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly);
- GDMonoAssembly **get_loaded_assembly(const String &p_name);
+ void add_assembly(int32_t p_domain_id, GDMonoAssembly *p_assembly);
+ GDMonoAssembly *get_loaded_assembly(const String &p_name);
_FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized && !mono_runtime_is_shutting_down() /* stays true after shutdown finished */; }
@@ -239,6 +235,7 @@ public:
bool load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly = false);
bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false);
+ bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs);
bool load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly = false);
Error finalize_and_unload_domain(MonoDomain *p_domain);
@@ -253,23 +250,22 @@ public:
namespace gdmono {
class ScopeDomain {
-
MonoDomain *prev_domain;
public:
ScopeDomain(MonoDomain *p_domain) {
- MonoDomain *prev_domain = mono_domain_get();
+ prev_domain = mono_domain_get();
if (prev_domain != p_domain) {
- this->prev_domain = prev_domain;
mono_domain_set(p_domain, false);
} else {
- this->prev_domain = NULL;
+ prev_domain = nullptr;
}
}
~ScopeDomain() {
- if (prev_domain)
+ if (prev_domain) {
mono_domain_set(prev_domain, false);
+ }
}
};
@@ -282,11 +278,11 @@ public:
}
~ScopeExitDomainUnload() {
- if (domain)
+ if (domain) {
GDMono::get_singleton()->finalize_and_unload_domain(domain);
+ }
}
};
-
} // namespace gdmono
#define _GDMONO_SCOPE_DOMAIN_(m_mono_domain) \
@@ -304,9 +300,6 @@ class _GodotSharp : public Object {
bool _is_domain_finalizing_for_unload(int32_t p_domain_id);
- List<NodePath *> np_delete_queue;
- List<RID *> rid_delete_queue;
-
void _reload_assemblies(bool p_soft_reload);
protected:
@@ -324,7 +317,6 @@ public:
bool is_scripts_domain_loaded();
- bool is_domain_finalizing_for_unload();
bool is_domain_finalizing_for_unload(int32_t p_domain_id);
bool is_domain_finalizing_for_unload(MonoDomain *p_domain);
diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp
index 6cf5377e2c..67d6f3ef29 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.cpp
+++ b/modules/mono/mono_gd/gd_mono_assembly.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -33,38 +33,35 @@
#include <mono/metadata/mono-debug.h>
#include <mono/metadata/tokentype.h>
-#include "core/list.h"
-#include "core/os/file_access.h"
+#include "core/config/project_settings.h"
+#include "core/io/file_access.h"
+#include "core/io/file_access_pack.h"
#include "core/os/os.h"
-#include "core/project_settings.h"
+#include "core/templates/list.h"
#include "../godotsharp_dirs.h"
#include "gd_mono_cache.h"
#include "gd_mono_class.h"
-bool GDMonoAssembly::no_search = false;
-bool GDMonoAssembly::in_preload = false;
-
Vector<String> GDMonoAssembly::search_dirs;
void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config, const String &p_custom_bcl_dir) {
-
String framework_dir;
- if (!p_custom_bcl_dir.empty()) {
+ if (!p_custom_bcl_dir.is_empty()) {
framework_dir = p_custom_bcl_dir;
} else if (mono_assembly_getrootdir()) {
framework_dir = String::utf8(mono_assembly_getrootdir()).plus_file("mono").plus_file("4.5");
}
- if (!framework_dir.empty()) {
+ if (!framework_dir.is_empty()) {
r_search_dirs.push_back(framework_dir);
r_search_dirs.push_back(framework_dir.plus_file("Facades"));
}
#if !defined(TOOLS_ENABLED)
String data_game_assemblies_dir = GodotSharpDirs::get_data_game_assemblies_dir();
- if (!data_game_assemblies_dir.empty()) {
+ if (!data_game_assemblies_dir.is_empty()) {
r_search_dirs.push_back(data_game_assemblies_dir);
}
#endif
@@ -75,10 +72,10 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin
r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir());
}
- if (p_custom_config.empty()) {
+ if (p_custom_config.is_empty()) {
r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir());
} else {
- String api_config = p_custom_config == "Release" ? "Release" : "Debug";
+ String api_config = p_custom_config == "ExportRelease" ? "Release" : "Debug";
r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir().plus_file(api_config));
}
@@ -94,19 +91,30 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin
#endif
}
-void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, void *user_data) {
+// This is how these assembly loading hooks work:
+//
+// - The 'search' hook checks if the assembly has already been loaded, to avoid loading again.
+// - The 'preload' hook does the actual loading and is only called if the
+// 'search' hook didn't find the assembly in the list of loaded assemblies.
+// - The 'load' hook is called after the assembly has been loaded. Its job is to add the
+// assembly to the list of loaded assemblies so that the 'search' hook can look it up.
- if (no_search)
- return;
+void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, [[maybe_unused]] void *user_data) {
+ String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly)));
+
+ MonoImage *image = mono_assembly_get_image(assembly);
+
+ GDMonoAssembly *gdassembly = memnew(GDMonoAssembly(name, image, assembly));
+
+#ifdef GD_MONO_HOT_RELOAD
+ String path = String::utf8(mono_image_get_filename(image));
+ if (FileAccess::exists(path)) {
+ gdassembly->modified_time = FileAccess::get_modified_time(path);
+ }
+#endif
- // If our search and preload hooks fail to load the assembly themselves, the mono runtime still might.
- // Just do Assembly.LoadFrom("/Full/Path/On/Disk.dll");
- // In this case, we wouldn't have the assembly known in GDMono, which causes crashes
- // if any class inside the assembly is looked up by Godot.
- // And causing a lookup like that is as easy as throwing an exception defined in it...
- // No, we can't make the assembly load hooks smart enough because they get passed a MonoAssemblyName* only,
- // not the disk path passed to say Assembly.LoadFrom().
- _wrap_mono_assembly(assembly);
+ MonoDomain *domain = mono_domain_get();
+ GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, gdassembly);
}
MonoAssembly *GDMonoAssembly::assembly_search_hook(MonoAssemblyName *aname, void *user_data) {
@@ -125,78 +133,25 @@ MonoAssembly *GDMonoAssembly::assembly_refonly_preload_hook(MonoAssemblyName *an
return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, true);
}
-MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly) {
-
- (void)user_data; // UNUSED
-
+MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, [[maybe_unused]] void *user_data, bool refonly) {
String name = String::utf8(mono_assembly_name_get_name(aname));
bool has_extension = name.ends_with(".dll") || name.ends_with(".exe");
- if (no_search)
- return NULL;
-
- GDMonoAssembly **loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name);
- if (loaded_asm)
- return (*loaded_asm)->get_assembly();
-
- no_search = true; // Avoid the recursion madness
-
- GDMonoAssembly *res = _load_assembly_search(name, search_dirs, refonly);
-
- no_search = false;
-
- return res ? res->get_assembly() : NULL;
-}
-
-static _THREAD_LOCAL_(MonoImage *) image_corlib_loading = NULL;
-
-MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, void *user_data, bool refonly) {
-
- (void)user_data; // UNUSED
-
- {
- // If we find the assembly here, we load it with 'mono_assembly_load_from_full',
- // which in turn invokes load hooks before returning the MonoAssembly to us.
- // One of the load hooks is 'load_aot_module'. This hook can end up calling preload hooks
- // again for the same assembly in certain in certain circumstances (the 'do_load_image' part).
- // If this is the case and we return NULL due to the no_search condition below,
- // it will result in an internal crash later on. Therefore we need to return the assembly we didn't
- // get yet from 'mono_assembly_load_from_full'. Luckily we have the image, which already got it.
- // This must be done here. If done in search hooks, it would cause 'mono_assembly_load_from_full'
- // to think another MonoAssembly for this assembly was already loaded, making it delete its own,
- // when in fact both pointers were the same... This hooks thing is confusing.
- if (image_corlib_loading) {
- return mono_image_get_assembly(image_corlib_loading);
- }
+ GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name);
+ if (loaded_asm) {
+ return loaded_asm->get_assembly();
}
- if (no_search)
- return NULL;
-
- no_search = true;
- in_preload = true;
+ return nullptr;
+}
+MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, [[maybe_unused]] void *user_data, bool refonly) {
String name = String::utf8(mono_assembly_name_get_name(aname));
- bool has_extension = name.ends_with(".dll");
-
- GDMonoAssembly *res = NULL;
- if (has_extension ? name == "mscorlib.dll" : name == "mscorlib") {
- GDMonoAssembly **stored_assembly = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name);
- if (stored_assembly)
- return (*stored_assembly)->get_assembly();
-
- res = _load_assembly_search("mscorlib.dll", search_dirs, refonly);
- }
-
- no_search = false;
- in_preload = false;
-
- return res ? res->get_assembly() : NULL;
+ return _load_assembly_search(name, aname, refonly, search_dirs);
}
-GDMonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly) {
-
- GDMonoAssembly *res = NULL;
+MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {
+ MonoAssembly *res = nullptr;
String path;
bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe");
@@ -207,32 +162,34 @@ GDMonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, cons
if (has_extension) {
path = search_dir.plus_file(p_name);
if (FileAccess::exists(path)) {
- res = _load_assembly_from(p_name.get_basename(), path, p_refonly);
- if (res != NULL)
+ res = _real_load_assembly_from(path, p_refonly, p_aname);
+ if (res != nullptr) {
return res;
+ }
}
} else {
path = search_dir.plus_file(p_name + ".dll");
if (FileAccess::exists(path)) {
- res = _load_assembly_from(p_name, path, p_refonly);
- if (res != NULL)
+ res = _real_load_assembly_from(path, p_refonly, p_aname);
+ if (res != nullptr) {
return res;
+ }
}
path = search_dir.plus_file(p_name + ".exe");
if (FileAccess::exists(path)) {
- res = _load_assembly_from(p_name, path, p_refonly);
- if (res != NULL)
+ res = _real_load_assembly_from(path, p_refonly, p_aname);
+ if (res != nullptr) {
return res;
+ }
}
}
}
- return NULL;
+ return nullptr;
}
String GDMonoAssembly::find_assembly(const String &p_name) {
-
String path;
bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe");
@@ -242,110 +199,97 @@ String GDMonoAssembly::find_assembly(const String &p_name) {
if (has_extension) {
path = search_dir.plus_file(p_name);
- if (FileAccess::exists(path))
+ if (FileAccess::exists(path)) {
return path;
+ }
} else {
path = search_dir.plus_file(p_name + ".dll");
- if (FileAccess::exists(path))
+ if (FileAccess::exists(path)) {
return path;
+ }
path = search_dir.plus_file(p_name + ".exe");
- if (FileAccess::exists(path))
+ if (FileAccess::exists(path)) {
return path;
+ }
}
}
return String();
}
-GDMonoAssembly *GDMonoAssembly::_load_assembly_from(const String &p_name, const String &p_path, bool p_refonly) {
-
- GDMonoAssembly *assembly = memnew(GDMonoAssembly(p_name, p_path));
-
- Error err = assembly->load(p_refonly);
-
- if (err != OK) {
- memdelete(assembly);
- ERR_FAIL_V(NULL);
- }
-
- MonoDomain *domain = mono_domain_get();
- GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, assembly);
-
- return assembly;
-}
-
-void GDMonoAssembly::_wrap_mono_assembly(MonoAssembly *assembly) {
- String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly)));
-
- MonoImage *image = mono_assembly_get_image(assembly);
-
- GDMonoAssembly *gdassembly = memnew(GDMonoAssembly(name, mono_image_get_filename(image)));
- Error err = gdassembly->wrapper_for_image(image);
-
- if (err != OK) {
- memdelete(gdassembly);
- ERR_FAIL();
- }
-
- MonoDomain *domain = mono_domain_get();
- GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, gdassembly);
-}
-
void GDMonoAssembly::initialize() {
-
fill_search_dirs(search_dirs);
- mono_install_assembly_search_hook(&assembly_search_hook, NULL);
- mono_install_assembly_refonly_search_hook(&assembly_refonly_search_hook, NULL);
- mono_install_assembly_preload_hook(&assembly_preload_hook, NULL);
- mono_install_assembly_refonly_preload_hook(&assembly_refonly_preload_hook, NULL);
- mono_install_assembly_load_hook(&assembly_load_hook, NULL);
+ mono_install_assembly_search_hook(&assembly_search_hook, nullptr);
+ mono_install_assembly_refonly_search_hook(&assembly_refonly_search_hook, nullptr);
+ mono_install_assembly_preload_hook(&assembly_preload_hook, nullptr);
+ mono_install_assembly_refonly_preload_hook(&assembly_refonly_preload_hook, nullptr);
+ mono_install_assembly_load_hook(&assembly_load_hook, nullptr);
}
-Error GDMonoAssembly::load(bool p_refonly) {
-
- ERR_FAIL_COND_V(loaded, ERR_FILE_ALREADY_IN_USE);
-
- refonly = p_refonly;
-
- uint64_t last_modified_time = FileAccess::get_modified_time(path);
-
- Vector<uint8_t> data = FileAccess::get_file_as_array(path);
- ERR_FAIL_COND_V(data.empty(), ERR_FILE_CANT_READ);
+MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname) {
+ Vector<uint8_t> data = FileAccess::get_file_as_array(p_path);
+ ERR_FAIL_COND_V_MSG(data.is_empty(), nullptr, "Could read the assembly in the specified location");
String image_filename;
#ifdef ANDROID_ENABLED
- if (path.begins_with("res://")) {
- image_filename = path.substr(6, path.length());
+ if (p_path.begins_with("res://")) {
+ image_filename = p_path.substr(6, p_path.length());
} else {
- image_filename = ProjectSettings::get_singleton()->globalize_path(path);
+ image_filename = ProjectSettings::get_singleton()->globalize_path(p_path);
}
#else
// FIXME: globalize_path does not work on exported games
- image_filename = ProjectSettings::get_singleton()->globalize_path(path);
+ image_filename = ProjectSettings::get_singleton()->globalize_path(p_path);
#endif
MonoImageOpenStatus status = MONO_IMAGE_OK;
- image = mono_image_open_from_data_with_name(
+ MonoImage *image = mono_image_open_from_data_with_name(
(char *)&data[0], data.size(),
- true, &status, refonly,
- image_filename.utf8().get_data());
+ true, &status, p_refonly,
+ image_filename.utf8());
+
+ ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from memory: '" + p_path + "'.");
+
+ if (p_aname != nullptr) {
+ // Check assembly version
+ const MonoTableInfo *table = mono_image_get_table_info(image, MONO_TABLE_ASSEMBLY);
+
+ ERR_FAIL_NULL_V(table, nullptr);
+
+ if (mono_table_info_get_rows(table)) {
+ uint32_t cols[MONO_ASSEMBLY_SIZE];
+ mono_metadata_decode_row(table, 0, cols, MONO_ASSEMBLY_SIZE);
+
+ // Not sure about .NET's policy. We will only ensure major and minor are equal, and ignore build and revision.
+ uint16_t major = cols[MONO_ASSEMBLY_MAJOR_VERSION];
+ uint16_t minor = cols[MONO_ASSEMBLY_MINOR_VERSION];
- ERR_FAIL_COND_V(status != MONO_IMAGE_OK, ERR_FILE_CANT_OPEN);
- ERR_FAIL_NULL_V(image, ERR_FILE_CANT_OPEN);
+ uint16_t required_minor;
+ uint16_t required_major = mono_assembly_name_get_version(p_aname, &required_minor, nullptr, nullptr);
+
+ if (required_major != 0) {
+ if (major != required_major && minor != required_minor) {
+ mono_image_close(image);
+ return nullptr;
+ }
+ }
+ }
+ }
#ifdef DEBUG_ENABLED
Vector<uint8_t> pdb_data;
- String pdb_path(path + ".pdb");
+ String pdb_path(p_path + ".pdb");
if (!FileAccess::exists(pdb_path)) {
- pdb_path = path.get_basename() + ".pdb"; // without .dll
+ pdb_path = p_path.get_basename() + ".pdb"; // without .dll
- if (!FileAccess::exists(pdb_path))
+ if (!FileAccess::exists(pdb_path)) {
goto no_pdb;
+ }
}
pdb_data = FileAccess::get_file_as_array(pdb_path);
@@ -357,72 +301,105 @@ no_pdb:
#endif
- bool is_corlib_preload = in_preload && name == "mscorlib";
+ bool need_manual_load_hook = mono_image_get_assembly(image) != nullptr; // Re-using an existing image with an assembly loaded
- if (is_corlib_preload)
- image_corlib_loading = image;
+ status = MONO_IMAGE_OK;
- assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, refonly);
+ MonoAssembly *assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, p_refonly);
- if (is_corlib_preload)
- image_corlib_loading = NULL;
+ ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !assembly, nullptr, "Failed to load assembly for image");
- ERR_FAIL_COND_V(status != MONO_IMAGE_OK || assembly == NULL, ERR_FILE_CANT_OPEN);
+ if (need_manual_load_hook) {
+ // For some reason if an assembly survived domain reloading (maybe because it's referenced somewhere else),
+ // the mono internal search hook don't detect it, yet mono_image_open_from_data_with_name re-uses the image
+ // and assembly, and mono_assembly_load_from_full doesn't call the load hook. We need to call it manually.
+ String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly)));
+ bool has_extension = name.ends_with(".dll") || name.ends_with(".exe");
+ GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name);
+ if (!loaded_asm) {
+ assembly_load_hook(assembly, nullptr);
+ }
+ }
// Decrement refcount which was previously incremented by mono_image_open_from_data_with_name
mono_image_close(image);
- loaded = true;
- modified_time = last_modified_time;
-
- return OK;
+ return assembly;
}
-Error GDMonoAssembly::wrapper_for_image(MonoImage *p_image) {
+void GDMonoAssembly::unload() {
+ ERR_FAIL_NULL(image); // Should not be called if already unloaded
- ERR_FAIL_COND_V(loaded, ERR_FILE_ALREADY_IN_USE);
+ for (Map<MonoClass *, GDMonoClass *>::Element *E = cached_raw.front(); E; E = E->next()) {
+ memdelete(E->value());
+ }
- assembly = mono_image_get_assembly(p_image);
- ERR_FAIL_NULL_V(assembly, FAILED);
+ cached_classes.clear();
+ cached_raw.clear();
- image = p_image;
+ assembly = nullptr;
+ image = nullptr;
+}
- loaded = true;
+String GDMonoAssembly::get_path() const {
+ return String::utf8(mono_image_get_filename(image));
+}
- return OK;
+bool GDMonoAssembly::has_attribute(GDMonoClass *p_attr_class) {
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_NULL_V(p_attr_class, false);
+#endif
+
+ if (!attrs_fetched) {
+ fetch_attributes();
+ }
+
+ if (!attributes) {
+ return false;
+ }
+
+ return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr());
}
-void GDMonoAssembly::unload() {
+MonoObject *GDMonoAssembly::get_attribute(GDMonoClass *p_attr_class) {
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_NULL_V(p_attr_class, nullptr);
+#endif
- ERR_FAIL_COND(!loaded);
+ if (!attrs_fetched) {
+ fetch_attributes();
+ }
- for (Map<MonoClass *, GDMonoClass *>::Element *E = cached_raw.front(); E; E = E->next()) {
- memdelete(E->value());
+ if (!attributes) {
+ return nullptr;
}
- cached_classes.clear();
- cached_raw.clear();
+ return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr());
+}
+
+void GDMonoAssembly::fetch_attributes() {
+ ERR_FAIL_COND(attributes != nullptr);
- assembly = NULL;
- image = NULL;
- loaded = false;
+ attributes = mono_custom_attrs_from_assembly(assembly);
+ attrs_fetched = true;
}
GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) {
-
- ERR_FAIL_COND_V(!loaded, NULL);
+ ERR_FAIL_NULL_V(image, nullptr);
ClassKey key(p_namespace, p_name);
GDMonoClass **match = cached_classes.getptr(key);
- if (match)
+ if (match) {
return *match;
+ }
MonoClass *mono_class = mono_class_from_name(image, String(p_namespace).utf8(), String(p_name).utf8());
- if (!mono_class)
- return NULL;
+ if (!mono_class) {
+ return nullptr;
+ }
GDMonoClass *wrapped_class = memnew(GDMonoClass(p_namespace, p_name, mono_class, this));
@@ -433,16 +410,16 @@ GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const Stri
}
GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) {
-
- ERR_FAIL_COND_V(!loaded, NULL);
+ ERR_FAIL_NULL_V(image, nullptr);
Map<MonoClass *, GDMonoClass *>::Element *match = cached_raw.find(p_mono_class);
- if (match)
+ if (match) {
return match->value();
+ }
- StringName namespace_name = mono_class_get_namespace(p_mono_class);
- StringName class_name = mono_class_get_name(p_mono_class);
+ StringName namespace_name = String::utf8(mono_class_get_namespace(p_mono_class));
+ StringName class_name = String::utf8(mono_class_get_name(p_mono_class));
GDMonoClass *wrapped_class = memnew(GDMonoClass(namespace_name, class_name, p_mono_class, this));
@@ -452,94 +429,54 @@ GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) {
return wrapped_class;
}
-GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) {
-
- GDMonoClass *match = NULL;
-
- if (gdobject_class_cache_updated) {
- Map<StringName, GDMonoClass *>::Element *result = gdobject_class_cache.find(p_class);
-
- if (result)
- match = result->get();
- } else {
- List<GDMonoClass *> nested_classes;
-
- int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF);
-
- for (int i = 1; i < rows; i++) {
- MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF);
-
- if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class))
- continue;
-
- GDMonoClass *current = get_class(mono_class);
-
- if (!current)
- continue;
-
- nested_classes.push_back(current);
-
- if (!match && current->get_name() == p_class)
- match = current;
-
- while (!nested_classes.empty()) {
- GDMonoClass *current_nested = nested_classes.front()->get();
- nested_classes.pop_back();
-
- void *iter = NULL;
-
- while (true) {
- MonoClass *raw_nested = mono_class_get_nested_types(current_nested->get_mono_ptr(), &iter);
-
- if (!raw_nested)
- break;
+GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) {
+ if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll")) {
+ return GDMono::get_singleton()->get_corlib_assembly();
+ }
- GDMonoClass *nested_class = get_class(raw_nested);
+ // We need to manually call the search hook in this case, as it won't be called in the next step
+ MonoAssembly *assembly = mono_assembly_invoke_search_hook(p_aname);
- if (nested_class) {
- gdobject_class_cache.insert(nested_class->get_name(), nested_class);
- nested_classes.push_back(nested_class);
- }
- }
- }
-
- gdobject_class_cache.insert(current->get_name(), current);
+ if (!assembly) {
+ assembly = _load_assembly_search(p_name, p_aname, p_refonly, p_search_dirs);
+ if (!assembly) {
+ return nullptr;
}
-
- gdobject_class_cache_updated = true;
}
- return match;
+ GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
+ ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?");
+ ERR_FAIL_COND_V(loaded_asm->get_assembly() != assembly, nullptr);
+
+ return loaded_asm;
}
GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) {
+ if (p_name == "mscorlib" || p_name == "mscorlib.dll") {
+ return GDMono::get_singleton()->get_corlib_assembly();
+ }
- GDMonoAssembly **loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
- if (loaded_asm)
- return *loaded_asm;
-#ifdef DEBUG_ENABLED
- CRASH_COND(!FileAccess::exists(p_path));
-#endif
- no_search = true;
- GDMonoAssembly *res = _load_assembly_from(p_name, p_path, p_refonly);
- no_search = false;
- return res;
-}
+ // We need to manually call the search hook in this case, as it won't be called in the next step
+ MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8());
+ MonoAssembly *assembly = mono_assembly_invoke_search_hook(aname);
+ mono_assembly_name_free(aname);
+ mono_free(aname);
+
+ if (!assembly) {
+ assembly = _real_load_assembly_from(p_path, p_refonly);
+ if (!assembly) {
+ return nullptr;
+ }
+ }
-GDMonoAssembly::GDMonoAssembly(const String &p_name, const String &p_path) {
+ GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
+ ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?");
- loaded = false;
- gdobject_class_cache_updated = false;
- name = p_name;
- path = p_path;
- refonly = false;
- modified_time = 0;
- assembly = NULL;
- image = NULL;
+ return loaded_asm;
}
GDMonoAssembly::~GDMonoAssembly() {
-
- if (loaded)
+ if (image) {
unload();
+ }
}
diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h
index 4740e10339..6191c491f4 100644
--- a/modules/mono/mono_gd/gd_mono_assembly.h
+++ b/modules/mono/mono_gd/gd_mono_assembly.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -34,13 +34,12 @@
#include <mono/jit/jit.h>
#include <mono/metadata/assembly.h>
-#include "core/hash_map.h"
-#include "core/map.h"
-#include "core/ustring.h"
+#include "core/string/ustring.h"
+#include "core/templates/hash_map.h"
+#include "core/templates/map.h"
#include "gd_mono_utils.h"
class GDMonoAssembly {
-
struct ClassKey {
struct Hasher {
static _FORCE_INLINE_ uint32_t hash(const ClassKey &p_key) {
@@ -68,24 +67,20 @@ class GDMonoAssembly {
StringName class_name;
};
- MonoAssembly *assembly;
+ String name;
MonoImage *image;
+ MonoAssembly *assembly;
- bool refonly;
- bool loaded;
+ bool attrs_fetched = false;
+ MonoCustomAttrInfo *attributes = nullptr;
- String name;
- String path;
- uint64_t modified_time;
+#ifdef GD_MONO_HOT_RELOAD
+ uint64_t modified_time = 0;
+#endif
HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes;
Map<MonoClass *, GDMonoClass *> cached_raw;
- bool gdobject_class_cache_updated;
- Map<StringName, GDMonoClass *> gdobject_class_cache;
-
- static bool no_search;
- static bool in_preload;
static Vector<String> search_dirs;
static void assembly_load_hook(MonoAssembly *assembly, void *user_data);
@@ -97,38 +92,46 @@ class GDMonoAssembly {
static MonoAssembly *_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly);
static MonoAssembly *_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly);
- static GDMonoAssembly *_load_assembly_from(const String &p_name, const String &p_path, bool p_refonly);
- static GDMonoAssembly *_load_assembly_search(const String &p_name, const Vector<String> &p_search_dirs, bool p_refonly);
- static void _wrap_mono_assembly(MonoAssembly *assembly);
+ static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname = nullptr);
+ static MonoAssembly *_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs);
friend class GDMono;
static void initialize();
public:
- Error load(bool p_refonly);
- Error wrapper_for_image(MonoImage *p_image);
void unload();
- _FORCE_INLINE_ bool is_refonly() const { return refonly; }
- _FORCE_INLINE_ bool is_loaded() const { return loaded; }
_FORCE_INLINE_ MonoImage *get_image() const { return image; }
_FORCE_INLINE_ MonoAssembly *get_assembly() const { return assembly; }
_FORCE_INLINE_ String get_name() const { return name; }
- _FORCE_INLINE_ String get_path() const { return path; }
+
+#ifdef GD_MONO_HOT_RELOAD
_FORCE_INLINE_ uint64_t get_modified_time() const { return modified_time; }
+#endif
+
+ String get_path() const;
+
+ bool has_attribute(GDMonoClass *p_attr_class);
+ MonoObject *get_attribute(GDMonoClass *p_attr_class);
+
+ void fetch_attributes();
GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name);
GDMonoClass *get_class(MonoClass *p_mono_class);
- GDMonoClass *get_object_derived_class(const StringName &p_class);
-
static String find_assembly(const String &p_name);
static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String());
+ static const Vector<String> &get_default_search_dirs() { return search_dirs; }
+ static GDMonoAssembly *load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs);
static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly);
- GDMonoAssembly(const String &p_name, const String &p_path = String());
+ GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly) :
+ name(p_name),
+ image(p_image),
+ assembly(p_assembly) {
+ }
~GDMonoAssembly();
};
diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp
index f1f6524cd2..341ca88728 100644
--- a/modules/mono/mono_gd/gd_mono_cache.cpp
+++ b/modules/mono/mono_gd/gd_mono_cache.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -40,11 +40,11 @@ namespace GDMonoCache {
CachedData cached_data;
-#define CACHE_AND_CHECK(m_var, m_val) \
- { \
- CRASH_COND(m_var != NULL); \
- m_var = m_val; \
- ERR_FAIL_COND_MSG(m_var == NULL, "Mono Cache: Member " #m_var " is null."); \
+#define CACHE_AND_CHECK(m_var, m_val) \
+ { \
+ CRASH_COND(m_var != nullptr); \
+ m_var = m_val; \
+ ERR_FAIL_COND_MSG(m_var == nullptr, "Mono Cache: Member " #m_var " is null."); \
}
#define CACHE_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(cached_data.class_##m_class, m_val)
@@ -54,140 +54,148 @@ CachedData cached_data;
#define CACHE_METHOD_AND_CHECK(m_class, m_method, m_val) CACHE_AND_CHECK(cached_data.method_##m_class##_##m_method, m_val)
#define CACHE_PROPERTY_AND_CHECK(m_class, m_property, m_val) CACHE_AND_CHECK(cached_data.property_##m_class##_##m_property, m_val)
-#define CACHE_METHOD_THUNK_AND_CHECK_IMPL(m_var, m_val) \
- { \
- CRASH_COND(!m_var.is_null()); \
- ERR_FAIL_COND_MSG(m_val == NULL, "Mono Cache: Method for member " #m_var " is null."); \
- m_var.set_from_method(m_val); \
- ERR_FAIL_COND_MSG(m_var.is_null(), "Mono Cache: Member " #m_var " is null."); \
+#define CACHE_METHOD_THUNK_AND_CHECK_IMPL(m_var, m_val) \
+ { \
+ CRASH_COND(!m_var.is_null()); \
+ ERR_FAIL_COND_MSG(m_val == nullptr, "Mono Cache: Method for member " #m_var " is null."); \
+ m_var.set_from_method(m_val); \
+ ERR_FAIL_COND_MSG(m_var.is_null(), "Mono Cache: Member " #m_var " is null."); \
}
#define CACHE_METHOD_THUNK_AND_CHECK(m_class, m_method, m_val) CACHE_METHOD_THUNK_AND_CHECK_IMPL(cached_data.methodthunk_##m_class##_##m_method, m_val)
void CachedData::clear_corlib_cache() {
-
corlib_cache_updated = false;
- class_MonoObject = NULL;
- class_bool = NULL;
- class_int8_t = NULL;
- class_int16_t = NULL;
- class_int32_t = NULL;
- class_int64_t = NULL;
- class_uint8_t = NULL;
- class_uint16_t = NULL;
- class_uint32_t = NULL;
- class_uint64_t = NULL;
- class_float = NULL;
- class_double = NULL;
- class_String = NULL;
- class_IntPtr = NULL;
-
- class_System_Collections_IEnumerable = NULL;
- class_System_Collections_IDictionary = NULL;
+ class_MonoObject = nullptr;
+ class_bool = nullptr;
+ class_int8_t = nullptr;
+ class_int16_t = nullptr;
+ class_int32_t = nullptr;
+ class_int64_t = nullptr;
+ class_uint8_t = nullptr;
+ class_uint16_t = nullptr;
+ class_uint32_t = nullptr;
+ class_uint64_t = nullptr;
+ class_float = nullptr;
+ class_double = nullptr;
+ class_String = nullptr;
+ class_IntPtr = nullptr;
+
+ class_System_Collections_IEnumerable = nullptr;
+ class_System_Collections_ICollection = nullptr;
+ class_System_Collections_IDictionary = nullptr;
#ifdef DEBUG_ENABLED
- class_System_Diagnostics_StackTrace = NULL;
+ class_System_Diagnostics_StackTrace = nullptr;
methodthunk_System_Diagnostics_StackTrace_GetFrames.nullify();
- method_System_Diagnostics_StackTrace_ctor_bool = NULL;
- method_System_Diagnostics_StackTrace_ctor_Exception_bool = NULL;
+ method_System_Diagnostics_StackTrace_ctor_bool = nullptr;
+ method_System_Diagnostics_StackTrace_ctor_Exception_bool = nullptr;
#endif
- class_KeyNotFoundException = NULL;
+ class_KeyNotFoundException = nullptr;
}
void CachedData::clear_godot_api_cache() {
-
godot_api_cache_updated = false;
- rawclass_Dictionary = NULL;
-
- class_Vector2 = NULL;
- class_Rect2 = NULL;
- class_Transform2D = NULL;
- class_Vector3 = NULL;
- class_Basis = NULL;
- class_Quat = NULL;
- class_Transform = NULL;
- class_AABB = NULL;
- class_Color = NULL;
- class_Plane = NULL;
- class_NodePath = NULL;
- class_RID = NULL;
- class_GodotObject = NULL;
- class_GodotResource = NULL;
- class_Node = NULL;
- class_Control = NULL;
- class_Spatial = NULL;
- class_WeakRef = NULL;
- class_Array = NULL;
- class_Dictionary = NULL;
- class_MarshalUtils = NULL;
- class_ISerializationListener = NULL;
+ rawclass_Dictionary = nullptr;
+
+ class_Vector2 = nullptr;
+ class_Vector2i = nullptr;
+ class_Rect2 = nullptr;
+ class_Rect2i = nullptr;
+ class_Transform2D = nullptr;
+ class_Vector3 = nullptr;
+ class_Vector3i = nullptr;
+ class_Basis = nullptr;
+ class_Quaternion = nullptr;
+ class_Transform3D = nullptr;
+ class_AABB = nullptr;
+ class_Color = nullptr;
+ class_Plane = nullptr;
+ class_StringName = nullptr;
+ class_NodePath = nullptr;
+ class_RID = nullptr;
+ class_GodotObject = nullptr;
+ class_GodotResource = nullptr;
+ class_Node = nullptr;
+ class_Control = nullptr;
+ class_Node3D = nullptr;
+ class_WeakRef = nullptr;
+ class_Callable = nullptr;
+ class_SignalInfo = nullptr;
+ class_Array = nullptr;
+ class_Dictionary = nullptr;
+ class_MarshalUtils = nullptr;
+ class_ISerializationListener = nullptr;
#ifdef DEBUG_ENABLED
- class_DebuggingUtils = NULL;
+ class_DebuggingUtils = nullptr;
methodthunk_DebuggingUtils_GetStackFrameInfo.nullify();
#endif
- class_ExportAttribute = NULL;
- field_ExportAttribute_hint = NULL;
- field_ExportAttribute_hintString = NULL;
- class_SignalAttribute = NULL;
- class_ToolAttribute = NULL;
- class_RemoteAttribute = NULL;
- class_SyncAttribute = NULL;
- class_MasterAttribute = NULL;
- class_PuppetAttribute = NULL;
- class_SlaveAttribute = NULL;
- class_RemoteSyncAttribute = NULL;
- class_MasterSyncAttribute = NULL;
- class_PuppetSyncAttribute = NULL;
- class_GodotMethodAttribute = NULL;
- field_GodotMethodAttribute_methodName = NULL;
-
- field_GodotObject_ptr = NULL;
- field_NodePath_ptr = NULL;
- field_Image_ptr = NULL;
- field_RID_ptr = NULL;
+ class_ExportAttribute = nullptr;
+ field_ExportAttribute_hint = nullptr;
+ field_ExportAttribute_hintString = nullptr;
+ class_SignalAttribute = nullptr;
+ class_ToolAttribute = nullptr;
+ class_RemoteAttribute = nullptr;
+ class_MasterAttribute = nullptr;
+ class_PuppetAttribute = nullptr;
+ class_RemoteSyncAttribute = nullptr;
+ class_MasterSyncAttribute = nullptr;
+ class_PuppetSyncAttribute = nullptr;
+ class_GodotMethodAttribute = nullptr;
+ field_GodotMethodAttribute_methodName = nullptr;
+ class_ScriptPathAttribute = nullptr;
+ field_ScriptPathAttribute_path = nullptr;
+ class_AssemblyHasScriptsAttribute = nullptr;
+ field_AssemblyHasScriptsAttribute_requiresLookup = nullptr;
+ field_AssemblyHasScriptsAttribute_scriptTypes = nullptr;
+
+ field_GodotObject_ptr = nullptr;
+ field_StringName_ptr = nullptr;
+ field_NodePath_ptr = nullptr;
+ field_Image_ptr = nullptr;
+ field_RID_ptr = nullptr;
methodthunk_GodotObject_Dispose.nullify();
methodthunk_Array_GetPtr.nullify();
methodthunk_Dictionary_GetPtr.nullify();
methodthunk_SignalAwaiter_SignalCallback.nullify();
- methodthunk_SignalAwaiter_FailureCallback.nullify();
methodthunk_GodotTaskScheduler_Activate.nullify();
+ methodthunk_Delegate_Equals.nullify();
+
+ methodthunk_DelegateUtils_TrySerializeDelegate.nullify();
+ methodthunk_DelegateUtils_TryDeserializeDelegate.nullify();
+
// Start of MarshalUtils methods
methodthunk_MarshalUtils_TypeIsGenericArray.nullify();
methodthunk_MarshalUtils_TypeIsGenericDictionary.nullify();
+ methodthunk_MarshalUtils_TypeIsSystemGenericList.nullify();
+ methodthunk_MarshalUtils_TypeIsSystemGenericDictionary.nullify();
+ methodthunk_MarshalUtils_TypeIsGenericIEnumerable.nullify();
+ methodthunk_MarshalUtils_TypeIsGenericICollection.nullify();
+ methodthunk_MarshalUtils_TypeIsGenericIDictionary.nullify();
methodthunk_MarshalUtils_ArrayGetElementType.nullify();
methodthunk_MarshalUtils_DictionaryGetKeyValueTypes.nullify();
- methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType.nullify();
- methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType.nullify();
- methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType_with_info.nullify();
- methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType_with_info.nullify();
-
methodthunk_MarshalUtils_MakeGenericArrayType.nullify();
methodthunk_MarshalUtils_MakeGenericDictionaryType.nullify();
- methodthunk_MarshalUtils_EnumerableToArray.nullify();
- methodthunk_MarshalUtils_IDictionaryToDictionary.nullify();
- methodthunk_MarshalUtils_GenericIDictionaryToDictionary.nullify();
-
// End of MarshalUtils methods
- task_scheduler_handle = Ref<MonoGCHandle>();
+ task_scheduler_handle = Ref<MonoGCHandleRef>();
}
#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class))
#define GODOT_API_NS_CLASS(m_ns, m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(m_ns, #m_class))
void update_corlib_cache() {
-
CACHE_CLASS_AND_CHECK(MonoObject, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_object_class()));
CACHE_CLASS_AND_CHECK(bool, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_boolean_class()));
CACHE_CLASS_AND_CHECK(int8_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_sbyte_class()));
@@ -204,6 +212,7 @@ void update_corlib_cache() {
CACHE_CLASS_AND_CHECK(IntPtr, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_intptr_class()));
CACHE_CLASS_AND_CHECK(System_Collections_IEnumerable, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "IEnumerable"));
+ CACHE_CLASS_AND_CHECK(System_Collections_ICollection, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "ICollection"));
CACHE_CLASS_AND_CHECK(System_Collections_IDictionary, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "IDictionary"));
#ifdef DEBUG_ENABLED
@@ -213,31 +222,38 @@ void update_corlib_cache() {
CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_Exception_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(System.Exception,bool)", true));
#endif
+ CACHE_METHOD_THUNK_AND_CHECK(Delegate, Equals, GDMono::get_singleton()->get_corlib_assembly()->get_class("System", "Delegate")->get_method_with_desc("System.Delegate:Equals(object)", true));
+
CACHE_CLASS_AND_CHECK(KeyNotFoundException, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections.Generic", "KeyNotFoundException"));
cached_data.corlib_cache_updated = true;
}
void update_godot_api_cache() {
-
CACHE_CLASS_AND_CHECK(Vector2, GODOT_API_CLASS(Vector2));
+ CACHE_CLASS_AND_CHECK(Vector2i, GODOT_API_CLASS(Vector2i));
CACHE_CLASS_AND_CHECK(Rect2, GODOT_API_CLASS(Rect2));
+ CACHE_CLASS_AND_CHECK(Rect2i, GODOT_API_CLASS(Rect2i));
CACHE_CLASS_AND_CHECK(Transform2D, GODOT_API_CLASS(Transform2D));
CACHE_CLASS_AND_CHECK(Vector3, GODOT_API_CLASS(Vector3));
+ CACHE_CLASS_AND_CHECK(Vector3i, GODOT_API_CLASS(Vector3i));
CACHE_CLASS_AND_CHECK(Basis, GODOT_API_CLASS(Basis));
- CACHE_CLASS_AND_CHECK(Quat, GODOT_API_CLASS(Quat));
- CACHE_CLASS_AND_CHECK(Transform, GODOT_API_CLASS(Transform));
+ CACHE_CLASS_AND_CHECK(Quaternion, GODOT_API_CLASS(Quaternion));
+ CACHE_CLASS_AND_CHECK(Transform3D, GODOT_API_CLASS(Transform3D));
CACHE_CLASS_AND_CHECK(AABB, GODOT_API_CLASS(AABB));
CACHE_CLASS_AND_CHECK(Color, GODOT_API_CLASS(Color));
CACHE_CLASS_AND_CHECK(Plane, GODOT_API_CLASS(Plane));
+ CACHE_CLASS_AND_CHECK(StringName, GODOT_API_CLASS(StringName));
CACHE_CLASS_AND_CHECK(NodePath, GODOT_API_CLASS(NodePath));
CACHE_CLASS_AND_CHECK(RID, GODOT_API_CLASS(RID));
CACHE_CLASS_AND_CHECK(GodotObject, GODOT_API_CLASS(Object));
CACHE_CLASS_AND_CHECK(GodotResource, GODOT_API_CLASS(Resource));
CACHE_CLASS_AND_CHECK(Node, GODOT_API_CLASS(Node));
CACHE_CLASS_AND_CHECK(Control, GODOT_API_CLASS(Control));
- CACHE_CLASS_AND_CHECK(Spatial, GODOT_API_CLASS(Spatial));
+ CACHE_CLASS_AND_CHECK(Node3D, GODOT_API_CLASS(Node3D));
CACHE_CLASS_AND_CHECK(WeakRef, GODOT_API_CLASS(WeakRef));
+ CACHE_CLASS_AND_CHECK(Callable, GODOT_API_CLASS(Callable));
+ CACHE_CLASS_AND_CHECK(SignalInfo, GODOT_API_CLASS(SignalInfo));
CACHE_CLASS_AND_CHECK(Array, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Array));
CACHE_CLASS_AND_CHECK(Dictionary, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary));
CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils));
@@ -254,17 +270,21 @@ void update_godot_api_cache() {
CACHE_CLASS_AND_CHECK(SignalAttribute, GODOT_API_CLASS(SignalAttribute));
CACHE_CLASS_AND_CHECK(ToolAttribute, GODOT_API_CLASS(ToolAttribute));
CACHE_CLASS_AND_CHECK(RemoteAttribute, GODOT_API_CLASS(RemoteAttribute));
- CACHE_CLASS_AND_CHECK(SyncAttribute, GODOT_API_CLASS(SyncAttribute));
CACHE_CLASS_AND_CHECK(MasterAttribute, GODOT_API_CLASS(MasterAttribute));
CACHE_CLASS_AND_CHECK(PuppetAttribute, GODOT_API_CLASS(PuppetAttribute));
- CACHE_CLASS_AND_CHECK(SlaveAttribute, GODOT_API_CLASS(SlaveAttribute));
CACHE_CLASS_AND_CHECK(RemoteSyncAttribute, GODOT_API_CLASS(RemoteSyncAttribute));
CACHE_CLASS_AND_CHECK(MasterSyncAttribute, GODOT_API_CLASS(MasterSyncAttribute));
CACHE_CLASS_AND_CHECK(PuppetSyncAttribute, GODOT_API_CLASS(PuppetSyncAttribute));
CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute));
CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName"));
+ CACHE_CLASS_AND_CHECK(ScriptPathAttribute, GODOT_API_CLASS(ScriptPathAttribute));
+ CACHE_FIELD_AND_CHECK(ScriptPathAttribute, path, CACHED_CLASS(ScriptPathAttribute)->get_field("path"));
+ CACHE_CLASS_AND_CHECK(AssemblyHasScriptsAttribute, GODOT_API_CLASS(AssemblyHasScriptsAttribute));
+ CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, requiresLookup, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("requiresLookup"));
+ CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, scriptTypes, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("scriptTypes"));
CACHE_FIELD_AND_CHECK(GodotObject, ptr, CACHED_CLASS(GodotObject)->get_field(BINDINGS_PTR_FIELD));
+ CACHE_FIELD_AND_CHECK(StringName, ptr, CACHED_CLASS(StringName)->get_field(BINDINGS_PTR_FIELD));
CACHE_FIELD_AND_CHECK(NodePath, ptr, CACHED_CLASS(NodePath)->get_field(BINDINGS_PTR_FIELD));
CACHE_FIELD_AND_CHECK(RID, ptr, CACHED_CLASS(RID)->get_field(BINDINGS_PTR_FIELD));
@@ -272,29 +292,27 @@ void update_godot_api_cache() {
CACHE_METHOD_THUNK_AND_CHECK(Array, GetPtr, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Array)->get_method("GetPtr", 0));
CACHE_METHOD_THUNK_AND_CHECK(Dictionary, GetPtr, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)->get_method("GetPtr", 0));
CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, SignalCallback, GODOT_API_CLASS(SignalAwaiter)->get_method("SignalCallback", 1));
- CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, FailureCallback, GODOT_API_CLASS(SignalAwaiter)->get_method("FailureCallback", 0));
CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, GODOT_API_CLASS(GodotTaskScheduler)->get_method("Activate", 0));
+ CACHE_METHOD_THUNK_AND_CHECK(DelegateUtils, TrySerializeDelegate, GODOT_API_CLASS(DelegateUtils)->get_method("TrySerializeDelegate", 2));
+ CACHE_METHOD_THUNK_AND_CHECK(DelegateUtils, TryDeserializeDelegate, GODOT_API_CLASS(DelegateUtils)->get_method("TryDeserializeDelegate", 2));
+
// Start of MarshalUtils methods
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericArray, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericArray", 1));
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericDictionary", 1));
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsSystemGenericList, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsSystemGenericList", 1));
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsSystemGenericDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsSystemGenericDictionary", 1));
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericIEnumerable, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericIEnumerable", 1));
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericICollection, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericICollection", 1));
+ CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericIDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericIDictionary", 1));
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, ArrayGetElementType, GODOT_API_CLASS(MarshalUtils)->get_method("ArrayGetElementType", 2));
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, DictionaryGetKeyValueTypes, GODOT_API_CLASS(MarshalUtils)->get_method("DictionaryGetKeyValueTypes", 3));
- CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIEnumerableIsAssignableFromType, GODOT_API_CLASS(MarshalUtils)->get_method("GenericIEnumerableIsAssignableFromType", 1));
- CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryIsAssignableFromType, GODOT_API_CLASS(MarshalUtils)->get_method("GenericIDictionaryIsAssignableFromType", 1));
- CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info, GODOT_API_CLASS(MarshalUtils)->get_method("GenericIEnumerableIsAssignableFromType", 2));
- CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info, GODOT_API_CLASS(MarshalUtils)->get_method("GenericIDictionaryIsAssignableFromType", 3));
-
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericArrayType, GODOT_API_CLASS(MarshalUtils)->get_method("MakeGenericArrayType", 1));
CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericDictionaryType, GODOT_API_CLASS(MarshalUtils)->get_method("MakeGenericDictionaryType", 2));
- CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, EnumerableToArray, GODOT_API_CLASS(MarshalUtils)->get_method("EnumerableToArray", 2));
- CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, IDictionaryToDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("IDictionaryToDictionary", 2));
- CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GenericIDictionaryToDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("GenericIDictionaryToDictionary", 2));
-
// End of MarshalUtils methods
#ifdef DEBUG_ENABLED
@@ -304,9 +322,8 @@ void update_godot_api_cache() {
// TODO Move to CSharpLanguage::init() and do handle disposal
MonoObject *task_scheduler = mono_object_new(mono_domain_get(), GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr());
GDMonoUtils::runtime_object_init(task_scheduler, GODOT_API_CLASS(GodotTaskScheduler));
- cached_data.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler);
+ cached_data.task_scheduler_handle = MonoGCHandleRef::create_strong(task_scheduler);
cached_data.godot_api_cache_updated = true;
}
-
} // namespace GDMonoCache
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index 1dc6b70479..3d867060f5 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -37,7 +37,6 @@
namespace GDMonoCache {
struct CachedData {
-
// -----------------------------------------------
// corlib classes
@@ -58,6 +57,7 @@ struct CachedData {
GDMonoClass *class_IntPtr; // System.IntPtr
GDMonoClass *class_System_Collections_IEnumerable;
+ GDMonoClass *class_System_Collections_ICollection;
GDMonoClass *class_System_Collections_IDictionary;
#ifdef DEBUG_ENABLED
@@ -73,23 +73,29 @@ struct CachedData {
// -----------------------------------------------
GDMonoClass *class_Vector2;
+ GDMonoClass *class_Vector2i;
GDMonoClass *class_Rect2;
+ GDMonoClass *class_Rect2i;
GDMonoClass *class_Transform2D;
GDMonoClass *class_Vector3;
+ GDMonoClass *class_Vector3i;
GDMonoClass *class_Basis;
- GDMonoClass *class_Quat;
- GDMonoClass *class_Transform;
+ GDMonoClass *class_Quaternion;
+ GDMonoClass *class_Transform3D;
GDMonoClass *class_AABB;
GDMonoClass *class_Color;
GDMonoClass *class_Plane;
+ GDMonoClass *class_StringName;
GDMonoClass *class_NodePath;
GDMonoClass *class_RID;
GDMonoClass *class_GodotObject;
GDMonoClass *class_GodotResource;
GDMonoClass *class_Node;
GDMonoClass *class_Control;
- GDMonoClass *class_Spatial;
+ GDMonoClass *class_Node3D;
GDMonoClass *class_WeakRef;
+ GDMonoClass *class_Callable;
+ GDMonoClass *class_SignalInfo;
GDMonoClass *class_Array;
GDMonoClass *class_Dictionary;
GDMonoClass *class_MarshalUtils;
@@ -106,17 +112,21 @@ struct CachedData {
GDMonoClass *class_SignalAttribute;
GDMonoClass *class_ToolAttribute;
GDMonoClass *class_RemoteAttribute;
- GDMonoClass *class_SyncAttribute;
+ GDMonoClass *class_MasterAttribute;
+ GDMonoClass *class_PuppetAttribute;
GDMonoClass *class_RemoteSyncAttribute;
GDMonoClass *class_MasterSyncAttribute;
GDMonoClass *class_PuppetSyncAttribute;
- GDMonoClass *class_MasterAttribute;
- GDMonoClass *class_PuppetAttribute;
- GDMonoClass *class_SlaveAttribute;
GDMonoClass *class_GodotMethodAttribute;
GDMonoField *field_GodotMethodAttribute_methodName;
+ GDMonoClass *class_ScriptPathAttribute;
+ GDMonoField *field_ScriptPathAttribute_path;
+ GDMonoClass *class_AssemblyHasScriptsAttribute;
+ GDMonoField *field_AssemblyHasScriptsAttribute_requiresLookup;
+ GDMonoField *field_AssemblyHasScriptsAttribute_scriptTypes;
GDMonoField *field_GodotObject_ptr;
+ GDMonoField *field_StringName_ptr;
GDMonoField *field_NodePath_ptr;
GDMonoField *field_Image_ptr;
GDMonoField *field_RID_ptr;
@@ -125,32 +135,32 @@ struct CachedData {
GDMonoMethodThunkR<Array *, MonoObject *> methodthunk_Array_GetPtr;
GDMonoMethodThunkR<Dictionary *, MonoObject *> methodthunk_Dictionary_GetPtr;
GDMonoMethodThunk<MonoObject *, MonoArray *> methodthunk_SignalAwaiter_SignalCallback;
- GDMonoMethodThunk<MonoObject *> methodthunk_SignalAwaiter_FailureCallback;
GDMonoMethodThunk<MonoObject *> methodthunk_GodotTaskScheduler_Activate;
+ GDMonoMethodThunkR<MonoBoolean, MonoObject *, MonoObject *> methodthunk_Delegate_Equals;
+
+ GDMonoMethodThunkR<MonoBoolean, MonoDelegate *, MonoObject *> methodthunk_DelegateUtils_TrySerializeDelegate;
+ GDMonoMethodThunkR<MonoBoolean, MonoObject *, MonoDelegate **> methodthunk_DelegateUtils_TryDeserializeDelegate;
+
// Start of MarshalUtils methods
GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericArray;
GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericDictionary;
+ GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsSystemGenericList;
+ GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsSystemGenericDictionary;
+ GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericIEnumerable;
+ GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericICollection;
+ GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericIDictionary;
GDMonoMethodThunk<MonoReflectionType *, MonoReflectionType **> methodthunk_MarshalUtils_ArrayGetElementType;
GDMonoMethodThunk<MonoReflectionType *, MonoReflectionType **, MonoReflectionType **> methodthunk_MarshalUtils_DictionaryGetKeyValueTypes;
- GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType;
- GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType;
- GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *, MonoReflectionType **> methodthunk_MarshalUtils_GenericIEnumerableIsAssignableFromType_with_info;
- GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *, MonoReflectionType **, MonoReflectionType **> methodthunk_MarshalUtils_GenericIDictionaryIsAssignableFromType_with_info;
-
GDMonoMethodThunkR<MonoReflectionType *, MonoReflectionType *> methodthunk_MarshalUtils_MakeGenericArrayType;
GDMonoMethodThunkR<MonoReflectionType *, MonoReflectionType *, MonoReflectionType *> methodthunk_MarshalUtils_MakeGenericDictionaryType;
- GDMonoMethodThunk<MonoObject *, Array *> methodthunk_MarshalUtils_EnumerableToArray;
- GDMonoMethodThunk<MonoObject *, Dictionary *> methodthunk_MarshalUtils_IDictionaryToDictionary;
- GDMonoMethodThunk<MonoObject *, Dictionary *> methodthunk_MarshalUtils_GenericIDictionaryToDictionary;
-
// End of MarshalUtils methods
- Ref<MonoGCHandle> task_scheduler_handle;
+ Ref<MonoGCHandleRef> task_scheduler_handle;
bool corlib_cache_updated;
bool godot_api_cache_updated;
@@ -176,15 +186,6 @@ inline void clear_corlib_cache() {
inline void clear_godot_api_cache() {
cached_data.clear_godot_api_cache();
}
-
-_FORCE_INLINE_ bool tools_godot_api_check() {
-#ifdef TOOLS_ENABLED
- return cached_data.godot_api_cache_updated;
-#else
- return true; // Assume it's updated if this was called, otherwise it's a bug
-#endif
-}
-
} // namespace GDMonoCache
#define CACHED_CLASS(m_class) (GDMonoCache::cached_data.class_##m_class)
@@ -195,10 +196,4 @@ _FORCE_INLINE_ bool tools_godot_api_check() {
#define CACHED_METHOD_THUNK(m_class, m_method) (GDMonoCache::cached_data.methodthunk_##m_class##_##m_method)
#define CACHED_PROPERTY(m_class, m_property) (GDMonoCache::cached_data.property_##m_class##_##m_property)
-#ifdef REAL_T_IS_DOUBLE
-#define REAL_T_MONOCLASS CACHED_CLASS_RAW(double)
-#else
-#define REAL_T_MONOCLASS CACHED_CLASS_RAW(float)
-#endif
-
#endif // GD_MONO_CACHE_H
diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp
index 2132fd36f7..f9fddd931b 100644
--- a/modules/mono/mono_gd/gd_mono_class.cpp
+++ b/modules/mono/mono_gd/gd_mono_class.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,6 +31,7 @@
#include "gd_mono_class.h"
#include <mono/metadata/attrdefs.h>
+#include <mono/metadata/debug-helpers.h>
#include "gd_mono_assembly.h"
#include "gd_mono_cache.h"
@@ -40,7 +41,7 @@ String GDMonoClass::get_full_name(MonoClass *p_mono_class) {
// mono_type_get_full_name is not exposed to embedders, but this seems to do the job
MonoReflectionType *type_obj = mono_type_get_object(mono_domain_get(), get_mono_type(p_mono_class));
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
MonoString *str = GDMonoUtils::object_to_string((MonoObject *)type_obj, &exc);
UNHANDLED_EXCEPTION(exc);
@@ -55,7 +56,11 @@ String GDMonoClass::get_full_name() const {
return get_full_name(mono_class);
}
-MonoType *GDMonoClass::get_mono_type() {
+String GDMonoClass::get_type_desc() const {
+ return GDMonoUtils::get_type_desc(get_mono_type());
+}
+
+MonoType *GDMonoClass::get_mono_type() const {
// Careful, you cannot compare two MonoType*.
// There is mono_metadata_type_equal, how is this different from comparing two MonoClass*?
return get_mono_type(mono_class);
@@ -74,27 +79,42 @@ bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const {
return mono_class_is_assignable_from(mono_class, p_from->mono_class);
}
-GDMonoClass *GDMonoClass::get_parent_class() {
+StringName GDMonoClass::get_namespace() const {
+ GDMonoClass *nesting_class = get_nesting_class();
+ if (!nesting_class) {
+ return namespace_name;
+ }
+ return nesting_class->get_namespace();
+}
+
+String GDMonoClass::get_name_for_lookup() const {
+ GDMonoClass *nesting_class = get_nesting_class();
+ if (!nesting_class) {
+ return class_name;
+ }
+ return nesting_class->get_name_for_lookup() + "/" + class_name;
+}
+
+GDMonoClass *GDMonoClass::get_parent_class() const {
MonoClass *parent_mono_class = mono_class_get_parent(mono_class);
- return parent_mono_class ? GDMono::get_singleton()->get_class(parent_mono_class) : NULL;
+ return parent_mono_class ? GDMono::get_singleton()->get_class(parent_mono_class) : nullptr;
}
-GDMonoClass *GDMonoClass::get_nesting_class() {
+GDMonoClass *GDMonoClass::get_nesting_class() const {
MonoClass *nesting_type = mono_class_get_nesting_type(mono_class);
- return nesting_type ? GDMono::get_singleton()->get_class(nesting_type) : NULL;
+ return nesting_type ? GDMono::get_singleton()->get_class(nesting_type) : nullptr;
}
#ifdef TOOLS_ENABLED
Vector<MonoClassField *> GDMonoClass::get_enum_fields() {
-
bool class_is_enum = mono_class_is_enum(mono_class);
ERR_FAIL_COND_V(!class_is_enum, Vector<MonoClassField *>());
Vector<MonoClassField *> enum_fields;
- void *iter = NULL;
- MonoClassField *raw_field = NULL;
- while ((raw_field = mono_class_get_fields(get_mono_ptr(), &iter)) != NULL) {
+ void *iter = nullptr;
+ MonoClassField *raw_field = nullptr;
+ while ((raw_field = mono_class_get_fields(get_mono_ptr(), &iter)) != nullptr) {
uint32_t field_flags = mono_field_get_flags(raw_field);
// Enums have an instance field named value__ which holds the value of the enum.
@@ -109,65 +129,65 @@ Vector<MonoClassField *> GDMonoClass::get_enum_fields() {
#endif
bool GDMonoClass::has_attribute(GDMonoClass *p_attr_class) {
-
#ifdef DEBUG_ENABLED
ERR_FAIL_NULL_V(p_attr_class, false);
#endif
- if (!attrs_fetched)
+ if (!attrs_fetched) {
fetch_attributes();
+ }
- if (!attributes)
+ if (!attributes) {
return false;
+ }
return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr());
}
MonoObject *GDMonoClass::get_attribute(GDMonoClass *p_attr_class) {
-
#ifdef DEBUG_ENABLED
- ERR_FAIL_NULL_V(p_attr_class, NULL);
+ ERR_FAIL_NULL_V(p_attr_class, nullptr);
#endif
- if (!attrs_fetched)
+ if (!attrs_fetched) {
fetch_attributes();
+ }
- if (!attributes)
- return NULL;
+ if (!attributes) {
+ return nullptr;
+ }
return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr());
}
void GDMonoClass::fetch_attributes() {
-
- ERR_FAIL_COND(attributes != NULL);
+ ERR_FAIL_COND(attributes != nullptr);
attributes = mono_custom_attrs_from_class(get_mono_ptr());
attrs_fetched = true;
}
void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base) {
-
CRASH_COND(!CACHED_CLASS(GodotObject)->is_assignable_from(this));
- if (methods_fetched)
+ if (methods_fetched) {
return;
+ }
- void *iter = NULL;
- MonoMethod *raw_method = NULL;
- while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != NULL) {
- StringName name = mono_method_get_name(raw_method);
+ void *iter = nullptr;
+ MonoMethod *raw_method = nullptr;
+ while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != nullptr) {
+ StringName name = String::utf8(mono_method_get_name(raw_method));
// get_method implicitly fetches methods and adds them to this->methods
GDMonoMethod *method = get_method(raw_method, name);
ERR_CONTINUE(!method);
if (method->get_name() != name) {
-
#ifdef DEBUG_ENABLED
String fullname = method->get_ret_type_full_name() + " " + name + "(" + method->get_signature_desc(true) + ")";
- WARN_PRINTS("Method '" + fullname + "' is hidden by Godot API method. Should be '" +
- method->get_full_name_no_class() + "'. In class '" + namespace_name + "." + class_name + "'.");
+ WARN_PRINT("Method '" + fullname + "' is hidden by Godot API method. Should be '" +
+ method->get_full_name_no_class() + "'. In class '" + namespace_name + "." + class_name + "'.");
#endif
continue;
}
@@ -177,7 +197,6 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base
// This allows us to warn the user here if he is using snake_case by mistake.
if (p_native_base != this) {
-
GDMonoClass *native_top = p_native_base;
while (native_top) {
GDMonoMethod *m = native_top->get_method(name, method->get_parameters_count());
@@ -185,23 +204,25 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base
if (m && m->get_name() != name) {
// found
String fullname = m->get_ret_type_full_name() + " " + name + "(" + m->get_signature_desc(true) + ")";
- WARN_PRINTS("Method '" + fullname + "' should be '" + m->get_full_name_no_class() +
- "'. In class '" + namespace_name + "." + class_name + "'.");
+ WARN_PRINT("Method '" + fullname + "' should be '" + m->get_full_name_no_class() +
+ "'. In class '" + namespace_name + "." + class_name + "'.");
break;
}
- if (native_top == CACHED_CLASS(GodotObject))
+ if (native_top == CACHED_CLASS(GodotObject)) {
break;
+ }
native_top = native_top->get_parent_class();
}
}
#endif
- uint32_t flags = mono_method_get_flags(method->mono_method, NULL);
+ uint32_t flags = mono_method_get_flags(method->mono_method, nullptr);
- if (!(flags & MONO_METHOD_ATTR_VIRTUAL))
+ if (!(flags & MONO_METHOD_ATTR_VIRTUAL)) {
continue;
+ }
// Virtual method of Godot Object derived type, let's try to find GodotMethod attribute
@@ -223,15 +244,17 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base
#endif
MethodKey key = MethodKey(godot_method_name, method->get_parameters_count());
GDMonoMethod **existing_method = methods.getptr(key);
- if (existing_method)
+ if (existing_method) {
memdelete(*existing_method); // Must delete old one
+ }
methods.set(key, method);
break;
}
- if (top == CACHED_CLASS(GodotObject))
+ if (top == CACHED_CLASS(GodotObject)) {
break;
+ }
top = top->get_parent_class();
}
@@ -241,40 +264,44 @@ void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base
}
GDMonoMethod *GDMonoClass::get_fetched_method_unknown_params(const StringName &p_name) {
+ ERR_FAIL_COND_V(!methods_fetched, nullptr);
- ERR_FAIL_COND_V(!methods_fetched, NULL);
-
- const MethodKey *k = NULL;
+ const MethodKey *k = nullptr;
while ((k = methods.next(k))) {
- if (k->name == p_name)
+ if (k->name == p_name) {
return methods.get(*k);
+ }
}
- return NULL;
+ return nullptr;
}
bool GDMonoClass::has_fetched_method_unknown_params(const StringName &p_name) {
-
- return get_fetched_method_unknown_params(p_name) != NULL;
+ return get_fetched_method_unknown_params(p_name) != nullptr;
}
bool GDMonoClass::implements_interface(GDMonoClass *p_interface) {
-
return mono_class_implements_interface(mono_class, p_interface->get_mono_ptr());
}
-GDMonoMethod *GDMonoClass::get_method(const StringName &p_name, int p_params_count) {
+bool GDMonoClass::has_public_parameterless_ctor() {
+ GDMonoMethod *ctor = get_method(".ctor", 0);
+ return ctor && ctor->get_visibility() == IMonoClassMember::PUBLIC;
+}
+GDMonoMethod *GDMonoClass::get_method(const StringName &p_name, uint16_t p_params_count) {
MethodKey key = MethodKey(p_name, p_params_count);
GDMonoMethod **match = methods.getptr(key);
- if (match)
+ if (match) {
return *match;
+ }
- if (methods_fetched)
- return NULL;
+ if (methods_fetched) {
+ return nullptr;
+ }
MonoMethod *raw_method = mono_class_get_method_from_name(mono_class, String(p_name).utf8().get_data(), p_params_count);
@@ -285,36 +312,34 @@ GDMonoMethod *GDMonoClass::get_method(const StringName &p_name, int p_params_cou
return method;
}
- return NULL;
+ return nullptr;
}
GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method) {
-
MonoMethodSignature *sig = mono_method_signature(p_raw_method);
int params_count = mono_signature_get_param_count(sig);
- StringName method_name = mono_method_get_name(p_raw_method);
+ StringName method_name = String::utf8(mono_method_get_name(p_raw_method));
return get_method(p_raw_method, method_name, params_count);
}
GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method, const StringName &p_name) {
-
MonoMethodSignature *sig = mono_method_signature(p_raw_method);
int params_count = mono_signature_get_param_count(sig);
return get_method(p_raw_method, p_name, params_count);
}
-GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method, const StringName &p_name, int p_params_count) {
-
- ERR_FAIL_NULL_V(p_raw_method, NULL);
+GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method, const StringName &p_name, uint16_t p_params_count) {
+ ERR_FAIL_NULL_V(p_raw_method, nullptr);
MethodKey key = MethodKey(p_name, p_params_count);
GDMonoMethod **match = methods.getptr(key);
- if (match)
+ if (match) {
return *match;
+ }
GDMonoMethod *method = memnew(GDMonoMethod(p_name, p_raw_method));
methods.set(key, method);
@@ -323,25 +348,29 @@ GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method, const StringName
}
GDMonoMethod *GDMonoClass::get_method_with_desc(const String &p_description, bool p_include_namespace) {
-
MonoMethodDesc *desc = mono_method_desc_new(p_description.utf8().get_data(), p_include_namespace);
MonoMethod *method = mono_method_desc_search_in_class(desc, mono_class);
mono_method_desc_free(desc);
- ERR_FAIL_COND_V(mono_method_get_class(method) != mono_class, NULL);
+ if (!method) {
+ return nullptr;
+ }
+
+ ERR_FAIL_COND_V(mono_method_get_class(method) != mono_class, nullptr);
return get_method(method);
}
GDMonoField *GDMonoClass::get_field(const StringName &p_name) {
-
Map<StringName, GDMonoField *>::Element *result = fields.find(p_name);
- if (result)
+ if (result) {
return result->value();
+ }
- if (fields_fetched)
- return NULL;
+ if (fields_fetched) {
+ return nullptr;
+ }
MonoClassField *raw_field = mono_class_get_field_from_name(mono_class, String(p_name).utf8().get_data());
@@ -352,18 +381,18 @@ GDMonoField *GDMonoClass::get_field(const StringName &p_name) {
return field;
}
- return NULL;
+ return nullptr;
}
const Vector<GDMonoField *> &GDMonoClass::get_all_fields() {
-
- if (fields_fetched)
+ if (fields_fetched) {
return fields_list;
+ }
- void *iter = NULL;
- MonoClassField *raw_field = NULL;
- while ((raw_field = mono_class_get_fields(mono_class, &iter)) != NULL) {
- StringName name = mono_field_get_name(raw_field);
+ void *iter = nullptr;
+ MonoClassField *raw_field = nullptr;
+ while ((raw_field = mono_class_get_fields(mono_class, &iter)) != nullptr) {
+ StringName name = String::utf8(mono_field_get_name(raw_field));
Map<StringName, GDMonoField *>::Element *match = fields.find(name);
@@ -382,14 +411,15 @@ const Vector<GDMonoField *> &GDMonoClass::get_all_fields() {
}
GDMonoProperty *GDMonoClass::get_property(const StringName &p_name) {
-
Map<StringName, GDMonoProperty *>::Element *result = properties.find(p_name);
- if (result)
+ if (result) {
return result->value();
+ }
- if (properties_fetched)
- return NULL;
+ if (properties_fetched) {
+ return nullptr;
+ }
MonoProperty *raw_property = mono_class_get_property_from_name(mono_class, String(p_name).utf8().get_data());
@@ -400,18 +430,18 @@ GDMonoProperty *GDMonoClass::get_property(const StringName &p_name) {
return property;
}
- return NULL;
+ return nullptr;
}
const Vector<GDMonoProperty *> &GDMonoClass::get_all_properties() {
-
- if (properties_fetched)
+ if (properties_fetched) {
return properties_list;
+ }
- void *iter = NULL;
- MonoProperty *raw_property = NULL;
- while ((raw_property = mono_class_get_properties(mono_class, &iter)) != NULL) {
- StringName name = mono_property_get_name(raw_property);
+ void *iter = nullptr;
+ MonoProperty *raw_property = nullptr;
+ while ((raw_property = mono_class_get_properties(mono_class, &iter)) != nullptr) {
+ StringName name = String::utf8(mono_property_get_name(raw_property));
Map<StringName, GDMonoProperty *>::Element *match = properties.find(name);
@@ -430,21 +460,22 @@ const Vector<GDMonoProperty *> &GDMonoClass::get_all_properties() {
}
const Vector<GDMonoClass *> &GDMonoClass::get_all_delegates() {
- if (delegates_fetched)
+ if (delegates_fetched) {
return delegates_list;
+ }
- void *iter = NULL;
- MonoClass *raw_class = NULL;
- while ((raw_class = mono_class_get_nested_types(mono_class, &iter)) != NULL) {
+ void *iter = nullptr;
+ MonoClass *raw_class = nullptr;
+ while ((raw_class = mono_class_get_nested_types(mono_class, &iter)) != nullptr) {
if (mono_class_is_delegate(raw_class)) {
- StringName name = mono_class_get_name(raw_class);
+ StringName name = String::utf8(mono_class_get_name(raw_class));
Map<StringName, GDMonoClass *>::Element *match = delegates.find(name);
if (match) {
delegates_list.push_back(match->get());
} else {
- GDMonoClass *delegate = memnew(GDMonoClass(mono_class_get_namespace(raw_class), mono_class_get_name(raw_class), raw_class, assembly));
+ GDMonoClass *delegate = memnew(GDMonoClass(String::utf8(mono_class_get_namespace(raw_class)), String::utf8(mono_class_get_name(raw_class)), raw_class, assembly));
delegates.insert(name, delegate);
delegates_list.push_back(delegate);
}
@@ -457,12 +488,11 @@ const Vector<GDMonoClass *> &GDMonoClass::get_all_delegates() {
}
const Vector<GDMonoMethod *> &GDMonoClass::get_all_methods() {
-
if (!method_list_fetched) {
- void *iter = NULL;
- MonoMethod *raw_method = NULL;
- while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != NULL) {
- method_list.push_back(memnew(GDMonoMethod(mono_method_get_name(raw_method), raw_method)));
+ void *iter = nullptr;
+ MonoMethod *raw_method = nullptr;
+ while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != nullptr) {
+ method_list.push_back(memnew(GDMonoMethod(String::utf8(mono_method_get_name(raw_method)), raw_method)));
}
method_list_fetched = true;
@@ -472,14 +502,13 @@ const Vector<GDMonoMethod *> &GDMonoClass::get_all_methods() {
}
GDMonoClass::GDMonoClass(const StringName &p_namespace, const StringName &p_name, MonoClass *p_class, GDMonoAssembly *p_assembly) {
-
namespace_name = p_namespace;
class_name = p_name;
mono_class = p_class;
assembly = p_assembly;
attrs_fetched = false;
- attributes = NULL;
+ attributes = nullptr;
methods_fetched = false;
method_list_fetched = false;
@@ -489,7 +518,6 @@ GDMonoClass::GDMonoClass(const StringName &p_namespace, const StringName &p_name
}
GDMonoClass::~GDMonoClass() {
-
if (attributes) {
mono_custom_attrs_free(attributes);
}
@@ -512,7 +540,7 @@ GDMonoClass::~GDMonoClass() {
Vector<GDMonoMethod *> deleted_methods;
deleted_methods.resize(methods.size());
- const MethodKey *k = NULL;
+ const MethodKey *k = nullptr;
while ((k = methods.next(k))) {
GDMonoMethod *method = methods.get(*k);
diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h
index 0c9a8cdafe..daea75bae8 100644
--- a/modules/mono/mono_gd/gd_mono_class.h
+++ b/modules/mono/mono_gd/gd_mono_class.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,10 +31,8 @@
#ifndef GD_MONO_CLASS_H
#define GD_MONO_CLASS_H
-#include <mono/metadata/debug-helpers.h>
-
-#include "core/map.h"
-#include "core/ustring.h"
+#include "core/string/ustring.h"
+#include "core/templates/map.h"
#include "gd_mono_field.h"
#include "gd_mono_header.h"
@@ -61,13 +59,12 @@ class GDMonoClass {
MethodKey() {}
- MethodKey(const StringName &p_name, int p_params_count) {
- name = p_name;
- params_count = p_params_count;
+ MethodKey(const StringName &p_name, uint16_t p_params_count) :
+ name(p_name), params_count(p_params_count) {
}
StringName name;
- int params_count;
+ uint16_t params_count = 0;
};
StringName namespace_name;
@@ -107,21 +104,23 @@ public:
static MonoType *get_mono_type(MonoClass *p_mono_class);
String get_full_name() const;
- MonoType *get_mono_type();
+ String get_type_desc() const;
+ MonoType *get_mono_type() const;
uint32_t get_flags() const;
bool is_static() const;
bool is_assignable_from(GDMonoClass *p_from) const;
- _FORCE_INLINE_ StringName get_namespace() const { return namespace_name; }
+ StringName get_namespace() const;
_FORCE_INLINE_ StringName get_name() const { return class_name; }
+ String get_name_for_lookup() const;
_FORCE_INLINE_ MonoClass *get_mono_ptr() const { return mono_class; }
_FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; }
- GDMonoClass *get_parent_class();
- GDMonoClass *get_nesting_class();
+ GDMonoClass *get_parent_class() const;
+ GDMonoClass *get_nesting_class() const;
#ifdef TOOLS_ENABLED
Vector<MonoClassField *> get_enum_fields();
@@ -137,11 +136,12 @@ public:
void fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base);
bool implements_interface(GDMonoClass *p_interface);
+ bool has_public_parameterless_ctor();
- GDMonoMethod *get_method(const StringName &p_name, int p_params_count = 0);
+ GDMonoMethod *get_method(const StringName &p_name, uint16_t p_params_count = 0);
GDMonoMethod *get_method(MonoMethod *p_raw_method);
GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name);
- GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name, int p_params_count);
+ GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name, uint16_t p_params_count);
GDMonoMethod *get_method_with_desc(const String &p_description, bool p_include_namespace);
GDMonoField *get_field(const StringName &p_name);
diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp
index 3e0f9a3f15..111eaa0bbf 100644
--- a/modules/mono/mono_gd/gd_mono_field.cpp
+++ b/modules/mono/mono_gd/gd_mono_field.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -37,34 +37,24 @@
#include "gd_mono_marshal.h"
#include "gd_mono_utils.h"
+void GDMonoField::set_value(MonoObject *p_object, MonoObject *p_value) {
+ mono_field_set_value(p_object, mono_field, p_value);
+}
+
void GDMonoField::set_value_raw(MonoObject *p_object, void *p_ptr) {
mono_field_set_value(p_object, mono_field, &p_ptr);
}
void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_value) {
-#define SET_FROM_STRUCT(m_type) \
- { \
- GDMonoMarshal::M_##m_type from = MARSHALLED_OUT(m_type, p_value.operator ::m_type()); \
- mono_field_set_value(p_object, mono_field, &from); \
- }
-
-#define SET_FROM_ARRAY(m_type) \
- { \
- MonoArray *managed = GDMonoMarshal::m_type##_to_mono_array(p_value.operator ::m_type()); \
- mono_field_set_value(p_object, mono_field, managed); \
- }
-
switch (type.type_encoding) {
case MONO_TYPE_BOOLEAN: {
MonoBoolean val = p_value.operator bool();
mono_field_set_value(p_object, mono_field, &val);
} break;
-
case MONO_TYPE_CHAR: {
int16_t val = p_value.operator unsigned short();
mono_field_set_value(p_object, mono_field, &val);
} break;
-
case MONO_TYPE_I1: {
int8_t val = p_value.operator signed char();
mono_field_set_value(p_object, mono_field, &val);
@@ -81,7 +71,6 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
int64_t val = p_value.operator int64_t();
mono_field_set_value(p_object, mono_field, &val);
} break;
-
case MONO_TYPE_U1: {
uint8_t val = p_value.operator unsigned char();
mono_field_set_value(p_object, mono_field, &val);
@@ -98,78 +87,104 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
uint64_t val = p_value.operator uint64_t();
mono_field_set_value(p_object, mono_field, &val);
} break;
-
case MONO_TYPE_R4: {
float val = p_value.operator float();
mono_field_set_value(p_object, mono_field, &val);
} break;
-
case MONO_TYPE_R8: {
double val = p_value.operator double();
mono_field_set_value(p_object, mono_field, &val);
} break;
-
- case MONO_TYPE_STRING: {
- if (p_value.get_type() == Variant::NIL) {
- // Otherwise, Variant -> String would return the string "Null"
- MonoString *mono_string = NULL;
- mono_field_set_value(p_object, mono_field, mono_string);
- } else {
- MonoString *mono_string = GDMonoMarshal::mono_string_from_godot(p_value);
- mono_field_set_value(p_object, mono_field, mono_string);
- }
- } break;
-
case MONO_TYPE_VALUETYPE: {
GDMonoClass *tclass = type.type_class;
if (tclass == CACHED_CLASS(Vector2)) {
- SET_FROM_STRUCT(Vector2);
+ GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_value.operator ::Vector2());
+ mono_field_set_value(p_object, mono_field, &from);
+ break;
+ }
+
+ if (tclass == CACHED_CLASS(Vector2i)) {
+ GDMonoMarshal::M_Vector2i from = MARSHALLED_OUT(Vector2i, p_value.operator ::Vector2i());
+ mono_field_set_value(p_object, mono_field, &from);
break;
}
if (tclass == CACHED_CLASS(Rect2)) {
- SET_FROM_STRUCT(Rect2);
+ GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_value.operator ::Rect2());
+ mono_field_set_value(p_object, mono_field, &from);
+ break;
+ }
+
+ if (tclass == CACHED_CLASS(Rect2i)) {
+ GDMonoMarshal::M_Rect2i from = MARSHALLED_OUT(Rect2i, p_value.operator ::Rect2i());
+ mono_field_set_value(p_object, mono_field, &from);
break;
}
if (tclass == CACHED_CLASS(Transform2D)) {
- SET_FROM_STRUCT(Transform2D);
+ GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_value.operator ::Transform2D());
+ mono_field_set_value(p_object, mono_field, &from);
break;
}
if (tclass == CACHED_CLASS(Vector3)) {
- SET_FROM_STRUCT(Vector3);
+ GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_value.operator ::Vector3());
+ mono_field_set_value(p_object, mono_field, &from);
+ break;
+ }
+
+ if (tclass == CACHED_CLASS(Vector3i)) {
+ GDMonoMarshal::M_Vector3i from = MARSHALLED_OUT(Vector3i, p_value.operator ::Vector3i());
+ mono_field_set_value(p_object, mono_field, &from);
break;
}
if (tclass == CACHED_CLASS(Basis)) {
- SET_FROM_STRUCT(Basis);
+ GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_value.operator ::Basis());
+ mono_field_set_value(p_object, mono_field, &from);
break;
}
- if (tclass == CACHED_CLASS(Quat)) {
- SET_FROM_STRUCT(Quat);
+ if (tclass == CACHED_CLASS(Quaternion)) {
+ GDMonoMarshal::M_Quaternion from = MARSHALLED_OUT(Quaternion, p_value.operator ::Quaternion());
+ mono_field_set_value(p_object, mono_field, &from);
break;
}
- if (tclass == CACHED_CLASS(Transform)) {
- SET_FROM_STRUCT(Transform);
+ if (tclass == CACHED_CLASS(Transform3D)) {
+ GDMonoMarshal::M_Transform3D from = MARSHALLED_OUT(Transform3D, p_value.operator ::Transform3D());
+ mono_field_set_value(p_object, mono_field, &from);
break;
}
if (tclass == CACHED_CLASS(AABB)) {
- SET_FROM_STRUCT(AABB);
+ GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_value.operator ::AABB());
+ mono_field_set_value(p_object, mono_field, &from);
break;
}
if (tclass == CACHED_CLASS(Color)) {
- SET_FROM_STRUCT(Color);
+ GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_value.operator ::Color());
+ mono_field_set_value(p_object, mono_field, &from);
break;
}
if (tclass == CACHED_CLASS(Plane)) {
- SET_FROM_STRUCT(Plane);
+ GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_value.operator ::Plane());
+ mono_field_set_value(p_object, mono_field, &from);
+ break;
+ }
+
+ if (tclass == CACHED_CLASS(Callable)) {
+ GDMonoMarshal::M_Callable val = GDMonoMarshal::callable_to_managed(p_value.operator Callable());
+ mono_field_set_value(p_object, mono_field, &val);
+ break;
+ }
+
+ if (tclass == CACHED_CLASS(SignalInfo)) {
+ GDMonoMarshal::M_SignalInfo val = GDMonoMarshal::signal_info_to_managed(p_value.operator Signal());
+ mono_field_set_value(p_object, mono_field, &val);
break;
}
@@ -236,129 +251,35 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
ERR_FAIL_MSG("Attempted to set the value of a field of unmarshallable type: '" + tclass->get_name() + "'.");
} break;
-
+ case MONO_TYPE_STRING: {
+ if (p_value.get_type() == Variant::NIL) {
+ // Otherwise, Variant -> String would return the string "Null"
+ MonoString *mono_string = nullptr;
+ mono_field_set_value(p_object, mono_field, mono_string);
+ } else {
+ MonoString *mono_string = GDMonoMarshal::mono_string_from_godot(p_value);
+ mono_field_set_value(p_object, mono_field, mono_string);
+ }
+ } break;
case MONO_TYPE_ARRAY:
case MONO_TYPE_SZARRAY: {
- MonoArrayType *array_type = mono_type_get_array_type(type.type_class->get_mono_type());
-
- if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) {
- SET_FROM_ARRAY(Array);
- break;
- }
-
- if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) {
- SET_FROM_ARRAY(PoolByteArray);
- break;
- }
-
- if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) {
- SET_FROM_ARRAY(PoolIntArray);
- break;
- }
-
- if (array_type->eklass == REAL_T_MONOCLASS) {
- SET_FROM_ARRAY(PoolRealArray);
- break;
- }
-
- if (array_type->eklass == CACHED_CLASS_RAW(String)) {
- SET_FROM_ARRAY(PoolStringArray);
- break;
- }
-
- if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) {
- SET_FROM_ARRAY(PoolVector2Array);
- break;
- }
-
- if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) {
- SET_FROM_ARRAY(PoolVector3Array);
- break;
- }
-
- if (array_type->eklass == CACHED_CLASS_RAW(Color)) {
- SET_FROM_ARRAY(PoolColorArray);
- break;
+ MonoArray *managed = GDMonoMarshal::variant_to_mono_array(p_value, type.type_class);
+ if (likely(managed != nullptr)) {
+ mono_field_set_value(p_object, mono_field, managed);
}
-
- ERR_FAIL_MSG("Attempted to convert Variant to a managed array of unmarshallable element type.");
} break;
-
case MONO_TYPE_CLASS: {
- GDMonoClass *type_class = type.type_class;
-
- // GodotObject
- if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) {
- MonoObject *managed = GDMonoUtils::unmanaged_get_managed(p_value.operator Object *());
+ MonoObject *managed = GDMonoMarshal::variant_to_mono_object_of_class(p_value, type.type_class);
+ if (likely(managed != nullptr)) {
mono_field_set_value(p_object, mono_field, managed);
- break;
}
-
- if (CACHED_CLASS(NodePath) == type_class) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator NodePath());
- mono_field_set_value(p_object, mono_field, managed);
- break;
- }
-
- if (CACHED_CLASS(RID) == type_class) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator RID());
- mono_field_set_value(p_object, mono_field, managed);
- break;
- }
-
- if (CACHED_CLASS(Dictionary) == type_class) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary));
- mono_field_set_value(p_object, mono_field, managed);
- break;
- }
-
- if (CACHED_CLASS(Array) == type_class) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
- mono_field_set_value(p_object, mono_field, managed);
- break;
- }
-
- // The order in which we check the following interfaces is very important (dictionaries and generics first)
-
- MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
-
- MonoReflectionType *key_reftype, *value_reftype;
- if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(),
- GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype));
- mono_field_set_value(p_object, mono_field, managed);
- break;
- }
-
- if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary));
- mono_field_set_value(p_object, mono_field, managed);
- break;
- }
-
- MonoReflectionType *elem_reftype;
- if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(),
- GDMonoUtils::Marshal::make_generic_array_type(elem_reftype));
+ } break;
+ case MONO_TYPE_GENERICINST: {
+ MonoObject *managed = GDMonoMarshal::variant_to_mono_object_of_genericinst(p_value, type.type_class);
+ if (likely(managed != nullptr)) {
mono_field_set_value(p_object, mono_field, managed);
- break;
}
-
- if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- if (GDMonoCache::tools_godot_api_check()) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
- mono_field_set_value(p_object, mono_field, managed);
- break;
- } else {
- MonoObject *managed = (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_value.operator Array());
- mono_field_set_value(p_object, mono_field, managed);
- break;
- }
- }
-
- ERR_FAIL_MSG("Attempted to set the value of a field of unmarshallable type: '" + type_class->get_name() + "'.");
} break;
-
case MONO_TYPE_OBJECT: {
// Variant
switch (p_value.get_type()) {
@@ -370,7 +291,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
int32_t val = p_value.operator signed int();
mono_field_set_value(p_object, mono_field, &val);
} break;
- case Variant::REAL: {
+ case Variant::FLOAT: {
#ifdef REAL_T_IS_DOUBLE
double val = p_value.operator double();
mono_field_set_value(p_object, mono_field, &val);
@@ -384,48 +305,81 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
mono_field_set_value(p_object, mono_field, mono_string);
} break;
case Variant::VECTOR2: {
- SET_FROM_STRUCT(Vector2);
+ GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_value.operator ::Vector2());
+ mono_field_set_value(p_object, mono_field, &from);
+ } break;
+ case Variant::VECTOR2I: {
+ GDMonoMarshal::M_Vector2i from = MARSHALLED_OUT(Vector2i, p_value.operator ::Vector2i());
+ mono_field_set_value(p_object, mono_field, &from);
} break;
case Variant::RECT2: {
- SET_FROM_STRUCT(Rect2);
+ GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_value.operator ::Rect2());
+ mono_field_set_value(p_object, mono_field, &from);
+ } break;
+ case Variant::RECT2I: {
+ GDMonoMarshal::M_Rect2i from = MARSHALLED_OUT(Rect2i, p_value.operator ::Rect2i());
+ mono_field_set_value(p_object, mono_field, &from);
} break;
case Variant::VECTOR3: {
- SET_FROM_STRUCT(Vector3);
+ GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_value.operator ::Vector3());
+ mono_field_set_value(p_object, mono_field, &from);
+ } break;
+ case Variant::VECTOR3I: {
+ GDMonoMarshal::M_Vector3i from = MARSHALLED_OUT(Vector3i, p_value.operator ::Vector3i());
+ mono_field_set_value(p_object, mono_field, &from);
} break;
case Variant::TRANSFORM2D: {
- SET_FROM_STRUCT(Transform2D);
+ GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_value.operator ::Transform2D());
+ mono_field_set_value(p_object, mono_field, &from);
} break;
case Variant::PLANE: {
- SET_FROM_STRUCT(Plane);
+ GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_value.operator ::Plane());
+ mono_field_set_value(p_object, mono_field, &from);
} break;
- case Variant::QUAT: {
- SET_FROM_STRUCT(Quat);
+ case Variant::QUATERNION: {
+ GDMonoMarshal::M_Quaternion from = MARSHALLED_OUT(Quaternion, p_value.operator ::Quaternion());
+ mono_field_set_value(p_object, mono_field, &from);
} break;
case Variant::AABB: {
- SET_FROM_STRUCT(AABB);
+ GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_value.operator ::AABB());
+ mono_field_set_value(p_object, mono_field, &from);
} break;
case Variant::BASIS: {
- SET_FROM_STRUCT(Basis);
+ GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_value.operator ::Basis());
+ mono_field_set_value(p_object, mono_field, &from);
} break;
- case Variant::TRANSFORM: {
- SET_FROM_STRUCT(Transform);
+ case Variant::TRANSFORM3D: {
+ GDMonoMarshal::M_Transform3D from = MARSHALLED_OUT(Transform3D, p_value.operator ::Transform3D());
+ mono_field_set_value(p_object, mono_field, &from);
} break;
case Variant::COLOR: {
- SET_FROM_STRUCT(Color);
+ GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_value.operator ::Color());
+ mono_field_set_value(p_object, mono_field, &from);
+ } break;
+ case Variant::STRING_NAME: {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator StringName());
+ mono_field_set_value(p_object, mono_field, managed);
} break;
case Variant::NODE_PATH: {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator NodePath());
mono_field_set_value(p_object, mono_field, managed);
} break;
- case Variant::_RID: {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator RID());
+ case Variant::RID: {
+ MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator ::RID());
mono_field_set_value(p_object, mono_field, managed);
} break;
case Variant::OBJECT: {
MonoObject *managed = GDMonoUtils::unmanaged_get_managed(p_value.operator Object *());
mono_field_set_value(p_object, mono_field, managed);
- break;
- }
+ } break;
+ case Variant::CALLABLE: {
+ GDMonoMarshal::M_Callable val = GDMonoMarshal::callable_to_managed(p_value.operator Callable());
+ mono_field_set_value(p_object, mono_field, &val);
+ } break;
+ case Variant::SIGNAL: {
+ GDMonoMarshal::M_SignalInfo val = GDMonoMarshal::signal_info_to_managed(p_value.operator Signal());
+ mono_field_set_value(p_object, mono_field, &val);
+ } break;
case Variant::DICTIONARY: {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary));
mono_field_set_value(p_object, mono_field, managed);
@@ -434,90 +388,50 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
mono_field_set_value(p_object, mono_field, managed);
} break;
- case Variant::POOL_BYTE_ARRAY: {
- SET_FROM_ARRAY(PoolByteArray);
+ case Variant::PACKED_BYTE_ARRAY: {
+ MonoArray *managed = GDMonoMarshal::PackedByteArray_to_mono_array(p_value.operator ::PackedByteArray());
+ mono_field_set_value(p_object, mono_field, managed);
} break;
- case Variant::POOL_INT_ARRAY: {
- SET_FROM_ARRAY(PoolIntArray);
+ case Variant::PACKED_INT32_ARRAY: {
+ MonoArray *managed = GDMonoMarshal::PackedInt32Array_to_mono_array(p_value.operator ::PackedInt32Array());
+ mono_field_set_value(p_object, mono_field, managed);
} break;
- case Variant::POOL_REAL_ARRAY: {
- SET_FROM_ARRAY(PoolRealArray);
+ case Variant::PACKED_INT64_ARRAY: {
+ MonoArray *managed = GDMonoMarshal::PackedInt64Array_to_mono_array(p_value.operator ::PackedInt64Array());
+ mono_field_set_value(p_object, mono_field, managed);
} break;
- case Variant::POOL_STRING_ARRAY: {
- SET_FROM_ARRAY(PoolStringArray);
+ case Variant::PACKED_FLOAT32_ARRAY: {
+ MonoArray *managed = GDMonoMarshal::PackedFloat32Array_to_mono_array(p_value.operator ::PackedFloat32Array());
+ mono_field_set_value(p_object, mono_field, managed);
} break;
- case Variant::POOL_VECTOR2_ARRAY: {
- SET_FROM_ARRAY(PoolVector2Array);
+ case Variant::PACKED_FLOAT64_ARRAY: {
+ MonoArray *managed = GDMonoMarshal::PackedFloat64Array_to_mono_array(p_value.operator ::PackedFloat64Array());
+ mono_field_set_value(p_object, mono_field, managed);
} break;
- case Variant::POOL_VECTOR3_ARRAY: {
- SET_FROM_ARRAY(PoolVector3Array);
+ case Variant::PACKED_STRING_ARRAY: {
+ MonoArray *managed = GDMonoMarshal::PackedStringArray_to_mono_array(p_value.operator ::PackedStringArray());
+ mono_field_set_value(p_object, mono_field, managed);
} break;
- case Variant::POOL_COLOR_ARRAY: {
- SET_FROM_ARRAY(PoolColorArray);
+ case Variant::PACKED_VECTOR2_ARRAY: {
+ MonoArray *managed = GDMonoMarshal::PackedVector2Array_to_mono_array(p_value.operator ::PackedVector2Array());
+ mono_field_set_value(p_object, mono_field, managed);
} break;
- default: break;
- }
- } break;
-
- case MONO_TYPE_GENERICINST: {
- MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type());
-
- if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), type.type_class);
- mono_field_set_value(p_object, mono_field, managed);
- break;
- }
-
- if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), type.type_class);
- mono_field_set_value(p_object, mono_field, managed);
- break;
- }
-
- // The order in which we check the following interfaces is very important (dictionaries and generics first)
-
- MonoReflectionType *key_reftype, *value_reftype;
- if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(),
- GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype));
- mono_field_set_value(p_object, mono_field, managed);
- break;
- }
-
- if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary));
- mono_field_set_value(p_object, mono_field, managed);
- break;
- }
-
- MonoReflectionType *elem_reftype;
- if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(),
- GDMonoUtils::Marshal::make_generic_array_type(elem_reftype));
- mono_field_set_value(p_object, mono_field, managed);
- break;
- }
-
- if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- if (GDMonoCache::tools_godot_api_check()) {
- MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
+ case Variant::PACKED_VECTOR3_ARRAY: {
+ MonoArray *managed = GDMonoMarshal::PackedVector3Array_to_mono_array(p_value.operator ::PackedVector3Array());
mono_field_set_value(p_object, mono_field, managed);
- break;
- } else {
- MonoObject *managed = (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_value.operator Array());
+ } break;
+ case Variant::PACKED_COLOR_ARRAY: {
+ MonoArray *managed = GDMonoMarshal::PackedColorArray_to_mono_array(p_value.operator ::PackedColorArray());
mono_field_set_value(p_object, mono_field, managed);
+ } break;
+ default:
break;
- }
}
} break;
-
default: {
- ERR_PRINTS("Attempted to set the value of a field of unexpected type encoding: " + itos(type.type_encoding) + ".");
+ ERR_PRINT("Attempted to set the value of a field of unexpected type encoding: " + itos(type.type_encoding) + ".");
} break;
}
-
-#undef SET_FROM_ARRAY_AND_BREAK
-#undef SET_FROM_STRUCT_AND_BREAK
}
MonoObject *GDMonoField::get_value(MonoObject *p_object) {
@@ -540,29 +454,33 @@ String GDMonoField::get_string_value(MonoObject *p_object) {
bool GDMonoField::has_attribute(GDMonoClass *p_attr_class) {
ERR_FAIL_NULL_V(p_attr_class, false);
- if (!attrs_fetched)
+ if (!attrs_fetched) {
fetch_attributes();
+ }
- if (!attributes)
+ if (!attributes) {
return false;
+ }
return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr());
}
MonoObject *GDMonoField::get_attribute(GDMonoClass *p_attr_class) {
- ERR_FAIL_NULL_V(p_attr_class, NULL);
+ ERR_FAIL_NULL_V(p_attr_class, nullptr);
- if (!attrs_fetched)
+ if (!attrs_fetched) {
fetch_attributes();
+ }
- if (!attributes)
- return NULL;
+ if (!attributes) {
+ return nullptr;
+ }
return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr());
}
void GDMonoField::fetch_attributes() {
- ERR_FAIL_COND(attributes != NULL);
+ ERR_FAIL_COND(attributes != nullptr);
attributes = mono_custom_attrs_from_field(owner->get_mono_ptr(), mono_field);
attrs_fetched = true;
}
@@ -591,14 +509,14 @@ IMonoClassMember::Visibility GDMonoField::get_visibility() {
GDMonoField::GDMonoField(MonoClassField *p_mono_field, GDMonoClass *p_owner) {
owner = p_owner;
mono_field = p_mono_field;
- name = mono_field_get_name(mono_field);
+ name = String::utf8(mono_field_get_name(mono_field));
MonoType *field_type = mono_field_get_type(mono_field);
type.type_encoding = mono_type_get_type(field_type);
MonoClass *field_type_class = mono_class_from_mono_type(field_type);
type.type_class = GDMono::get_singleton()->get_class(field_type_class);
attrs_fetched = false;
- attributes = NULL;
+ attributes = nullptr;
}
GDMonoField::~GDMonoField() {
diff --git a/modules/mono/mono_gd/gd_mono_field.h b/modules/mono/mono_gd/gd_mono_field.h
index 76ee0963c4..ed5078c673 100644
--- a/modules/mono/mono_gd/gd_mono_field.h
+++ b/modules/mono/mono_gd/gd_mono_field.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -36,7 +36,6 @@
#include "i_mono_class_member.h"
class GDMonoField : public IMonoClassMember {
-
GDMonoClass *owner;
MonoClassField *mono_field;
@@ -47,21 +46,22 @@ class GDMonoField : public IMonoClassMember {
MonoCustomAttrInfo *attributes;
public:
- virtual GDMonoClass *get_enclosing_class() const GD_FINAL { return owner; }
+ virtual GDMonoClass *get_enclosing_class() const final { return owner; }
- virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_FIELD; }
+ virtual MemberType get_member_type() const final { return MEMBER_TYPE_FIELD; }
- virtual StringName get_name() const GD_FINAL { return name; }
+ virtual StringName get_name() const final { return name; }
- virtual bool is_static() GD_FINAL;
- virtual Visibility get_visibility() GD_FINAL;
+ virtual bool is_static() final;
+ virtual Visibility get_visibility() final;
- virtual bool has_attribute(GDMonoClass *p_attr_class) GD_FINAL;
- virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) GD_FINAL;
+ virtual bool has_attribute(GDMonoClass *p_attr_class) final;
+ virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) final;
void fetch_attributes();
_FORCE_INLINE_ ManagedType get_type() const { return type; }
+ void set_value(MonoObject *p_object, MonoObject *p_value);
void set_value_raw(MonoObject *p_object, void *p_ptr);
void set_value_from_variant(MonoObject *p_object, const Variant &p_value);
diff --git a/modules/mono/mono_gd/gd_mono_header.h b/modules/mono/mono_gd/gd_mono_header.h
index 0f4f888546..483030610f 100644
--- a/modules/mono/mono_gd/gd_mono_header.h
+++ b/modules/mono/mono_gd/gd_mono_header.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,7 +31,7 @@
#ifndef GD_MONO_HEADER_H
#define GD_MONO_HEADER_H
-#include "core/int_types.h"
+#include <cstdint>
#ifdef WIN32
#define GD_MONO_STDCALL __stdcall
diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp
index 75aa77c7b0..d7df18d5da 100644
--- a/modules/mono/mono_gd/gd_mono_internals.cpp
+++ b/modules/mono/mono_gd/gd_mono_internals.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -33,17 +33,17 @@
#include "../csharp_script.h"
#include "../mono_gc_handle.h"
#include "../utils/macros.h"
-#include "../utils/thread_local.h"
#include "gd_mono_class.h"
#include "gd_mono_marshal.h"
#include "gd_mono_utils.h"
+#include "core/debugger/engine_debugger.h"
+#include "core/debugger/script_debugger.h"
+
#include <mono/metadata/exception.h>
namespace GDMonoInternals {
-
void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
-
// This method should not fail
CRASH_COND(!unmanaged);
@@ -51,7 +51,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
// All mono objects created from the managed world (e.g.: 'new Player()')
// need to have a CSharpScript in order for their methods to be callable from the unmanaged side
- Reference *ref = Object::cast_to<Reference>(unmanaged);
+ RefCounted *rc = Object::cast_to<RefCounted>(unmanaged);
GDMonoClass *klass = GDMonoUtils::get_object_class(managed);
@@ -59,7 +59,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
GDMonoClass *native = GDMonoUtils::get_class_native_base(klass);
- CRASH_COND(native == NULL);
+ CRASH_COND(native == nullptr);
if (native == klass) {
// If it's just a wrapper Godot class and not a custom inheriting class, then attach a
@@ -73,18 +73,18 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
script_binding.inited = true;
script_binding.type_name = NATIVE_GDMONOCLASS_NAME(klass);
script_binding.wrapper_class = klass;
- script_binding.gchandle = ref ? MonoGCHandle::create_weak(managed) : MonoGCHandle::create_strong(managed);
+ script_binding.gchandle = rc ? MonoGCHandleData::new_weak_handle(managed) : MonoGCHandleData::new_strong_handle(managed);
script_binding.owner = unmanaged;
- if (ref) {
+ if (rc) {
// Unsafe refcount increment. The managed instance also counts as a reference.
// This way if the unmanaged world has no references to our owner
// but the managed instance is alive, the refcount will be 1 instead of 0.
- // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)
+ // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr)
// May not me referenced yet, so we must use init_ref() instead of reference()
- if (ref->init_ref()) {
- CSharpLanguage::get_singleton()->post_unsafe_reference(ref);
+ if (rc->init_ref()) {
+ CSharpLanguage::get_singleton()->post_unsafe_reference(rc);
}
}
@@ -99,31 +99,43 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
return;
}
- Ref<MonoGCHandle> gchandle = ref ? MonoGCHandle::create_weak(managed) : MonoGCHandle::create_strong(managed);
+ MonoGCHandleData gchandle = rc ? MonoGCHandleData::new_weak_handle(managed) : MonoGCHandleData::new_strong_handle(managed);
Ref<CSharpScript> script = CSharpScript::create_for_managed_type(klass, native);
CRASH_COND(script.is_null());
- ScriptInstance *si = CSharpInstance::create_for_managed_type(unmanaged, script.ptr(), gchandle);
+ CSharpInstance *csharp_instance = CSharpInstance::create_for_managed_type(unmanaged, script.ptr(), gchandle);
- unmanaged->set_script_and_instance(script.get_ref_ptr(), si);
+ unmanaged->set_script_and_instance(script, csharp_instance);
}
void unhandled_exception(MonoException *p_exc) {
- mono_unhandled_exception((MonoObject *)p_exc); // prints the exception as well
+ mono_print_unhandled_exception((MonoObject *)p_exc);
+ gd_unhandled_exception_event(p_exc);
if (GDMono::get_singleton()->get_unhandled_exception_policy() == GDMono::POLICY_TERMINATE_APP) {
// Too bad 'mono_invoke_unhandled_exception_hook' is not exposed to embedders
- GDMono::unhandled_exception_hook((MonoObject *)p_exc, NULL);
+ mono_unhandled_exception((MonoObject *)p_exc);
+ GDMono::unhandled_exception_hook((MonoObject *)p_exc, nullptr);
GD_UNREACHABLE();
} else {
#ifdef DEBUG_ENABLED
- GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc);
- if (ScriptDebugger::get_singleton())
- ScriptDebugger::get_singleton()->idle_poll();
+ GDMonoUtils::debug_send_unhandled_exception_error(p_exc);
+ if (EngineDebugger::is_active()) {
+ EngineDebugger::get_singleton()->poll_events(false);
+ }
#endif
}
}
+void gd_unhandled_exception_event(MonoException *p_exc) {
+ MonoImage *mono_image = GDMono::get_singleton()->get_core_api_assembly()->get_image();
+
+ MonoClass *gd_klass = mono_class_from_name(mono_image, "Godot", "GD");
+ MonoMethod *unhandled_exception_method = mono_class_get_method_from_name(gd_klass, "OnUnhandledException", -1);
+ void *args[1];
+ args[0] = p_exc;
+ mono_runtime_invoke(unhandled_exception_method, nullptr, (void **)args, nullptr);
+}
} // namespace GDMonoInternals
diff --git a/modules/mono/mono_gd/gd_mono_internals.h b/modules/mono/mono_gd/gd_mono_internals.h
index 038d17f782..26eb270eee 100644
--- a/modules/mono/mono_gd/gd_mono_internals.h
+++ b/modules/mono/mono_gd/gd_mono_internals.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -35,10 +35,9 @@
#include "../utils/macros.h"
-#include "core/object.h"
+#include "core/object/class_db.h"
namespace GDMonoInternals {
-
void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged);
/**
@@ -47,6 +46,7 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged);
*/
void unhandled_exception(MonoException *p_exc);
+void gd_unhandled_exception_event(MonoException *p_exc);
} // namespace GDMonoInternals
#endif // GD_MONO_INTERNALS_H
diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp
index ad68a4d90e..179bbfb40c 100644
--- a/modules/mono/mono_gd/gd_mono_log.cpp
+++ b/modules/mono/mono_gd/gd_mono_log.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -32,7 +32,7 @@
#include <stdlib.h> // abort
-#include "core/os/dir_access.h"
+#include "core/io/dir_access.h"
#include "core/os/os.h"
#include "../godotsharp_dirs.h"
@@ -46,44 +46,50 @@ static CharString get_default_log_level() {
#endif
}
-GDMonoLog *GDMonoLog::singleton = NULL;
+GDMonoLog *GDMonoLog::singleton = nullptr;
-#if !defined(JAVASCRIPT_ENABLED)
+#ifdef GD_MONO_LOG_ENABLED
static int get_log_level_id(const char *p_log_level) {
-
- const char *valid_log_levels[] = { "error", "critical", "warning", "message", "info", "debug", NULL };
+ const char *valid_log_levels[] = { "error", "critical", "warning", "message", "info", "debug", nullptr };
int i = 0;
while (valid_log_levels[i]) {
- if (!strcmp(valid_log_levels[i], p_log_level))
+ if (!strcmp(valid_log_levels[i], p_log_level)) {
return i;
+ }
i++;
}
return -1;
}
-void GDMonoLog::mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *) {
+static String make_text(const char *log_domain, const char *log_level, const char *message) {
+ String text(message);
+ text += " (in domain ";
+ text += log_domain;
+ if (log_level) {
+ text += ", ";
+ text += log_level;
+ }
+ text += ")";
+ return text;
+}
+void GDMonoLog::mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *) {
FileAccess *f = GDMonoLog::get_singleton()->log_file;
if (GDMonoLog::get_singleton()->log_level_id >= get_log_level_id(log_level)) {
- String text(message);
- text += " (in domain ";
- text += log_domain;
- if (log_level) {
- text += ", ";
- text += log_level;
- }
- text += ")\n";
+ String text = make_text(log_domain, log_level, message);
+ text += "\n";
f->seek_end();
f->store_string(text);
}
if (fatal) {
- ERR_PRINTS("Mono: FATAL ERROR, ABORTING! Logfile: '" + GDMonoLog::get_singleton()->log_file_path + "'.");
+ String text = make_text(log_domain, log_level, message);
+ ERR_PRINT("Mono: FATAL ERROR '" + text + "', ABORTING! Logfile: '" + GDMonoLog::get_singleton()->log_file_path + "'.");
// Make sure to flush before aborting
f->flush();
f->close();
@@ -94,7 +100,6 @@ void GDMonoLog::mono_log_callback(const char *log_domain, const char *log_level,
}
bool GDMonoLog::_try_create_logs_dir(const String &p_logs_dir) {
-
if (!DirAccess::exists(p_logs_dir)) {
DirAccessRef diraccess = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!diraccess, false);
@@ -106,7 +111,6 @@ bool GDMonoLog::_try_create_logs_dir(const String &p_logs_dir) {
}
void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) {
-
static const uint64_t MAX_SECS = 5 * 86400; // 5 days
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
@@ -119,10 +123,12 @@ void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) {
String current;
while ((current = da->get_next()).length()) {
- if (da->current_is_dir())
+ if (da->current_is_dir()) {
continue;
- if (!current.ends_with(".txt"))
+ }
+ if (!current.ends_with(".txt")) {
continue;
+ }
uint64_t modified_time = FileAccess::get_modified_time(da->get_current_dir().plus_file(current));
@@ -135,11 +141,10 @@ void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) {
}
void GDMonoLog::initialize() {
-
CharString log_level = OS::get_singleton()->get_environment("GODOT_MONO_LOG_LEVEL").utf8();
if (log_level.length() != 0 && get_log_level_id(log_level.get_data()) == -1) {
- ERR_PRINTS(String() + "Mono: Ignoring invalid log level (GODOT_MONO_LOG_LEVEL): '" + log_level.get_data() + "'.");
+ ERR_PRINT(String() + "Mono: Ignoring invalid log level (GODOT_MONO_LOG_LEVEL): '" + log_level.get_data() + "'.");
log_level = CharString();
}
@@ -155,19 +160,19 @@ void GDMonoLog::initialize() {
OS::Date date_now = OS::get_singleton()->get_date();
OS::Time time_now = OS::get_singleton()->get_time();
- String log_file_name = str_format("%d_%02d_%02d %02d.%02d.%02d",
- date_now.year, date_now.month, date_now.day,
- time_now.hour, time_now.min, time_now.sec);
+ String log_file_name = str_format("%04d-%02d-%02d_%02d.%02d.%02d",
+ (int)date_now.year, (int)date_now.month, (int)date_now.day,
+ (int)time_now.hour, (int)time_now.minute, (int)time_now.second);
- log_file_name += str_format(" (%d)", OS::get_singleton()->get_process_id());
+ log_file_name += str_format("_%d", OS::get_singleton()->get_process_id());
- log_file_name += ".txt";
+ log_file_name += ".log";
log_file_path = logs_dir.plus_file(log_file_name);
log_file = FileAccess::open(log_file_path, FileAccess::WRITE);
if (!log_file) {
- ERR_PRINTS("Mono: Cannot create log file at: " + log_file_path);
+ ERR_PRINT("Mono: Cannot create log file at: " + log_file_path);
}
}
@@ -175,7 +180,7 @@ void GDMonoLog::initialize() {
log_level_id = get_log_level_id(log_level.get_data());
if (log_file) {
- OS::get_singleton()->print("Mono: Logfile is: %s\n", log_file_path.utf8().get_data());
+ OS::get_singleton()->print("Mono: Log file is: '%s'\n", log_file_path.utf8().get_data());
mono_trace_set_log_handler(mono_log_callback, this);
} else {
OS::get_singleton()->printerr("Mono: No log file, using default log handler\n");
@@ -183,15 +188,11 @@ void GDMonoLog::initialize() {
}
GDMonoLog::GDMonoLog() {
-
singleton = this;
-
- log_level_id = -1;
}
GDMonoLog::~GDMonoLog() {
-
- singleton = NULL;
+ singleton = nullptr;
if (log_file) {
log_file->close();
@@ -207,13 +208,11 @@ void GDMonoLog::initialize() {
}
GDMonoLog::GDMonoLog() {
-
singleton = this;
}
GDMonoLog::~GDMonoLog() {
-
- singleton = NULL;
+ singleton = nullptr;
}
#endif // !defined(JAVASCRIPT_ENABLED)
diff --git a/modules/mono/mono_gd/gd_mono_log.h b/modules/mono/mono_gd/gd_mono_log.h
index ecf4c78b1a..9ddbd251ac 100644
--- a/modules/mono/mono_gd/gd_mono_log.h
+++ b/modules/mono/mono_gd/gd_mono_log.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -35,16 +35,20 @@
#include "core/typedefs.h"
-#if !defined(JAVASCRIPT_ENABLED)
-#include "core/os/file_access.h"
+#if !defined(JAVASCRIPT_ENABLED) && !defined(IPHONE_ENABLED)
+// We have custom mono log callbacks for WASM and iOS
+#define GD_MONO_LOG_ENABLED
#endif
-class GDMonoLog {
+#ifdef GD_MONO_LOG_ENABLED
+#include "core/io/file_access.h"
+#endif
-#if !defined(JAVASCRIPT_ENABLED)
- int log_level_id;
+class GDMonoLog {
+#ifdef GD_MONO_LOG_ENABLED
+ int log_level_id = -1;
- FileAccess *log_file;
+ FileAccess *log_file = nullptr;
String log_file_path;
bool _try_create_logs_dir(const String &p_logs_dir);
diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp
index b81c20348d..9f2977bfa2 100644
--- a/modules/mono/mono_gd/gd_mono_marshal.cpp
+++ b/modules/mono/mono_gd/gd_mono_marshal.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,13 +30,14 @@
#include "gd_mono_marshal.h"
+#include "../signal_awaiter_utils.h"
#include "gd_mono.h"
#include "gd_mono_cache.h"
#include "gd_mono_class.h"
namespace GDMonoMarshal {
-Variant::Type managed_to_variant_type(const ManagedType &p_type) {
+Variant::Type managed_to_variant_type(const ManagedType &p_type, bool *r_nil_is_variant) {
switch (p_type.type_encoding) {
case MONO_TYPE_BOOLEAN:
return Variant::BOOL;
@@ -60,9 +61,9 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
return Variant::INT;
case MONO_TYPE_R4:
- return Variant::REAL;
+ return Variant::FLOAT;
case MONO_TYPE_R8:
- return Variant::REAL;
+ return Variant::FLOAT;
case MONO_TYPE_STRING: {
return Variant::STRING;
@@ -71,67 +72,119 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
case MONO_TYPE_VALUETYPE: {
GDMonoClass *vtclass = p_type.type_class;
- if (vtclass == CACHED_CLASS(Vector2))
+ if (vtclass == CACHED_CLASS(Vector2)) {
return Variant::VECTOR2;
+ }
- if (vtclass == CACHED_CLASS(Rect2))
+ if (vtclass == CACHED_CLASS(Vector2i)) {
+ return Variant::VECTOR2I;
+ }
+
+ if (vtclass == CACHED_CLASS(Rect2)) {
return Variant::RECT2;
+ }
+
+ if (vtclass == CACHED_CLASS(Rect2i)) {
+ return Variant::RECT2I;
+ }
- if (vtclass == CACHED_CLASS(Transform2D))
+ if (vtclass == CACHED_CLASS(Transform2D)) {
return Variant::TRANSFORM2D;
+ }
- if (vtclass == CACHED_CLASS(Vector3))
+ if (vtclass == CACHED_CLASS(Vector3)) {
return Variant::VECTOR3;
+ }
+
+ if (vtclass == CACHED_CLASS(Vector3i)) {
+ return Variant::VECTOR3I;
+ }
- if (vtclass == CACHED_CLASS(Basis))
+ if (vtclass == CACHED_CLASS(Basis)) {
return Variant::BASIS;
+ }
- if (vtclass == CACHED_CLASS(Quat))
- return Variant::QUAT;
+ if (vtclass == CACHED_CLASS(Quaternion)) {
+ return Variant::QUATERNION;
+ }
- if (vtclass == CACHED_CLASS(Transform))
- return Variant::TRANSFORM;
+ if (vtclass == CACHED_CLASS(Transform3D)) {
+ return Variant::TRANSFORM3D;
+ }
- if (vtclass == CACHED_CLASS(AABB))
+ if (vtclass == CACHED_CLASS(AABB)) {
return Variant::AABB;
+ }
- if (vtclass == CACHED_CLASS(Color))
+ if (vtclass == CACHED_CLASS(Color)) {
return Variant::COLOR;
+ }
- if (vtclass == CACHED_CLASS(Plane))
+ if (vtclass == CACHED_CLASS(Plane)) {
return Variant::PLANE;
+ }
+
+ if (vtclass == CACHED_CLASS(Callable)) {
+ return Variant::CALLABLE;
+ }
- if (mono_class_is_enum(vtclass->get_mono_ptr()))
+ if (vtclass == CACHED_CLASS(SignalInfo)) {
+ return Variant::SIGNAL;
+ }
+
+ if (mono_class_is_enum(vtclass->get_mono_ptr())) {
return Variant::INT;
+ }
} break;
case MONO_TYPE_ARRAY:
case MONO_TYPE_SZARRAY: {
MonoArrayType *array_type = mono_type_get_array_type(p_type.type_class->get_mono_type());
- if (array_type->eklass == CACHED_CLASS_RAW(MonoObject))
+ if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) {
return Variant::ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(uint8_t))
- return Variant::POOL_BYTE_ARRAY;
+ if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) {
+ return Variant::PACKED_BYTE_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(int32_t))
- return Variant::POOL_INT_ARRAY;
+ if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) {
+ return Variant::PACKED_INT32_ARRAY;
+ }
- if (array_type->eklass == REAL_T_MONOCLASS)
- return Variant::POOL_REAL_ARRAY;
+ if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) {
+ return Variant::PACKED_INT64_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(String))
- return Variant::POOL_STRING_ARRAY;
+ if (array_type->eklass == CACHED_CLASS_RAW(float)) {
+ return Variant::PACKED_FLOAT32_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Vector2))
- return Variant::POOL_VECTOR2_ARRAY;
+ if (array_type->eklass == CACHED_CLASS_RAW(double)) {
+ return Variant::PACKED_FLOAT64_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Vector3))
- return Variant::POOL_VECTOR3_ARRAY;
+ if (array_type->eklass == CACHED_CLASS_RAW(String)) {
+ return Variant::PACKED_STRING_ARRAY;
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Color))
- return Variant::POOL_COLOR_ARRAY;
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) {
+ return Variant::PACKED_VECTOR2_ARRAY;
+ }
+
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) {
+ return Variant::PACKED_VECTOR3_ARRAY;
+ }
+
+ if (array_type->eklass == CACHED_CLASS_RAW(Color)) {
+ return Variant::PACKED_COLOR_ARRAY;
+ }
+
+ GDMonoClass *array_type_class = GDMono::get_singleton()->get_class(array_type->eklass);
+ if (CACHED_CLASS(GodotObject)->is_assignable_from(array_type_class)) {
+ return Variant::ARRAY;
+ }
} break;
case MONO_TYPE_CLASS: {
@@ -142,12 +195,16 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
return Variant::OBJECT;
}
+ if (CACHED_CLASS(StringName) == type_class) {
+ return Variant::STRING_NAME;
+ }
+
if (CACHED_CLASS(NodePath) == type_class) {
return Variant::NODE_PATH;
}
if (CACHED_CLASS(RID) == type_class) {
- return Variant::_RID;
+ return Variant::RID;
}
if (CACHED_CLASS(Dictionary) == type_class) {
@@ -158,51 +215,55 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
return Variant::ARRAY;
}
- // The order in which we check the following interfaces is very important (dictionaries and generics first)
-
- MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
-
- if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) {
- return Variant::DICTIONARY;
- }
-
- if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
+ // IDictionary
+ if (p_type.type_class == CACHED_CLASS(System_Collections_IDictionary)) {
return Variant::DICTIONARY;
}
- if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype)) {
+ // ICollection or IEnumerable
+ if (p_type.type_class == CACHED_CLASS(System_Collections_ICollection) ||
+ p_type.type_class == CACHED_CLASS(System_Collections_IEnumerable)) {
return Variant::ARRAY;
}
+ } break;
- if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- return Variant::ARRAY;
+ case MONO_TYPE_OBJECT: {
+ if (r_nil_is_variant) {
+ *r_nil_is_variant = true;
}
+ return Variant::NIL;
} break;
case MONO_TYPE_GENERICINST: {
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type());
+ // Godot.Collections.Dictionary<TKey, TValue>
if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
return Variant::DICTIONARY;
}
+ // Godot.Collections.Array<T>
if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) {
return Variant::ARRAY;
}
- // The order in which we check the following interfaces is very important (dictionaries and generics first)
-
- if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype))
- return Variant::DICTIONARY;
-
- if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
+ // System.Collections.Generic.Dictionary<TKey, TValue>
+ if (GDMonoUtils::Marshal::type_is_system_generic_dictionary(reftype)) {
return Variant::DICTIONARY;
}
- if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype))
+ // System.Collections.Generic.List<T>
+ if (GDMonoUtils::Marshal::type_is_system_generic_list(reftype)) {
return Variant::ARRAY;
+ }
- if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
+ // IDictionary<TKey, TValue>
+ if (GDMonoUtils::Marshal::type_is_generic_idictionary(reftype)) {
+ return Variant::DICTIONARY;
+ }
+
+ // ICollection<T> or IEnumerable<T>
+ if (GDMonoUtils::Marshal::type_is_generic_icollection(reftype) || GDMonoUtils::Marshal::type_is_generic_ienumerable(reftype)) {
return Variant::ARRAY;
}
} break;
@@ -211,16 +272,30 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
} break;
}
+ if (r_nil_is_variant) {
+ *r_nil_is_variant = false;
+ }
+
// Unknown
return Variant::NIL;
}
bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type) {
switch (p_array_type.type_encoding) {
+ case MONO_TYPE_ARRAY:
+ case MONO_TYPE_SZARRAY: {
+ MonoArrayType *array_type = mono_type_get_array_type(p_array_type.type_class->get_mono_type());
+ GDMonoClass *array_type_class = GDMono::get_singleton()->get_class(array_type->eklass);
+ r_elem_type = ManagedType::from_class(array_type_class);
+ return true;
+ } break;
case MONO_TYPE_GENERICINST: {
MonoReflectionType *array_reftype = mono_type_get_object(mono_domain_get(), p_array_type.type_class->get_mono_type());
- if (GDMonoUtils::Marshal::type_is_generic_array(array_reftype)) {
+ if (GDMonoUtils::Marshal::type_is_generic_array(array_reftype) ||
+ GDMonoUtils::Marshal::type_is_system_generic_list(array_reftype) ||
+ GDMonoUtils::Marshal::type_is_generic_icollection(array_reftype) ||
+ GDMonoUtils::Marshal::type_is_generic_ienumerable(array_reftype)) {
MonoReflectionType *elem_reftype;
GDMonoUtils::Marshal::array_get_element_type(array_reftype, &elem_reftype);
@@ -228,12 +303,6 @@ bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_
r_elem_type = ManagedType::from_reftype(elem_reftype);
return true;
}
-
- MonoReflectionType *elem_reftype;
- if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(array_reftype, &elem_reftype)) {
- r_elem_type = ManagedType::from_reftype(elem_reftype);
- return true;
- }
} break;
default: {
} break;
@@ -242,194 +311,591 @@ bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_
return false;
}
-bool try_get_dictionary_key_value_types(const ManagedType &p_dictionary_type, ManagedType &r_key_type, ManagedType &r_value_type) {
- switch (p_dictionary_type.type_encoding) {
- case MONO_TYPE_GENERICINST: {
- MonoReflectionType *dict_reftype = mono_type_get_object(mono_domain_get(), p_dictionary_type.type_class->get_mono_type());
+MonoString *variant_to_mono_string(const Variant &p_var) {
+ if (p_var.get_type() == Variant::NIL) {
+ return nullptr; // Otherwise, Variant -> String would return the string "Null"
+ }
+ return mono_string_from_godot(p_var.operator String());
+}
- if (GDMonoUtils::Marshal::type_is_generic_dictionary(dict_reftype)) {
- MonoReflectionType *key_reftype;
- MonoReflectionType *value_reftype;
+MonoArray *variant_to_mono_array(const Variant &p_var, GDMonoClass *p_type_class) {
+ MonoArrayType *array_type = mono_type_get_array_type(p_type_class->get_mono_type());
- GDMonoUtils::Marshal::dictionary_get_key_value_types(dict_reftype, &key_reftype, &value_reftype);
+ if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) {
+ return Array_to_mono_array(p_var.operator Array());
+ }
- r_key_type = ManagedType::from_reftype(key_reftype);
- r_value_type = ManagedType::from_reftype(value_reftype);
- return true;
- }
+ if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) {
+ return PackedByteArray_to_mono_array(p_var.operator PackedByteArray());
+ }
- MonoReflectionType *key_reftype, *value_reftype;
- if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(dict_reftype, &key_reftype, &value_reftype)) {
- r_key_type = ManagedType::from_reftype(key_reftype);
- r_value_type = ManagedType::from_reftype(value_reftype);
- return true;
- }
- } break;
- default: {
- } break;
+ if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) {
+ return PackedInt32Array_to_mono_array(p_var.operator PackedInt32Array());
}
- return false;
+ if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) {
+ return PackedInt64Array_to_mono_array(p_var.operator PackedInt64Array());
+ }
+
+ if (array_type->eklass == CACHED_CLASS_RAW(float)) {
+ return PackedFloat32Array_to_mono_array(p_var.operator PackedFloat32Array());
+ }
+
+ if (array_type->eklass == CACHED_CLASS_RAW(double)) {
+ return PackedFloat64Array_to_mono_array(p_var.operator PackedFloat64Array());
+ }
+
+ if (array_type->eklass == CACHED_CLASS_RAW(String)) {
+ return PackedStringArray_to_mono_array(p_var.operator PackedStringArray());
+ }
+
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) {
+ return PackedVector2Array_to_mono_array(p_var.operator PackedVector2Array());
+ }
+
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) {
+ return PackedVector3Array_to_mono_array(p_var.operator PackedVector3Array());
+ }
+
+ if (array_type->eklass == CACHED_CLASS_RAW(Color)) {
+ return PackedColorArray_to_mono_array(p_var.operator PackedColorArray());
+ }
+
+ if (mono_class_is_assignable_from(CACHED_CLASS(GodotObject)->get_mono_ptr(), array_type->eklass)) {
+ return Array_to_mono_array(p_var.operator ::Array(), array_type->eklass);
+ }
+
+ ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to array of unsupported element type:" +
+ GDMonoClass::get_full_name(array_type->eklass) + "'.");
}
-String mono_to_utf8_string(MonoString *p_mono_string) {
- MonoError error;
- char *utf8 = mono_string_to_utf8_checked(p_mono_string, &error);
+MonoObject *variant_to_mono_object_of_class(const Variant &p_var, GDMonoClass *p_type_class) {
+ // GodotObject
+ if (CACHED_CLASS(GodotObject)->is_assignable_from(p_type_class)) {
+ return GDMonoUtils::unmanaged_get_managed(p_var.operator Object *());
+ }
- if (!mono_error_ok(&error)) {
- ERR_PRINTS(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&error) + "'.");
- mono_error_cleanup(&error);
- return String();
+ if (CACHED_CLASS(StringName) == p_type_class) {
+ return GDMonoUtils::create_managed_from(p_var.operator StringName());
}
- String ret = String::utf8(utf8);
+ if (CACHED_CLASS(NodePath) == p_type_class) {
+ return GDMonoUtils::create_managed_from(p_var.operator NodePath());
+ }
- mono_free(utf8);
+ if (CACHED_CLASS(RID) == p_type_class) {
+ return GDMonoUtils::create_managed_from(p_var.operator ::RID());
+ }
- return ret;
+ // Godot.Collections.Dictionary or IDictionary
+ if (CACHED_CLASS(Dictionary) == p_type_class || CACHED_CLASS(System_Collections_IDictionary) == p_type_class) {
+ return GDMonoUtils::create_managed_from(p_var.operator Dictionary(), CACHED_CLASS(Dictionary));
+ }
+
+ // Godot.Collections.Array or ICollection or IEnumerable
+ if (CACHED_CLASS(Array) == p_type_class ||
+ CACHED_CLASS(System_Collections_ICollection) == p_type_class ||
+ CACHED_CLASS(System_Collections_IEnumerable) == p_type_class) {
+ return GDMonoUtils::create_managed_from(p_var.operator Array(), CACHED_CLASS(Array));
+ }
+
+ ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported type: '" +
+ p_type_class->get_full_name() + "'.");
}
-String mono_to_utf16_string(MonoString *p_mono_string) {
- int len = mono_string_length(p_mono_string);
- String ret;
+MonoObject *variant_to_mono_object_of_genericinst(const Variant &p_var, GDMonoClass *p_type_class) {
+ MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type_class->get_mono_type());
- if (len == 0)
- return ret;
+ // Godot.Collections.Dictionary<TKey, TValue>
+ if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
+ return GDMonoUtils::create_managed_from(p_var.operator Dictionary(), p_type_class);
+ }
- ret.resize(len + 1);
- ret.set(len, 0);
+ // Godot.Collections.Array<T>
+ if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) {
+ return GDMonoUtils::create_managed_from(p_var.operator Array(), p_type_class);
+ }
- CharType *src = (CharType *)mono_string_chars(p_mono_string);
- CharType *dst = ret.ptrw();
+ // System.Collections.Generic.Dictionary<TKey, TValue>
+ if (GDMonoUtils::Marshal::type_is_system_generic_dictionary(reftype)) {
+ MonoReflectionType *key_reftype = nullptr;
+ MonoReflectionType *value_reftype = nullptr;
+ GDMonoUtils::Marshal::dictionary_get_key_value_types(reftype, &key_reftype, &value_reftype);
+ return Dictionary_to_system_generic_dict(p_var.operator Dictionary(), p_type_class, key_reftype, value_reftype);
+ }
- for (int i = 0; i < len; i++) {
- dst[i] = src[i];
+ // System.Collections.Generic.List<T>
+ if (GDMonoUtils::Marshal::type_is_system_generic_list(reftype)) {
+ MonoReflectionType *elem_reftype = nullptr;
+ GDMonoUtils::Marshal::array_get_element_type(reftype, &elem_reftype);
+ return Array_to_system_generic_list(p_var.operator Array(), p_type_class, elem_reftype);
}
- return ret;
+ // IDictionary<TKey, TValue>
+ if (GDMonoUtils::Marshal::type_is_generic_idictionary(reftype)) {
+ MonoReflectionType *key_reftype;
+ MonoReflectionType *value_reftype;
+ GDMonoUtils::Marshal::dictionary_get_key_value_types(reftype, &key_reftype, &value_reftype);
+ GDMonoClass *godot_dict_class = GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype);
+
+ return GDMonoUtils::create_managed_from(p_var.operator Dictionary(), godot_dict_class);
+ }
+
+ // ICollection<T> or IEnumerable<T>
+ if (GDMonoUtils::Marshal::type_is_generic_icollection(reftype) || GDMonoUtils::Marshal::type_is_generic_ienumerable(reftype)) {
+ MonoReflectionType *elem_reftype;
+ GDMonoUtils::Marshal::array_get_element_type(reftype, &elem_reftype);
+ GDMonoClass *godot_array_class = GDMonoUtils::Marshal::make_generic_array_type(elem_reftype);
+
+ return GDMonoUtils::create_managed_from(p_var.operator Array(), godot_array_class);
+ }
+
+ ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported generic type: '" +
+ p_type_class->get_full_name() + "'.");
+}
+
+MonoObject *variant_to_mono_object(const Variant &p_var) {
+ // Variant
+ switch (p_var.get_type()) {
+ case Variant::BOOL: {
+ MonoBoolean val = p_var.operator bool();
+ return BOX_BOOLEAN(val);
+ }
+ case Variant::INT: {
+ int64_t val = p_var.operator int64_t();
+ return BOX_INT64(val);
+ }
+ case Variant::FLOAT: {
+#ifdef REAL_T_IS_DOUBLE
+ double val = p_var.operator double();
+ return BOX_DOUBLE(val);
+#else
+ float val = p_var.operator float();
+ return BOX_FLOAT(val);
+#endif
+ }
+ case Variant::STRING:
+ return (MonoObject *)mono_string_from_godot(p_var.operator String());
+ case Variant::VECTOR2: {
+ GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_var.operator ::Vector2());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2), &from);
+ }
+ case Variant::VECTOR2I: {
+ GDMonoMarshal::M_Vector2i from = MARSHALLED_OUT(Vector2i, p_var.operator ::Vector2i());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2i), &from);
+ }
+ case Variant::RECT2: {
+ GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_var.operator ::Rect2());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2), &from);
+ }
+ case Variant::RECT2I: {
+ GDMonoMarshal::M_Rect2i from = MARSHALLED_OUT(Rect2i, p_var.operator ::Rect2i());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2i), &from);
+ }
+ case Variant::VECTOR3: {
+ GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_var.operator ::Vector3());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3), &from);
+ }
+ case Variant::VECTOR3I: {
+ GDMonoMarshal::M_Vector3i from = MARSHALLED_OUT(Vector3i, p_var.operator ::Vector3i());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3i), &from);
+ }
+ case Variant::TRANSFORM2D: {
+ GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_var.operator ::Transform2D());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform2D), &from);
+ }
+ case Variant::PLANE: {
+ GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_var.operator ::Plane());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Plane), &from);
+ }
+ case Variant::QUATERNION: {
+ GDMonoMarshal::M_Quaternion from = MARSHALLED_OUT(Quaternion, p_var.operator ::Quaternion());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Quaternion), &from);
+ }
+ case Variant::AABB: {
+ GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_var.operator ::AABB());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(AABB), &from);
+ }
+ case Variant::BASIS: {
+ GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_var.operator ::Basis());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Basis), &from);
+ }
+ case Variant::TRANSFORM3D: {
+ GDMonoMarshal::M_Transform3D from = MARSHALLED_OUT(Transform3D, p_var.operator ::Transform3D());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform3D), &from);
+ }
+ case Variant::COLOR: {
+ GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_var.operator ::Color());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Color), &from);
+ }
+ case Variant::STRING_NAME:
+ return GDMonoUtils::create_managed_from(p_var.operator StringName());
+ case Variant::NODE_PATH:
+ return GDMonoUtils::create_managed_from(p_var.operator NodePath());
+ case Variant::RID:
+ return GDMonoUtils::create_managed_from(p_var.operator ::RID());
+ case Variant::OBJECT:
+ return GDMonoUtils::unmanaged_get_managed(p_var.operator Object *());
+ case Variant::CALLABLE: {
+ GDMonoMarshal::M_Callable from = GDMonoMarshal::callable_to_managed(p_var.operator Callable());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Callable), &from);
+ }
+ case Variant::SIGNAL: {
+ GDMonoMarshal::M_SignalInfo from = GDMonoMarshal::signal_info_to_managed(p_var.operator Signal());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(SignalInfo), &from);
+ }
+ case Variant::DICTIONARY:
+ return GDMonoUtils::create_managed_from(p_var.operator Dictionary(), CACHED_CLASS(Dictionary));
+ case Variant::ARRAY:
+ return GDMonoUtils::create_managed_from(p_var.operator Array(), CACHED_CLASS(Array));
+ case Variant::PACKED_BYTE_ARRAY:
+ return (MonoObject *)PackedByteArray_to_mono_array(p_var.operator PackedByteArray());
+ case Variant::PACKED_INT32_ARRAY:
+ return (MonoObject *)PackedInt32Array_to_mono_array(p_var.operator PackedInt32Array());
+ case Variant::PACKED_INT64_ARRAY:
+ return (MonoObject *)PackedInt64Array_to_mono_array(p_var.operator PackedInt64Array());
+ case Variant::PACKED_FLOAT32_ARRAY:
+ return (MonoObject *)PackedFloat32Array_to_mono_array(p_var.operator PackedFloat32Array());
+ case Variant::PACKED_FLOAT64_ARRAY:
+ return (MonoObject *)PackedFloat64Array_to_mono_array(p_var.operator PackedFloat64Array());
+ case Variant::PACKED_STRING_ARRAY:
+ return (MonoObject *)PackedStringArray_to_mono_array(p_var.operator PackedStringArray());
+ case Variant::PACKED_VECTOR2_ARRAY:
+ return (MonoObject *)PackedVector2Array_to_mono_array(p_var.operator PackedVector2Array());
+ case Variant::PACKED_VECTOR3_ARRAY:
+ return (MonoObject *)PackedVector3Array_to_mono_array(p_var.operator PackedVector3Array());
+ case Variant::PACKED_COLOR_ARRAY:
+ return (MonoObject *)PackedColorArray_to_mono_array(p_var.operator PackedColorArray());
+ default:
+ return nullptr;
+ }
}
-MonoObject *variant_to_mono_object(const Variant *p_var) {
- ManagedType type;
+size_t variant_get_managed_unboxed_size(const ManagedType &p_type) {
+ // This method prints no errors for unsupported types. It's called on all methods, not only
+ // those that end up being invoked with Variant parameters.
+
+ // For MonoObject* we return 0, as it doesn't need to be stored.
+ constexpr size_t zero_for_mono_object = 0;
+
+ switch (p_type.type_encoding) {
+ case MONO_TYPE_BOOLEAN:
+ return sizeof(MonoBoolean);
+ case MONO_TYPE_CHAR:
+ return sizeof(uint16_t);
+ case MONO_TYPE_I1:
+ return sizeof(int8_t);
+ case MONO_TYPE_I2:
+ return sizeof(int16_t);
+ case MONO_TYPE_I4:
+ return sizeof(int32_t);
+ case MONO_TYPE_I8:
+ return sizeof(int64_t);
+ case MONO_TYPE_U1:
+ return sizeof(uint8_t);
+ case MONO_TYPE_U2:
+ return sizeof(uint16_t);
+ case MONO_TYPE_U4:
+ return sizeof(uint32_t);
+ case MONO_TYPE_U8:
+ return sizeof(uint64_t);
+ case MONO_TYPE_R4:
+ return sizeof(float);
+ case MONO_TYPE_R8:
+ return sizeof(double);
+ case MONO_TYPE_VALUETYPE: {
+ GDMonoClass *vtclass = p_type.type_class;
+
+#define RETURN_CHECK_FOR_STRUCT(m_struct) \
+ if (vtclass == CACHED_CLASS(m_struct)) { \
+ return sizeof(M_##m_struct); \
+ }
+
+ RETURN_CHECK_FOR_STRUCT(Vector2);
+ RETURN_CHECK_FOR_STRUCT(Vector2i);
+ RETURN_CHECK_FOR_STRUCT(Rect2);
+ RETURN_CHECK_FOR_STRUCT(Rect2i);
+ RETURN_CHECK_FOR_STRUCT(Transform2D);
+ RETURN_CHECK_FOR_STRUCT(Vector3);
+ RETURN_CHECK_FOR_STRUCT(Vector3i);
+ RETURN_CHECK_FOR_STRUCT(Basis);
+ RETURN_CHECK_FOR_STRUCT(Quaternion);
+ RETURN_CHECK_FOR_STRUCT(Transform3D);
+ RETURN_CHECK_FOR_STRUCT(AABB);
+ RETURN_CHECK_FOR_STRUCT(Color);
+ RETURN_CHECK_FOR_STRUCT(Plane);
+ RETURN_CHECK_FOR_STRUCT(Callable);
+ RETURN_CHECK_FOR_STRUCT(SignalInfo);
+
+#undef RETURN_CHECK_FOR_STRUCT
+
+ if (mono_class_is_enum(vtclass->get_mono_ptr())) {
+ MonoType *enum_basetype = mono_class_enum_basetype(vtclass->get_mono_ptr());
+ switch (mono_type_get_type(enum_basetype)) {
+ case MONO_TYPE_BOOLEAN:
+ return sizeof(MonoBoolean);
+ case MONO_TYPE_CHAR:
+ return sizeof(uint16_t);
+ case MONO_TYPE_I1:
+ return sizeof(int8_t);
+ case MONO_TYPE_I2:
+ return sizeof(int16_t);
+ case MONO_TYPE_I4:
+ return sizeof(int32_t);
+ case MONO_TYPE_I8:
+ return sizeof(int64_t);
+ case MONO_TYPE_U1:
+ return sizeof(uint8_t);
+ case MONO_TYPE_U2:
+ return sizeof(uint16_t);
+ case MONO_TYPE_U4:
+ return sizeof(uint32_t);
+ case MONO_TYPE_U8:
+ return sizeof(uint64_t);
+ default: {
+ // Enum with unsupported base type. We return nullptr MonoObject* on error.
+ return zero_for_mono_object;
+ }
+ }
+ }
+
+ // Enum with unsupported value type. We return nullptr MonoObject* on error.
+ } break;
+ case MONO_TYPE_STRING:
+ return zero_for_mono_object;
+ case MONO_TYPE_ARRAY:
+ case MONO_TYPE_SZARRAY:
+ case MONO_TYPE_CLASS:
+ case MONO_TYPE_GENERICINST:
+ return zero_for_mono_object;
+ case MONO_TYPE_OBJECT:
+ return zero_for_mono_object;
+ }
+
+ // Unsupported type encoding. We return nullptr MonoObject* on error.
+ return zero_for_mono_object;
+}
+
+void *variant_to_managed_unboxed(const Variant &p_var, const ManagedType &p_type, void *r_buffer, unsigned int &r_offset) {
+#define RETURN_TYPE_VAL(m_type, m_val) \
+ *reinterpret_cast<m_type *>(r_buffer) = m_val; \
+ r_offset += sizeof(m_type); \
+ return r_buffer;
+
+ switch (p_type.type_encoding) {
+ case MONO_TYPE_BOOLEAN:
+ RETURN_TYPE_VAL(MonoBoolean, (MonoBoolean)p_var.operator bool());
+ case MONO_TYPE_CHAR:
+ RETURN_TYPE_VAL(uint16_t, p_var.operator unsigned short());
+ case MONO_TYPE_I1:
+ RETURN_TYPE_VAL(int8_t, p_var.operator signed char());
+ case MONO_TYPE_I2:
+ RETURN_TYPE_VAL(int16_t, p_var.operator signed short());
+ case MONO_TYPE_I4:
+ RETURN_TYPE_VAL(int32_t, p_var.operator signed int());
+ case MONO_TYPE_I8:
+ RETURN_TYPE_VAL(int64_t, p_var.operator int64_t());
+ case MONO_TYPE_U1:
+ RETURN_TYPE_VAL(uint8_t, p_var.operator unsigned char());
+ case MONO_TYPE_U2:
+ RETURN_TYPE_VAL(uint16_t, p_var.operator unsigned short());
+ case MONO_TYPE_U4:
+ RETURN_TYPE_VAL(uint32_t, p_var.operator unsigned int());
+ case MONO_TYPE_U8:
+ RETURN_TYPE_VAL(uint64_t, p_var.operator uint64_t());
+ case MONO_TYPE_R4:
+ RETURN_TYPE_VAL(float, p_var.operator float());
+ case MONO_TYPE_R8:
+ RETURN_TYPE_VAL(double, p_var.operator double());
+ case MONO_TYPE_VALUETYPE: {
+ GDMonoClass *vtclass = p_type.type_class;
+
+#define RETURN_CHECK_FOR_STRUCT(m_struct) \
+ if (vtclass == CACHED_CLASS(m_struct)) { \
+ GDMonoMarshal::M_##m_struct from = MARSHALLED_OUT(m_struct, p_var.operator ::m_struct()); \
+ RETURN_TYPE_VAL(M_##m_struct, from); \
+ }
+
+ RETURN_CHECK_FOR_STRUCT(Vector2);
+ RETURN_CHECK_FOR_STRUCT(Vector2i);
+ RETURN_CHECK_FOR_STRUCT(Rect2);
+ RETURN_CHECK_FOR_STRUCT(Rect2i);
+ RETURN_CHECK_FOR_STRUCT(Transform2D);
+ RETURN_CHECK_FOR_STRUCT(Vector3);
+ RETURN_CHECK_FOR_STRUCT(Vector3i);
+ RETURN_CHECK_FOR_STRUCT(Basis);
+ RETURN_CHECK_FOR_STRUCT(Quaternion);
+ RETURN_CHECK_FOR_STRUCT(Transform3D);
+ RETURN_CHECK_FOR_STRUCT(AABB);
+ RETURN_CHECK_FOR_STRUCT(Color);
+ RETURN_CHECK_FOR_STRUCT(Plane);
+
+#undef RETURN_CHECK_FOR_STRUCT
+
+ if (vtclass == CACHED_CLASS(Callable)) {
+ GDMonoMarshal::M_Callable from = GDMonoMarshal::callable_to_managed(p_var.operator Callable());
+ RETURN_TYPE_VAL(M_Callable, from);
+ }
+
+ if (vtclass == CACHED_CLASS(SignalInfo)) {
+ GDMonoMarshal::M_SignalInfo from = GDMonoMarshal::signal_info_to_managed(p_var.operator Signal());
+ RETURN_TYPE_VAL(M_SignalInfo, from);
+ }
+
+ if (mono_class_is_enum(vtclass->get_mono_ptr())) {
+ MonoType *enum_basetype = mono_class_enum_basetype(vtclass->get_mono_ptr());
+ switch (mono_type_get_type(enum_basetype)) {
+ case MONO_TYPE_BOOLEAN: {
+ MonoBoolean val = p_var.operator bool();
+ RETURN_TYPE_VAL(MonoBoolean, val);
+ }
+ case MONO_TYPE_CHAR: {
+ uint16_t val = p_var.operator unsigned short();
+ RETURN_TYPE_VAL(uint16_t, val);
+ }
+ case MONO_TYPE_I1: {
+ int8_t val = p_var.operator signed char();
+ RETURN_TYPE_VAL(int8_t, val);
+ }
+ case MONO_TYPE_I2: {
+ int16_t val = p_var.operator signed short();
+ RETURN_TYPE_VAL(int16_t, val);
+ }
+ case MONO_TYPE_I4: {
+ int32_t val = p_var.operator signed int();
+ RETURN_TYPE_VAL(int32_t, val);
+ }
+ case MONO_TYPE_I8: {
+ int64_t val = p_var.operator int64_t();
+ RETURN_TYPE_VAL(int64_t, val);
+ }
+ case MONO_TYPE_U1: {
+ uint8_t val = p_var.operator unsigned char();
+ RETURN_TYPE_VAL(uint8_t, val);
+ }
+ case MONO_TYPE_U2: {
+ uint16_t val = p_var.operator unsigned short();
+ RETURN_TYPE_VAL(uint16_t, val);
+ }
+ case MONO_TYPE_U4: {
+ uint32_t val = p_var.operator unsigned int();
+ RETURN_TYPE_VAL(uint32_t, val);
+ }
+ case MONO_TYPE_U8: {
+ uint64_t val = p_var.operator uint64_t();
+ RETURN_TYPE_VAL(uint64_t, val);
+ }
+ default: {
+ ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to enum value of unsupported base type: '" +
+ GDMonoClass::get_full_name(mono_class_from_mono_type(enum_basetype)) + "'.");
+ }
+ }
+ }
- type.type_encoding = MONO_TYPE_OBJECT;
- // type.type_class is not needed when we specify the MONO_TYPE_OBJECT encoding
+ ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported value type: '" +
+ p_type.type_class->get_full_name() + "'.");
+ } break;
+#undef RETURN_TYPE_VAL
+ case MONO_TYPE_STRING:
+ return variant_to_mono_string(p_var);
+ case MONO_TYPE_ARRAY:
+ case MONO_TYPE_SZARRAY:
+ return variant_to_mono_array(p_var, p_type.type_class);
+ case MONO_TYPE_CLASS:
+ return variant_to_mono_object_of_class(p_var, p_type.type_class);
+ case MONO_TYPE_GENERICINST:
+ return variant_to_mono_object_of_genericinst(p_var, p_type.type_class);
+ case MONO_TYPE_OBJECT:
+ return variant_to_mono_object(p_var);
+ }
- return variant_to_mono_object(p_var, type);
+ ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported type with encoding: " +
+ itos(p_type.type_encoding) + ".");
}
-MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_type) {
+MonoObject *variant_to_mono_object(const Variant &p_var, const ManagedType &p_type) {
switch (p_type.type_encoding) {
case MONO_TYPE_BOOLEAN: {
- MonoBoolean val = p_var->operator bool();
+ MonoBoolean val = p_var.operator bool();
return BOX_BOOLEAN(val);
}
-
case MONO_TYPE_CHAR: {
- uint16_t val = p_var->operator unsigned short();
+ uint16_t val = p_var.operator unsigned short();
return BOX_UINT16(val);
}
-
case MONO_TYPE_I1: {
- int8_t val = p_var->operator signed char();
+ int8_t val = p_var.operator signed char();
return BOX_INT8(val);
}
case MONO_TYPE_I2: {
- int16_t val = p_var->operator signed short();
+ int16_t val = p_var.operator signed short();
return BOX_INT16(val);
}
case MONO_TYPE_I4: {
- int32_t val = p_var->operator signed int();
+ int32_t val = p_var.operator signed int();
return BOX_INT32(val);
}
case MONO_TYPE_I8: {
- int64_t val = p_var->operator int64_t();
+ int64_t val = p_var.operator int64_t();
return BOX_INT64(val);
}
-
case MONO_TYPE_U1: {
- uint8_t val = p_var->operator unsigned char();
+ uint8_t val = p_var.operator unsigned char();
return BOX_UINT8(val);
}
case MONO_TYPE_U2: {
- uint16_t val = p_var->operator unsigned short();
+ uint16_t val = p_var.operator unsigned short();
return BOX_UINT16(val);
}
case MONO_TYPE_U4: {
- uint32_t val = p_var->operator unsigned int();
+ uint32_t val = p_var.operator unsigned int();
return BOX_UINT32(val);
}
case MONO_TYPE_U8: {
- uint64_t val = p_var->operator uint64_t();
+ uint64_t val = p_var.operator uint64_t();
return BOX_UINT64(val);
}
-
case MONO_TYPE_R4: {
- float val = p_var->operator float();
+ float val = p_var.operator float();
return BOX_FLOAT(val);
}
case MONO_TYPE_R8: {
- double val = p_var->operator double();
+ double val = p_var.operator double();
return BOX_DOUBLE(val);
}
-
- case MONO_TYPE_STRING: {
- if (p_var->get_type() == Variant::NIL)
- return NULL; // Otherwise, Variant -> String would return the string "Null"
- return (MonoObject *)mono_string_from_godot(p_var->operator String());
- } break;
-
case MONO_TYPE_VALUETYPE: {
GDMonoClass *vtclass = p_type.type_class;
- if (vtclass == CACHED_CLASS(Vector2)) {
- GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_var->operator ::Vector2());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2), &from);
- }
-
- if (vtclass == CACHED_CLASS(Rect2)) {
- GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_var->operator ::Rect2());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2), &from);
- }
-
- if (vtclass == CACHED_CLASS(Transform2D)) {
- GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_var->operator ::Transform2D());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform2D), &from);
- }
-
- if (vtclass == CACHED_CLASS(Vector3)) {
- GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_var->operator ::Vector3());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3), &from);
- }
-
- if (vtclass == CACHED_CLASS(Basis)) {
- GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_var->operator ::Basis());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Basis), &from);
- }
-
- if (vtclass == CACHED_CLASS(Quat)) {
- GDMonoMarshal::M_Quat from = MARSHALLED_OUT(Quat, p_var->operator ::Quat());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Quat), &from);
- }
-
- if (vtclass == CACHED_CLASS(Transform)) {
- GDMonoMarshal::M_Transform from = MARSHALLED_OUT(Transform, p_var->operator ::Transform());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform), &from);
- }
-
- if (vtclass == CACHED_CLASS(AABB)) {
- GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_var->operator ::AABB());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(AABB), &from);
- }
+#define RETURN_CHECK_FOR_STRUCT(m_struct) \
+ if (vtclass == CACHED_CLASS(m_struct)) { \
+ GDMonoMarshal::M_##m_struct from = MARSHALLED_OUT(m_struct, p_var.operator ::m_struct()); \
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(m_struct), &from); \
+ }
- if (vtclass == CACHED_CLASS(Color)) {
- GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_var->operator ::Color());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Color), &from);
+ RETURN_CHECK_FOR_STRUCT(Vector2);
+ RETURN_CHECK_FOR_STRUCT(Vector2i);
+ RETURN_CHECK_FOR_STRUCT(Rect2);
+ RETURN_CHECK_FOR_STRUCT(Rect2i);
+ RETURN_CHECK_FOR_STRUCT(Transform2D);
+ RETURN_CHECK_FOR_STRUCT(Vector3);
+ RETURN_CHECK_FOR_STRUCT(Vector3i);
+ RETURN_CHECK_FOR_STRUCT(Basis);
+ RETURN_CHECK_FOR_STRUCT(Quaternion);
+ RETURN_CHECK_FOR_STRUCT(Transform3D);
+ RETURN_CHECK_FOR_STRUCT(AABB);
+ RETURN_CHECK_FOR_STRUCT(Color);
+ RETURN_CHECK_FOR_STRUCT(Plane);
+
+#undef RETURN_CHECK_FOR_STRUCT
+
+ if (vtclass == CACHED_CLASS(Callable)) {
+ GDMonoMarshal::M_Callable from = GDMonoMarshal::callable_to_managed(p_var.operator Callable());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Callable), &from);
}
- if (vtclass == CACHED_CLASS(Plane)) {
- GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_var->operator ::Plane());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Plane), &from);
+ if (vtclass == CACHED_CLASS(SignalInfo)) {
+ GDMonoMarshal::M_SignalInfo from = GDMonoMarshal::signal_info_to_managed(p_var.operator Signal());
+ return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(SignalInfo), &from);
}
if (mono_class_is_enum(vtclass->get_mono_ptr())) {
@@ -437,280 +903,84 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
MonoClass *enum_baseclass = mono_class_from_mono_type(enum_basetype);
switch (mono_type_get_type(enum_basetype)) {
case MONO_TYPE_BOOLEAN: {
- MonoBoolean val = p_var->operator bool();
+ MonoBoolean val = p_var.operator bool();
return BOX_ENUM(enum_baseclass, val);
}
case MONO_TYPE_CHAR: {
- uint16_t val = p_var->operator unsigned short();
+ uint16_t val = p_var.operator unsigned short();
return BOX_ENUM(enum_baseclass, val);
}
case MONO_TYPE_I1: {
- int8_t val = p_var->operator signed char();
+ int8_t val = p_var.operator signed char();
return BOX_ENUM(enum_baseclass, val);
}
case MONO_TYPE_I2: {
- int16_t val = p_var->operator signed short();
+ int16_t val = p_var.operator signed short();
return BOX_ENUM(enum_baseclass, val);
}
case MONO_TYPE_I4: {
- int32_t val = p_var->operator signed int();
+ int32_t val = p_var.operator signed int();
return BOX_ENUM(enum_baseclass, val);
}
case MONO_TYPE_I8: {
- int64_t val = p_var->operator int64_t();
+ int64_t val = p_var.operator int64_t();
return BOX_ENUM(enum_baseclass, val);
}
case MONO_TYPE_U1: {
- uint8_t val = p_var->operator unsigned char();
+ uint8_t val = p_var.operator unsigned char();
return BOX_ENUM(enum_baseclass, val);
}
case MONO_TYPE_U2: {
- uint16_t val = p_var->operator unsigned short();
+ uint16_t val = p_var.operator unsigned short();
return BOX_ENUM(enum_baseclass, val);
}
case MONO_TYPE_U4: {
- uint32_t val = p_var->operator unsigned int();
+ uint32_t val = p_var.operator unsigned int();
return BOX_ENUM(enum_baseclass, val);
}
case MONO_TYPE_U8: {
- uint64_t val = p_var->operator uint64_t();
+ uint64_t val = p_var.operator uint64_t();
return BOX_ENUM(enum_baseclass, val);
}
default: {
- ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to a managed enum value of unmarshallable base type.");
+ ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to enum value of unsupported base type: '" +
+ GDMonoClass::get_full_name(enum_baseclass) + "'.");
}
}
}
- } break;
-
- case MONO_TYPE_ARRAY:
- case MONO_TYPE_SZARRAY: {
- MonoArrayType *array_type = mono_type_get_array_type(p_type.type_class->get_mono_type());
-
- if (array_type->eklass == CACHED_CLASS_RAW(MonoObject))
- return (MonoObject *)Array_to_mono_array(p_var->operator Array());
-
- if (array_type->eklass == CACHED_CLASS_RAW(uint8_t))
- return (MonoObject *)PoolByteArray_to_mono_array(p_var->operator PoolByteArray());
-
- if (array_type->eklass == CACHED_CLASS_RAW(int32_t))
- return (MonoObject *)PoolIntArray_to_mono_array(p_var->operator PoolIntArray());
-
- if (array_type->eklass == REAL_T_MONOCLASS)
- return (MonoObject *)PoolRealArray_to_mono_array(p_var->operator PoolRealArray());
- if (array_type->eklass == CACHED_CLASS_RAW(String))
- return (MonoObject *)PoolStringArray_to_mono_array(p_var->operator PoolStringArray());
-
- if (array_type->eklass == CACHED_CLASS_RAW(Vector2))
- return (MonoObject *)PoolVector2Array_to_mono_array(p_var->operator PoolVector2Array());
-
- if (array_type->eklass == CACHED_CLASS_RAW(Vector3))
- return (MonoObject *)PoolVector3Array_to_mono_array(p_var->operator PoolVector3Array());
-
- if (array_type->eklass == CACHED_CLASS_RAW(Color))
- return (MonoObject *)PoolColorArray_to_mono_array(p_var->operator PoolColorArray());
-
- ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to a managed array of unmarshallable element type.");
- } break;
-
- case MONO_TYPE_CLASS: {
- GDMonoClass *type_class = p_type.type_class;
-
- // GodotObject
- if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) {
- return GDMonoUtils::unmanaged_get_managed(p_var->operator Object *());
- }
-
- if (CACHED_CLASS(NodePath) == type_class) {
- return GDMonoUtils::create_managed_from(p_var->operator NodePath());
- }
-
- if (CACHED_CLASS(RID) == type_class) {
- return GDMonoUtils::create_managed_from(p_var->operator RID());
- }
-
- if (CACHED_CLASS(Dictionary) == type_class) {
- return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary));
- }
-
- if (CACHED_CLASS(Array) == type_class) {
- return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
- }
-
- // The order in which we check the following interfaces is very important (dictionaries and generics first)
-
- MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
-
- MonoReflectionType *key_reftype, *value_reftype;
- if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
- return GDMonoUtils::create_managed_from(p_var->operator Dictionary(),
- GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype));
- }
-
- if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
- return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary));
- }
-
- MonoReflectionType *elem_reftype;
- if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) {
- return GDMonoUtils::create_managed_from(p_var->operator Array(),
- GDMonoUtils::Marshal::make_generic_array_type(elem_reftype));
- }
-
- if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- if (GDMonoCache::tools_godot_api_check()) {
- return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
- } else {
- return (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_var->operator Array());
- }
- }
- } break;
- case MONO_TYPE_OBJECT: {
- // Variant
- switch (p_var->get_type()) {
- case Variant::BOOL: {
- MonoBoolean val = p_var->operator bool();
- return BOX_BOOLEAN(val);
- }
- case Variant::INT: {
- int32_t val = p_var->operator signed int();
- return BOX_INT32(val);
- }
- case Variant::REAL: {
-#ifdef REAL_T_IS_DOUBLE
- double val = p_var->operator double();
- return BOX_DOUBLE(val);
-#else
- float val = p_var->operator float();
- return BOX_FLOAT(val);
-#endif
- }
- case Variant::STRING:
- return (MonoObject *)mono_string_from_godot(p_var->operator String());
- case Variant::VECTOR2: {
- GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_var->operator ::Vector2());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2), &from);
- }
- case Variant::RECT2: {
- GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_var->operator ::Rect2());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2), &from);
- }
- case Variant::VECTOR3: {
- GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_var->operator ::Vector3());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3), &from);
- }
- case Variant::TRANSFORM2D: {
- GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_var->operator ::Transform2D());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform2D), &from);
- }
- case Variant::PLANE: {
- GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_var->operator ::Plane());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Plane), &from);
- }
- case Variant::QUAT: {
- GDMonoMarshal::M_Quat from = MARSHALLED_OUT(Quat, p_var->operator ::Quat());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Quat), &from);
- }
- case Variant::AABB: {
- GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_var->operator ::AABB());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(AABB), &from);
- }
- case Variant::BASIS: {
- GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_var->operator ::Basis());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Basis), &from);
- }
- case Variant::TRANSFORM: {
- GDMonoMarshal::M_Transform from = MARSHALLED_OUT(Transform, p_var->operator ::Transform());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform), &from);
- }
- case Variant::COLOR: {
- GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_var->operator ::Color());
- return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Color), &from);
- }
- case Variant::NODE_PATH:
- return GDMonoUtils::create_managed_from(p_var->operator NodePath());
- case Variant::_RID:
- return GDMonoUtils::create_managed_from(p_var->operator RID());
- case Variant::OBJECT:
- return GDMonoUtils::unmanaged_get_managed(p_var->operator Object *());
- case Variant::DICTIONARY:
- return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary));
- case Variant::ARRAY:
- return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
- case Variant::POOL_BYTE_ARRAY:
- return (MonoObject *)PoolByteArray_to_mono_array(p_var->operator PoolByteArray());
- case Variant::POOL_INT_ARRAY:
- return (MonoObject *)PoolIntArray_to_mono_array(p_var->operator PoolIntArray());
- case Variant::POOL_REAL_ARRAY:
- return (MonoObject *)PoolRealArray_to_mono_array(p_var->operator PoolRealArray());
- case Variant::POOL_STRING_ARRAY:
- return (MonoObject *)PoolStringArray_to_mono_array(p_var->operator PoolStringArray());
- case Variant::POOL_VECTOR2_ARRAY:
- return (MonoObject *)PoolVector2Array_to_mono_array(p_var->operator PoolVector2Array());
- case Variant::POOL_VECTOR3_ARRAY:
- return (MonoObject *)PoolVector3Array_to_mono_array(p_var->operator PoolVector3Array());
- case Variant::POOL_COLOR_ARRAY:
- return (MonoObject *)PoolColorArray_to_mono_array(p_var->operator PoolColorArray());
- default:
- return NULL;
- }
- break;
- case MONO_TYPE_GENERICINST: {
- MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type());
-
- if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
- return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), p_type.type_class);
- }
-
- if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) {
- return GDMonoUtils::create_managed_from(p_var->operator Array(), p_type.type_class);
- }
-
- // The order in which we check the following interfaces is very important (dictionaries and generics first)
-
- MonoReflectionType *key_reftype, *value_reftype;
- if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
- return GDMonoUtils::create_managed_from(p_var->operator Dictionary(),
- GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype));
- }
-
- if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
- return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), CACHED_CLASS(Dictionary));
- }
-
- MonoReflectionType *elem_reftype;
- if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype, &elem_reftype)) {
- return GDMonoUtils::create_managed_from(p_var->operator Array(),
- GDMonoUtils::Marshal::make_generic_array_type(elem_reftype));
- }
-
- if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- if (GDMonoCache::tools_godot_api_check()) {
- return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
- } else {
- return (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_var->operator Array());
- }
- }
- } break;
+ ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported value type: '" +
+ p_type.type_class->get_full_name() + "'.");
} break;
+ case MONO_TYPE_STRING:
+ return (MonoObject *)variant_to_mono_string(p_var);
+ case MONO_TYPE_ARRAY:
+ case MONO_TYPE_SZARRAY:
+ return (MonoObject *)variant_to_mono_array(p_var, p_type.type_class);
+ case MONO_TYPE_CLASS:
+ return variant_to_mono_object_of_class(p_var, p_type.type_class);
+ case MONO_TYPE_GENERICINST:
+ return variant_to_mono_object_of_genericinst(p_var, p_type.type_class);
+ case MONO_TYPE_OBJECT:
+ return variant_to_mono_object(p_var);
}
- ERR_FAIL_V_MSG(NULL, "Attempted to convert Variant to an unmarshallable managed type. Name: '" +
- p_type.type_class->get_name() + "' Encoding: " + itos(p_type.type_encoding) + ".");
+ ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported type with encoding: " +
+ itos(p_type.type_encoding) + ".");
}
Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type, bool p_fail_with_err = true) {
-
ERR_FAIL_COND_V(!p_type.type_class, Variant());
+#ifdef DEBUG_ENABLED
+ CRASH_COND_MSG(p_type.type_encoding == MONO_TYPE_OBJECT, "Type of object should be known.");
+#endif
+
switch (p_type.type_encoding) {
case MONO_TYPE_BOOLEAN:
return (bool)unbox<MonoBoolean>(p_obj);
-
case MONO_TYPE_CHAR:
return unbox<uint16_t>(p_obj);
-
case MONO_TYPE_I1:
return unbox<int8_t>(p_obj);
case MONO_TYPE_I2:
@@ -719,7 +989,6 @@ Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type
return unbox<int32_t>(p_obj);
case MONO_TYPE_I8:
return unbox<int64_t>(p_obj);
-
case MONO_TYPE_U1:
return unbox<uint8_t>(p_obj);
case MONO_TYPE_U2:
@@ -728,82 +997,131 @@ Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type
return unbox<uint32_t>(p_obj);
case MONO_TYPE_U8:
return unbox<uint64_t>(p_obj);
-
case MONO_TYPE_R4:
return unbox<float>(p_obj);
case MONO_TYPE_R8:
return unbox<double>(p_obj);
-
- case MONO_TYPE_STRING: {
- if (p_obj == NULL)
- return Variant(); // NIL
- return mono_string_to_godot_not_null((MonoString *)p_obj);
- } break;
-
case MONO_TYPE_VALUETYPE: {
GDMonoClass *vtclass = p_type.type_class;
- if (vtclass == CACHED_CLASS(Vector2))
- return MARSHALLED_IN(Vector2, (GDMonoMarshal::M_Vector2 *)mono_object_unbox(p_obj));
+ if (vtclass == CACHED_CLASS(Vector2)) {
+ return MARSHALLED_IN(Vector2, unbox_addr<GDMonoMarshal::M_Vector2>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Rect2))
- return MARSHALLED_IN(Rect2, (GDMonoMarshal::M_Rect2 *)mono_object_unbox(p_obj));
+ if (vtclass == CACHED_CLASS(Vector2i)) {
+ return MARSHALLED_IN(Vector2i, unbox_addr<GDMonoMarshal::M_Vector2i>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Transform2D))
- return MARSHALLED_IN(Transform2D, (GDMonoMarshal::M_Transform2D *)mono_object_unbox(p_obj));
+ if (vtclass == CACHED_CLASS(Rect2)) {
+ return MARSHALLED_IN(Rect2, unbox_addr<GDMonoMarshal::M_Rect2>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Vector3))
- return MARSHALLED_IN(Vector3, (GDMonoMarshal::M_Vector3 *)mono_object_unbox(p_obj));
+ if (vtclass == CACHED_CLASS(Rect2i)) {
+ return MARSHALLED_IN(Rect2i, unbox_addr<GDMonoMarshal::M_Rect2i>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Basis))
- return MARSHALLED_IN(Basis, (GDMonoMarshal::M_Basis *)mono_object_unbox(p_obj));
+ if (vtclass == CACHED_CLASS(Transform2D)) {
+ return MARSHALLED_IN(Transform2D, unbox_addr<GDMonoMarshal::M_Transform2D>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Quat))
- return MARSHALLED_IN(Quat, (GDMonoMarshal::M_Quat *)mono_object_unbox(p_obj));
+ if (vtclass == CACHED_CLASS(Vector3)) {
+ return MARSHALLED_IN(Vector3, unbox_addr<GDMonoMarshal::M_Vector3>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Transform))
- return MARSHALLED_IN(Transform, (GDMonoMarshal::M_Transform *)mono_object_unbox(p_obj));
+ if (vtclass == CACHED_CLASS(Vector3i)) {
+ return MARSHALLED_IN(Vector3i, unbox_addr<GDMonoMarshal::M_Vector3i>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(AABB))
- return MARSHALLED_IN(AABB, (GDMonoMarshal::M_AABB *)mono_object_unbox(p_obj));
+ if (vtclass == CACHED_CLASS(Basis)) {
+ return MARSHALLED_IN(Basis, unbox_addr<GDMonoMarshal::M_Basis>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Color))
- return MARSHALLED_IN(Color, (GDMonoMarshal::M_Color *)mono_object_unbox(p_obj));
+ if (vtclass == CACHED_CLASS(Quaternion)) {
+ return MARSHALLED_IN(Quaternion, unbox_addr<GDMonoMarshal::M_Quaternion>(p_obj));
+ }
- if (vtclass == CACHED_CLASS(Plane))
- return MARSHALLED_IN(Plane, (GDMonoMarshal::M_Plane *)mono_object_unbox(p_obj));
+ if (vtclass == CACHED_CLASS(Transform3D)) {
+ return MARSHALLED_IN(Transform3D, unbox_addr<GDMonoMarshal::M_Transform3D>(p_obj));
+ }
- if (mono_class_is_enum(vtclass->get_mono_ptr()))
+ if (vtclass == CACHED_CLASS(AABB)) {
+ return MARSHALLED_IN(AABB, unbox_addr<GDMonoMarshal::M_AABB>(p_obj));
+ }
+
+ if (vtclass == CACHED_CLASS(Color)) {
+ return MARSHALLED_IN(Color, unbox_addr<GDMonoMarshal::M_Color>(p_obj));
+ }
+
+ if (vtclass == CACHED_CLASS(Plane)) {
+ return MARSHALLED_IN(Plane, unbox_addr<GDMonoMarshal::M_Plane>(p_obj));
+ }
+
+ if (vtclass == CACHED_CLASS(Callable)) {
+ return managed_to_callable(unbox<GDMonoMarshal::M_Callable>(p_obj));
+ }
+
+ if (vtclass == CACHED_CLASS(SignalInfo)) {
+ return managed_to_signal_info(unbox<GDMonoMarshal::M_SignalInfo>(p_obj));
+ }
+
+ if (mono_class_is_enum(vtclass->get_mono_ptr())) {
return unbox<int32_t>(p_obj);
+ }
+ } break;
+ case MONO_TYPE_STRING: {
+ if (p_obj == nullptr) {
+ return Variant(); // NIL
+ }
+ return mono_string_to_godot_not_null((MonoString *)p_obj);
} break;
-
case MONO_TYPE_ARRAY:
case MONO_TYPE_SZARRAY: {
MonoArrayType *array_type = mono_type_get_array_type(p_type.type_class->get_mono_type());
- if (array_type->eklass == CACHED_CLASS_RAW(MonoObject))
+ if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) {
return mono_array_to_Array((MonoArray *)p_obj);
+ }
+
+ if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) {
+ return mono_array_to_PackedByteArray((MonoArray *)p_obj);
+ }
+
+ if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) {
+ return mono_array_to_PackedInt32Array((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(uint8_t))
- return mono_array_to_PoolByteArray((MonoArray *)p_obj);
+ if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) {
+ return mono_array_to_PackedInt64Array((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(int32_t))
- return mono_array_to_PoolIntArray((MonoArray *)p_obj);
+ if (array_type->eklass == CACHED_CLASS_RAW(float)) {
+ return mono_array_to_PackedFloat32Array((MonoArray *)p_obj);
+ }
- if (array_type->eklass == REAL_T_MONOCLASS)
- return mono_array_to_PoolRealArray((MonoArray *)p_obj);
+ if (array_type->eklass == CACHED_CLASS_RAW(double)) {
+ return mono_array_to_PackedFloat64Array((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(String))
- return mono_array_to_PoolStringArray((MonoArray *)p_obj);
+ if (array_type->eklass == CACHED_CLASS_RAW(String)) {
+ return mono_array_to_PackedStringArray((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Vector2))
- return mono_array_to_PoolVector2Array((MonoArray *)p_obj);
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) {
+ return mono_array_to_PackedVector2Array((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Vector3))
- return mono_array_to_PoolVector3Array((MonoArray *)p_obj);
+ if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) {
+ return mono_array_to_PackedVector3Array((MonoArray *)p_obj);
+ }
- if (array_type->eklass == CACHED_CLASS_RAW(Color))
- return mono_array_to_PoolColorArray((MonoArray *)p_obj);
+ if (array_type->eklass == CACHED_CLASS_RAW(Color)) {
+ return mono_array_to_PackedColorArray((MonoArray *)p_obj);
+ }
+
+ GDMonoClass *array_type_class = GDMono::get_singleton()->get_class(array_type->eklass);
+ if (CACHED_CLASS(GodotObject)->is_assignable_from(array_type_class)) {
+ return mono_array_to_Array((MonoArray *)p_obj);
+ }
if (p_fail_with_err) {
ERR_FAIL_V_MSG(Variant(), "Attempted to convert a managed array of unmarshallable element type to Variant.");
@@ -811,20 +1129,24 @@ Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type
return Variant();
}
} break;
-
case MONO_TYPE_CLASS: {
GDMonoClass *type_class = p_type.type_class;
// GodotObject
if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) {
Object *ptr = unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_obj));
- if (ptr != NULL) {
- Reference *ref = Object::cast_to<Reference>(ptr);
- return ref ? Variant(Ref<Reference>(ref)) : Variant(ptr);
+ if (ptr != nullptr) {
+ RefCounted *rc = Object::cast_to<RefCounted>(ptr);
+ return rc ? Variant(Ref<RefCounted>(rc)) : Variant(ptr);
}
return Variant();
}
+ if (CACHED_CLASS(StringName) == type_class) {
+ StringName *ptr = unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_obj));
+ return ptr ? Variant(*ptr) : Variant();
+ }
+
if (CACHED_CLASS(NodePath) == type_class) {
NodePath *ptr = unbox<NodePath *>(CACHED_FIELD(NodePath, ptr)->get_value(p_obj));
return ptr ? Variant(*ptr) : Variant();
@@ -835,74 +1157,54 @@ Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type
return ptr ? Variant(*ptr) : Variant();
}
- if (CACHED_CLASS(Array) == type_class) {
- MonoException *exc = NULL;
- Array *ptr = CACHED_METHOD_THUNK(Array, GetPtr).invoke(p_obj, &exc);
- UNHANDLED_EXCEPTION(exc);
- return ptr ? Variant(*ptr) : Variant();
- }
-
+ // Godot.Collections.Dictionary
if (CACHED_CLASS(Dictionary) == type_class) {
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
Dictionary *ptr = CACHED_METHOD_THUNK(Dictionary, GetPtr).invoke(p_obj, &exc);
UNHANDLED_EXCEPTION(exc);
return ptr ? Variant(*ptr) : Variant();
}
- // The order in which we check the following interfaces is very important (dictionaries and generics first)
-
- MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
-
- if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) {
- return GDMonoUtils::Marshal::generic_idictionary_to_dictionary(p_obj);
- }
-
- if (type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
- return GDMonoUtils::Marshal::idictionary_to_dictionary(p_obj);
- }
-
- if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype)) {
- return GDMonoUtils::Marshal::enumerable_to_array(p_obj);
- }
-
- if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- return GDMonoUtils::Marshal::enumerable_to_array(p_obj);
+ // Godot.Collections.Array
+ if (CACHED_CLASS(Array) == type_class) {
+ MonoException *exc = nullptr;
+ Array *ptr = CACHED_METHOD_THUNK(Array, GetPtr).invoke(p_obj, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return ptr ? Variant(*ptr) : Variant();
}
} break;
-
case MONO_TYPE_GENERICINST: {
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type());
+ // Godot.Collections.Dictionary<TKey, TValue>
if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
MonoObject *ret = p_type.type_class->get_method("GetPtr")->invoke(p_obj, &exc);
UNHANDLED_EXCEPTION(exc);
return *unbox<Dictionary *>(ret);
}
+ // Godot.Collections.Array<T>
if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) {
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
MonoObject *ret = p_type.type_class->get_method("GetPtr")->invoke(p_obj, &exc);
UNHANDLED_EXCEPTION(exc);
return *unbox<Array *>(ret);
}
- // The order in which we check the following interfaces is very important (dictionaries and generics first)
-
- if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) {
- return GDMonoUtils::Marshal::generic_idictionary_to_dictionary(p_obj);
+ // System.Collections.Generic.Dictionary<TKey, TValue>
+ if (GDMonoUtils::Marshal::type_is_system_generic_dictionary(reftype)) {
+ MonoReflectionType *key_reftype = nullptr;
+ MonoReflectionType *value_reftype = nullptr;
+ GDMonoUtils::Marshal::dictionary_get_key_value_types(reftype, &key_reftype, &value_reftype);
+ return system_generic_dict_to_Dictionary(p_obj, p_type.type_class, key_reftype, value_reftype);
}
- if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IDictionary))) {
- return GDMonoUtils::Marshal::idictionary_to_dictionary(p_obj);
- }
-
- if (GDMonoUtils::Marshal::generic_ienumerable_is_assignable_from(reftype)) {
- return GDMonoUtils::Marshal::enumerable_to_array(p_obj);
- }
-
- if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
- return GDMonoUtils::Marshal::enumerable_to_array(p_obj);
+ // System.Collections.Generic.List<T>
+ if (GDMonoUtils::Marshal::type_is_system_generic_list(reftype)) {
+ MonoReflectionType *elem_reftype = nullptr;
+ GDMonoUtils::Marshal::array_get_element_type(reftype, &elem_reftype);
+ return system_generic_list_to_Array_variant(p_obj, p_type.type_class, elem_reftype);
}
} break;
}
@@ -916,8 +1218,9 @@ Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type
}
Variant mono_object_to_variant(MonoObject *p_obj) {
- if (!p_obj)
+ if (!p_obj) {
return Variant();
+ }
ManagedType type = ManagedType::from_class(mono_object_get_class(p_obj));
@@ -925,31 +1228,38 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
}
Variant mono_object_to_variant(MonoObject *p_obj, const ManagedType &p_type) {
- if (!p_obj)
+ if (!p_obj) {
return Variant();
+ }
return mono_object_to_variant_impl(p_obj, p_type);
}
Variant mono_object_to_variant_no_err(MonoObject *p_obj, const ManagedType &p_type) {
- if (!p_obj)
+ if (!p_obj) {
return Variant();
+ }
return mono_object_to_variant_impl(p_obj, p_type, /* fail_with_err: */ false);
}
String mono_object_to_variant_string(MonoObject *p_obj, MonoException **r_exc) {
+ if (p_obj == nullptr) {
+ return String("null");
+ }
+
ManagedType type = ManagedType::from_class(mono_object_get_class(p_obj));
Variant var = GDMonoMarshal::mono_object_to_variant_no_err(p_obj, type);
- if (var.get_type() == Variant::NIL && p_obj != NULL) {
+ if (var.get_type() == Variant::NIL) { // `&& p_obj != nullptr` but omitted because always true
// Cannot convert MonoObject* to Variant; fallback to 'ToString()'.
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
MonoString *mono_str = GDMonoUtils::object_to_string(p_obj, &exc);
if (exc) {
- if (r_exc)
+ if (r_exc) {
*r_exc = exc;
+ }
return String();
}
@@ -959,10 +1269,105 @@ String mono_object_to_variant_string(MonoObject *p_obj, MonoException **r_exc) {
}
}
+MonoObject *Dictionary_to_system_generic_dict(const Dictionary &p_dict, GDMonoClass *p_class, MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) {
+ String ctor_desc = ":.ctor(System.Collections.Generic.IDictionary`2<" + GDMonoUtils::get_type_desc(p_key_reftype) +
+ ", " + GDMonoUtils::get_type_desc(p_value_reftype) + ">)";
+ GDMonoMethod *ctor = p_class->get_method_with_desc(ctor_desc, true);
+ CRASH_COND(ctor == nullptr);
+
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
+ ERR_FAIL_NULL_V(mono_object, nullptr);
+
+ GDMonoClass *godot_dict_class = GDMonoUtils::Marshal::make_generic_dictionary_type(p_key_reftype, p_value_reftype);
+ MonoObject *godot_dict = GDMonoUtils::create_managed_from(p_dict, godot_dict_class);
+
+ void *ctor_args[1] = { godot_dict };
+
+ MonoException *exc = nullptr;
+ ctor->invoke_raw(mono_object, ctor_args, &exc);
+ UNHANDLED_EXCEPTION(exc);
+
+ return mono_object;
+}
+
+Dictionary system_generic_dict_to_Dictionary(MonoObject *p_obj, [[maybe_unused]] GDMonoClass *p_class, MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) {
+ GDMonoClass *godot_dict_class = GDMonoUtils::Marshal::make_generic_dictionary_type(p_key_reftype, p_value_reftype);
+ String ctor_desc = ":.ctor(System.Collections.Generic.IDictionary`2<" + GDMonoUtils::get_type_desc(p_key_reftype) +
+ ", " + GDMonoUtils::get_type_desc(p_value_reftype) + ">)";
+ GDMonoMethod *godot_dict_ctor = godot_dict_class->get_method_with_desc(ctor_desc, true);
+ CRASH_COND(godot_dict_ctor == nullptr);
+
+ MonoObject *godot_dict = mono_object_new(mono_domain_get(), godot_dict_class->get_mono_ptr());
+ ERR_FAIL_NULL_V(godot_dict, Dictionary());
+
+ void *ctor_args[1] = { p_obj };
+
+ MonoException *exc = nullptr;
+ godot_dict_ctor->invoke_raw(godot_dict, ctor_args, &exc);
+ UNHANDLED_EXCEPTION(exc);
+
+ exc = nullptr;
+ MonoObject *ret = godot_dict_class->get_method("GetPtr")->invoke(godot_dict, &exc);
+ UNHANDLED_EXCEPTION(exc);
+
+ return *unbox<Dictionary *>(ret);
+}
+
+MonoObject *Array_to_system_generic_list(const Array &p_array, GDMonoClass *p_class, MonoReflectionType *p_elem_reftype) {
+ MonoType *elem_type = mono_reflection_type_get_type(p_elem_reftype);
+ MonoClass *elem_class = mono_class_from_mono_type(elem_type);
+
+ String ctor_desc = ":.ctor(System.Collections.Generic.IEnumerable`1<" + GDMonoUtils::get_type_desc(elem_type) + ">)";
+ GDMonoMethod *ctor = p_class->get_method_with_desc(ctor_desc, true);
+ CRASH_COND(ctor == nullptr);
+
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
+ ERR_FAIL_NULL_V(mono_object, nullptr);
+
+ void *ctor_args[1] = { Array_to_mono_array(p_array, elem_class) };
+
+ MonoException *exc = nullptr;
+ ctor->invoke_raw(mono_object, ctor_args, &exc);
+ UNHANDLED_EXCEPTION(exc);
+
+ return mono_object;
+}
+
+Variant system_generic_list_to_Array_variant(MonoObject *p_obj, GDMonoClass *p_class, [[maybe_unused]] MonoReflectionType *p_elem_reftype) {
+ GDMonoMethod *to_array = p_class->get_method("ToArray", 0);
+ CRASH_COND(to_array == nullptr);
+
+ MonoException *exc = nullptr;
+ MonoObject *array = to_array->invoke_raw(p_obj, nullptr, &exc);
+ UNHANDLED_EXCEPTION(exc);
+
+ ERR_FAIL_NULL_V(array, Variant());
+
+ ManagedType type = ManagedType::from_class(mono_object_get_class(array));
+
+ bool result_is_array = type.type_encoding != MONO_TYPE_SZARRAY && type.type_encoding != MONO_TYPE_ARRAY;
+ ERR_FAIL_COND_V(result_is_array, Variant());
+
+ return mono_object_to_variant(array, type);
+}
+
MonoArray *Array_to_mono_array(const Array &p_array) {
- MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_array.size());
+ int length = p_array.size();
+ MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), length);
- for (int i = 0; i < p_array.size(); i++) {
+ for (int i = 0; i < length; i++) {
+ MonoObject *boxed = variant_to_mono_object(p_array[i]);
+ mono_array_setref(ret, i, boxed);
+ }
+
+ return ret;
+}
+
+MonoArray *Array_to_mono_array(const Array &p_array, MonoClass *p_array_type_class) {
+ int length = p_array.size();
+ MonoArray *ret = mono_array_new(mono_domain_get(), p_array_type_class, length);
+
+ for (int i = 0; i < length; i++) {
MonoObject *boxed = variant_to_mono_object(p_array[i]);
mono_array_setref(ret, i, boxed);
}
@@ -972,8 +1377,9 @@ MonoArray *Array_to_mono_array(const Array &p_array) {
Array mono_array_to_Array(MonoArray *p_array) {
Array ret;
- if (!p_array)
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
@@ -985,95 +1391,148 @@ Array mono_array_to_Array(MonoArray *p_array) {
return ret;
}
-// TODO: Use memcpy where possible
+MonoArray *PackedInt32Array_to_mono_array(const PackedInt32Array &p_array) {
+ const int32_t *src = p_array.ptr();
+ int length = p_array.size();
-MonoArray *PoolIntArray_to_mono_array(const PoolIntArray &p_array) {
- PoolIntArray::Read r = p_array.read();
+ MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int32_t), length);
- MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int32_t), p_array.size());
+ int32_t *dst = mono_array_addr(ret, int32_t, 0);
+ memcpy(dst, src, length * sizeof(int32_t));
- for (int i = 0; i < p_array.size(); i++) {
- mono_array_set(ret, int32_t, i, r[i]);
+ return ret;
+}
+
+PackedInt32Array mono_array_to_PackedInt32Array(MonoArray *p_array) {
+ PackedInt32Array ret;
+ if (!p_array) {
+ return ret;
}
+ int length = mono_array_length(p_array);
+ ret.resize(length);
+ int32_t *dst = ret.ptrw();
+
+ const int32_t *src = mono_array_addr(p_array, int32_t, 0);
+ memcpy(dst, src, length * sizeof(int32_t));
return ret;
}
-PoolIntArray mono_array_to_PoolIntArray(MonoArray *p_array) {
- PoolIntArray ret;
- if (!p_array)
+MonoArray *PackedInt64Array_to_mono_array(const PackedInt64Array &p_array) {
+ const int64_t *src = p_array.ptr();
+ int length = p_array.size();
+
+ MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int64_t), length);
+
+ int64_t *dst = mono_array_addr(ret, int64_t, 0);
+ memcpy(dst, src, length * sizeof(int64_t));
+
+ return ret;
+}
+
+PackedInt64Array mono_array_to_PackedInt64Array(MonoArray *p_array) {
+ PackedInt64Array ret;
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
- PoolIntArray::Write w = ret.write();
+ int64_t *dst = ret.ptrw();
- for (int i = 0; i < length; i++) {
- w[i] = mono_array_get(p_array, int32_t, i);
- }
+ const int64_t *src = mono_array_addr(p_array, int64_t, 0);
+ memcpy(dst, src, length * sizeof(int64_t));
return ret;
}
-MonoArray *PoolByteArray_to_mono_array(const PoolByteArray &p_array) {
- PoolByteArray::Read r = p_array.read();
+MonoArray *PackedByteArray_to_mono_array(const PackedByteArray &p_array) {
+ const uint8_t *src = p_array.ptr();
+ int length = p_array.size();
- MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), p_array.size());
+ MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), length);
- for (int i = 0; i < p_array.size(); i++) {
- mono_array_set(ret, uint8_t, i, r[i]);
- }
+ uint8_t *dst = mono_array_addr(ret, uint8_t, 0);
+ memcpy(dst, src, length * sizeof(uint8_t));
return ret;
}
-PoolByteArray mono_array_to_PoolByteArray(MonoArray *p_array) {
- PoolByteArray ret;
- if (!p_array)
+PackedByteArray mono_array_to_PackedByteArray(MonoArray *p_array) {
+ PackedByteArray ret;
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
- PoolByteArray::Write w = ret.write();
+ uint8_t *dst = ret.ptrw();
- for (int i = 0; i < length; i++) {
- w[i] = mono_array_get(p_array, uint8_t, i);
- }
+ const uint8_t *src = mono_array_addr(p_array, uint8_t, 0);
+ memcpy(dst, src, length * sizeof(uint8_t));
return ret;
}
-MonoArray *PoolRealArray_to_mono_array(const PoolRealArray &p_array) {
- PoolRealArray::Read r = p_array.read();
+MonoArray *PackedFloat32Array_to_mono_array(const PackedFloat32Array &p_array) {
+ const float *src = p_array.ptr();
+ int length = p_array.size();
- MonoArray *ret = mono_array_new(mono_domain_get(), REAL_T_MONOCLASS, p_array.size());
+ MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(float), length);
- for (int i = 0; i < p_array.size(); i++) {
- mono_array_set(ret, real_t, i, r[i]);
- }
+ float *dst = mono_array_addr(ret, float, 0);
+ memcpy(dst, src, length * sizeof(float));
return ret;
}
-PoolRealArray mono_array_to_PoolRealArray(MonoArray *p_array) {
- PoolRealArray ret;
- if (!p_array)
+PackedFloat32Array mono_array_to_PackedFloat32Array(MonoArray *p_array) {
+ PackedFloat32Array ret;
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
- PoolRealArray::Write w = ret.write();
+ float *dst = ret.ptrw();
- for (int i = 0; i < length; i++) {
- w[i] = mono_array_get(p_array, real_t, i);
+ const float *src = mono_array_addr(p_array, float, 0);
+ memcpy(dst, src, length * sizeof(float));
+
+ return ret;
+}
+
+MonoArray *PackedFloat64Array_to_mono_array(const PackedFloat64Array &p_array) {
+ const double *src = p_array.ptr();
+ int length = p_array.size();
+
+ MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(double), length);
+
+ double *dst = mono_array_addr(ret, double, 0);
+ memcpy(dst, src, length * sizeof(double));
+
+ return ret;
+}
+
+PackedFloat64Array mono_array_to_PackedFloat64Array(MonoArray *p_array) {
+ PackedFloat64Array ret;
+ if (!p_array) {
+ return ret;
}
+ int length = mono_array_length(p_array);
+ ret.resize(length);
+ double *dst = ret.ptrw();
+
+ const double *src = mono_array_addr(p_array, double, 0);
+ memcpy(dst, src, length * sizeof(double));
return ret;
}
-MonoArray *PoolStringArray_to_mono_array(const PoolStringArray &p_array) {
- PoolStringArray::Read r = p_array.read();
+MonoArray *PackedStringArray_to_mono_array(const PackedStringArray &p_array) {
+ const String *r = p_array.ptr();
+ int length = p_array.size();
- MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), p_array.size());
+ MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), length);
- for (int i = 0; i < p_array.size(); i++) {
+ for (int i = 0; i < length; i++) {
MonoString *boxed = mono_string_from_godot(r[i]);
mono_array_setref(ret, i, boxed);
}
@@ -1081,13 +1540,14 @@ MonoArray *PoolStringArray_to_mono_array(const PoolStringArray &p_array) {
return ret;
}
-PoolStringArray mono_array_to_PoolStringArray(MonoArray *p_array) {
- PoolStringArray ret;
- if (!p_array)
+PackedStringArray mono_array_to_PackedStringArray(MonoArray *p_array) {
+ PackedStringArray ret;
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
- PoolStringArray::Write w = ret.write();
+ String *w = ret.ptrw();
for (int i = 0; i < length; i++) {
MonoString *elem = mono_array_get(p_array, MonoString *, i);
@@ -1097,88 +1557,190 @@ PoolStringArray mono_array_to_PoolStringArray(MonoArray *p_array) {
return ret;
}
-MonoArray *PoolColorArray_to_mono_array(const PoolColorArray &p_array) {
- PoolColorArray::Read r = p_array.read();
+MonoArray *PackedColorArray_to_mono_array(const PackedColorArray &p_array) {
+ const Color *src = p_array.ptr();
+ int length = p_array.size();
- MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Color), p_array.size());
+ MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Color), length);
- for (int i = 0; i < p_array.size(); i++) {
- M_Color *raw = (M_Color *)mono_array_addr_with_size(ret, sizeof(M_Color), i);
- *raw = MARSHALLED_OUT(Color, r[i]);
+ if constexpr (InteropLayout::MATCHES_Color) {
+ Color *dst = mono_array_addr(ret, Color, 0);
+ memcpy(dst, src, length * sizeof(Color));
+ } else {
+ for (int i = 0; i < length; i++) {
+ M_Color *raw = (M_Color *)mono_array_addr_with_size(ret, sizeof(M_Color), i);
+ *raw = MARSHALLED_OUT(Color, src[i]);
+ }
}
return ret;
}
-PoolColorArray mono_array_to_PoolColorArray(MonoArray *p_array) {
- PoolColorArray ret;
- if (!p_array)
+PackedColorArray mono_array_to_PackedColorArray(MonoArray *p_array) {
+ PackedColorArray ret;
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
- PoolColorArray::Write w = ret.write();
+ Color *dst = ret.ptrw();
- for (int i = 0; i < length; i++) {
- w[i] = MARSHALLED_IN(Color, (M_Color *)mono_array_addr_with_size(p_array, sizeof(M_Color), i));
+ if constexpr (InteropLayout::MATCHES_Color) {
+ const Color *src = mono_array_addr(p_array, Color, 0);
+ memcpy(dst, src, length * sizeof(Color));
+ } else {
+ for (int i = 0; i < length; i++) {
+ dst[i] = MARSHALLED_IN(Color, (M_Color *)mono_array_addr_with_size(p_array, sizeof(M_Color), i));
+ }
}
return ret;
}
-MonoArray *PoolVector2Array_to_mono_array(const PoolVector2Array &p_array) {
- PoolVector2Array::Read r = p_array.read();
+MonoArray *PackedVector2Array_to_mono_array(const PackedVector2Array &p_array) {
+ const Vector2 *src = p_array.ptr();
+ int length = p_array.size();
- MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector2), p_array.size());
+ MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector2), length);
- for (int i = 0; i < p_array.size(); i++) {
- M_Vector2 *raw = (M_Vector2 *)mono_array_addr_with_size(ret, sizeof(M_Vector2), i);
- *raw = MARSHALLED_OUT(Vector2, r[i]);
+ if constexpr (InteropLayout::MATCHES_Vector2) {
+ Vector2 *dst = mono_array_addr(ret, Vector2, 0);
+ memcpy(dst, src, length * sizeof(Vector2));
+ } else {
+ for (int i = 0; i < length; i++) {
+ M_Vector2 *raw = (M_Vector2 *)mono_array_addr_with_size(ret, sizeof(M_Vector2), i);
+ *raw = MARSHALLED_OUT(Vector2, src[i]);
+ }
}
return ret;
}
-PoolVector2Array mono_array_to_PoolVector2Array(MonoArray *p_array) {
- PoolVector2Array ret;
- if (!p_array)
+PackedVector2Array mono_array_to_PackedVector2Array(MonoArray *p_array) {
+ PackedVector2Array ret;
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
- PoolVector2Array::Write w = ret.write();
+ Vector2 *dst = ret.ptrw();
- for (int i = 0; i < length; i++) {
- w[i] = MARSHALLED_IN(Vector2, (M_Vector2 *)mono_array_addr_with_size(p_array, sizeof(M_Vector2), i));
+ if constexpr (InteropLayout::MATCHES_Vector2) {
+ const Vector2 *src = mono_array_addr(p_array, Vector2, 0);
+ memcpy(dst, src, length * sizeof(Vector2));
+ } else {
+ for (int i = 0; i < length; i++) {
+ dst[i] = MARSHALLED_IN(Vector2, (M_Vector2 *)mono_array_addr_with_size(p_array, sizeof(M_Vector2), i));
+ }
}
return ret;
}
-MonoArray *PoolVector3Array_to_mono_array(const PoolVector3Array &p_array) {
- PoolVector3Array::Read r = p_array.read();
+MonoArray *PackedVector3Array_to_mono_array(const PackedVector3Array &p_array) {
+ const Vector3 *src = p_array.ptr();
+ int length = p_array.size();
- MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector3), p_array.size());
+ MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector3), length);
- for (int i = 0; i < p_array.size(); i++) {
- M_Vector3 *raw = (M_Vector3 *)mono_array_addr_with_size(ret, sizeof(M_Vector3), i);
- *raw = MARSHALLED_OUT(Vector3, r[i]);
+ if constexpr (InteropLayout::MATCHES_Vector3) {
+ Vector3 *dst = mono_array_addr(ret, Vector3, 0);
+ memcpy(dst, src, length * sizeof(Vector3));
+ } else {
+ for (int i = 0; i < length; i++) {
+ M_Vector3 *raw = (M_Vector3 *)mono_array_addr_with_size(ret, sizeof(M_Vector3), i);
+ *raw = MARSHALLED_OUT(Vector3, src[i]);
+ }
}
return ret;
}
-PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array) {
- PoolVector3Array ret;
- if (!p_array)
+PackedVector3Array mono_array_to_PackedVector3Array(MonoArray *p_array) {
+ PackedVector3Array ret;
+ if (!p_array) {
return ret;
+ }
int length = mono_array_length(p_array);
ret.resize(length);
- PoolVector3Array::Write w = ret.write();
+ Vector3 *dst = ret.ptrw();
- for (int i = 0; i < length; i++) {
- w[i] = MARSHALLED_IN(Vector3, (M_Vector3 *)mono_array_addr_with_size(p_array, sizeof(M_Vector3), i));
+ if constexpr (InteropLayout::MATCHES_Vector3) {
+ const Vector3 *src = mono_array_addr(p_array, Vector3, 0);
+ memcpy(dst, src, length * sizeof(Vector3));
+ } else {
+ for (int i = 0; i < length; i++) {
+ dst[i] = MARSHALLED_IN(Vector3, (M_Vector3 *)mono_array_addr_with_size(p_array, sizeof(M_Vector3), i));
+ }
}
return ret;
}
+Callable managed_to_callable(const M_Callable &p_managed_callable) {
+ if (p_managed_callable.delegate) {
+ // TODO: Use pooling for ManagedCallable instances.
+ CallableCustom *managed_callable = memnew(ManagedCallable(p_managed_callable.delegate));
+ return Callable(managed_callable);
+ } else {
+ Object *target = p_managed_callable.target ?
+ unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_managed_callable.target)) :
+ nullptr;
+ StringName *method_ptr = unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_managed_callable.method_string_name));
+ StringName method = method_ptr ? *method_ptr : StringName();
+ return Callable(target, method);
+ }
+}
+
+M_Callable callable_to_managed(const Callable &p_callable) {
+ if (p_callable.is_custom()) {
+ CallableCustom *custom = p_callable.get_custom();
+ CallableCustom::CompareEqualFunc compare_equal_func = custom->get_compare_equal_func();
+
+ if (compare_equal_func == ManagedCallable::compare_equal_func_ptr) {
+ ManagedCallable *managed_callable = static_cast<ManagedCallable *>(custom);
+ return {
+ nullptr, nullptr,
+ managed_callable->get_delegate()
+ };
+ } else if (compare_equal_func == SignalAwaiterCallable::compare_equal_func_ptr) {
+ SignalAwaiterCallable *signal_awaiter_callable = static_cast<SignalAwaiterCallable *>(custom);
+ return {
+ GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(signal_awaiter_callable->get_object())),
+ GDMonoUtils::create_managed_from(signal_awaiter_callable->get_signal()),
+ nullptr
+ };
+ } else if (compare_equal_func == EventSignalCallable::compare_equal_func_ptr) {
+ EventSignalCallable *event_signal_callable = static_cast<EventSignalCallable *>(custom);
+ return {
+ GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(event_signal_callable->get_object())),
+ GDMonoUtils::create_managed_from(event_signal_callable->get_signal()),
+ nullptr
+ };
+ }
+
+ // Some other CallableCustom. We only support ManagedCallable.
+ return { nullptr, nullptr, nullptr };
+ } else {
+ MonoObject *target_managed = GDMonoUtils::unmanaged_get_managed(p_callable.get_object());
+ MonoObject *method_string_name_managed = GDMonoUtils::create_managed_from(p_callable.get_method());
+ return { target_managed, method_string_name_managed, nullptr };
+ }
+}
+
+Signal managed_to_signal_info(const M_SignalInfo &p_managed_signal) {
+ Object *owner = p_managed_signal.owner ?
+ unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_managed_signal.owner)) :
+ nullptr;
+ StringName *name_ptr = unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_managed_signal.name_string_name));
+ StringName name = name_ptr ? *name_ptr : StringName();
+ return Signal(owner, name);
+}
+
+M_SignalInfo signal_info_to_managed(const Signal &p_signal) {
+ Object *owner = p_signal.get_object();
+ MonoObject *owner_managed = GDMonoUtils::unmanaged_get_managed(owner);
+ MonoObject *name_string_name_managed = GDMonoUtils::create_managed_from(p_signal.get_name());
+ return { owner_managed, name_string_name_managed };
+}
} // namespace GDMonoMarshal
diff --git a/modules/mono/mono_gd/gd_mono_marshal.h b/modules/mono/mono_gd/gd_mono_marshal.h
index e662e7814e..88afc7ebc5 100644
--- a/modules/mono/mono_gd/gd_mono_marshal.h
+++ b/modules/mono/mono_gd/gd_mono_marshal.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,8 +31,9 @@
#ifndef GDMONOMARSHAL_H
#define GDMONOMARSHAL_H
-#include "core/variant.h"
+#include "core/variant/variant.h"
+#include "../managed_callable.h"
#include "gd_mono.h"
#include "gd_mono_utils.h"
@@ -62,56 +63,67 @@ T *unbox_addr(MonoObject *p_obj) {
#define BOX_PTR(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(IntPtr), x)
#define BOX_ENUM(m_enum_class, x) mono_value_box(mono_domain_get(), m_enum_class, &x)
-Variant::Type managed_to_variant_type(const ManagedType &p_type);
+Variant::Type managed_to_variant_type(const ManagedType &p_type, bool *r_nil_is_variant = nullptr);
bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type);
-bool try_get_dictionary_key_value_types(const ManagedType &p_dictionary_type, ManagedType &r_key_type, ManagedType &r_value_type);
// String
-String mono_to_utf8_string(MonoString *p_mono_string);
-String mono_to_utf16_string(MonoString *p_mono_string);
-
_FORCE_INLINE_ String mono_string_to_godot_not_null(MonoString *p_mono_string) {
- if (sizeof(CharType) == 2)
- return mono_to_utf16_string(p_mono_string);
-
- return mono_to_utf8_string(p_mono_string);
+ char32_t *utf32 = (char32_t *)mono_string_to_utf32(p_mono_string);
+ String ret = String(utf32);
+ mono_free(utf32);
+ return ret;
}
_FORCE_INLINE_ String mono_string_to_godot(MonoString *p_mono_string) {
- if (p_mono_string == NULL)
+ if (p_mono_string == nullptr) {
return String();
+ }
return mono_string_to_godot_not_null(p_mono_string);
}
-_FORCE_INLINE_ MonoString *mono_from_utf8_string(const String &p_string) {
- return mono_string_new(mono_domain_get(), p_string.utf8().get_data());
-}
-
-_FORCE_INLINE_ MonoString *mono_from_utf16_string(const String &p_string) {
- return mono_string_from_utf16((mono_unichar2 *)p_string.c_str());
-}
-
_FORCE_INLINE_ MonoString *mono_string_from_godot(const String &p_string) {
- if (sizeof(CharType) == 2)
- return mono_from_utf16_string(p_string);
-
- return mono_from_utf8_string(p_string);
+ return mono_string_from_utf32((mono_unichar4 *)(p_string.get_data()));
}
// Variant
-MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_type);
-MonoObject *variant_to_mono_object(const Variant *p_var);
+size_t variant_get_managed_unboxed_size(const ManagedType &p_type);
+void *variant_to_managed_unboxed(const Variant &p_var, const ManagedType &p_type, void *r_buffer, unsigned int &r_offset);
+MonoObject *variant_to_mono_object(const Variant &p_var, const ManagedType &p_type);
-_FORCE_INLINE_ MonoObject *variant_to_mono_object(const Variant &p_var) {
- return variant_to_mono_object(&p_var);
-}
+MonoObject *variant_to_mono_object(const Variant &p_var);
+MonoArray *variant_to_mono_array(const Variant &p_var, GDMonoClass *p_type_class);
+MonoObject *variant_to_mono_object_of_class(const Variant &p_var, GDMonoClass *p_type_class);
+MonoObject *variant_to_mono_object_of_genericinst(const Variant &p_var, GDMonoClass *p_type_class);
+MonoString *variant_to_mono_string(const Variant &p_var);
+
+// These overloads were added to avoid passing a `const Variant *` to the `const Variant &`
+// parameter. That would result in the `Variant(bool)` copy constructor being called as
+// pointers are implicitly converted to bool. Implicit conversions are f-ing evil.
-_FORCE_INLINE_ MonoObject *variant_to_mono_object(const Variant &p_var, const ManagedType &p_type) {
- return variant_to_mono_object(&p_var, p_type);
+_FORCE_INLINE_ void *variant_to_managed_unboxed(const Variant *p_var, const ManagedType &p_type, void *r_buffer, unsigned int &r_offset) {
+ return variant_to_managed_unboxed(*p_var, p_type, r_buffer, r_offset);
+}
+_FORCE_INLINE_ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_type) {
+ return variant_to_mono_object(*p_var, p_type);
+}
+_FORCE_INLINE_ MonoObject *variant_to_mono_object(const Variant *p_var) {
+ return variant_to_mono_object(*p_var);
+}
+_FORCE_INLINE_ MonoArray *variant_to_mono_array(const Variant *p_var, GDMonoClass *p_type_class) {
+ return variant_to_mono_array(*p_var, p_type_class);
+}
+_FORCE_INLINE_ MonoObject *variant_to_mono_object_of_class(const Variant *p_var, GDMonoClass *p_type_class) {
+ return variant_to_mono_object_of_class(*p_var, p_type_class);
+}
+_FORCE_INLINE_ MonoObject *variant_to_mono_object_of_genericinst(const Variant *p_var, GDMonoClass *p_type_class) {
+ return variant_to_mono_object_of_genericinst(*p_var, p_type_class);
+}
+_FORCE_INLINE_ MonoString *variant_to_mono_string(const Variant *p_var) {
+ return variant_to_mono_string(*p_var);
}
Variant mono_object_to_variant(MonoObject *p_obj);
@@ -122,51 +134,95 @@ Variant mono_object_to_variant_no_err(MonoObject *p_obj, const ManagedType &p_ty
/// If the MonoObject* cannot be converted to Variant, then 'ToString()' is called instead.
String mono_object_to_variant_string(MonoObject *p_obj, MonoException **r_exc);
+// System.Collections.Generic
+
+MonoObject *Dictionary_to_system_generic_dict(const Dictionary &p_dict, GDMonoClass *p_class, MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype);
+Dictionary system_generic_dict_to_Dictionary(MonoObject *p_obj, GDMonoClass *p_class, MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype);
+
+MonoObject *Array_to_system_generic_list(const Array &p_array, GDMonoClass *p_class, MonoReflectionType *p_elem_reftype);
+Variant system_generic_list_to_Array_variant(MonoObject *p_obj, GDMonoClass *p_class, MonoReflectionType *p_elem_reftype);
+
// Array
MonoArray *Array_to_mono_array(const Array &p_array);
+MonoArray *Array_to_mono_array(const Array &p_array, MonoClass *p_array_type_class);
Array mono_array_to_Array(MonoArray *p_array);
-// PoolIntArray
+// PackedInt32Array
+
+MonoArray *PackedInt32Array_to_mono_array(const PackedInt32Array &p_array);
+PackedInt32Array mono_array_to_PackedInt32Array(MonoArray *p_array);
+
+// PackedInt64Array
+
+MonoArray *PackedInt64Array_to_mono_array(const PackedInt64Array &p_array);
+PackedInt64Array mono_array_to_PackedInt64Array(MonoArray *p_array);
+
+// PackedByteArray
-MonoArray *PoolIntArray_to_mono_array(const PoolIntArray &p_array);
-PoolIntArray mono_array_to_PoolIntArray(MonoArray *p_array);
+MonoArray *PackedByteArray_to_mono_array(const PackedByteArray &p_array);
+PackedByteArray mono_array_to_PackedByteArray(MonoArray *p_array);
-// PoolByteArray
+// PackedFloat32Array
-MonoArray *PoolByteArray_to_mono_array(const PoolByteArray &p_array);
-PoolByteArray mono_array_to_PoolByteArray(MonoArray *p_array);
+MonoArray *PackedFloat32Array_to_mono_array(const PackedFloat32Array &p_array);
+PackedFloat32Array mono_array_to_PackedFloat32Array(MonoArray *p_array);
-// PoolRealArray
+// PackedFloat64Array
-MonoArray *PoolRealArray_to_mono_array(const PoolRealArray &p_array);
-PoolRealArray mono_array_to_PoolRealArray(MonoArray *p_array);
+MonoArray *PackedFloat64Array_to_mono_array(const PackedFloat64Array &p_array);
+PackedFloat64Array mono_array_to_PackedFloat64Array(MonoArray *p_array);
-// PoolStringArray
+// PackedStringArray
-MonoArray *PoolStringArray_to_mono_array(const PoolStringArray &p_array);
-PoolStringArray mono_array_to_PoolStringArray(MonoArray *p_array);
+MonoArray *PackedStringArray_to_mono_array(const PackedStringArray &p_array);
+PackedStringArray mono_array_to_PackedStringArray(MonoArray *p_array);
-// PoolColorArray
+// PackedColorArray
+
+MonoArray *PackedColorArray_to_mono_array(const PackedColorArray &p_array);
+PackedColorArray mono_array_to_PackedColorArray(MonoArray *p_array);
+
+// PackedVector2Array
+
+MonoArray *PackedVector2Array_to_mono_array(const PackedVector2Array &p_array);
+PackedVector2Array mono_array_to_PackedVector2Array(MonoArray *p_array);
+
+// PackedVector3Array
+
+MonoArray *PackedVector3Array_to_mono_array(const PackedVector3Array &p_array);
+PackedVector3Array mono_array_to_PackedVector3Array(MonoArray *p_array);
+
+#pragma pack(push, 1)
-MonoArray *PoolColorArray_to_mono_array(const PoolColorArray &p_array);
-PoolColorArray mono_array_to_PoolColorArray(MonoArray *p_array);
+struct M_Callable {
+ MonoObject *target;
+ MonoObject *method_string_name;
+ MonoDelegate *delegate;
+};
-// PoolVector2Array
+struct M_SignalInfo {
+ MonoObject *owner;
+ MonoObject *name_string_name;
+};
-MonoArray *PoolVector2Array_to_mono_array(const PoolVector2Array &p_array);
-PoolVector2Array mono_array_to_PoolVector2Array(MonoArray *p_array);
+#pragma pack(pop)
-// PoolVector3Array
+// Callable
+Callable managed_to_callable(const M_Callable &p_managed_callable);
+M_Callable callable_to_managed(const Callable &p_callable);
-MonoArray *PoolVector3Array_to_mono_array(const PoolVector3Array &p_array);
-PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array);
+// SignalInfo
+Signal managed_to_signal_info(const M_SignalInfo &p_managed_signal);
+M_SignalInfo signal_info_to_managed(const Signal &p_signal);
// Structures
namespace InteropLayout {
enum {
+ MATCHES_int = (sizeof(int32_t) == sizeof(uint32_t)),
+
MATCHES_float = (sizeof(float) == sizeof(uint32_t)),
MATCHES_double = (sizeof(double) == sizeof(uint64_t)),
@@ -181,10 +237,18 @@ enum {
offsetof(Vector2, x) == (sizeof(real_t) * 0) &&
offsetof(Vector2, y) == (sizeof(real_t) * 1)),
+ MATCHES_Vector2i = (MATCHES_int && (sizeof(Vector2i) == (sizeof(int32_t) * 2)) &&
+ offsetof(Vector2i, x) == (sizeof(int32_t) * 0) &&
+ offsetof(Vector2i, y) == (sizeof(int32_t) * 1)),
+
MATCHES_Rect2 = (MATCHES_Vector2 && (sizeof(Rect2) == (sizeof(Vector2) * 2)) &&
offsetof(Rect2, position) == (sizeof(Vector2) * 0) &&
offsetof(Rect2, size) == (sizeof(Vector2) * 1)),
+ MATCHES_Rect2i = (MATCHES_Vector2i && (sizeof(Rect2i) == (sizeof(Vector2i) * 2)) &&
+ offsetof(Rect2i, position) == (sizeof(Vector2i) * 0) &&
+ offsetof(Rect2i, size) == (sizeof(Vector2i) * 1)),
+
MATCHES_Transform2D = (MATCHES_Vector2 && (sizeof(Transform2D) == (sizeof(Vector2) * 3))), // No field offset required, it stores an array
MATCHES_Vector3 = (MATCHES_real_t && (sizeof(Vector3) == (sizeof(real_t) * 3)) &&
@@ -192,17 +256,22 @@ enum {
offsetof(Vector3, y) == (sizeof(real_t) * 1) &&
offsetof(Vector3, z) == (sizeof(real_t) * 2)),
+ MATCHES_Vector3i = (MATCHES_int && (sizeof(Vector3i) == (sizeof(int32_t) * 3)) &&
+ offsetof(Vector3i, x) == (sizeof(int32_t) * 0) &&
+ offsetof(Vector3i, y) == (sizeof(int32_t) * 1) &&
+ offsetof(Vector3i, z) == (sizeof(int32_t) * 2)),
+
MATCHES_Basis = (MATCHES_Vector3 && (sizeof(Basis) == (sizeof(Vector3) * 3))), // No field offset required, it stores an array
- MATCHES_Quat = (MATCHES_real_t && (sizeof(Quat) == (sizeof(real_t) * 4)) &&
- offsetof(Quat, x) == (sizeof(real_t) * 0) &&
- offsetof(Quat, y) == (sizeof(real_t) * 1) &&
- offsetof(Quat, z) == (sizeof(real_t) * 2) &&
- offsetof(Quat, w) == (sizeof(real_t) * 3)),
+ MATCHES_Quaternion = (MATCHES_real_t && (sizeof(Quaternion) == (sizeof(real_t) * 4)) &&
+ offsetof(Quaternion, x) == (sizeof(real_t) * 0) &&
+ offsetof(Quaternion, y) == (sizeof(real_t) * 1) &&
+ offsetof(Quaternion, z) == (sizeof(real_t) * 2) &&
+ offsetof(Quaternion, w) == (sizeof(real_t) * 3)),
- MATCHES_Transform = (MATCHES_Basis && MATCHES_Vector3 && (sizeof(Transform) == (sizeof(Basis) + sizeof(Vector3))) &&
- offsetof(Transform, basis) == 0 &&
- offsetof(Transform, origin) == sizeof(Basis)),
+ MATCHES_Transform3D = (MATCHES_Basis && MATCHES_Vector3 && (sizeof(Transform3D) == (sizeof(Basis) + sizeof(Vector3))) &&
+ offsetof(Transform3D, basis) == 0 &&
+ offsetof(Transform3D, origin) == sizeof(Basis)),
MATCHES_AABB = (MATCHES_Vector3 && (sizeof(AABB) == (sizeof(Vector3) * 2)) &&
offsetof(AABB, position) == (sizeof(Vector3) * 0) &&
@@ -222,11 +291,11 @@ enum {
// In the future we may force this if we want to ref return these structs
#ifdef GD_MONO_FORCE_INTEROP_STRUCT_COPY
/* clang-format off */
-GD_STATIC_ASSERT(MATCHES_Vector2 && MATCHES_Rect2 && MATCHES_Transform2D && MATCHES_Vector3 &&
- MATCHES_Basis && MATCHES_Quat && MATCHES_Transform && MATCHES_AABB && MATCHES_Color &&MATCHES_Plane);
+static_assert(MATCHES_Vector2 && MATCHES_Rect2 && MATCHES_Transform2D && MATCHES_Vector3 &&
+ MATCHES_Basis && MATCHES_Quaternion && MATCHES_Transform3D && MATCHES_AABB && MATCHES_Color &&
+ MATCHES_Plane && MATCHES_Vector2i && MATCHES_Rect2i && MATCHES_Vector3i);
/* clang-format on */
#endif
-
} // namespace InteropLayout
#pragma pack(push, 1)
@@ -244,6 +313,19 @@ struct M_Vector2 {
}
};
+struct M_Vector2i {
+ int32_t x, y;
+
+ static _FORCE_INLINE_ Vector2i convert_to(const M_Vector2i &p_from) {
+ return Vector2i(p_from.x, p_from.y);
+ }
+
+ static _FORCE_INLINE_ M_Vector2i convert_from(const Vector2i &p_from) {
+ M_Vector2i ret = { p_from.x, p_from.y };
+ return ret;
+ }
+};
+
struct M_Rect2 {
M_Vector2 position;
M_Vector2 size;
@@ -259,6 +341,21 @@ struct M_Rect2 {
}
};
+struct M_Rect2i {
+ M_Vector2i position;
+ M_Vector2i size;
+
+ static _FORCE_INLINE_ Rect2i convert_to(const M_Rect2i &p_from) {
+ return Rect2i(M_Vector2i::convert_to(p_from.position),
+ M_Vector2i::convert_to(p_from.size));
+ }
+
+ static _FORCE_INLINE_ M_Rect2i convert_from(const Rect2i &p_from) {
+ M_Rect2i ret = { M_Vector2i::convert_from(p_from.position), M_Vector2i::convert_from(p_from.size) };
+ return ret;
+ }
+};
+
struct M_Transform2D {
M_Vector2 elements[3];
@@ -291,6 +388,19 @@ struct M_Vector3 {
}
};
+struct M_Vector3i {
+ int32_t x, y, z;
+
+ static _FORCE_INLINE_ Vector3i convert_to(const M_Vector3i &p_from) {
+ return Vector3i(p_from.x, p_from.y, p_from.z);
+ }
+
+ static _FORCE_INLINE_ M_Vector3i convert_from(const Vector3i &p_from) {
+ M_Vector3i ret = { p_from.x, p_from.y, p_from.z };
+ return ret;
+ }
+};
+
struct M_Basis {
M_Vector3 elements[3];
@@ -310,29 +420,29 @@ struct M_Basis {
}
};
-struct M_Quat {
+struct M_Quaternion {
real_t x, y, z, w;
- static _FORCE_INLINE_ Quat convert_to(const M_Quat &p_from) {
- return Quat(p_from.x, p_from.y, p_from.z, p_from.w);
+ static _FORCE_INLINE_ Quaternion convert_to(const M_Quaternion &p_from) {
+ return Quaternion(p_from.x, p_from.y, p_from.z, p_from.w);
}
- static _FORCE_INLINE_ M_Quat convert_from(const Quat &p_from) {
- M_Quat ret = { p_from.x, p_from.y, p_from.z, p_from.w };
+ static _FORCE_INLINE_ M_Quaternion convert_from(const Quaternion &p_from) {
+ M_Quaternion ret = { p_from.x, p_from.y, p_from.z, p_from.w };
return ret;
}
};
-struct M_Transform {
+struct M_Transform3D {
M_Basis basis;
M_Vector3 origin;
- static _FORCE_INLINE_ Transform convert_to(const M_Transform &p_from) {
- return Transform(M_Basis::convert_to(p_from.basis), M_Vector3::convert_to(p_from.origin));
+ static _FORCE_INLINE_ Transform3D convert_to(const M_Transform3D &p_from) {
+ return Transform3D(M_Basis::convert_to(p_from.basis), M_Vector3::convert_to(p_from.origin));
}
- static _FORCE_INLINE_ M_Transform convert_from(const Transform &p_from) {
- M_Transform ret = { M_Basis::convert_from(p_from.basis), M_Vector3::convert_from(p_from.origin) };
+ static _FORCE_INLINE_ M_Transform3D convert_from(const Transform3D &p_from) {
+ M_Transform3D ret = { M_Basis::convert_from(p_from.basis), M_Vector3::convert_from(p_from.origin) };
return ret;
}
};
@@ -416,19 +526,21 @@ struct M_Plane {
}
DECL_TYPE_MARSHAL_TEMPLATES(Vector2)
+DECL_TYPE_MARSHAL_TEMPLATES(Vector2i)
DECL_TYPE_MARSHAL_TEMPLATES(Rect2)
+DECL_TYPE_MARSHAL_TEMPLATES(Rect2i)
DECL_TYPE_MARSHAL_TEMPLATES(Transform2D)
DECL_TYPE_MARSHAL_TEMPLATES(Vector3)
+DECL_TYPE_MARSHAL_TEMPLATES(Vector3i)
DECL_TYPE_MARSHAL_TEMPLATES(Basis)
-DECL_TYPE_MARSHAL_TEMPLATES(Quat)
-DECL_TYPE_MARSHAL_TEMPLATES(Transform)
+DECL_TYPE_MARSHAL_TEMPLATES(Quaternion)
+DECL_TYPE_MARSHAL_TEMPLATES(Transform3D)
DECL_TYPE_MARSHAL_TEMPLATES(AABB)
DECL_TYPE_MARSHAL_TEMPLATES(Color)
DECL_TYPE_MARSHAL_TEMPLATES(Plane)
#define MARSHALLED_IN(m_type, m_from_ptr) (GDMonoMarshal::marshalled_in_##m_type(m_from_ptr))
#define MARSHALLED_OUT(m_type, m_from) (GDMonoMarshal::marshalled_out_##m_type(m_from))
-
} // namespace GDMonoMarshal
#endif // GDMONOMARSHAL_H
diff --git a/modules/mono/mono_gd/gd_mono_method.cpp b/modules/mono/mono_gd/gd_mono_method.cpp
index 971c5ac737..67aabcde10 100644
--- a/modules/mono/mono_gd/gd_mono_method.cpp
+++ b/modules/mono/mono_gd/gd_mono_method.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,13 +30,14 @@
#include "gd_mono_method.h"
+#include <mono/metadata/attrdefs.h>
+#include <mono/metadata/debug-helpers.h>
+
#include "gd_mono_cache.h"
#include "gd_mono_class.h"
#include "gd_mono_marshal.h"
#include "gd_mono_utils.h"
-#include <mono/metadata/attrdefs.h>
-
void GDMonoMethod::_update_signature() {
// Apparently MonoMethodSignature needs not to be freed.
// mono_method_signature caches the result, we don't need to cache it ourselves.
@@ -58,9 +59,9 @@ void GDMonoMethod::_update_signature(MonoMethodSignature *p_method_sig) {
}
}
- void *iter = NULL;
+ void *iter = nullptr;
MonoType *param_raw_type;
- while ((param_raw_type = mono_signature_get_params(p_method_sig, &iter)) != NULL) {
+ while ((param_raw_type = mono_signature_get_params(p_method_sig, &iter)) != nullptr) {
ManagedType param_type;
param_type.type_encoding = mono_type_get_type(param_raw_type);
@@ -74,6 +75,10 @@ void GDMonoMethod::_update_signature(MonoMethodSignature *p_method_sig) {
// clear the cache
method_info_fetched = false;
method_info = MethodInfo();
+
+ for (int i = 0; i < params_count; i++) {
+ params_buffer_size += GDMonoMarshal::variant_get_managed_unboxed_size(param_types[i]);
+ }
}
GDMonoClass *GDMonoMethod::get_enclosing_class() const {
@@ -81,11 +86,11 @@ GDMonoClass *GDMonoMethod::get_enclosing_class() const {
}
bool GDMonoMethod::is_static() {
- return mono_method_get_flags(mono_method, NULL) & MONO_METHOD_ATTR_STATIC;
+ return mono_method_get_flags(mono_method, nullptr) & MONO_METHOD_ATTR_STATIC;
}
IMonoClassMember::Visibility GDMonoMethod::get_visibility() {
- switch (mono_method_get_flags(mono_method, NULL) & MONO_METHOD_ATTR_ACCESS_MASK) {
+ switch (mono_method_get_flags(mono_method, nullptr) & MONO_METHOD_ATTR_ACCESS_MASK) {
case MONO_METHOD_ATTR_PRIVATE:
return IMonoClassMember::PRIVATE;
case MONO_METHOD_ATTR_FAM_AND_ASSEM:
@@ -101,55 +106,47 @@ IMonoClassMember::Visibility GDMonoMethod::get_visibility() {
}
}
-MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc) {
- if (get_return_type().type_encoding != MONO_TYPE_VOID || get_parameters_count() > 0) {
- MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), get_parameters_count());
+MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc) const {
+ MonoException *exc = nullptr;
+ MonoObject *ret;
- for (int i = 0; i < params_count; i++) {
- MonoObject *boxed_param = GDMonoMarshal::variant_to_mono_object(p_params[i], param_types[i]);
- mono_array_setref(params, i, boxed_param);
- }
-
- MonoException *exc = NULL;
- MonoObject *ret = GDMonoUtils::runtime_invoke_array(mono_method, p_object, params, &exc);
+ if (params_count > 0) {
+ void **params = (void **)alloca(params_count * sizeof(void *));
+ uint8_t *buffer = (uint8_t *)alloca(params_buffer_size);
+ unsigned int offset = 0;
- if (exc) {
- ret = NULL;
- if (r_exc) {
- *r_exc = exc;
- } else {
- GDMonoUtils::set_pending_exception(exc);
- }
+ for (int i = 0; i < params_count; i++) {
+ params[i] = GDMonoMarshal::variant_to_managed_unboxed(p_params[i], param_types[i], buffer + offset, offset);
}
- return ret;
+ ret = GDMonoUtils::runtime_invoke(mono_method, p_object, params, &exc);
} else {
- MonoException *exc = NULL;
- GDMonoUtils::runtime_invoke(mono_method, p_object, NULL, &exc);
-
- if (exc) {
- if (r_exc) {
- *r_exc = exc;
- } else {
- GDMonoUtils::set_pending_exception(exc);
- }
- }
+ ret = GDMonoUtils::runtime_invoke(mono_method, p_object, nullptr, &exc);
+ }
- return NULL;
+ if (exc) {
+ ret = nullptr;
+ if (r_exc) {
+ *r_exc = exc;
+ } else {
+ GDMonoUtils::set_pending_exception(exc);
+ }
}
+
+ return ret;
}
-MonoObject *GDMonoMethod::invoke(MonoObject *p_object, MonoException **r_exc) {
- ERR_FAIL_COND_V(get_parameters_count() > 0, NULL);
- return invoke_raw(p_object, NULL, r_exc);
+MonoObject *GDMonoMethod::invoke(MonoObject *p_object, MonoException **r_exc) const {
+ ERR_FAIL_COND_V(get_parameters_count() > 0, nullptr);
+ return invoke_raw(p_object, nullptr, r_exc);
}
-MonoObject *GDMonoMethod::invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc) {
- MonoException *exc = NULL;
+MonoObject *GDMonoMethod::invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc) const {
+ MonoException *exc = nullptr;
MonoObject *ret = GDMonoUtils::runtime_invoke(mono_method, p_object, p_params, &exc);
if (exc) {
- ret = NULL;
+ ret = nullptr;
if (r_exc) {
*r_exc = exc;
} else {
@@ -163,29 +160,33 @@ MonoObject *GDMonoMethod::invoke_raw(MonoObject *p_object, void **p_params, Mono
bool GDMonoMethod::has_attribute(GDMonoClass *p_attr_class) {
ERR_FAIL_NULL_V(p_attr_class, false);
- if (!attrs_fetched)
+ if (!attrs_fetched) {
fetch_attributes();
+ }
- if (!attributes)
+ if (!attributes) {
return false;
+ }
return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr());
}
MonoObject *GDMonoMethod::get_attribute(GDMonoClass *p_attr_class) {
- ERR_FAIL_NULL_V(p_attr_class, NULL);
+ ERR_FAIL_NULL_V(p_attr_class, nullptr);
- if (!attrs_fetched)
+ if (!attrs_fetched) {
fetch_attributes();
+ }
- if (!attributes)
- return NULL;
+ if (!attributes) {
+ return nullptr;
+ }
return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr());
}
void GDMonoMethod::fetch_attributes() {
- ERR_FAIL_COND(attributes != NULL);
+ ERR_FAIL_COND(attributes != nullptr);
attributes = mono_custom_attrs_from_method(mono_method);
attrs_fetched = true;
}
@@ -247,22 +248,32 @@ void GDMonoMethod::get_parameter_names(Vector<StringName> &names) const {
}
void GDMonoMethod::get_parameter_types(Vector<ManagedType> &types) const {
- for (int i = 0; i < param_types.size(); ++i) {
+ for (int i = 0; i < params_count; ++i) {
types.push_back(param_types[i]);
}
}
const MethodInfo &GDMonoMethod::get_method_info() {
-
if (!method_info_fetched) {
method_info.name = name;
- method_info.return_val = PropertyInfo(GDMonoMarshal::managed_to_variant_type(return_type), "");
+
+ bool nil_is_variant = false;
+ method_info.return_val = PropertyInfo(GDMonoMarshal::managed_to_variant_type(return_type, &nil_is_variant), "");
+ if (method_info.return_val.type == Variant::NIL && nil_is_variant) {
+ method_info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
Vector<StringName> names;
get_parameter_names(names);
for (int i = 0; i < params_count; ++i) {
- method_info.arguments.push_back(PropertyInfo(GDMonoMarshal::managed_to_variant_type(param_types[i]), names[i]));
+ nil_is_variant = false;
+ PropertyInfo arg_info = PropertyInfo(GDMonoMarshal::managed_to_variant_type(param_types[i], &nil_is_variant), names[i]);
+ if (arg_info.type == Variant::NIL && nil_is_variant) {
+ arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
+ }
+
+ method_info.arguments.push_back(arg_info);
}
// TODO: default arguments
@@ -273,16 +284,8 @@ const MethodInfo &GDMonoMethod::get_method_info() {
return method_info;
}
-GDMonoMethod::GDMonoMethod(StringName p_name, MonoMethod *p_method) {
- name = p_name;
-
- mono_method = p_method;
-
- method_info_fetched = false;
-
- attrs_fetched = false;
- attributes = NULL;
-
+GDMonoMethod::GDMonoMethod(StringName p_name, MonoMethod *p_method) :
+ name(p_name), mono_method(p_method) {
_update_signature();
}
diff --git a/modules/mono/mono_gd/gd_mono_method.h b/modules/mono/mono_gd/gd_mono_method.h
index b47e42dec2..c08ffe904b 100644
--- a/modules/mono/mono_gd/gd_mono_method.h
+++ b/modules/mono/mono_gd/gd_mono_method.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -36,18 +36,18 @@
#include "i_mono_class_member.h"
class GDMonoMethod : public IMonoClassMember {
-
StringName name;
- int params_count;
+ uint16_t params_count;
+ unsigned int params_buffer_size = 0;
ManagedType return_type;
Vector<ManagedType> param_types;
- bool method_info_fetched;
+ bool method_info_fetched = false;
MethodInfo method_info;
- bool attrs_fetched;
- MonoCustomAttrInfo *attributes;
+ bool attrs_fetched = false;
+ MonoCustomAttrInfo *attributes = nullptr;
void _update_signature();
void _update_signature(MonoMethodSignature *p_method_sig);
@@ -57,28 +57,28 @@ class GDMonoMethod : public IMonoClassMember {
MonoMethod *mono_method;
public:
- virtual GDMonoClass *get_enclosing_class() const GD_FINAL;
+ virtual GDMonoClass *get_enclosing_class() const final;
- virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_METHOD; }
+ virtual MemberType get_member_type() const final { return MEMBER_TYPE_METHOD; }
- virtual StringName get_name() const GD_FINAL { return name; }
+ virtual StringName get_name() const final { return name; }
- virtual bool is_static() GD_FINAL;
+ virtual bool is_static() final;
- virtual Visibility get_visibility() GD_FINAL;
+ virtual Visibility get_visibility() final;
- virtual bool has_attribute(GDMonoClass *p_attr_class) GD_FINAL;
- virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) GD_FINAL;
+ virtual bool has_attribute(GDMonoClass *p_attr_class) final;
+ virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) final;
void fetch_attributes();
- _FORCE_INLINE_ MonoMethod *get_mono_ptr() { return mono_method; }
+ _FORCE_INLINE_ MonoMethod *get_mono_ptr() const { return mono_method; }
- _FORCE_INLINE_ int get_parameters_count() { return params_count; }
- _FORCE_INLINE_ ManagedType get_return_type() { return return_type; }
+ _FORCE_INLINE_ uint16_t get_parameters_count() const { return params_count; }
+ _FORCE_INLINE_ ManagedType get_return_type() const { return return_type; }
- MonoObject *invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc = NULL);
- MonoObject *invoke(MonoObject *p_object, MonoException **r_exc = NULL);
- MonoObject *invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc = NULL);
+ MonoObject *invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc = nullptr) const;
+ MonoObject *invoke(MonoObject *p_object, MonoException **r_exc = nullptr) const;
+ MonoObject *invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc = nullptr) const;
String get_full_name(bool p_signature = false) const;
String get_full_name_no_class() const;
diff --git a/modules/mono/mono_gd/gd_mono_method_thunk.h b/modules/mono/mono_gd/gd_mono_method_thunk.h
index d8c9a5eb02..091d26df1d 100644
--- a/modules/mono/mono_gd/gd_mono_method_thunk.h
+++ b/modules/mono/mono_gd/gd_mono_method_thunk.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -39,7 +39,7 @@
#include "gd_mono_method.h"
#include "gd_mono_utils.h"
-#if !defined(JAVASCRIPT_ENABLED)
+#if !defined(JAVASCRIPT_ENABLED) && !defined(IPHONE_ENABLED)
#define HAVE_METHOD_THUNKS
#endif
@@ -47,10 +47,9 @@
template <class... ParamTypes>
struct GDMonoMethodThunk {
-
typedef void(GD_MONO_STDCALL *M)(ParamTypes... p_args, MonoException **);
- M mono_method_thunk;
+ M mono_method_thunk = nullptr;
public:
_FORCE_INLINE_ void invoke(ParamTypes... p_args, MonoException **r_exc) {
@@ -60,16 +59,16 @@ public:
}
_FORCE_INLINE_ bool is_null() {
- return mono_method_thunk == NULL;
+ return mono_method_thunk == nullptr;
}
_FORCE_INLINE_ void nullify() {
- mono_method_thunk = NULL;
+ mono_method_thunk = nullptr;
}
_FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) {
#ifdef DEBUG_ENABLED
- CRASH_COND(p_mono_method == NULL);
+ CRASH_COND(p_mono_method == nullptr);
CRASH_COND(p_mono_method->get_return_type().type_encoding != MONO_TYPE_VOID);
if (p_mono_method->is_static()) {
@@ -81,9 +80,7 @@ public:
mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr());
}
- GDMonoMethodThunk() :
- mono_method_thunk(NULL) {
- }
+ GDMonoMethodThunk() {}
explicit GDMonoMethodThunk(GDMonoMethod *p_mono_method) {
set_from_method(p_mono_method);
@@ -92,10 +89,9 @@ public:
template <class R, class... ParamTypes>
struct GDMonoMethodThunkR {
-
typedef R(GD_MONO_STDCALL *M)(ParamTypes... p_args, MonoException **);
- M mono_method_thunk;
+ M mono_method_thunk = nullptr;
public:
_FORCE_INLINE_ R invoke(ParamTypes... p_args, MonoException **r_exc) {
@@ -106,16 +102,16 @@ public:
}
_FORCE_INLINE_ bool is_null() {
- return mono_method_thunk == NULL;
+ return mono_method_thunk == nullptr;
}
_FORCE_INLINE_ void nullify() {
- mono_method_thunk = NULL;
+ mono_method_thunk = nullptr;
}
_FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) {
#ifdef DEBUG_ENABLED
- CRASH_COND(p_mono_method == NULL);
+ CRASH_COND(p_mono_method == nullptr);
CRASH_COND(p_mono_method->get_return_type().type_encoding == MONO_TYPE_VOID);
if (p_mono_method->is_static()) {
@@ -127,13 +123,11 @@ public:
mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr());
}
- GDMonoMethodThunkR() :
- mono_method_thunk(NULL) {
- }
+ GDMonoMethodThunkR() {}
explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) {
#ifdef DEBUG_ENABLED
- CRASH_COND(p_mono_method == NULL);
+ CRASH_COND(p_mono_method == nullptr);
#endif
mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr());
}
@@ -146,7 +140,7 @@ struct VariadicInvokeMonoMethodImpl {
static void invoke(GDMonoMethod *p_mono_method, P1 p_arg1, ParamTypes... p_args, MonoException **r_exc) {
if (p_mono_method->is_static()) {
void *args[ThunkParamCount] = { p_arg1, p_args... };
- p_mono_method->invoke_raw(NULL, args, r_exc);
+ p_mono_method->invoke_raw(nullptr, args, r_exc);
} else {
void *args[ThunkParamCount] = { p_args... };
p_mono_method->invoke_raw((MonoObject *)p_arg1, args, r_exc);
@@ -167,7 +161,7 @@ struct VariadicInvokeMonoMethod<0> {
#ifdef DEBUG_ENABLED
CRASH_COND(!p_mono_method->is_static());
#endif
- p_mono_method->invoke_raw(NULL, NULL, r_exc);
+ p_mono_method->invoke_raw(nullptr, nullptr, r_exc);
}
};
@@ -176,9 +170,9 @@ struct VariadicInvokeMonoMethod<1, P1> {
static void invoke(GDMonoMethod *p_mono_method, P1 p_arg1, MonoException **r_exc) {
if (p_mono_method->is_static()) {
void *args[1] = { p_arg1 };
- p_mono_method->invoke_raw(NULL, args, r_exc);
+ p_mono_method->invoke_raw(nullptr, args, r_exc);
} else {
- p_mono_method->invoke_raw((MonoObject *)p_arg1, NULL, r_exc);
+ p_mono_method->invoke_raw((MonoObject *)p_arg1, nullptr, r_exc);
}
}
};
@@ -203,7 +197,7 @@ struct VariadicInvokeMonoMethodRImpl {
static R invoke(GDMonoMethod *p_mono_method, P1 p_arg1, ParamTypes... p_args, MonoException **r_exc) {
if (p_mono_method->is_static()) {
void *args[ThunkParamCount] = { p_arg1, p_args... };
- MonoObject *r = p_mono_method->invoke_raw(NULL, args, r_exc);
+ MonoObject *r = p_mono_method->invoke_raw(nullptr, args, r_exc);
return unbox_if_needed<R>(r, p_mono_method->get_return_type());
} else {
void *args[ThunkParamCount] = { p_args... };
@@ -226,7 +220,7 @@ struct VariadicInvokeMonoMethodR<0, R> {
#ifdef DEBUG_ENABLED
CRASH_COND(!p_mono_method->is_static());
#endif
- MonoObject *r = p_mono_method->invoke_raw(NULL, NULL, r_exc);
+ MonoObject *r = p_mono_method->invoke_raw(nullptr, nullptr, r_exc);
return unbox_if_needed<R>(r, p_mono_method->get_return_type());
}
};
@@ -236,10 +230,10 @@ struct VariadicInvokeMonoMethodR<1, R, P1> {
static R invoke(GDMonoMethod *p_mono_method, P1 p_arg1, MonoException **r_exc) {
if (p_mono_method->is_static()) {
void *args[1] = { p_arg1 };
- MonoObject *r = p_mono_method->invoke_raw(NULL, args, r_exc);
+ MonoObject *r = p_mono_method->invoke_raw(nullptr, args, r_exc);
return unbox_if_needed<R>(r, p_mono_method->get_return_type());
} else {
- MonoObject *r = p_mono_method->invoke_raw((MonoObject *)p_arg1, NULL, r_exc);
+ MonoObject *r = p_mono_method->invoke_raw((MonoObject *)p_arg1, nullptr, r_exc);
return unbox_if_needed<R>(r, p_mono_method->get_return_type());
}
}
@@ -247,8 +241,7 @@ struct VariadicInvokeMonoMethodR<1, R, P1> {
template <class... ParamTypes>
struct GDMonoMethodThunk {
-
- GDMonoMethod *mono_method;
+ GDMonoMethod *mono_method = nullptr;
public:
_FORCE_INLINE_ void invoke(ParamTypes... p_args, MonoException **r_exc) {
@@ -256,16 +249,16 @@ public:
}
_FORCE_INLINE_ bool is_null() {
- return mono_method == NULL;
+ return mono_method == nullptr;
}
_FORCE_INLINE_ void nullify() {
- mono_method = NULL;
+ mono_method = nullptr;
}
_FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) {
#ifdef DEBUG_ENABLED
- CRASH_COND(p_mono_method == NULL);
+ CRASH_COND(p_mono_method == nullptr);
CRASH_COND(p_mono_method->get_return_type().type_encoding != MONO_TYPE_VOID);
if (p_mono_method->is_static()) {
@@ -277,9 +270,7 @@ public:
mono_method = p_mono_method;
}
- GDMonoMethodThunk() :
- mono_method(NULL) {
- }
+ GDMonoMethodThunk() {}
explicit GDMonoMethodThunk(GDMonoMethod *p_mono_method) {
set_from_method(p_mono_method);
@@ -288,8 +279,7 @@ public:
template <class R, class... ParamTypes>
struct GDMonoMethodThunkR {
-
- GDMonoMethod *mono_method;
+ GDMonoMethod *mono_method = nullptr;
public:
_FORCE_INLINE_ R invoke(ParamTypes... p_args, MonoException **r_exc) {
@@ -297,16 +287,16 @@ public:
}
_FORCE_INLINE_ bool is_null() {
- return mono_method == NULL;
+ return mono_method == nullptr;
}
_FORCE_INLINE_ void nullify() {
- mono_method = NULL;
+ mono_method = nullptr;
}
_FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) {
#ifdef DEBUG_ENABLED
- CRASH_COND(p_mono_method == NULL);
+ CRASH_COND(p_mono_method == nullptr);
CRASH_COND(p_mono_method->get_return_type().type_encoding == MONO_TYPE_VOID);
if (p_mono_method->is_static()) {
@@ -318,9 +308,7 @@ public:
mono_method = p_mono_method;
}
- GDMonoMethodThunkR() :
- mono_method(NULL) {
- }
+ GDMonoMethodThunkR() {}
explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) {
set_from_method(p_mono_method);
diff --git a/modules/mono/mono_gd/gd_mono_property.cpp b/modules/mono/mono_gd/gd_mono_property.cpp
index 3b5ce58d80..5391b7775e 100644
--- a/modules/mono/mono_gd/gd_mono_property.cpp
+++ b/modules/mono/mono_gd/gd_mono_property.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -40,7 +40,7 @@
GDMonoProperty::GDMonoProperty(MonoProperty *p_mono_property, GDMonoClass *p_owner) {
owner = p_owner;
mono_property = p_mono_property;
- name = mono_property_get_name(mono_property);
+ name = String::utf8(mono_property_get_name(mono_property));
MonoMethod *prop_method = mono_property_get_get_method(mono_property);
@@ -57,7 +57,7 @@ GDMonoProperty::GDMonoProperty(MonoProperty *p_mono_property, GDMonoClass *p_own
MonoMethodSignature *setter_sig = mono_method_signature(prop_method);
- void *iter = NULL;
+ void *iter = nullptr;
MonoType *param_raw_type = mono_signature_get_params(setter_sig, &iter);
type.type_encoding = mono_type_get_type(param_raw_type);
@@ -66,7 +66,7 @@ GDMonoProperty::GDMonoProperty(MonoProperty *p_mono_property, GDMonoClass *p_own
}
attrs_fetched = false;
- attributes = NULL;
+ attributes = nullptr;
}
GDMonoProperty::~GDMonoProperty() {
@@ -77,17 +77,19 @@ GDMonoProperty::~GDMonoProperty() {
bool GDMonoProperty::is_static() {
MonoMethod *prop_method = mono_property_get_get_method(mono_property);
- if (prop_method == NULL)
+ if (prop_method == nullptr) {
prop_method = mono_property_get_set_method(mono_property);
- return mono_method_get_flags(prop_method, NULL) & MONO_METHOD_ATTR_STATIC;
+ }
+ return mono_method_get_flags(prop_method, nullptr) & MONO_METHOD_ATTR_STATIC;
}
IMonoClassMember::Visibility GDMonoProperty::get_visibility() {
MonoMethod *prop_method = mono_property_get_get_method(mono_property);
- if (prop_method == NULL)
+ if (prop_method == nullptr) {
prop_method = mono_property_get_set_method(mono_property);
+ }
- switch (mono_method_get_flags(prop_method, NULL) & MONO_METHOD_ATTR_ACCESS_MASK) {
+ switch (mono_method_get_flags(prop_method, nullptr) & MONO_METHOD_ATTR_ACCESS_MASK) {
case MONO_METHOD_ATTR_PRIVATE:
return IMonoClassMember::PRIVATE;
case MONO_METHOD_ATTR_FAM_AND_ASSEM:
@@ -106,47 +108,50 @@ IMonoClassMember::Visibility GDMonoProperty::get_visibility() {
bool GDMonoProperty::has_attribute(GDMonoClass *p_attr_class) {
ERR_FAIL_NULL_V(p_attr_class, false);
- if (!attrs_fetched)
+ if (!attrs_fetched) {
fetch_attributes();
+ }
- if (!attributes)
+ if (!attributes) {
return false;
+ }
return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr());
}
MonoObject *GDMonoProperty::get_attribute(GDMonoClass *p_attr_class) {
- ERR_FAIL_NULL_V(p_attr_class, NULL);
+ ERR_FAIL_NULL_V(p_attr_class, nullptr);
- if (!attrs_fetched)
+ if (!attrs_fetched) {
fetch_attributes();
+ }
- if (!attributes)
- return NULL;
+ if (!attributes) {
+ return nullptr;
+ }
return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr());
}
void GDMonoProperty::fetch_attributes() {
- ERR_FAIL_COND(attributes != NULL);
+ ERR_FAIL_COND(attributes != nullptr);
attributes = mono_custom_attrs_from_property(owner->get_mono_ptr(), mono_property);
attrs_fetched = true;
}
bool GDMonoProperty::has_getter() {
- return mono_property_get_get_method(mono_property) != NULL;
+ return mono_property_get_get_method(mono_property) != nullptr;
}
bool GDMonoProperty::has_setter() {
- return mono_property_get_set_method(mono_property) != NULL;
+ return mono_property_get_set_method(mono_property) != nullptr;
}
void GDMonoProperty::set_value(MonoObject *p_object, MonoObject *p_value, MonoException **r_exc) {
MonoMethod *prop_method = mono_property_get_set_method(mono_property);
- MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), 1);
- mono_array_setref(params, 0, p_value);
- MonoException *exc = NULL;
- GDMonoUtils::runtime_invoke_array(prop_method, p_object, params, &exc);
+ void *params[1] = { p_value };
+ MonoException *exc = nullptr;
+ GDMonoUtils::runtime_invoke(prop_method, p_object, params, &exc);
if (exc) {
if (r_exc) {
*r_exc = exc;
@@ -157,7 +162,7 @@ void GDMonoProperty::set_value(MonoObject *p_object, MonoObject *p_value, MonoEx
}
void GDMonoProperty::set_value(MonoObject *p_object, void **p_params, MonoException **r_exc) {
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
GDMonoUtils::property_set_value(mono_property, p_object, p_params, &exc);
if (exc) {
@@ -170,11 +175,11 @@ void GDMonoProperty::set_value(MonoObject *p_object, void **p_params, MonoExcept
}
MonoObject *GDMonoProperty::get_value(MonoObject *p_object, MonoException **r_exc) {
- MonoException *exc = NULL;
- MonoObject *ret = GDMonoUtils::property_get_value(mono_property, p_object, NULL, &exc);
+ MonoException *exc = nullptr;
+ MonoObject *ret = GDMonoUtils::property_get_value(mono_property, p_object, nullptr, &exc);
if (exc) {
- ret = NULL;
+ ret = nullptr;
if (r_exc) {
*r_exc = exc;
} else {
diff --git a/modules/mono/mono_gd/gd_mono_property.h b/modules/mono/mono_gd/gd_mono_property.h
index 692037f76a..af7a2c02e5 100644
--- a/modules/mono/mono_gd/gd_mono_property.h
+++ b/modules/mono/mono_gd/gd_mono_property.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -36,7 +36,6 @@
#include "i_mono_class_member.h"
class GDMonoProperty : public IMonoClassMember {
-
GDMonoClass *owner;
MonoProperty *mono_property;
@@ -47,17 +46,17 @@ class GDMonoProperty : public IMonoClassMember {
MonoCustomAttrInfo *attributes;
public:
- virtual GDMonoClass *get_enclosing_class() const GD_FINAL { return owner; }
+ virtual GDMonoClass *get_enclosing_class() const final { return owner; }
- virtual MemberType get_member_type() const GD_FINAL { return MEMBER_TYPE_PROPERTY; }
+ virtual MemberType get_member_type() const final { return MEMBER_TYPE_PROPERTY; }
- virtual StringName get_name() const GD_FINAL { return name; }
+ virtual StringName get_name() const final { return name; }
- virtual bool is_static() GD_FINAL;
- virtual Visibility get_visibility() GD_FINAL;
+ virtual bool is_static() final;
+ virtual Visibility get_visibility() final;
- virtual bool has_attribute(GDMonoClass *p_attr_class) GD_FINAL;
- virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) GD_FINAL;
+ virtual bool has_attribute(GDMonoClass *p_attr_class) final;
+ virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) final;
void fetch_attributes();
bool has_getter();
@@ -65,9 +64,9 @@ public:
_FORCE_INLINE_ ManagedType get_type() const { return type; }
- void set_value(MonoObject *p_object, MonoObject *p_value, MonoException **r_exc = NULL);
- void set_value(MonoObject *p_object, void **p_params, MonoException **r_exc = NULL);
- MonoObject *get_value(MonoObject *p_object, MonoException **r_exc = NULL);
+ void set_value(MonoObject *p_object, MonoObject *p_value, MonoException **r_exc = nullptr);
+ void set_value(MonoObject *p_object, void **p_params, MonoException **r_exc = nullptr);
+ MonoObject *get_value(MonoObject *p_object, MonoException **r_exc = nullptr);
bool get_bool_value(MonoObject *p_object);
int get_int_value(MonoObject *p_object);
diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp
index 4e7f590a69..0b9a577e01 100644
--- a/modules/mono/mono_gd/gd_mono_utils.cpp
+++ b/modules/mono/mono_gd/gd_mono_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,32 +30,33 @@
#include "gd_mono_utils.h"
+#include <mono/metadata/debug-helpers.h>
#include <mono/metadata/exception.h>
-#include "core/os/dir_access.h"
+#include "core/debugger/engine_debugger.h"
+#include "core/debugger/script_debugger.h"
+#include "core/io/dir_access.h"
+#include "core/object/ref_counted.h"
+#include "core/os/mutex.h"
#include "core/os/os.h"
-#include "core/project_settings.h"
-#include "core/reference.h"
#ifdef TOOLS_ENABLED
-#include "editor/script_editor_debugger.h"
+#include "editor/debugger/editor_debugger_node.h"
#endif
#include "../csharp_script.h"
#include "../utils/macros.h"
-#include "../utils/mutex_utils.h"
#include "gd_mono.h"
#include "gd_mono_cache.h"
#include "gd_mono_class.h"
#include "gd_mono_marshal.h"
-#include "gd_mono_method_thunk.h"
namespace GDMonoUtils {
MonoObject *unmanaged_get_managed(Object *unmanaged) {
-
- if (!unmanaged)
- return NULL;
+ if (!unmanaged) {
+ return nullptr;
+ }
if (unmanaged->get_script_instance()) {
CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(unmanaged->get_script_instance());
@@ -69,28 +70,28 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) {
void *data = unmanaged->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index());
- ERR_FAIL_NULL_V(data, NULL);
+ ERR_FAIL_NULL_V(data, nullptr);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->value();
if (!script_binding.inited) {
- SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->get_language_bind_mutex());
+ MutexLock lock(CSharpLanguage::get_singleton()->get_language_bind_mutex());
if (!script_binding.inited) { // Other thread may have set it up
// Already had a binding that needs to be setup
CSharpLanguage::get_singleton()->setup_csharp_script_binding(script_binding, unmanaged);
- ERR_FAIL_COND_V(!script_binding.inited, NULL);
+ ERR_FAIL_COND_V(!script_binding.inited, nullptr);
}
}
- Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
- ERR_FAIL_COND_V(gchandle.is_null(), NULL);
+ MonoGCHandleData &gchandle = script_binding.gchandle;
- MonoObject *target = gchandle->get_target();
+ MonoObject *target = gchandle.get_target();
- if (target)
+ if (target) {
return target;
+ }
CSharpLanguage::get_singleton()->release_script_gchandle(gchandle);
@@ -98,24 +99,24 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) {
#ifdef DEBUG_ENABLED
CRASH_COND(script_binding.type_name == StringName());
- CRASH_COND(script_binding.wrapper_class == NULL);
+ CRASH_COND(script_binding.wrapper_class == nullptr);
#endif
MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(script_binding.wrapper_class, script_binding.type_name, unmanaged);
- ERR_FAIL_NULL_V(mono_object, NULL);
+ ERR_FAIL_NULL_V(mono_object, nullptr);
- gchandle->set_handle(MonoGCHandle::new_strong_handle(mono_object), MonoGCHandle::STRONG_HANDLE);
+ gchandle = MonoGCHandleData::new_strong_handle(mono_object);
// Tie managed to unmanaged
- Reference *ref = Object::cast_to<Reference>(unmanaged);
+ RefCounted *rc = Object::cast_to<RefCounted>(unmanaged);
- if (ref) {
+ if (rc) {
// Unsafe refcount increment. The managed instance also counts as a reference.
// This way if the unmanaged world has no references to our owner
// but the managed instance is alive, the refcount will be 1 instead of 0.
- // See: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)
- ref->reference();
- CSharpLanguage::get_singleton()->post_unsafe_reference(ref);
+ // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr)
+ rc->reference();
+ CSharpLanguage::get_singleton()->post_unsafe_reference(rc);
}
return mono_object;
@@ -126,10 +127,15 @@ void set_main_thread(MonoThread *p_thread) {
}
MonoThread *attach_current_thread() {
- ERR_FAIL_COND_V(!GDMono::get_singleton()->is_runtime_initialized(), NULL);
+ ERR_FAIL_COND_V(!GDMono::get_singleton()->is_runtime_initialized(), nullptr);
MonoDomain *scripts_domain = GDMono::get_singleton()->get_scripts_domain();
+#ifndef GD_MONO_SINGLE_APPDOMAIN
MonoThread *mono_thread = mono_thread_attach(scripts_domain ? scripts_domain : mono_get_root_domain());
- ERR_FAIL_NULL_V(mono_thread, NULL);
+#else
+ // The scripts domain is the root domain
+ MonoThread *mono_thread = mono_thread_attach(scripts_domain);
+#endif
+ ERR_FAIL_NULL_V(mono_thread, nullptr);
return mono_thread;
}
@@ -151,13 +157,36 @@ MonoThread *get_current_thread() {
}
bool is_thread_attached() {
- return mono_domain_get() != NULL;
+ return mono_domain_get() != nullptr;
+}
+
+uint32_t new_strong_gchandle(MonoObject *p_object) {
+ return mono_gchandle_new(p_object, /* pinned: */ false);
+}
+
+uint32_t new_strong_gchandle_pinned(MonoObject *p_object) {
+ return mono_gchandle_new(p_object, /* pinned: */ true);
+}
+
+uint32_t new_weak_gchandle(MonoObject *p_object) {
+ return mono_gchandle_new_weakref(p_object, /* track_resurrection: */ false);
+}
+
+void free_gchandle(uint32_t p_gchandle) {
+ mono_gchandle_free(p_gchandle);
}
void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc) {
GDMonoMethod *ctor = p_class->get_method(".ctor", 0);
ERR_FAIL_NULL(ctor);
- ctor->invoke_raw(p_this_obj, NULL, r_exc);
+ ctor->invoke_raw(p_this_obj, nullptr, r_exc);
+}
+
+bool mono_delegate_equal(MonoDelegate *p_a, MonoDelegate *p_b) {
+ MonoException *exc = nullptr;
+ MonoBoolean res = CACHED_METHOD_THUNK(Delegate, Equals).invoke((MonoObject *)p_a, (MonoObject *)p_b, &exc);
+ UNHANDLED_EXCEPTION(exc);
+ return (bool)res;
}
GDMonoClass *get_object_class(MonoObject *p_object) {
@@ -167,8 +196,9 @@ GDMonoClass *get_object_class(MonoObject *p_object) {
GDMonoClass *type_get_proxy_class(const StringName &p_type) {
String class_name = p_type;
- if (class_name[0] == '_')
+ if (class_name[0] == '_') {
class_name = class_name.substr(1, class_name.length());
+ }
GDMonoClass *klass = GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name);
@@ -191,24 +221,27 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class) {
do {
const GDMonoAssembly *assembly = klass->get_assembly();
- if (assembly == GDMono::get_singleton()->get_core_api_assembly())
+
+ if (assembly == GDMono::get_singleton()->get_core_api_assembly()) {
return klass;
+ }
#ifdef TOOLS_ENABLED
- if (assembly == GDMono::get_singleton()->get_editor_api_assembly())
+ if (assembly == GDMono::get_singleton()->get_editor_api_assembly()) {
return klass;
+ }
#endif
- } while ((klass = klass->get_parent_class()) != NULL);
+ } while ((klass = klass->get_parent_class()) != nullptr);
- return NULL;
+ return nullptr;
}
MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object) {
bool parent_is_object_class = ClassDB::is_parent_class(p_object->get_class_name(), p_native);
- ERR_FAIL_COND_V_MSG(!parent_is_object_class, NULL,
- "Type inherits from native type '" + p_native + "', so it can't be instanced in object of type: '" + p_object->get_class() + "'.");
+ ERR_FAIL_COND_V_MSG(!parent_is_object_class, nullptr,
+ "Type inherits from native type '" + p_native + "', so it can't be instantiated in object of type: '" + p_object->get_class() + "'.");
MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
- ERR_FAIL_NULL_V(mono_object, NULL);
+ ERR_FAIL_NULL_V(mono_object, nullptr);
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object);
@@ -218,9 +251,21 @@ MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringNa
return mono_object;
}
+MonoObject *create_managed_from(const StringName &p_from) {
+ MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(StringName));
+ ERR_FAIL_NULL_V(mono_object, nullptr);
+
+ // Construct
+ GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(StringName));
+
+ CACHED_FIELD(StringName, ptr)->set_value_raw(mono_object, memnew(StringName(p_from)));
+
+ return mono_object;
+}
+
MonoObject *create_managed_from(const NodePath &p_from) {
MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(NodePath));
- ERR_FAIL_NULL_V(mono_object, NULL);
+ ERR_FAIL_NULL_V(mono_object, nullptr);
// Construct
GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(NodePath));
@@ -232,7 +277,7 @@ MonoObject *create_managed_from(const NodePath &p_from) {
MonoObject *create_managed_from(const RID &p_from) {
MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(RID));
- ERR_FAIL_NULL_V(mono_object, NULL);
+ ERR_FAIL_NULL_V(mono_object, nullptr);
// Construct
GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(RID));
@@ -244,15 +289,15 @@ MonoObject *create_managed_from(const RID &p_from) {
MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) {
MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
- ERR_FAIL_NULL_V(mono_object, NULL);
+ ERR_FAIL_NULL_V(mono_object, nullptr);
// Search constructor that takes a pointer as parameter
MonoMethod *m;
- void *iter = NULL;
+ void *iter = nullptr;
while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) {
if (strcmp(mono_method_get_name(m), ".ctor") == 0) {
MonoMethodSignature *sig = mono_method_signature(m);
- void *front = NULL;
+ void *front = nullptr;
if (mono_signature_get_param_count(sig) == 1 &&
mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) {
break;
@@ -260,12 +305,12 @@ MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) {
}
}
- CRASH_COND(m == NULL);
+ CRASH_COND(m == nullptr);
Array *new_array = memnew(Array(p_from));
void *args[1] = { &new_array };
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
GDMonoUtils::runtime_invoke(m, mono_object, args, &exc);
UNHANDLED_EXCEPTION(exc);
@@ -274,15 +319,15 @@ MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) {
MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) {
MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
- ERR_FAIL_NULL_V(mono_object, NULL);
+ ERR_FAIL_NULL_V(mono_object, nullptr);
// Search constructor that takes a pointer as parameter
MonoMethod *m;
- void *iter = NULL;
+ void *iter = nullptr;
while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) {
if (strcmp(mono_method_get_name(m), ".ctor") == 0) {
MonoMethodSignature *sig = mono_method_signature(m);
- void *front = NULL;
+ void *front = nullptr;
if (mono_signature_get_param_count(sig) == 1 &&
mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) {
break;
@@ -290,12 +335,12 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class)
}
}
- CRASH_COND(m == NULL);
+ CRASH_COND(m == nullptr);
Dictionary *new_dict = memnew(Dictionary(p_from));
void *args[1] = { &new_dict };
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
GDMonoUtils::runtime_invoke(m, mono_object, args, &exc);
UNHANDLED_EXCEPTION(exc);
@@ -305,7 +350,7 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class)
MonoDomain *create_domain(const String &p_friendly_name) {
print_verbose("Mono: Creating domain '" + p_friendly_name + "'...");
- MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), NULL);
+ MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), nullptr);
if (domain) {
// Workaround to avoid this exception:
@@ -317,6 +362,14 @@ MonoDomain *create_domain(const String &p_friendly_name) {
return domain;
}
+String get_type_desc(MonoType *p_type) {
+ return mono_type_full_name(p_type);
+}
+
+String get_type_desc(MonoReflectionType *p_reftype) {
+ return get_type_desc(mono_reflection_type_get_type(p_reftype));
+}
+
String get_exception_name_and_message(MonoException *p_exc) {
String res;
@@ -330,20 +383,12 @@ String get_exception_name_and_message(MonoException *p_exc) {
res += ": ";
MonoProperty *prop = mono_class_get_property_from_name(klass, "Message");
- MonoString *msg = (MonoString *)property_get_value(prop, (MonoObject *)p_exc, NULL, NULL);
+ MonoString *msg = (MonoString *)property_get_value(prop, (MonoObject *)p_exc, nullptr, nullptr);
res += GDMonoMarshal::mono_string_to_godot(msg);
return res;
}
-void set_exception_message(MonoException *p_exc, String message) {
- MonoClass *klass = mono_object_get_class((MonoObject *)p_exc);
- MonoProperty *prop = mono_class_get_property_from_name(klass, "Message");
- MonoString *msg = GDMonoMarshal::mono_string_from_godot(message);
- void *params[1] = { msg };
- property_set_value(prop, (MonoObject *)p_exc, params, NULL);
-}
-
void debug_print_unhandled_exception(MonoException *p_exc) {
print_unhandled_exception(p_exc);
debug_send_unhandled_exception_error(p_exc);
@@ -351,16 +396,21 @@ void debug_print_unhandled_exception(MonoException *p_exc) {
void debug_send_unhandled_exception_error(MonoException *p_exc) {
#ifdef DEBUG_ENABLED
- if (!ScriptDebugger::get_singleton()) {
+ if (!EngineDebugger::is_active()) {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
- ERR_PRINTS(GDMonoUtils::get_exception_name_and_message(p_exc));
+ ERR_PRINT(GDMonoUtils::get_exception_name_and_message(p_exc));
}
#endif
return;
}
- _TLS_RECURSION_GUARD_;
+ static thread_local bool _recursion_flag_ = false;
+ if (_recursion_flag_) {
+ return;
+ }
+ _recursion_flag_ = true;
+ SCOPE_EXIT { _recursion_flag_ = false; };
ScriptLanguage::StackInfo separator;
separator.file = String();
@@ -370,14 +420,14 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) {
Vector<ScriptLanguage::StackInfo> si;
String exc_msg;
- while (p_exc != NULL) {
+ while (p_exc != nullptr) {
GDMonoClass *st_klass = CACHED_CLASS(System_Diagnostics_StackTrace);
MonoObject *stack_trace = mono_object_new(mono_domain_get(), st_klass->get_mono_ptr());
MonoBoolean need_file_info = true;
void *ctor_args[2] = { p_exc, &need_file_info };
- MonoException *unexpected_exc = NULL;
+ MonoException *unexpected_exc = nullptr;
CACHED_METHOD(System_Diagnostics_StackTrace, ctor_Exception_bool)->invoke_raw(stack_trace, ctor_args, &unexpected_exc);
if (unexpected_exc) {
@@ -386,21 +436,23 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) {
}
Vector<ScriptLanguage::StackInfo> _si;
- if (stack_trace != NULL) {
+ if (stack_trace != nullptr) {
_si = CSharpLanguage::get_singleton()->stack_trace_get_info(stack_trace);
- for (int i = _si.size() - 1; i >= 0; i--)
+ for (int i = _si.size() - 1; i >= 0; i--) {
si.insert(0, _si[i]);
+ }
}
exc_msg += (exc_msg.length() > 0 ? " ---> " : "") + GDMonoUtils::get_exception_name_and_message(p_exc);
GDMonoClass *exc_class = GDMono::get_singleton()->get_class(mono_get_exception_class());
GDMonoProperty *inner_exc_prop = exc_class->get_property("InnerException");
- CRASH_COND(inner_exc_prop == NULL);
+ CRASH_COND(inner_exc_prop == nullptr);
MonoObject *inner_exc = inner_exc_prop->get_value((MonoObject *)p_exc);
- if (inner_exc != NULL)
+ if (inner_exc != nullptr) {
si.insert(0, separator);
+ }
p_exc = (MonoException *)inner_exc;
}
@@ -410,7 +462,7 @@ void debug_send_unhandled_exception_error(MonoException *p_exc) {
int line = si.size() ? si[0].line : __LINE__;
String error_msg = "Unhandled exception";
- ScriptDebugger::get_singleton()->send_error(func, file, line, error_msg, exc_msg, ERR_HANDLER_ERROR, si);
+ EngineDebugger::get_script_debugger()->send_error(func, file, line, error_msg, exc_msg, ERR_HANDLER_ERROR, si);
#endif
}
@@ -428,17 +480,17 @@ void set_pending_exception(MonoException *p_exc) {
#else
if (get_runtime_invoke_count() == 0) {
debug_unhandled_exception(p_exc);
+ return;
}
if (!mono_runtime_set_pending_exception(p_exc, false)) {
- ERR_PRINTS("Exception thrown from managed code, but it could not be set as pending:");
+ ERR_PRINT("Exception thrown from managed code, but it could not be set as pending:");
GDMonoUtils::debug_print_unhandled_exception(p_exc);
}
#endif
}
-_THREAD_LOCAL_(int)
-current_invoke_count = 0;
+thread_local int current_invoke_count = 0;
MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **r_exc) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
@@ -447,13 +499,6 @@ MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, M
return ret;
}
-MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **r_exc) {
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- MonoObject *ret = mono_runtime_invoke_array(p_method, p_obj, p_params, (MonoObject **)r_exc);
- GD_MONO_END_RUNTIME_INVOKE;
- return ret;
-}
-
MonoString *object_to_string(MonoObject *p_obj, MonoException **r_exc) {
GD_MONO_BEGIN_RUNTIME_INVOKE;
MonoString *ret = mono_object_to_string(p_obj, (MonoObject **)r_exc);
@@ -511,9 +556,10 @@ namespace Marshal {
#ifdef MONO_GLUE_ENABLED
#ifdef TOOLS_ENABLED
-#define NO_GLUE_RET(m_ret) \
- { \
- if (!GDMonoCache::cached_data.godot_api_cache_updated) return m_ret; \
+#define NO_GLUE_RET(m_ret) \
+ { \
+ if (!GDMonoCache::cached_data.godot_api_cache_updated) \
+ return m_ret; \
}
#else
#define NO_GLUE_RET(m_ret) \
@@ -526,7 +572,7 @@ namespace Marshal {
bool type_is_generic_array(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray).invoke(p_reftype, &exc);
UNHANDLED_EXCEPTION(exc);
return (bool)res;
@@ -534,103 +580,82 @@ bool type_is_generic_array(MonoReflectionType *p_reftype) {
bool type_is_generic_dictionary(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
- MonoException *exc = NULL;
+ MonoException *exc = nullptr;
MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary).invoke(p_reftype, &exc);
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
-void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype) {
- MonoException *exc = NULL;
- CACHED_METHOD_THUNK(MarshalUtils, ArrayGetElementType).invoke(p_array_reftype, r_elem_reftype, &exc);
- UNHANDLED_EXCEPTION(exc);
-}
-
-void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) {
- MonoException *exc = NULL;
- CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes).invoke(p_dict_reftype, r_key_reftype, r_value_reftype, &exc);
- UNHANDLED_EXCEPTION(exc);
-}
-
-bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype) {
+bool type_is_system_generic_list(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
- MonoException *exc = NULL;
- MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType).invoke(p_reftype, &exc);
+ MonoException *exc = nullptr;
+ MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsSystemGenericList).invoke(p_reftype, &exc);
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
-bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype) {
+bool type_is_system_generic_dictionary(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
- MonoException *exc = NULL;
- MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType).invoke(p_reftype, &exc);
+ MonoException *exc = nullptr;
+ MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsSystemGenericDictionary).invoke(p_reftype, &exc);
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
-bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype) {
+bool type_is_generic_ienumerable(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
- MonoException *exc = NULL;
- MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info).invoke(p_reftype, r_elem_reftype, &exc);
+ MonoException *exc = nullptr;
+ MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericIEnumerable).invoke(p_reftype, &exc);
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
-bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) {
+bool type_is_generic_icollection(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
- MonoException *exc = NULL;
- MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info).invoke(p_reftype, r_key_reftype, r_value_reftype, &exc);
+ MonoException *exc = nullptr;
+ MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericICollection).invoke(p_reftype, &exc);
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
-Array enumerable_to_array(MonoObject *p_enumerable) {
- NO_GLUE_RET(Array());
- Array result;
- MonoException *exc = NULL;
- CACHED_METHOD_THUNK(MarshalUtils, EnumerableToArray).invoke(p_enumerable, &result, &exc);
+bool type_is_generic_idictionary(MonoReflectionType *p_reftype) {
+ NO_GLUE_RET(false);
+ MonoException *exc = nullptr;
+ MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericIDictionary).invoke(p_reftype, &exc);
UNHANDLED_EXCEPTION(exc);
- return result;
+ return (bool)res;
}
-Dictionary idictionary_to_dictionary(MonoObject *p_idictionary) {
- NO_GLUE_RET(Dictionary());
- Dictionary result;
- MonoException *exc = NULL;
- CACHED_METHOD_THUNK(MarshalUtils, IDictionaryToDictionary).invoke(p_idictionary, &result, &exc);
+void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype) {
+ MonoException *exc = nullptr;
+ CACHED_METHOD_THUNK(MarshalUtils, ArrayGetElementType).invoke(p_array_reftype, r_elem_reftype, &exc);
UNHANDLED_EXCEPTION(exc);
- return result;
}
-Dictionary generic_idictionary_to_dictionary(MonoObject *p_generic_idictionary) {
- NO_GLUE_RET(Dictionary());
- Dictionary result;
- MonoException *exc = NULL;
- CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryToDictionary).invoke(p_generic_idictionary, &result, &exc);
+void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) {
+ MonoException *exc = nullptr;
+ CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes).invoke(p_dict_reftype, r_key_reftype, r_value_reftype, &exc);
UNHANDLED_EXCEPTION(exc);
- return result;
}
GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype) {
- NO_GLUE_RET(NULL);
- MonoException *exc = NULL;
+ NO_GLUE_RET(nullptr);
+ MonoException *exc = nullptr;
MonoReflectionType *reftype = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericArrayType).invoke(p_elem_reftype, &exc);
UNHANDLED_EXCEPTION(exc);
return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype)));
}
GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) {
- NO_GLUE_RET(NULL);
- MonoException *exc = NULL;
+ NO_GLUE_RET(nullptr);
+ MonoException *exc = nullptr;
MonoReflectionType *reftype = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericDictionaryType).invoke(p_key_reftype, p_value_reftype, &exc);
UNHANDLED_EXCEPTION(exc);
return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype)));
}
-
} // namespace Marshal
-ScopeThreadAttach::ScopeThreadAttach() :
- mono_thread(NULL) {
+ScopeThreadAttach::ScopeThreadAttach() {
if (likely(GDMono::get_singleton()->is_runtime_initialized()) && unlikely(!mono_domain_get())) {
mono_thread = GDMonoUtils::attach_current_thread();
}
@@ -642,6 +667,9 @@ ScopeThreadAttach::~ScopeThreadAttach() {
}
}
-// namespace Marshal
-
+StringName get_native_godot_class_name(GDMonoClass *p_class) {
+ MonoObject *native_name_obj = p_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(nullptr);
+ StringName *ptr = GDMonoMarshal::unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(native_name_obj));
+ return ptr ? *ptr : StringName();
+}
} // namespace GDMonoUtils
diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h
index db9f99bfdc..773501e93d 100644
--- a/modules/mono/mono_gd/gd_mono_utils.h
+++ b/modules/mono/mono_gd/gd_mono_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -35,17 +35,20 @@
#include "../mono_gc_handle.h"
#include "../utils/macros.h"
-#include "../utils/thread_local.h"
#include "gd_mono_header.h"
+#ifdef JAVASCRIPT_ENABLED
+#include "gd_mono_wasm_m2n.h"
+#endif
-#include "core/object.h"
-#include "core/reference.h"
+#include "core/object/class_db.h"
+#include "core/object/ref_counted.h"
#define UNHANDLED_EXCEPTION(m_exc) \
- if (unlikely(m_exc != NULL)) { \
+ if (unlikely(m_exc != nullptr)) { \
GDMonoUtils::debug_unhandled_exception(m_exc); \
GD_UNREACHABLE(); \
- }
+ } else \
+ ((void)0)
namespace GDMonoUtils {
@@ -53,22 +56,17 @@ namespace Marshal {
bool type_is_generic_array(MonoReflectionType *p_reftype);
bool type_is_generic_dictionary(MonoReflectionType *p_reftype);
+bool type_is_system_generic_list(MonoReflectionType *p_reftype);
+bool type_is_system_generic_dictionary(MonoReflectionType *p_reftype);
+bool type_is_generic_ienumerable(MonoReflectionType *p_reftype);
+bool type_is_generic_icollection(MonoReflectionType *p_reftype);
+bool type_is_generic_idictionary(MonoReflectionType *p_reftype);
void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype);
void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype);
-bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype);
-bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype);
-bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype);
-bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype);
-
GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype);
GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype);
-
-Array enumerable_to_array(MonoObject *p_enumerable);
-Dictionary idictionary_to_dictionary(MonoObject *p_idictionary);
-Dictionary generic_idictionary_to_dictionary(MonoObject *p_generic_idictionary);
-
} // namespace Marshal
_FORCE_INLINE_ void hash_combine(uint32_t &p_hash, const uint32_t &p_with_hash) {
@@ -78,7 +76,7 @@ _FORCE_INLINE_ void hash_combine(uint32_t &p_hash, const uint32_t &p_with_hash)
/**
* If the object has a csharp script, returns the target of the gchandle stored in the script instance
* Otherwise returns a newly constructed MonoObject* which is attached to the object
- * Returns NULL on error
+ * Returns nullptr on error
*/
MonoObject *unmanaged_get_managed(Object *unmanaged);
@@ -89,11 +87,14 @@ void detach_current_thread(MonoThread *p_mono_thread);
MonoThread *get_current_thread();
bool is_thread_attached();
-_FORCE_INLINE_ bool is_main_thread() {
- return mono_domain_get() != NULL && mono_thread_get_main() == mono_thread_current();
-}
+uint32_t new_strong_gchandle(MonoObject *p_object);
+uint32_t new_strong_gchandle_pinned(MonoObject *p_object);
+uint32_t new_weak_gchandle(MonoObject *p_object);
+void free_gchandle(uint32_t p_gchandle);
+
+void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc = nullptr);
-void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc = NULL);
+bool mono_delegate_equal(MonoDelegate *p_a, MonoDelegate *p_b);
GDMonoClass *get_object_class(MonoObject *p_object);
GDMonoClass *type_get_proxy_class(const StringName &p_type);
@@ -101,6 +102,7 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class);
MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object);
+MonoObject *create_managed_from(const StringName &p_from);
MonoObject *create_managed_from(const NodePath &p_from);
MonoObject *create_managed_from(const RID &p_from);
MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class);
@@ -108,8 +110,10 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class);
MonoDomain *create_domain(const String &p_friendly_name);
+String get_type_desc(MonoType *p_type);
+String get_type_desc(MonoReflectionType *p_reftype);
+
String get_exception_name_and_message(MonoException *p_exc);
-void set_exception_message(MonoException *p_exc, String message);
void debug_print_unhandled_exception(MonoException *p_exc);
void debug_send_unhandled_exception_error(MonoException *p_exc);
@@ -123,17 +127,17 @@ void print_unhandled_exception(MonoException *p_exc);
*/
void set_pending_exception(MonoException *p_exc);
-extern _THREAD_LOCAL_(int) current_invoke_count;
+extern thread_local int current_invoke_count;
_FORCE_INLINE_ int get_runtime_invoke_count() {
return current_invoke_count;
}
+
_FORCE_INLINE_ int &get_runtime_invoke_count_ref() {
return current_invoke_count;
}
MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **r_exc);
-MonoObject *runtime_invoke_array(MonoMethod *p_method, void *p_obj, MonoArray *p_params, MonoException **r_exc);
MonoString *object_to_string(MonoObject *p_obj, MonoException **r_exc);
@@ -149,29 +153,50 @@ struct ScopeThreadAttach {
~ScopeThreadAttach();
private:
- MonoThread *mono_thread;
+ MonoThread *mono_thread = nullptr;
};
+StringName get_native_godot_class_name(GDMonoClass *p_class);
+
+template <typename... P>
+void add_internal_call(const char *p_name, void (*p_func)(P...)) {
+#ifdef JAVASCRIPT_ENABLED
+ GDMonoWasmM2n::ICallTrampolines<P...>::add();
+#endif
+ mono_add_internal_call(p_name, (void *)p_func);
+}
+
+template <typename R, typename... P>
+void add_internal_call(const char *p_name, R (*p_func)(P...)) {
+#ifdef JAVASCRIPT_ENABLED
+ GDMonoWasmM2n::ICallTrampolinesR<R, P...>::add();
+#endif
+ mono_add_internal_call(p_name, (void *)p_func);
+}
} // namespace GDMonoUtils
-#define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoMarshal::mono_string_to_godot((MonoString *)m_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(NULL)))
+#define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoUtils::get_native_godot_class_name(m_class))
#define GD_MONO_BEGIN_RUNTIME_INVOKE \
int &_runtime_invoke_count_ref = GDMonoUtils::get_runtime_invoke_count_ref(); \
- _runtime_invoke_count_ref += 1;
+ _runtime_invoke_count_ref += 1; \
+ ((void)0)
-#define GD_MONO_END_RUNTIME_INVOKE \
- _runtime_invoke_count_ref -= 1;
+#define GD_MONO_END_RUNTIME_INVOKE \
+ _runtime_invoke_count_ref -= 1; \
+ ((void)0)
#define GD_MONO_SCOPE_THREAD_ATTACH \
GDMonoUtils::ScopeThreadAttach __gdmono__scope__thread__attach__; \
- (void)__gdmono__scope__thread__attach__;
+ (void)__gdmono__scope__thread__attach__; \
+ ((void)0)
#ifdef DEBUG_ENABLED
-#define GD_MONO_ASSERT_THREAD_ATTACHED \
- { CRASH_COND(!GDMonoUtils::is_thread_attached()); }
+#define GD_MONO_ASSERT_THREAD_ATTACHED \
+ CRASH_COND(!GDMonoUtils::is_thread_attached()); \
+ ((void)0)
#else
-#define GD_MONO_ASSERT_THREAD_ATTACHED
+#define GD_MONO_ASSERT_THREAD_ATTACHED ((void)0)
#endif
#endif // GD_MONOUTILS_H
diff --git a/modules/mono/utils/thread_local.cpp b/modules/mono/mono_gd/gd_mono_wasm_m2n.cpp
index 4f10e3fb85..a477c55456 100644
--- a/modules/mono/utils/thread_local.cpp
+++ b/modules/mono/mono_gd/gd_mono_wasm_m2n.cpp
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* thread_local.cpp */
+/* gd_mono_wasm_m2n.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,80 +28,52 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "thread_local.h"
+#include "gd_mono_wasm_m2n.h"
-#ifdef WINDOWS_ENABLED
-#include <windows.h>
-#else
-#include <pthread.h>
-#endif
+#ifdef JAVASCRIPT_ENABLED
-#include "core/os/memory.h"
-#include "core/print_string.h"
+#include "core/templates/oa_hash_map.h"
-struct ThreadLocalStorage::Impl {
+typedef mono_bool (*GodotMonoM2nIcallTrampolineDispatch)(const char *cookie, void *target_func, Mono_InterpMethodArguments *margs);
-#ifdef WINDOWS_ENABLED
- DWORD dwFlsIndex;
-#else
- pthread_key_t key;
-#endif
+// This extern function is implemented in our patched version of Mono
+MONO_API void godot_mono_register_m2n_icall_trampoline_dispatch_hook(GodotMonoM2nIcallTrampolineDispatch hook);
- void *get_value() const {
-#ifdef WINDOWS_ENABLED
- return FlsGetValue(dwFlsIndex);
-#else
- return pthread_getspecific(key);
-#endif
- }
+namespace GDMonoWasmM2n {
- void set_value(void *p_value) const {
-#ifdef WINDOWS_ENABLED
- FlsSetValue(dwFlsIndex, p_value);
-#else
- pthread_setspecific(key, p_value);
-#endif
+struct HashMapCookieComparator {
+ static bool compare(const char *p_lhs, const char *p_rhs) {
+ return strcmp(p_lhs, p_rhs) == 0;
}
+};
-#ifdef WINDOWS_ENABLED
-#define _CALLBACK_FUNC_ __stdcall
-#else
-#define _CALLBACK_FUNC_
-#endif
+// The default hasher supports 'const char *' C Strings, but we need a custom comparator
+OAHashMap<const char *, TrampolineFunc, HashMapHasherDefault, HashMapCookieComparator> trampolines;
- Impl(void(_CALLBACK_FUNC_ *p_destr_callback_func)(void *)) {
-#ifdef WINDOWS_ENABLED
- dwFlsIndex = FlsAlloc(p_destr_callback_func);
- ERR_FAIL_COND(dwFlsIndex == FLS_OUT_OF_INDEXES);
-#else
- pthread_key_create(&key, p_destr_callback_func);
-#endif
- }
+void set_trampoline(const char *cookies, GDMonoWasmM2n::TrampolineFunc trampoline_func) {
+ trampolines.set(cookies, trampoline_func);
+}
- ~Impl() {
-#ifdef WINDOWS_ENABLED
- FlsFree(dwFlsIndex);
-#else
- pthread_key_delete(key);
-#endif
+mono_bool trampoline_dispatch_hook(const char *cookie, void *target_func, Mono_InterpMethodArguments *margs) {
+ TrampolineFunc *trampoline_func = trampolines.lookup_ptr(cookie);
+
+ if (!trampoline_func) {
+ return false;
}
-};
-void *ThreadLocalStorage::get_value() const {
- return pimpl->get_value();
+ (*trampoline_func)(target_func, margs);
+ return true;
}
-void ThreadLocalStorage::set_value(void *p_value) const {
- pimpl->set_value(p_value);
-}
+bool initialized = false;
-void ThreadLocalStorage::alloc(void(_CALLBACK_FUNC_ *p_destr_callback)(void *)) {
- pimpl = memnew(ThreadLocalStorage::Impl(p_destr_callback));
+void lazy_initialize() {
+ // Doesn't need to be thread safe
+ if (!initialized) {
+ initialized = true;
+ godot_mono_register_m2n_icall_trampoline_dispatch_hook(&trampoline_dispatch_hook);
+ }
}
+} // namespace GDMonoWasmM2n
-#undef _CALLBACK_FUNC_
-
-void ThreadLocalStorage::free() {
- memdelete(pimpl);
- pimpl = NULL;
-}
+#endif
diff --git a/modules/mono/mono_gd/gd_mono_wasm_m2n.h b/modules/mono/mono_gd/gd_mono_wasm_m2n.h
new file mode 100644
index 0000000000..366662ff81
--- /dev/null
+++ b/modules/mono/mono_gd/gd_mono_wasm_m2n.h
@@ -0,0 +1,263 @@
+/*************************************************************************/
+/* gd_mono_wasm_m2n.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef GD_MONO_WASM_M2N_H
+#define GD_MONO_WASM_M2N_H
+
+#ifdef JAVASCRIPT_ENABLED
+
+#include "core/string/ustring.h"
+#include "core/typedefs.h"
+
+#include <mono/metadata/loader.h>
+#include <mono/utils/mono-publib.h>
+#include <stdexcept>
+#include <type_traits>
+
+extern "C" {
+
+struct Mono_InterpMethodArguments {
+ size_t ilen;
+ void **iargs;
+ size_t flen;
+ double *fargs;
+ void **retval;
+ size_t is_float_ret;
+ //#ifdef TARGET_WASM
+ void *sig;
+ //#endif
+};
+} // extern "C"
+
+namespace GDMonoWasmM2n {
+
+template <typename T, size_t Size>
+struct array {
+ T elems[Size];
+};
+
+template <typename T>
+constexpr char get_m2n_cookie_impl() {
+#define M2N_REG_COOKIE(m_type, m_cookie) \
+ if constexpr (std::is_same_v<m_type, T>) { \
+ return m_cookie; \
+ }
+
+ M2N_REG_COOKIE(MonoBoolean, 'I');
+ M2N_REG_COOKIE(int8_t, 'I');
+ M2N_REG_COOKIE(uint8_t, 'I');
+ M2N_REG_COOKIE(int16_t, 'I');
+ M2N_REG_COOKIE(uint16_t, 'I');
+ M2N_REG_COOKIE(int32_t, 'I');
+ M2N_REG_COOKIE(uint32_t, 'I');
+ M2N_REG_COOKIE(int64_t, 'L');
+ M2N_REG_COOKIE(uint64_t, 'L');
+ M2N_REG_COOKIE(float, 'F');
+ M2N_REG_COOKIE(double, 'D');
+
+ if constexpr (std::is_pointer_v<T>) {
+ if constexpr (sizeof(void *) == 4) {
+ return 'I';
+ } else {
+ return 'L';
+ }
+ }
+
+ if constexpr (std::is_void_v<T>) {
+ return 'V';
+ }
+
+ return 'X';
+
+#undef M2N_REG_COOKIE
+}
+
+template <typename T>
+constexpr char get_m2n_cookie() {
+ constexpr char cookie = get_m2n_cookie_impl<T>();
+ static_assert(cookie != 'X', "Type not supported in internal call signature.");
+ return cookie;
+}
+
+template <typename... T>
+constexpr array<const char, sizeof...(T) + 2> get_m2n_cookies() {
+ return array<const char, sizeof...(T) + 2>{ 'V', get_m2n_cookie<T>()..., '\0' };
+}
+
+template <typename R, typename... T>
+constexpr array<const char, sizeof...(T) + 2> get_m2n_cookies_r() {
+ return array<const char, sizeof...(T) + 2>{ get_m2n_cookie<R>(), get_m2n_cookie<T>()..., '\0' };
+}
+
+template <typename T>
+constexpr size_t calc_m2n_index(size_t &r_int_idx, size_t &r_float_idx) {
+ constexpr char cookie = get_m2n_cookie<T>();
+
+ static_assert(cookie == 'I' || cookie == 'L' || cookie == 'F' || cookie == 'D');
+
+ if constexpr (cookie == 'I' || cookie == 'L') {
+ size_t ret = r_int_idx;
+ r_int_idx += cookie == 'I' ? 1 : 2;
+ return ret;
+ } else {
+ size_t ret = r_float_idx;
+ r_float_idx += cookie == 'F' ? 1 : 2;
+ return ret;
+ }
+}
+
+template <typename... P>
+constexpr array<size_t, sizeof...(P)> get_indices_for_type() {
+ size_t int_idx = 0;
+ size_t float_idx = 0;
+ return array<size_t, sizeof...(P)>{ calc_m2n_index<P>(int_idx, float_idx)... };
+}
+
+constexpr size_t fidx(size_t p_x) {
+ if constexpr (sizeof(void *) == 4) {
+ return p_x * 2;
+ } else {
+ return p_x;
+ }
+}
+
+template <typename T>
+T m2n_arg_cast(Mono_InterpMethodArguments *p_margs, size_t p_idx) {
+ constexpr char cookie = get_m2n_cookie<T>();
+
+ static_assert(cookie == 'I' || cookie == 'L' || cookie == 'F' || cookie == 'D');
+
+ if constexpr (cookie == 'I') {
+ return (T)(size_t)p_margs->iargs[p_idx];
+ } else if constexpr (cookie == 'L') {
+ static_assert(std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t> ||
+ (sizeof(void *) == 8 && std::is_pointer_v<T>),
+ "Invalid type for cookie 'L'.");
+
+ union {
+ T l;
+ struct {
+ int32_t lo;
+ int32_t hi;
+ } pair;
+ } p;
+
+ p.pair.lo = (int32_t)(size_t)p_margs->iargs[p_idx];
+ p.pair.hi = (int32_t)(size_t)p_margs->iargs[p_idx + 1];
+
+ return p.l;
+ } else if constexpr (cookie == 'F') {
+ return *reinterpret_cast<float *>(&p_margs->fargs[fidx(p_idx)]);
+ } else if constexpr (cookie == 'D') {
+ return (T)p_margs->fargs[p_idx];
+ }
+}
+
+template <typename... P, size_t... Is>
+void m2n_trampoline_with_idx_seq(void *p_target_func, Mono_InterpMethodArguments *p_margs, IndexSequence<Is...>) {
+ constexpr array<size_t, sizeof...(P)> indices = get_indices_for_type<P...>();
+ typedef void (*Func)(P...);
+ Func func = (Func)p_target_func;
+ func(m2n_arg_cast<P>(p_margs, indices.elems[Is])...);
+}
+
+template <typename R, typename... P, size_t... Is>
+void m2n_trampoline_with_idx_seq_r(void *p_target_func, Mono_InterpMethodArguments *p_margs, IndexSequence<Is...>) {
+ constexpr array<size_t, sizeof...(P)> indices = get_indices_for_type<P...>();
+ typedef R (*Func)(P...);
+ Func func = (Func)p_target_func;
+ R res = func(m2n_arg_cast<P>(p_margs, indices.elems[Is])...);
+ *reinterpret_cast<R *>(p_margs->retval) = res;
+}
+
+inline void m2n_trampoline_with_idx_seq_0(void *p_target_func, Mono_InterpMethodArguments *p_margs) {
+ typedef void (*Func)();
+ Func func = (Func)p_target_func;
+ func();
+}
+
+template <typename R>
+void m2n_trampoline_with_idx_seq_r0(void *p_target_func, Mono_InterpMethodArguments *p_margs) {
+ typedef R (*Func)();
+ Func func = (Func)p_target_func;
+ R res = func();
+ *reinterpret_cast<R *>(p_margs->retval) = res;
+}
+
+template <typename... P>
+void m2n_trampoline(void *p_target_func, Mono_InterpMethodArguments *p_margs) {
+ if constexpr (sizeof...(P) == 0) {
+ m2n_trampoline_with_idx_seq_0(p_target_func, p_margs);
+ } else {
+ m2n_trampoline_with_idx_seq<P...>(p_target_func, p_margs, BuildIndexSequence<sizeof...(P)>{});
+ }
+}
+
+template <typename R, typename... P>
+void m2n_trampoline_r(void *p_target_func, Mono_InterpMethodArguments *p_margs) {
+ if constexpr (sizeof...(P) == 0) {
+ m2n_trampoline_with_idx_seq_r0<R>(p_target_func, p_margs);
+ } else {
+ m2n_trampoline_with_idx_seq_r<R, P...>(p_target_func, p_margs, BuildIndexSequence<sizeof...(P)>{});
+ }
+}
+
+typedef void (*TrampolineFunc)(void *p_target_func, Mono_InterpMethodArguments *p_margs);
+
+void set_trampoline(const char *cookies, TrampolineFunc trampoline_func);
+
+void lazy_initialize();
+
+template <typename... P>
+struct ICallTrampolines {
+ static constexpr auto cookies = get_m2n_cookies<P...>();
+
+ static void add() {
+ lazy_initialize();
+ set_trampoline(cookies.elems, &m2n_trampoline<P...>);
+ }
+};
+
+template <typename R, typename... P>
+struct ICallTrampolinesR {
+ static constexpr auto cookies = get_m2n_cookies_r<R, P...>();
+
+ static void add() {
+ lazy_initialize();
+ set_trampoline(cookies.elems, &m2n_trampoline_r<R, P...>);
+ }
+};
+
+void initialize();
+} // namespace GDMonoWasmM2n
+
+#endif
+
+#endif // GD_MONO_WASM_M2N_H
diff --git a/modules/mono/mono_gd/i_mono_class_member.h b/modules/mono/mono_gd/i_mono_class_member.h
index 2e8e01c80e..36e14ba27c 100644
--- a/modules/mono/mono_gd/i_mono_class_member.h
+++ b/modules/mono/mono_gd/i_mono_class_member.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/modules/mono/mono_gd/managed_type.cpp b/modules/mono/mono_gd/managed_type.cpp
index 3e971efece..0acfafe841 100644
--- a/modules/mono/mono_gd/managed_type.cpp
+++ b/modules/mono/mono_gd/managed_type.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
diff --git a/modules/mono/mono_gd/managed_type.h b/modules/mono/mono_gd/managed_type.h
index 11b832d0cc..0456a9a864 100644
--- a/modules/mono/mono_gd/managed_type.h
+++ b/modules/mono/mono_gd/managed_type.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -36,18 +36,15 @@
#include "gd_mono_header.h"
struct ManagedType {
- int type_encoding;
- GDMonoClass *type_class;
+ int type_encoding = 0;
+ GDMonoClass *type_class = nullptr;
static ManagedType from_class(GDMonoClass *p_class);
static ManagedType from_class(MonoClass *p_mono_class);
static ManagedType from_type(MonoType *p_mono_type);
static ManagedType from_reftype(MonoReflectionType *p_mono_reftype);
- ManagedType() :
- type_encoding(0),
- type_class(NULL) {
- }
+ ManagedType() {}
ManagedType(int p_type_encoding, GDMonoClass *p_type_class) :
type_encoding(p_type_encoding),
diff --git a/modules/mono/mono_gd/gd_mono_android.cpp b/modules/mono/mono_gd/support/android_support.cpp
index 27f394fae0..c65353dfd1 100644
--- a/modules/mono/mono_gd/gd_mono_android.cpp
+++ b/modules/mono/mono_gd/support/android_support.cpp
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* gd_mono_android.cpp */
+/* android_support.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "gd_mono_android.h"
+#include "android_support.h"
#if defined(ANDROID_ENABLED)
@@ -44,19 +44,21 @@
#endif
#include "core/os/os.h"
-#include "core/ustring.h"
+#include "core/string/ustring.h"
#include "platform/android/java_godot_wrapper.h"
#include "platform/android/os_android.h"
#include "platform/android/thread_jandroid.h"
-#include "../utils/path_utils.h"
-#include "../utils/string_utils.h"
-#include "gd_mono_cache.h"
-#include "gd_mono_marshal.h"
+#include "../../utils/path_utils.h"
+#include "../../utils/string_utils.h"
+#include "../gd_mono_cache.h"
+#include "../gd_mono_marshal.h"
// Warning: JNI boilerplate ahead... continue at your own risk
-namespace GDMonoAndroid {
+namespace gdmono {
+namespace android {
+namespace support {
template <typename T>
struct ScopedLocalRef {
@@ -67,7 +69,7 @@ struct ScopedLocalRef {
_FORCE_INLINE_ operator T() const { return local_ref; }
_FORCE_INLINE_ operator jvalue() const { return (jvalue)local_ref; }
- _FORCE_INLINE_ operator bool() const { return local_ref != NULL; }
+ _FORCE_INLINE_ operator bool() const { return local_ref != nullptr; }
_FORCE_INLINE_ bool operator==(std::nullptr_t) const {
return local_ref == nullptr;
@@ -107,7 +109,7 @@ bool jni_exception_check(JNIEnv *p_env) {
String app_native_lib_dir_cache;
String determine_app_native_lib_dir() {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
ScopedLocalRef<jclass> activityThreadClass(env, env->FindClass("android/app/ActivityThread"));
jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;");
@@ -122,7 +124,7 @@ String determine_app_native_lib_dir() {
String result;
- const char *const nativeLibraryDirUtf8 = env->GetStringUTFChars(nativeLibraryDir, NULL);
+ const char *const nativeLibraryDirUtf8 = env->GetStringUTFChars(nativeLibraryDir, nullptr);
if (nativeLibraryDirUtf8) {
result.parse_utf8(nativeLibraryDirUtf8);
env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDirUtf8);
@@ -132,7 +134,7 @@ String determine_app_native_lib_dir() {
}
String get_app_native_lib_dir() {
- if (app_native_lib_dir_cache.empty())
+ if (app_native_lib_dir_cache.is_empty())
app_native_lib_dir_cache = determine_app_native_lib_dir();
return app_native_lib_dir_cache;
}
@@ -150,21 +152,21 @@ int gd_mono_convert_dl_flags(int flags) {
return lflags;
}
-#ifndef GD_MONO_ANDROID_SO_NAME
-#define GD_MONO_ANDROID_SO_NAME "libmonosgen-2.0.so"
+#ifndef GD_MONO_SO_NAME
+#define GD_MONO_SO_NAME "libmonosgen-2.0.so"
#endif
-const char *mono_so_name = GD_MONO_ANDROID_SO_NAME;
+const char *mono_so_name = GD_MONO_SO_NAME;
const char *godot_so_name = "libgodot_android.so";
-void *mono_dl_handle = NULL;
-void *godot_dl_handle = NULL;
+void *mono_dl_handle = nullptr;
+void *godot_dl_handle = nullptr;
void *try_dlopen(const String &p_so_path, int p_flags) {
if (!FileAccess::exists(p_so_path)) {
if (OS::get_singleton()->is_stdout_verbose())
OS::get_singleton()->print("Cannot find shared library: '%s'\n", p_so_path.utf8().get_data());
- return NULL;
+ return nullptr;
}
int lflags = gd_mono_convert_dl_flags(p_flags);
@@ -174,7 +176,7 @@ void *try_dlopen(const String &p_so_path, int p_flags) {
if (!handle) {
if (OS::get_singleton()->is_stdout_verbose())
OS::get_singleton()->print("Failed to open shared library: '%s'. Error: '%s'\n", p_so_path.utf8().get_data(), dlerror());
- return NULL;
+ return nullptr;
}
if (OS::get_singleton()->is_stdout_verbose())
@@ -184,7 +186,7 @@ void *try_dlopen(const String &p_so_path, int p_flags) {
}
void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void *p_user_data) {
- if (p_name == NULL) {
+ if (p_name == nullptr) {
// __Internal
if (!mono_dl_handle) {
@@ -209,7 +211,7 @@ void *gd_mono_android_dlopen(const char *p_name, int p_flags, char **r_err, void
return try_dlopen(so_path, p_flags);
}
- return NULL;
+ return nullptr;
}
void *gd_mono_android_dlsym(void *p_handle, const char *p_name, char **r_err, void *p_user_data) {
@@ -230,7 +232,7 @@ void *gd_mono_android_dlsym(void *p_handle, const char *p_name, char **r_err, vo
if (r_err)
*r_err = str_format_new("%s\n", dlerror());
- return NULL;
+ return nullptr;
}
void *gd_mono_android_dlclose(void *p_handle, void *p_user_data) {
@@ -238,9 +240,9 @@ void *gd_mono_android_dlclose(void *p_handle, void *p_user_data) {
// Not sure if this ever happens. Does Mono close the handle for the main module?
if (p_handle == mono_dl_handle)
- mono_dl_handle = NULL;
+ mono_dl_handle = nullptr;
- return NULL;
+ return nullptr;
}
int32_t build_version_sdk_int = 0;
@@ -251,7 +253,7 @@ int32_t get_build_version_sdk_int() {
// android.os.Build.VERSION.SDK_INT
if (build_version_sdk_int == 0) {
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
jclass versionClass = env->FindClass("android/os/Build$VERSION");
ERR_FAIL_NULL_V(versionClass, 0);
@@ -265,7 +267,7 @@ int32_t get_build_version_sdk_int() {
return build_version_sdk_int;
}
-jobject certStore = NULL; // KeyStore
+jobject certStore = nullptr; // KeyStore
MonoBoolean _gd_mono_init_cert_store() {
// The JNI code is the equivalent of:
@@ -279,7 +281,7 @@ MonoBoolean _gd_mono_init_cert_store() {
// return false;
// }
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore"));
@@ -293,7 +295,7 @@ MonoBoolean _gd_mono_init_cert_store() {
if (jni_exception_check(env))
return 0;
- env->CallVoidMethod(certStoreLocal, load, NULL);
+ env->CallVoidMethod(certStoreLocal, load, nullptr);
if (jni_exception_check(env))
return 0;
@@ -315,31 +317,31 @@ MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) {
char *alias_utf8 = mono_string_to_utf8_checked(p_alias, &mono_error);
if (!mono_error_ok(&mono_error)) {
- ERR_PRINTS(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&mono_error) + "'.");
+ ERR_PRINT(String() + "Failed to convert MonoString* to UTF-8: '" + mono_error_get_message(&mono_error) + "'.");
mono_error_cleanup(&mono_error);
- return NULL;
+ return nullptr;
}
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
ScopedLocalRef<jstring> js_alias(env, env->NewStringUTF(alias_utf8));
mono_free(alias_utf8);
ScopedLocalRef<jclass> keyStoreClass(env, env->FindClass("java/security/KeyStore"));
- ERR_FAIL_NULL_V(keyStoreClass, NULL);
+ ERR_FAIL_NULL_V(keyStoreClass, nullptr);
ScopedLocalRef<jclass> certificateClass(env, env->FindClass("java/security/cert/Certificate"));
- ERR_FAIL_NULL_V(certificateClass, NULL);
+ ERR_FAIL_NULL_V(certificateClass, nullptr);
jmethodID getCertificate = env->GetMethodID(keyStoreClass, "getCertificate", "(Ljava/lang/String;)Ljava/security/cert/Certificate;");
- ERR_FAIL_NULL_V(getCertificate, NULL);
+ ERR_FAIL_NULL_V(getCertificate, nullptr);
jmethodID getEncoded = env->GetMethodID(certificateClass, "getEncoded", "()[B");
- ERR_FAIL_NULL_V(getEncoded, NULL);
+ ERR_FAIL_NULL_V(getEncoded, nullptr);
ScopedLocalRef<jobject> certificate(env, env->CallObjectMethod(certStore, getCertificate, js_alias.get()));
if (!certificate)
- return NULL;
+ return nullptr;
ScopedLocalRef<jbyteArray> encoded(env, (jbyteArray)env->CallObjectMethod(certificate, getEncoded));
jsize encodedLength = env->GetArrayLength(encoded);
@@ -352,11 +354,16 @@ MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) {
return encoded_ret;
}
+void register_internal_calls() {
+ GDMonoUtils::add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", _gd_mono_init_cert_store);
+ GDMonoUtils::add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", _gd_mono_android_cert_store_lookup);
+}
+
void initialize() {
// We need to set this environment variable to make the monodroid BCL use btls instead of legacy as the default provider
OS::get_singleton()->set_environment("XA_TLS_PROVIDER", "btls");
- mono_dl_fallback_register(gd_mono_android_dlopen, gd_mono_android_dlsym, gd_mono_android_dlclose, NULL);
+ mono_dl_fallback_register(gd_mono_android_dlopen, gd_mono_android_dlsym, gd_mono_android_dlclose, nullptr);
String app_native_lib_dir = get_app_native_lib_dir();
String so_path = path::join(app_native_lib_dir, godot_so_name);
@@ -364,31 +371,27 @@ void initialize() {
godot_dl_handle = try_dlopen(so_path, gd_mono_convert_dl_flags(MONO_DL_LAZY));
}
-void register_internal_calls() {
- mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_init_cert_store", (void *)_gd_mono_init_cert_store);
- mono_add_internal_call("Android.Runtime.AndroidEnvironment::_gd_mono_android_cert_store_lookup", (void *)_gd_mono_android_cert_store_lookup);
-}
-
void cleanup() {
// This is called after shutting down the Mono runtime
if (mono_dl_handle)
- gd_mono_android_dlclose(mono_dl_handle, NULL);
+ gd_mono_android_dlclose(mono_dl_handle, nullptr);
if (godot_dl_handle)
- gd_mono_android_dlclose(godot_dl_handle, NULL);
+ gd_mono_android_dlclose(godot_dl_handle, nullptr);
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
if (certStore) {
env->DeleteGlobalRef(certStore);
- certStore = NULL;
+ certStore = nullptr;
}
}
+} // namespace support
+} // namespace android
+} // namespace gdmono
-} // namespace GDMonoAndroid
-
-using namespace GDMonoAndroid;
+using namespace gdmono::android::support;
// The following are P/Invoke functions required by the monodroid profile of the BCL.
// These are P/Invoke functions and not internal calls, hence why they use
@@ -412,12 +415,11 @@ GD_PINVOKE_EXPORT int32_t monodroid_get_system_property(const char *p_name, char
if (r_value) {
if (len >= 0) {
*r_value = (char *)malloc(len + 1);
- if (!*r_value)
- return -1;
+ ERR_FAIL_NULL_V_MSG(*r_value, -1, "Out of memory.");
memcpy(*r_value, prop_value_str, len);
(*r_value)[len] = '\0';
} else {
- *r_value = NULL;
+ *r_value = nullptr;
}
}
@@ -434,7 +436,7 @@ GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_up_state(const char
*r_is_up = 0;
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface");
ERR_FAIL_NULL_V(networkInterfaceClass, 0);
@@ -466,7 +468,7 @@ GD_PINVOKE_EXPORT mono_bool _monodroid_get_network_interface_supports_multicast(
*r_supports_multicast = 0;
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
jclass networkInterfaceClass = env->FindClass("java/net/NetworkInterface");
ERR_FAIL_NULL_V(networkInterfaceClass, 0);
@@ -504,7 +506,7 @@ static void interop_get_active_network_dns_servers(char **r_dns_servers, int *dn
CRASH_COND(get_build_version_sdk_int() < 23);
#endif
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
GodotJavaWrapper *godot_java = ((OS_Android *)OS::get_singleton())->get_godot_java();
jobject activity = godot_java->get_activity();
@@ -604,7 +606,7 @@ GD_PINVOKE_EXPORT int32_t _monodroid_get_dns_servers(void **r_dns_servers_array)
if (!r_dns_servers_array)
return -1;
- *r_dns_servers_array = NULL;
+ *r_dns_servers_array = nullptr;
char *dns_servers[dns_servers_len];
int dns_servers_count = 0;
@@ -634,6 +636,7 @@ GD_PINVOKE_EXPORT int32_t _monodroid_get_dns_servers(void **r_dns_servers_array)
if (dns_servers_count > 0) {
size_t ret_size = sizeof(char *) * (size_t)dns_servers_count;
*r_dns_servers_array = malloc(ret_size); // freed by the BCL
+ ERR_FAIL_NULL_V_MSG(*r_dns_servers_array, -1, "Out of memory.");
memcpy(*r_dns_servers_array, dns_servers, ret_size);
}
@@ -645,26 +648,26 @@ GD_PINVOKE_EXPORT const char *_monodroid_timezone_get_default_id() {
//
// TimeZone.getDefault().getID()
- JNIEnv *env = ThreadAndroid::get_env();
+ JNIEnv *env = get_jni_env();
ScopedLocalRef<jclass> timeZoneClass(env, env->FindClass("java/util/TimeZone"));
- ERR_FAIL_NULL_V(timeZoneClass, NULL);
+ ERR_FAIL_NULL_V(timeZoneClass, nullptr);
jmethodID getDefault = env->GetStaticMethodID(timeZoneClass, "getDefault", "()Ljava/util/TimeZone;");
- ERR_FAIL_NULL_V(getDefault, NULL);
+ ERR_FAIL_NULL_V(getDefault, nullptr);
jmethodID getID = env->GetMethodID(timeZoneClass, "getID", "()Ljava/lang/String;");
- ERR_FAIL_NULL_V(getID, NULL);
+ ERR_FAIL_NULL_V(getID, nullptr);
ScopedLocalRef<jobject> defaultTimeZone(env, env->CallStaticObjectMethod(timeZoneClass, getDefault));
if (!defaultTimeZone)
- return NULL;
+ return nullptr;
ScopedLocalRef<jstring> defaultTimeZoneID(env, (jstring)env->CallObjectMethod(defaultTimeZone, getID));
if (!defaultTimeZoneID)
- return NULL;
+ return nullptr;
const char *default_time_zone_id = env->GetStringUTFChars(defaultTimeZoneID, 0);
diff --git a/modules/mono/mono_gd/gd_mono_android.h b/modules/mono/mono_gd/support/android_support.h
index 0e04847924..0c5dd2764c 100644
--- a/modules/mono/mono_gd/gd_mono_android.h
+++ b/modules/mono/mono_gd/support/android_support.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* gd_mono_android.h */
+/* android_support.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,25 +28,27 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef GD_MONO_ANDROID_H
-#define GD_MONO_ANDROID_H
+#ifndef ANDROID_SUPPORT_H
+#define ANDROID_SUPPORT_H
#if defined(ANDROID_ENABLED)
-#include "core/ustring.h"
+#include "core/string/ustring.h"
-namespace GDMonoAndroid {
+namespace gdmono {
+namespace android {
+namespace support {
String get_app_native_lib_dir();
void initialize();
-
-void register_internal_calls();
-
void cleanup();
-} // namespace GDMonoAndroid
+void register_internal_calls();
+} // namespace support
+} // namespace android
+} // namespace gdmono
#endif // ANDROID_ENABLED
-#endif // GD_MONO_ANDROID_H
+#endif // ANDROID_SUPPORT_H
diff --git a/modules/mono/editor/csharp_project.h b/modules/mono/mono_gd/support/ios_support.h
index 515b8d3d62..28a8806d0e 100644
--- a/modules/mono/editor/csharp_project.h
+++ b/modules/mono/mono_gd/support/ios_support.h
@@ -1,12 +1,12 @@
/*************************************************************************/
-/* csharp_project.h */
+/* ios_support.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,15 +28,23 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef CSHARP_PROJECT_H
-#define CSHARP_PROJECT_H
+#ifndef IOS_SUPPORT_H
+#define IOS_SUPPORT_H
-#include "core/ustring.h"
+#if defined(IPHONE_ENABLED)
-namespace CSharpProject {
+#include "core/string/ustring.h"
-void add_item(const String &p_project_path, const String &p_item_type, const String &p_include);
+namespace gdmono {
+namespace ios {
+namespace support {
-} // namespace CSharpProject
+void initialize();
+void cleanup();
+} // namespace support
+} // namespace ios
+} // namespace gdmono
-#endif // CSHARP_PROJECT_H
+#endif // IPHONE_ENABLED
+
+#endif // IOS_SUPPORT_H
diff --git a/modules/mono/mono_gd/support/ios_support.mm b/modules/mono/mono_gd/support/ios_support.mm
new file mode 100644
index 0000000000..23424fbaf9
--- /dev/null
+++ b/modules/mono/mono_gd/support/ios_support.mm
@@ -0,0 +1,149 @@
+/*************************************************************************/
+/* ios_support.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "ios_support.h"
+
+#if defined(IPHONE_ENABLED)
+
+#import <Foundation/Foundation.h>
+#include <os/log.h>
+
+#include "core/ustring.h"
+
+#include "../gd_mono_marshal.h"
+
+// Implemented mostly following: https://github.com/mono/mono/blob/master/sdks/ios/app/runtime.m
+
+// Definition generated by the Godot exporter
+extern "C" void gd_mono_setup_aot();
+
+namespace gdmono {
+namespace ios {
+namespace support {
+
+void ios_mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data) {
+ os_log_info(OS_LOG_DEFAULT, "(%s %s) %s", log_domain, log_level, message);
+ if (fatal) {
+ os_log_info(OS_LOG_DEFAULT, "Exit code: %d.", 1);
+ exit(1);
+ }
+}
+
+void initialize() {
+ mono_dllmap_insert(nullptr, "System.Native", nullptr, "__Internal", nullptr);
+ mono_dllmap_insert(nullptr, "System.IO.Compression.Native", nullptr, "__Internal", nullptr);
+ mono_dllmap_insert(nullptr, "System.Security.Cryptography.Native.Apple", nullptr, "__Internal", nullptr);
+
+#ifdef IOS_DEVICE
+ // This function is defined in an auto-generated source file
+ gd_mono_setup_aot();
+#endif
+
+ mono_set_signal_chaining(true);
+ mono_set_crash_chaining(true);
+}
+
+void cleanup() {
+}
+} // namespace support
+} // namespace ios
+} // namespace gdmono
+
+// The following are P/Invoke functions required by the monotouch profile of the BCL.
+// These are P/Invoke functions and not internal calls, hence why they use
+// 'mono_bool' and 'const char*' instead of 'MonoBoolean' and 'MonoString*'.
+
+#define GD_PINVOKE_EXPORT extern "C" __attribute__((visibility("default")))
+
+GD_PINVOKE_EXPORT const char *xamarin_get_locale_country_code() {
+ NSLocale *locale = [NSLocale currentLocale];
+ NSString *countryCode = [locale objectForKey:NSLocaleCountryCode];
+ if (countryCode == nullptr) {
+ return strdup("US");
+ }
+ return strdup([countryCode UTF8String]);
+}
+
+GD_PINVOKE_EXPORT void xamarin_log(const uint16_t *p_unicode_message) {
+ int length = 0;
+ const uint16_t *ptr = p_unicode_message;
+ while (*ptr++)
+ length += sizeof(uint16_t);
+ NSString *msg = [[NSString alloc] initWithBytes:p_unicode_message length:length encoding:NSUTF16LittleEndianStringEncoding];
+
+ os_log_info(OS_LOG_DEFAULT, "%{public}@", msg);
+}
+
+GD_PINVOKE_EXPORT const char *xamarin_GetFolderPath(int p_folder) {
+ NSSearchPathDirectory dd = (NSSearchPathDirectory)p_folder;
+ NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:dd inDomains:NSUserDomainMask] lastObject];
+ NSString *path = [url path];
+ return strdup([path UTF8String]);
+}
+
+GD_PINVOKE_EXPORT char *xamarin_timezone_get_local_name() {
+ NSTimeZone *tz = nil;
+ tz = [NSTimeZone localTimeZone];
+ NSString *name = [tz name];
+ return (name != nil) ? strdup([name UTF8String]) : strdup("Local");
+}
+
+GD_PINVOKE_EXPORT char **xamarin_timezone_get_names(uint32_t *p_count) {
+ NSArray *array = [NSTimeZone knownTimeZoneNames];
+ *p_count = array.count;
+ char **result = (char **)malloc(sizeof(char *) * (*p_count));
+ for (uint32_t i = 0; i < *p_count; i++) {
+ NSString *s = [array objectAtIndex:i];
+ result[i] = strdup(s.UTF8String);
+ }
+ return result;
+}
+
+GD_PINVOKE_EXPORT void *xamarin_timezone_get_data(const char *p_name, uint32_t *p_size) { // FIXME: uint32_t since Dec 2019, unsigned long before
+ NSTimeZone *tz = nil;
+ if (p_name) {
+ NSString *n = [[NSString alloc] initWithUTF8String:p_name];
+ tz = [[NSTimeZone alloc] initWithName:n];
+ } else {
+ tz = [NSTimeZone localTimeZone];
+ }
+ NSData *data = [tz data];
+ *p_size = [data length];
+ void *result = malloc(*p_size);
+ memcpy(result, data.bytes, *p_size);
+ return result;
+}
+
+GD_PINVOKE_EXPORT void xamarin_start_wwan(const char *p_uri) {
+ // FIXME: What's this for? No idea how to implement.
+ os_log_error(OS_LOG_DEFAULT, "Not implemented: 'xamarin_start_wwan'");
+}
+
+#endif // IPHONE_ENABLED
diff --git a/modules/mono/register_types.cpp b/modules/mono/register_types.cpp
index 4823ba3679..b4a6bfdcd4 100644
--- a/modules/mono/register_types.cpp
+++ b/modules/mono/register_types.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,15 +30,15 @@
#include "register_types.h"
-#include "core/engine.h"
+#include "core/config/engine.h"
#include "csharp_script.h"
-CSharpLanguage *script_language_cs = NULL;
+CSharpLanguage *script_language_cs = nullptr;
Ref<ResourceFormatLoaderCSharpScript> resource_loader_cs;
Ref<ResourceFormatSaverCSharpScript> resource_saver_cs;
-_GodotSharp *_godotsharp = NULL;
+_GodotSharp *_godotsharp = nullptr;
void register_mono_types() {
ClassDB::register_class<CSharpScript>();
@@ -52,18 +52,19 @@ void register_mono_types() {
script_language_cs->set_language_index(ScriptServer::get_language_count());
ScriptServer::register_language(script_language_cs);
- resource_loader_cs.instance();
+ resource_loader_cs.instantiate();
ResourceLoader::add_resource_format_loader(resource_loader_cs);
- resource_saver_cs.instance();
+ resource_saver_cs.instantiate();
ResourceSaver::add_resource_format_saver(resource_saver_cs);
}
void unregister_mono_types() {
ScriptServer::unregister_language(script_language_cs);
- if (script_language_cs)
+ if (script_language_cs) {
memdelete(script_language_cs);
+ }
ResourceLoader::remove_resource_format_loader(resource_loader_cs);
resource_loader_cs.unref();
@@ -71,6 +72,7 @@ void unregister_mono_types() {
ResourceSaver::remove_resource_format_saver(resource_saver_cs);
resource_saver_cs.unref();
- if (_godotsharp)
+ if (_godotsharp) {
memdelete(_godotsharp);
+ }
}
diff --git a/modules/mono/register_types.h b/modules/mono/register_types.h
index 7fd0d24eb0..1a2ff004b5 100644
--- a/modules/mono/register_types.h
+++ b/modules/mono/register_types.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,5 +28,10 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
+#ifndef MONO_REGISTER_TYPES_H
+#define MONO_REGISTER_TYPES_H
+
void register_mono_types();
void unregister_mono_types();
+
+#endif // MONO_REGISTER_TYPES_H
diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp
index d3226762ea..3aaf726fc8 100644
--- a/modules/mono/signal_awaiter_utils.cpp
+++ b/modules/mono/signal_awaiter_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -36,108 +36,193 @@
#include "mono_gd/gd_mono_marshal.h"
#include "mono_gd/gd_mono_utils.h"
-namespace SignalAwaiterUtils {
-
-Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p_target, MonoObject *p_awaiter) {
-
+Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signal, Object *p_target, MonoObject *p_awaiter) {
ERR_FAIL_NULL_V(p_source, ERR_INVALID_DATA);
ERR_FAIL_NULL_V(p_target, ERR_INVALID_DATA);
- Ref<SignalAwaiterHandle> sa_con = memnew(SignalAwaiterHandle(p_awaiter));
-#ifdef DEBUG_ENABLED
- sa_con->set_connection_target(p_target);
-#endif
+ // TODO: Use pooling for ManagedCallable instances.
+ SignalAwaiterCallable *awaiter_callable = memnew(SignalAwaiterCallable(p_target, p_awaiter, p_signal));
+ Callable callable = Callable(awaiter_callable);
- Vector<Variant> binds;
- binds.push_back(sa_con);
+ return p_source->connect(p_signal, callable, Vector<Variant>(), Object::CONNECT_ONESHOT);
+}
- Error err = p_source->connect(p_signal, sa_con.ptr(),
- CSharpLanguage::get_singleton()->get_string_names()._signal_callback,
- binds, Object::CONNECT_ONESHOT);
+bool SignalAwaiterCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
+ // Only called if both instances are of type SignalAwaiterCallable. Static cast is safe.
+ const SignalAwaiterCallable *a = static_cast<const SignalAwaiterCallable *>(p_a);
+ const SignalAwaiterCallable *b = static_cast<const SignalAwaiterCallable *>(p_b);
+ return a->awaiter_handle.handle == b->awaiter_handle.handle;
+}
- if (err != OK) {
- // Set it as completed to prevent it from calling the failure callback when released.
- // The awaiter will be aware of the failure by checking the returned error.
- sa_con->set_completed(true);
+bool SignalAwaiterCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
+ if (compare_equal(p_a, p_b)) {
+ return false;
}
+ return p_a < p_b;
+}
+
+uint32_t SignalAwaiterCallable::hash() const {
+ uint32_t hash = signal.hash();
+ return hash_djb2_one_64(target_id, hash);
+}
+
+String SignalAwaiterCallable::get_as_text() const {
+ Object *base = ObjectDB::get_instance(target_id);
+ if (base) {
+ String class_name = base->get_class();
+ Ref<Script> script = base->get_script();
+ if (script.is_valid() && script->get_path().is_resource_file()) {
+ class_name += "(" + script->get_path().get_file() + ")";
+ }
+ return class_name + "::SignalAwaiterMiddleman::" + String(signal);
+ } else {
+ return "null::SignalAwaiterMiddleman::" + String(signal);
+ }
+}
- return err;
+CallableCustom::CompareEqualFunc SignalAwaiterCallable::get_compare_equal_func() const {
+ return compare_equal_func_ptr;
}
-} // namespace SignalAwaiterUtils
-Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
+CallableCustom::CompareLessFunc SignalAwaiterCallable::get_compare_less_func() const {
+ return compare_less_func_ptr;
+}
+
+ObjectID SignalAwaiterCallable::get_object() const {
+ return target_id;
+}
+
+void SignalAwaiterCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
+ r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better
+ r_return_value = Variant();
#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_V_MSG(conn_target_id && !ObjectDB::get_instance(conn_target_id), Variant(),
+ ERR_FAIL_COND_MSG(target_id.is_valid() && !ObjectDB::get_instance(target_id),
"Resumed after await, but class instance is gone.");
#endif
- if (p_argcount < 1) {
- r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
- r_error.argument = 1;
- return Variant();
- }
+ MonoArray *signal_args = nullptr;
- Ref<SignalAwaiterHandle> self = *p_args[p_argcount - 1];
+ if (p_argcount > 0) {
+ signal_args = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_argcount);
- if (self.is_null()) {
- r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
- r_error.argument = p_argcount - 1;
- r_error.expected = Variant::OBJECT;
- return Variant();
+ for (int i = 0; i < p_argcount; i++) {
+ MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(*p_arguments[i]);
+ mono_array_setref(signal_args, i, boxed);
+ }
}
- set_completed(true);
-
- int signal_argc = p_argcount - 1;
- MonoArray *signal_args = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), signal_argc);
+ MonoObject *awaiter = awaiter_handle.get_target();
- for (int i = 0; i < signal_argc; i++) {
- MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(*p_args[i]);
- mono_array_setref(signal_args, i, boxed);
+ if (!awaiter) {
+ r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL;
+ return;
}
- MonoException *exc = NULL;
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback).invoke(get_target(), signal_args, &exc);
- GD_MONO_END_RUNTIME_INVOKE;
+ MonoException *exc = nullptr;
+ CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback).invoke(awaiter, signal_args, &exc);
if (exc) {
GDMonoUtils::set_pending_exception(exc);
- ERR_FAIL_V(Variant());
+ ERR_FAIL();
+ } else {
+ r_call_error.error = Callable::CallError::CALL_OK;
}
+}
+
+SignalAwaiterCallable::SignalAwaiterCallable(Object *p_target, MonoObject *p_awaiter, const StringName &p_signal) :
+ target_id(p_target->get_instance_id()),
+ awaiter_handle(MonoGCHandleData::new_strong_handle(p_awaiter)),
+ signal(p_signal) {
+}
- return Variant();
+SignalAwaiterCallable::~SignalAwaiterCallable() {
+ awaiter_handle.release();
}
-void SignalAwaiterHandle::_bind_methods() {
+bool EventSignalCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) {
+ const EventSignalCallable *a = static_cast<const EventSignalCallable *>(p_a);
+ const EventSignalCallable *b = static_cast<const EventSignalCallable *>(p_b);
- ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "_signal_callback", &SignalAwaiterHandle::_signal_callback, MethodInfo("_signal_callback"));
+ if (a->owner != b->owner) {
+ return false;
+ }
+
+ if (a->event_signal != b->event_signal) {
+ return false;
+ }
+
+ return true;
}
-SignalAwaiterHandle::SignalAwaiterHandle(MonoObject *p_managed) :
- MonoGCHandle(MonoGCHandle::new_strong_handle(p_managed), STRONG_HANDLE) {
+bool EventSignalCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) {
+ if (compare_equal(p_a, p_b)) {
+ return false;
+ }
+ return p_a < p_b;
+}
-#ifdef DEBUG_ENABLED
- conn_target_id = 0;
-#endif
+uint32_t EventSignalCallable::hash() const {
+ uint32_t hash = event_signal->field->get_name().hash();
+ return hash_djb2_one_64(owner->get_instance_id(), hash);
}
-SignalAwaiterHandle::~SignalAwaiterHandle() {
+String EventSignalCallable::get_as_text() const {
+ String class_name = owner->get_class();
+ Ref<Script> script = owner->get_script();
+ if (script.is_valid() && script->get_path().is_resource_file()) {
+ class_name += "(" + script->get_path().get_file() + ")";
+ }
+ StringName signal = event_signal->field->get_name();
+ return class_name + "::EventSignalMiddleman::" + String(signal);
+}
- if (!completed) {
- MonoObject *awaiter = get_target();
+CallableCustom::CompareEqualFunc EventSignalCallable::get_compare_equal_func() const {
+ return compare_equal_func_ptr;
+}
- if (awaiter) {
- MonoException *exc = NULL;
- GD_MONO_BEGIN_RUNTIME_INVOKE;
- CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback).invoke(awaiter, &exc);
- GD_MONO_END_RUNTIME_INVOKE;
+CallableCustom::CompareLessFunc EventSignalCallable::get_compare_less_func() const {
+ return compare_less_func_ptr;
+}
- if (exc) {
- GDMonoUtils::set_pending_exception(exc);
- ERR_FAIL();
- }
- }
+ObjectID EventSignalCallable::get_object() const {
+ return owner->get_instance_id();
+}
+
+StringName EventSignalCallable::get_signal() const {
+ return event_signal->field->get_name();
+}
+
+void EventSignalCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
+ r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better
+ r_return_value = Variant();
+
+ ERR_FAIL_COND(p_argcount < event_signal->invoke_method->get_parameters_count());
+
+ CSharpInstance *csharp_instance = CAST_CSHARP_INSTANCE(owner->get_script_instance());
+ ERR_FAIL_NULL(csharp_instance);
+
+ MonoObject *owner_managed = csharp_instance->get_mono_object();
+ ERR_FAIL_NULL(owner_managed);
+
+ MonoObject *delegate_field_value = event_signal->field->get_value(owner_managed);
+ if (!delegate_field_value) {
+ r_call_error.error = Callable::CallError::CALL_OK;
+ return;
+ }
+
+ MonoException *exc = nullptr;
+ event_signal->invoke_method->invoke(delegate_field_value, p_arguments, &exc);
+
+ if (exc) {
+ GDMonoUtils::set_pending_exception(exc);
+ ERR_FAIL();
+ } else {
+ r_call_error.error = Callable::CallError::CALL_OK;
}
}
+
+EventSignalCallable::EventSignalCallable(Object *p_owner, const CSharpScript::EventSignal *p_event_signal) :
+ owner(p_owner),
+ event_signal(p_event_signal) {
+}
diff --git a/modules/mono/signal_awaiter_utils.h b/modules/mono/signal_awaiter_utils.h
index a9956ad5ba..e12ea45b3f 100644
--- a/modules/mono/signal_awaiter_utils.h
+++ b/modules/mono/signal_awaiter_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,41 +31,67 @@
#ifndef SIGNAL_AWAITER_UTILS_H
#define SIGNAL_AWAITER_UTILS_H
-#include "core/reference.h"
+#include "core/object/ref_counted.h"
+
+#include "csharp_script.h"
#include "mono_gc_handle.h"
-namespace SignalAwaiterUtils {
+Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signal, Object *p_target, MonoObject *p_awaiter);
+
+class SignalAwaiterCallable : public CallableCustom {
+ ObjectID target_id;
+ MonoGCHandleData awaiter_handle;
+ StringName signal;
+
+public:
+ static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
+ static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
-Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p_target, MonoObject *p_awaiter);
-}
+ static constexpr CompareEqualFunc compare_equal_func_ptr = &SignalAwaiterCallable::compare_equal;
+ static constexpr CompareEqualFunc compare_less_func_ptr = &SignalAwaiterCallable::compare_less;
-class SignalAwaiterHandle : public MonoGCHandle {
+ uint32_t hash() const override;
- GDCLASS(SignalAwaiterHandle, MonoGCHandle);
+ String get_as_text() const override;
- bool completed;
+ CompareEqualFunc get_compare_equal_func() const override;
+ CompareLessFunc get_compare_less_func() const override;
-#ifdef DEBUG_ENABLED
- ObjectID conn_target_id;
-#endif
+ ObjectID get_object() const override;
- Variant _signal_callback(const Variant **p_args, int p_argcount, Variant::CallError &r_error);
+ _FORCE_INLINE_ StringName get_signal() const { return signal; }
+
+ void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
+
+ SignalAwaiterCallable(Object *p_target, MonoObject *p_awaiter, const StringName &p_signal);
+ ~SignalAwaiterCallable();
+};
-protected:
- static void _bind_methods();
+class EventSignalCallable : public CallableCustom {
+ Object *owner;
+ const CSharpScript::EventSignal *event_signal;
public:
- _FORCE_INLINE_ bool is_completed() { return completed; }
- _FORCE_INLINE_ void set_completed(bool p_completed) { completed = p_completed; }
+ static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b);
+ static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b);
+
+ static constexpr CompareEqualFunc compare_equal_func_ptr = &EventSignalCallable::compare_equal;
+ static constexpr CompareEqualFunc compare_less_func_ptr = &EventSignalCallable::compare_less;
+
+ uint32_t hash() const override;
+
+ String get_as_text() const override;
+
+ CompareEqualFunc get_compare_equal_func() const override;
+ CompareLessFunc get_compare_less_func() const override;
+
+ ObjectID get_object() const override;
+
+ StringName get_signal() const;
-#ifdef DEBUG_ENABLED
- _FORCE_INLINE_ void set_connection_target(Object *p_target) {
- conn_target_id = p_target->get_instance_id();
- }
-#endif
+ void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
- SignalAwaiterHandle(MonoObject *p_managed);
- ~SignalAwaiterHandle();
+ EventSignalCallable(Object *p_owner, const CSharpScript::EventSignal *p_event_signal);
};
#endif // SIGNAL_AWAITER_UTILS_H
diff --git a/modules/mono/utils/macros.h b/modules/mono/utils/macros.h
index 754000dc14..4a220d89c8 100644
--- a/modules/mono/utils/macros.h
+++ b/modules/mono/utils/macros.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -36,38 +36,6 @@
#define _GD_VARNAME_CONCAT_(m_a, m_b, m_c) _GD_VARNAME_CONCAT_A_(m_a, m_b, m_c)
#define GD_UNIQUE_NAME(m_name) _GD_VARNAME_CONCAT_(m_name, _, __COUNTER__)
-// static assert
-// TODO: Get rid of this macro once we upgrade to C++11
-
-#ifdef __cpp_static_assert
-#define GD_STATIC_ASSERT(m_cond) static_assert((m_cond), "Condition '" #m_cond "' failed")
-#else
-#define GD_STATIC_ASSERT(m_cond) typedef int GD_UNIQUE_NAME(godot_static_assert)[((m_cond) ? 1 : -1)]
-#endif
-
-// final
-// TODO: Get rid of this macro once we upgrade to C++11
-
-#if (__cplusplus >= 201103L)
-#define GD_FINAL final
-#else
-#define GD_FINAL
-#endif
-
-// noreturn
-// TODO: Get rid of this macro once we upgrade to C++11
-
-#if (__cplusplus >= 201103L)
-#define GD_NORETURN [[noreturn]]
-#elif defined(__GNUC__)
-#define GD_NORETURN __attribute__((noreturn))
-#elif defined(_MSC_VER)
-#define GD_NORETURN __declspec(noreturn)
-#else
-#define GD_NORETURN
-#pragma message "Macro GD_NORETURN will have no effect"
-#endif
-
// unreachable
#if defined(_MSC_VER)
@@ -78,7 +46,27 @@
#define GD_UNREACHABLE() \
CRASH_NOW(); \
do { \
- } while (true);
+ } while (true)
#endif
+namespace gdmono {
+
+template <typename F>
+struct ScopeExit {
+ ScopeExit(F p_exit_func) :
+ exit_func(p_exit_func) {}
+ ~ScopeExit() { exit_func(); }
+ F exit_func;
+};
+
+class ScopeExitAux {
+public:
+ template <typename F>
+ ScopeExit<F> operator+(F p_exit_func) { return ScopeExit<F>(p_exit_func); }
+};
+} // namespace gdmono
+
+#define SCOPE_EXIT \
+ auto GD_UNIQUE_NAME(gd_scope_exit) = gdmono::ScopeExitAux() + [=]() -> void
+
#endif // UTIL_MACROS_H
diff --git a/modules/mono/utils/mono_reg_utils.cpp b/modules/mono/utils/mono_reg_utils.cpp
index c1cd5f1db4..6b616dd52d 100644
--- a/modules/mono/utils/mono_reg_utils.cpp
+++ b/modules/mono/utils/mono_reg_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -29,7 +29,7 @@
/*************************************************************************/
#include "mono_reg_utils.h"
-#include "core/os/dir_access.h"
+#include "core/io/dir_access.h"
#ifdef WINDOWS_ENABLED
@@ -58,7 +58,6 @@ REGSAM _get_bitness_sam() {
}
LONG _RegOpenKey(HKEY hKey, LPCWSTR lpSubKey, PHKEY phkResult) {
-
LONG res = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ, phkResult);
if (res != ERROR_SUCCESS)
@@ -68,18 +67,16 @@ LONG _RegOpenKey(HKEY hKey, LPCWSTR lpSubKey, PHKEY phkResult) {
}
LONG _RegKeyQueryString(HKEY hKey, const String &p_value_name, String &r_value) {
-
Vector<WCHAR> buffer;
buffer.resize(512);
DWORD dwBufferSize = buffer.size();
- LONG res = RegQueryValueExW(hKey, p_value_name.c_str(), 0, NULL, (LPBYTE)buffer.ptr(), &dwBufferSize);
+ LONG res = RegQueryValueExW(hKey, (LPCWSTR)(p_value_name.utf16().get_data()), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize);
if (res == ERROR_MORE_DATA) {
// dwBufferSize now contains the actual size
- Vector<WCHAR> buffer;
buffer.resize(dwBufferSize);
- res = RegQueryValueExW(hKey, p_value_name.c_str(), 0, NULL, (LPBYTE)buffer.ptr(), &dwBufferSize);
+ res = RegQueryValueExW(hKey, (LPCWSTR)(p_value_name.utf16().get_data()), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize);
}
if (res == ERROR_SUCCESS) {
@@ -92,9 +89,8 @@ LONG _RegKeyQueryString(HKEY hKey, const String &p_value_name, String &r_value)
}
LONG _find_mono_in_reg(const String &p_subkey, MonoRegInfo &r_info, bool p_old_reg = false) {
-
HKEY hKey;
- LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, p_subkey.c_str(), &hKey);
+ LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, (LPCWSTR)(p_subkey.utf16().get_data()), &hKey);
if (res != ERROR_SUCCESS)
goto cleanup;
@@ -128,11 +124,10 @@ cleanup:
}
LONG _find_mono_in_reg_old(const String &p_subkey, MonoRegInfo &r_info) {
-
String default_clr;
HKEY hKey;
- LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, p_subkey.c_str(), &hKey);
+ LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, (LPCWSTR)(p_subkey.utf16().get_data()), &hKey);
if (res != ERROR_SUCCESS)
goto cleanup;
@@ -150,7 +145,6 @@ cleanup:
}
MonoRegInfo find_mono() {
-
MonoRegInfo info;
if (_find_mono_in_reg("Software\\Mono", info) == ERROR_SUCCESS)
@@ -163,7 +157,6 @@ MonoRegInfo find_mono() {
}
String find_msbuild_tools_path() {
-
String msbuild_tools_path;
// Try to find 15.0 with vswhere
@@ -180,7 +173,7 @@ String find_msbuild_tools_path() {
String output;
int exit_code;
- OS::get_singleton()->execute(vswhere_path, vswhere_args, true, NULL, &output, &exit_code);
+ OS::get_singleton()->execute(vswhere_path, vswhere_args, &output, &exit_code);
if (exit_code == 0) {
Vector<String> lines = output.split("\n");
@@ -195,7 +188,7 @@ String find_msbuild_tools_path() {
if (key == "installationPath") {
String val = line.substr(sep_idx + 1, line.length()).strip_edges();
- ERR_BREAK(val.empty());
+ ERR_BREAK(val.is_empty());
if (!val.ends_with("\\")) {
val += "\\";
diff --git a/modules/mono/utils/mono_reg_utils.h b/modules/mono/utils/mono_reg_utils.h
index f844a7233a..0e617761ea 100644
--- a/modules/mono/utils/mono_reg_utils.h
+++ b/modules/mono/utils/mono_reg_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -33,10 +33,9 @@
#ifdef WINDOWS_ENABLED
-#include "core/ustring.h"
+#include "core/string/ustring.h"
struct MonoRegInfo {
-
String version;
String install_root_dir;
String assembly_dir;
diff --git a/modules/mono/utils/osx_utils.cpp b/modules/mono/utils/osx_utils.cpp
index 432b306414..f4216c8129 100644
--- a/modules/mono/utils/osx_utils.cpp
+++ b/modules/mono/utils/osx_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -32,31 +32,27 @@
#ifdef OSX_ENABLED
-#include "core/print_string.h"
+#include "core/string/print_string.h"
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
bool osx_is_app_bundle_installed(const String &p_bundle_id) {
-
- CFURLRef app_url = NULL;
- CFStringRef bundle_id = CFStringCreateWithCString(NULL, p_bundle_id.utf8(), kCFStringEncodingUTF8);
- OSStatus result = LSFindApplicationForInfo(kLSUnknownCreator, bundle_id, NULL, NULL, &app_url);
+ CFStringRef bundle_id = CFStringCreateWithCString(nullptr, p_bundle_id.utf8(), kCFStringEncodingUTF8);
+ CFArrayRef result = LSCopyApplicationURLsForBundleIdentifier(bundle_id, nullptr);
CFRelease(bundle_id);
- if (app_url)
- CFRelease(app_url);
-
- switch (result) {
- case noErr:
+ if (result) {
+ if (CFArrayGetCount(result) > 0) {
+ CFRelease(result);
return true;
- case kLSApplicationNotFoundErr:
- break;
- default:
- break;
+ } else {
+ CFRelease(result);
+ return false;
+ }
+ } else {
+ return false;
}
-
- return false;
}
#endif
diff --git a/modules/mono/utils/osx_utils.h b/modules/mono/utils/osx_utils.h
index 55002702f8..6704f19077 100644
--- a/modules/mono/utils/osx_utils.h
+++ b/modules/mono/utils/osx_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "core/ustring.h"
+#include "core/string/ustring.h"
#ifndef OSX_UTILS_H
#define OSX_UTILS_H
diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp
index 545da6c79e..ec04d50704 100644
--- a/modules/mono/utils/path_utils.cpp
+++ b/modules/mono/utils/path_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,10 +30,10 @@
#include "path_utils.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
+#include "core/config/project_settings.h"
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
#include "core/os/os.h"
-#include "core/project_settings.h"
#ifdef WINDOWS_ENABLED
#include <windows.h>
@@ -50,59 +50,37 @@
namespace path {
-String find_executable(const String &p_name) {
-#ifdef WINDOWS_ENABLED
- Vector<String> exts = OS::get_singleton()->get_environment("PATHEXT").split(ENV_PATH_SEP, false);
-#endif
- Vector<String> env_path = OS::get_singleton()->get_environment("PATH").split(ENV_PATH_SEP, false);
-
- if (env_path.empty())
- return String();
-
- for (int i = 0; i < env_path.size(); i++) {
- String p = path::join(env_path[i], p_name);
-
-#ifdef WINDOWS_ENABLED
- for (int j = 0; j < exts.size(); j++) {
- String p2 = p + exts[j].to_lower(); // lowercase to reduce risk of case mismatch warning
-
- if (FileAccess::exists(p2))
- return p2;
- }
-#else
- if (FileAccess::exists(p))
- return p;
-#endif
- }
-
- return String();
-}
-
String cwd() {
#ifdef WINDOWS_ENABLED
- const DWORD expected_size = ::GetCurrentDirectoryW(0, NULL);
+ const DWORD expected_size = ::GetCurrentDirectoryW(0, nullptr);
- String buffer;
+ Char16String buffer;
buffer.resize((int)expected_size);
- if (::GetCurrentDirectoryW(expected_size, buffer.ptrw()) == 0)
+ if (::GetCurrentDirectoryW(expected_size, (wchar_t *)buffer.ptrw()) == 0)
return ".";
- return buffer.simplify_path();
+ String result;
+ if (result.parse_utf16(buffer.ptr())) {
+ return ".";
+ }
+ return result.simplify_path();
#else
char buffer[PATH_MAX];
- if (::getcwd(buffer, sizeof(buffer)) == NULL)
+ if (::getcwd(buffer, sizeof(buffer)) == nullptr) {
return ".";
+ }
String result;
- if (result.parse_utf8(buffer))
+ if (result.parse_utf8(buffer)) {
return ".";
+ }
return result.simplify_path();
#endif
}
String abspath(const String &p_path) {
- if (p_path.is_abs_path()) {
+ if (p_path.is_absolute_path()) {
return p_path.simplify_path();
} else {
return path::join(path::cwd(), p_path).simplify_path();
@@ -112,48 +90,57 @@ String abspath(const String &p_path) {
String realpath(const String &p_path) {
#ifdef WINDOWS_ENABLED
// Open file without read/write access
- HANDLE hFile = ::CreateFileW(p_path.c_str(), 0,
+ HANDLE hFile = ::CreateFileW((LPCWSTR)(p_path.utf16().get_data()), 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE)
return p_path;
- const DWORD expected_size = ::GetFinalPathNameByHandleW(hFile, NULL, 0, FILE_NAME_NORMALIZED);
+ const DWORD expected_size = ::GetFinalPathNameByHandleW(hFile, nullptr, 0, FILE_NAME_NORMALIZED);
if (expected_size == 0) {
::CloseHandle(hFile);
return p_path;
}
- String buffer;
+ Char16String buffer;
buffer.resize((int)expected_size);
- ::GetFinalPathNameByHandleW(hFile, buffer.ptrw(), expected_size, FILE_NAME_NORMALIZED);
+ ::GetFinalPathNameByHandleW(hFile, (wchar_t *)buffer.ptrw(), expected_size, FILE_NAME_NORMALIZED);
::CloseHandle(hFile);
- return buffer.simplify_path();
+
+ String result;
+ if (result.parse_utf16(buffer.ptr())) {
+ return p_path;
+ }
+
+ return result.simplify_path();
#elif UNIX_ENABLED
- char *resolved_path = ::realpath(p_path.utf8().get_data(), NULL);
+ char *resolved_path = ::realpath(p_path.utf8().get_data(), nullptr);
- if (!resolved_path)
+ if (!resolved_path) {
return p_path;
+ }
String result;
bool parse_ok = result.parse_utf8(resolved_path);
::free(resolved_path);
- if (parse_ok)
+ if (parse_ok) {
return p_path;
+ }
return result.simplify_path();
#endif
}
String join(const String &p_a, const String &p_b) {
- if (p_a.empty())
+ if (p_a.is_empty()) {
return p_b;
+ }
- const CharType a_last = p_a[p_a.length() - 1];
+ const char32_t a_last = p_a[p_a.length() - 1];
if ((a_last == '/' || a_last == '\\') ||
(p_b.size() > 0 && (p_b[0] == '/' || p_b[0] == '\\'))) {
return p_a + p_b;
@@ -178,8 +165,9 @@ String relative_to_impl(const String &p_path, const String &p_relative_to) {
} else {
String base_dir = p_relative_to.get_base_dir();
- if (base_dir.length() <= 2 && (base_dir.empty() || base_dir.ends_with(":")))
+ if (base_dir.length() <= 2 && (base_dir.is_empty() || base_dir.ends_with(":"))) {
return p_path;
+ }
return String("..").plus_file(relative_to_impl(p_path, base_dir));
}
@@ -206,5 +194,4 @@ String relative_to(const String &p_path, const String &p_relative_to) {
return relative_to_impl(path_abs_norm, relative_to_abs_norm);
}
-
} // namespace path
diff --git a/modules/mono/utils/path_utils.h b/modules/mono/utils/path_utils.h
index 9965f58b0a..82b8f95f49 100644
--- a/modules/mono/utils/path_utils.h
+++ b/modules/mono/utils/path_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,8 +31,8 @@
#ifndef PATH_UTILS_H
#define PATH_UTILS_H
-#include "core/string_builder.h"
-#include "core/ustring.h"
+#include "core/string/string_builder.h"
+#include "core/string/ustring.h"
namespace path {
@@ -40,8 +40,6 @@ String join(const String &p_a, const String &p_b);
String join(const String &p_a, const String &p_b, const String &p_c);
String join(const String &p_a, const String &p_b, const String &p_c, const String &p_d);
-String find_executable(const String &p_name);
-
/// Returns a normalized absolute path to the current working directory
String cwd();
@@ -58,7 +56,6 @@ String abspath(const String &p_path);
String realpath(const String &p_path);
String relative_to(const String &p_path, const String &p_relative_to);
-
} // namespace path
#endif // PATH_UTILS_H
diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp
index 911ac5c4a3..053618ebe4 100644
--- a/modules/mono/utils/string_utils.cpp
+++ b/modules/mono/utils/string_utils.cpp
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -30,7 +30,7 @@
#include "string_utils.h"
-#include "core/os/file_access.h"
+#include "core/io/file_access.h"
#include <stdio.h>
#include <stdlib.h>
@@ -38,16 +38,18 @@
namespace {
int sfind(const String &p_text, int p_from) {
- if (p_from < 0)
+ if (p_from < 0) {
return -1;
+ }
int src_len = 2;
int len = p_text.length();
- if (len == 0)
+ if (len == 0) {
return -1;
+ }
- const CharType *src = p_text.c_str();
+ const char32_t *src = p_text.get_data();
for (int i = p_from; i <= (len - src_len); i++) {
bool found = true;
@@ -62,7 +64,7 @@ int sfind(const String &p_text, int p_from) {
found = src[read_pos] == '%';
break;
case 1: {
- CharType c = src[read_pos];
+ char32_t c = src[read_pos];
found = src[read_pos] == 's' || (c >= '0' && c <= '4');
break;
}
@@ -75,8 +77,9 @@ int sfind(const String &p_text, int p_from) {
}
}
- if (found)
+ if (found) {
return i;
+ }
}
return -1;
@@ -84,8 +87,9 @@ int sfind(const String &p_text, int p_from) {
} // namespace
String sformat(const String &p_text, const Variant &p1, const Variant &p2, const Variant &p3, const Variant &p4, const Variant &p5) {
- if (p_text.length() < 2)
+ if (p_text.length() < 2) {
return p_text;
+ }
Array args;
@@ -116,7 +120,7 @@ String sformat(const String &p_text, const Variant &p1, const Variant &p2, const
int result = 0;
while ((result = sfind(p_text, search_from)) >= 0) {
- CharType c = p_text[result + 1];
+ char32_t c = p_text[result + 1];
int req_index = (c == 's' ? findex++ : c - '0');
@@ -132,7 +136,6 @@ String sformat(const String &p_text, const Variant &p1, const Variant &p2, const
#ifdef TOOLS_ENABLED
bool is_csharp_keyword(const String &p_name) {
-
// Reserved keywords
return p_name == "abstract" || p_name == "as" || p_name == "base" || p_name == "bool" ||
@@ -162,22 +165,22 @@ String escape_csharp_keyword(const String &p_name) {
#endif
Error read_all_file_utf8(const String &p_path, String &r_content) {
- PoolVector<uint8_t> sourcef;
+ Vector<uint8_t> sourcef;
Error err;
FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err);
ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open file '" + p_path + "'.");
- int len = f->get_len();
+ uint64_t len = f->get_length();
sourcef.resize(len + 1);
- PoolVector<uint8_t>::Write w = sourcef.write();
- int r = f->get_buffer(w.ptr(), len);
+ uint8_t *w = sourcef.ptrw();
+ uint64_t r = f->get_buffer(w, len);
f->close();
memdelete(f);
ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN);
w[len] = 0;
String source;
- if (source.parse_utf8((const char *)w.ptr())) {
+ if (source.parse_utf8((const char *)w)) {
ERR_FAIL_V(ERR_INVALID_DATA);
}
@@ -196,23 +199,13 @@ String str_format(const char *p_format, ...) {
return res;
}
-// va_copy was defined in the C99, but not in C++ standards before C++11.
-// When you compile C++ without --std=c++<XX> option, compilers still define
-// va_copy, otherwise you have to use the internal version (__va_copy).
-#if !defined(va_copy)
-#if defined(__GNUC__)
-#define va_copy(d, s) __va_copy((d), (s))
-#else
-#define va_copy(d, s) ((d) = (s))
-#endif
-#endif
#if defined(MINGW_ENABLED) || defined(_MSC_VER) && _MSC_VER < 1900
#define gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf_s(m_buffer, m_count, _TRUNCATE, m_format, m_args_copy)
#define gd_vscprintf(m_format, m_args_copy) _vscprintf(m_format, m_args_copy)
#else
#define gd_vsnprintf(m_buffer, m_count, m_format, m_args_copy) vsnprintf(m_buffer, m_count, m_format, m_args_copy)
-#define gd_vscprintf(m_format, m_args_copy) vsnprintf(NULL, 0, p_format, m_args_copy)
+#define gd_vscprintf(m_format, m_args_copy) vsnprintf(nullptr, 0, p_format, m_args_copy)
#endif
String str_format(const char *p_format, va_list p_list) {
diff --git a/modules/mono/utils/string_utils.h b/modules/mono/utils/string_utils.h
index 0318fec592..3290cb38b9 100644
--- a/modules/mono/utils/string_utils.h
+++ b/modules/mono/utils/string_utils.h
@@ -5,8 +5,8 @@
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
@@ -31,8 +31,8 @@
#ifndef STRING_FORMAT_H
#define STRING_FORMAT_H
-#include "core/ustring.h"
-#include "core/variant.h"
+#include "core/string/ustring.h"
+#include "core/variant/variant.h"
#include <stdarg.h>
diff --git a/modules/mono/utils/thread_local.h b/modules/mono/utils/thread_local.h
deleted file mode 100644
index b1cc2e37ea..0000000000
--- a/modules/mono/utils/thread_local.h
+++ /dev/null
@@ -1,177 +0,0 @@
-/*************************************************************************/
-/* thread_local.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef THREAD_LOCAL_H
-#define THREAD_LOCAL_H
-
-#ifdef HAVE_CXX11_THREAD_LOCAL
-#define _THREAD_LOCAL_(m_t) thread_local m_t
-#else
-
-#if !defined(__GNUC__) && !defined(_MSC_VER)
-#error Platform or compiler not supported
-#endif
-
-#if defined(__GNUC__)
-
-#ifdef HAVE_GCC___THREAD
-#define _THREAD_LOCAL_(m_t) __thread m_t
-#else
-#define USE_CUSTOM_THREAD_LOCAL
-#endif
-
-#elif defined(_MSC_VER)
-
-#ifdef HAVE_DECLSPEC_THREAD
-#define _THREAD_LOCAL_(m_t) __declspec(thread) m_t
-#else
-#define USE_CUSTOM_THREAD_LOCAL
-#endif
-
-#endif // __GNUC__ _MSC_VER
-
-#endif // HAVE_CXX11_THREAD_LOCAL
-
-#ifdef USE_CUSTOM_THREAD_LOCAL
-#define _THREAD_LOCAL_(m_t) ThreadLocal<m_t>
-#endif
-
-#include "core/typedefs.h"
-
-#ifdef WINDOWS_ENABLED
-#define _CALLBACK_FUNC_ __stdcall
-#else
-#define _CALLBACK_FUNC_
-#endif
-
-struct ThreadLocalStorage {
-
- void *get_value() const;
- void set_value(void *p_value) const;
-
- void alloc(void(_CALLBACK_FUNC_ *p_destr_callback)(void *));
- void free();
-
-private:
- struct Impl;
- Impl *pimpl;
-};
-
-template <typename T>
-class ThreadLocal {
-
- ThreadLocalStorage storage;
-
- T init_val;
-
- static void _CALLBACK_FUNC_ destr_callback(void *tls_data) {
- memdelete(static_cast<T *>(tls_data));
- }
-
- T *_tls_get_value() const {
- void *tls_data = storage.get_value();
-
- if (tls_data)
- return static_cast<T *>(tls_data);
-
- T *data = memnew(T(init_val));
-
- storage.set_value(data);
-
- return data;
- }
-
- void _initialize(const T &p_init_val) {
- init_val = p_init_val;
- storage.alloc(&destr_callback);
- }
-
-public:
- ThreadLocal() {
- _initialize(T());
- }
-
- ThreadLocal(const T &p_init_val) {
- _initialize(p_init_val);
- }
-
- ThreadLocal(const ThreadLocal &other) {
- _initialize(*other._tls_get_value());
- }
-
- ~ThreadLocal() {
- storage.free();
- }
-
- _FORCE_INLINE_ T *operator&() const {
- return _tls_get_value();
- }
-
- _FORCE_INLINE_ operator T &() const {
- return *_tls_get_value();
- }
-
- _FORCE_INLINE_ ThreadLocal &operator=(const T &val) {
- T *ptr = _tls_get_value();
- *ptr = val;
- return *this;
- }
-};
-
-struct FlagScopeGuard {
-
- FlagScopeGuard(bool &p_flag) :
- flag(p_flag) {
- flag = !flag;
- }
-
- ~FlagScopeGuard() {
- flag = !flag;
- }
-
-private:
- bool &flag;
-};
-
-#undef _CALLBACK_FUNC_
-
-#define _TLS_RECURSION_GUARD_V_(m_ret) \
- static _THREAD_LOCAL_(bool) _recursion_flag_ = false; \
- if (_recursion_flag_) \
- return m_ret; \
- FlagScopeGuard _recursion_guard_(_recursion_flag_);
-
-#define _TLS_RECURSION_GUARD_ \
- static _THREAD_LOCAL_(bool) _recursion_flag_ = false; \
- if (_recursion_flag_) \
- return; \
- FlagScopeGuard _recursion_guard_(_recursion_flag_);
-
-#endif // THREAD_LOCAL_H